Model of the colossus @ Rupy Brazil 2013

38
Model of the Colossus
  • date post

    12-Sep-2014
  • Category

    Technology

  • view

    918
  • download

    2

description

Saiba como não deixar seu model tornar-se um ameaçador colosso em sua app Rails. Dicas sobre como não deixar seu model cheio de responsabilidades, seguindo o SRP e refactories usando PORO dentre outras técnicas. Vamos ver alguns anti-patterns em models e soluções para resolvê-los. Também será apresentado alguns bad smells que podem estar dizendo que nosso model pode estar se tornando um colosso.

Transcript of Model of the colossus @ Rupy Brazil 2013

Page 1: Model of the colossus @ Rupy Brazil 2013

Model of the Colossus

Page 2: Model of the colossus @ Rupy Brazil 2013

Mauro quem...

Page 3: Model of the colossus @ Rupy Brazil 2013
Page 4: Model of the colossus @ Rupy Brazil 2013

RSpec Best Friends

Page 5: Model of the colossus @ Rupy Brazil 2013

maurogeorge.com.br

Page 6: Model of the colossus @ Rupy Brazil 2013

Seu model, um grande colosso

Page 7: Model of the colossus @ Rupy Brazil 2013

Seu model, um grande colosso

Rails 15 minutes blogMVC

Rails wayAplicações grandes

37 Signals stackERB

MySQLMiniTest

Fat Models, Skinny Controllers

Prime stackHaml

PostgreSQLRspec/Cucumber

Skinny models, controllers, and a service layer

Page 8: Model of the colossus @ Rupy Brazil 2013

AR quebra o SRP

Alto acoplamentoCallbackObserverFinders

Falta de coesãoPersiste dados

Envia e-mailAcessa Api externas

Page 9: Model of the colossus @ Rupy Brazil 2013

Anti-pattern

Model gerando conteúdo para a view

Page 10: Model of the colossus @ Rupy Brazil 2013

class User < ActiveRecord::Base

# ...

def info %Q{ <ul> <li>#{name}</li> <li>#{email}</li> </ul> } endend

app/models/user.rb

Anti-pattern: Model gerando conteudo para a view

Alto acoplamento

Falta de coesão

Page 11: Model of the colossus @ Rupy Brazil 2013

require 'delegate'

class UserDecorator < SimpleDelegator

def info %Q{ <ul> <li>#{name}</li> <li>#{email}</li> </ul> } endend

app/decorators/user_decorator.rb

Solução: Decorator

Baixo acoplamento

Alta coesão

# Exemplouser = User.find(1)user_decorator = UserDecorator.new(user)user_decorator.infouser_decorator.name

Page 12: Model of the colossus @ Rupy Brazil 2013

class User < ActiveRecord::Base

# ...

def wrote_post?(post) if post.user_id == id "<p>O post #{post.title} foi escrito por #{name}</p>" end endend

app/models/user.rb

Anti-pattern: Model gerando conteudo para a view

Alto acoplamento

Falta de coesão

Método de Postou User

Page 13: Model of the colossus @ Rupy Brazil 2013

class WritterPostPresenter

def initialize(user, post) @user, @post = user, post end

def post_is_wrote_by_writter? if wrote_post? "<p>O post #{post.title} foi escrito por #{user.name}</p>" end end

private

attr_reader :user, :post

def wrote_post? user == post.user endend

app/presenters/writter_post_presenter.rb

Solução: Presenter

Baixo acoplamento

Alta coesão

# Exemplouser = User.find(1)post = Post.find(1)writter_post_presenter = WritterPostPresenter.new(user, post)writter_post_presenter.post_is_wrote_by_writter?

Page 14: Model of the colossus @ Rupy Brazil 2013

Presenters, decorators, exhibit, View Objects e helpers???

HelpersProcedurais

DecoratorsPara uma única entidade

PresentersPara multiplas entidades

Page 15: Model of the colossus @ Rupy Brazil 2013

Anti-pattern

Model Callbacks

Page 16: Model of the colossus @ Rupy Brazil 2013

class Post < ActiveRecord::Base

# ...

after_save :notify_users

# ...

private

def notify_users NotifyMailer.delay.notify(self) endend

app/models/post.rb

Anti-pattern: Model Callbacks

Alto acoplamento

Falta de coesão

Testes lentos

Page 17: Model of the colossus @ Rupy Brazil 2013

class PostCreator

def initialize(post) @post = post end

def save post.save && notify_users end

private

attr_reader :post

def notify_users NotifyMailer.delay.notify(post) endend

app/models/post_creator.rb

Solução: PORO model

Baixo acoplamento

Alta coesão

Testes rápidos

Page 18: Model of the colossus @ Rupy Brazil 2013

class PostsController < ApplicationController

# ..

def create @post = current_user.posts.new(post_params) if redirect_to posts_path, notice: "Post criado com sucesso!" else render 'new' end endend

app/controllers/posts_controller.rb

Solução: PORO model

@post.savePostCreator.new(@post).save

Page 19: Model of the colossus @ Rupy Brazil 2013

Anti-pattern

Model salvando N models

Page 20: Model of the colossus @ Rupy Brazil 2013

class User < ActiveRecord::Base

# ...

accepts_nested_attributes_for :postsend

app/models/user.rb

Anti-pattern: Model salvando N models

Alto acoplamento

Falta de coesão

Page 21: Model of the colossus @ Rupy Brazil 2013

class UserWithPost include ActiveModel::Model

attr_accessor :user_name, :user_email, :post_title, :post_content validates :user_name, :user_email, :post_title, :post_content, presence: true

def save return false unless valid? user = User.create(name: user_name, email: user_email) user.posts.create(title: post_title, content: post_content) true end

end

app/models/app/models/user_with_post.rb

Solução: Form Object

Baixo acoplamento

Alta coesão

# Exemploparams = { user_name: "Mauro", user_email: "[email protected]",

post_title: "Post 1", post_content: "Content"}user_with_post = UserWithPost.new(params)user_with_post.save

Page 22: Model of the colossus @ Rupy Brazil 2013

Anti-pattern

Scopes para um único problema

Page 23: Model of the colossus @ Rupy Brazil 2013

class Post < ActiveRecord::Base

# ...

scope :from, ->(user) { where(user_id: user.id) } scope :recents, -> { order(created_at: :asc) } scope :top_likeds, -> { order(like_count: :asc) } scope :top_from, ->(user) { from(user).recents.top_likeds }

# ...

end

app/models/post.rb

Anti-pattern: Scopes para um único problema

Falta de coesão

Page 24: Model of the colossus @ Rupy Brazil 2013

class TopPostQuery

def initialize(relation = Post.all) @relation = relation.extending(Scopes) end

def top_from(user) @relation.from(user).recents.top_likeds end

module Scopes

def from(user) where(user_id: user.id) end

def recents order(created_at: :asc) end

def top_likeds order(like_count: :asc) end# ...

app/queries/top_post_query.rb

Solução: Query object

Alta coesão

# Exemplouser = User.find(1)top_post_query = TopPostQuery.newtop_post_query.top_from(user)

Page 25: Model of the colossus @ Rupy Brazil 2013

Anti-pattern

ActiveSupport::Concerns

Page 26: Model of the colossus @ Rupy Brazil 2013

module Likeable extend ActiveSupport::Concern

def liked_by(user) return false if user_already_liked?(user) up_one_like add_user_as_voted(user) end

def unliked_by(user) return false unless user_already_liked?(user) down_one_like remove_user_as_voted(user) end

private

attr_reader :likeable, :user

def up_one_like # ... end

# ...

def down_one_like # ... end

def add_user_as_voted(user) # ... end

def remove_user_as_voted(user) # ... end

def user_already_liked?(user) # ... endend

app/models/concerns/likeable.rb

Anti-pattern: ActiveSupport::Concerns

Alto acoplamento

Falta de coesão

Page 27: Model of the colossus @ Rupy Brazil 2013

class Post < ActiveRecord::Base include Likeable

# ...end

app/models/post.rb

Anti-pattern: ActiveSupport::Concerns

Esconde responsabilidade

Page 28: Model of the colossus @ Rupy Brazil 2013

class LikeManager

def initialize(likeable, user) @likeable, @user = likeable, user end

def like return false if user_already_liked? up_one_like add_user_as_voted end

def unlike return false unless user_already_liked? down_one_like remove_user_as_voted end

private

attr_reader :likeable, :user

def up_one_like # ... end

def down_one_like # ... end

def add_user_as_voted # ... end

def remove_user_as_voted # ... end

def user_already_liked? # ... endend

app/services/like_manager.rb

Solução: Service

Baixo acoplamento

Alta coesão

Única responsabilidade

Duck Typing

# Exemplouser = User.find(1)post = Post.find(1)like_manager = LikeManager.new(post, user)like_manager.likelike_manager.unlike

Page 29: Model of the colossus @ Rupy Brazil 2013

Bad Smells

Meu Model está virando um Colosso?

Page 30: Model of the colossus @ Rupy Brazil 2013

class Post < ActiveRecord::Base

# ...

def popular_comments # ... end

def most_viewed_comment # ... end

def most_replied_comment # ... endend

app/models/post.rb

Bad Smell: N métodos com nome de outra entidade

Page 31: Model of the colossus @ Rupy Brazil 2013

class Post < ActiveRecord::Base

# ...

def self.most_popular_from(user) self.top_posts_from(user) self.more_social_media_repercussion_from(user) # ... end

private

def self.top_posts_from(user) # ... end

def self.more_social_media_repercussion_from(user) # ... endend

app/models/post.rb

Bad Smell: N métodos recebendo o mesmo paramêtro

Page 32: Model of the colossus @ Rupy Brazil 2013

class Post < ActiveRecord::Base

# ...

def self.most_popular self.most_commented self.more_social_media_repercussion # ... end

private

def self.most_commented # ... end

def self.more_social_media_repercussion # ... endend

app/models/post.rb

Bad Smell: N métodos privados que são usados em apenas um método

Page 33: Model of the colossus @ Rupy Brazil 2013

Bad Smell

Classe gigante(Provavelmente uma God Class)

Prefira N classes pequenas

Page 34: Model of the colossus @ Rupy Brazil 2013

Futuro

DCIFuncional

Page 35: Model of the colossus @ Rupy Brazil 2013

Conclusão

Crie classes

Quebre Model e Classes grandes em classes menores

Divida responsabilidades

Classes que façam apenas uma coisa bem feita

Page 36: Model of the colossus @ Rupy Brazil 2013

Obrigado

Page 37: Model of the colossus @ Rupy Brazil 2013

maurogeorge.com.br

Page 38: Model of the colossus @ Rupy Brazil 2013

Referências

http://rubyweekly.com/archive/124.html

http://rubyweekly.com/archive/126.html

http://robots.thoughtbot.com/post/14825364877/evaluating-alternative-decorator-

implementations-in

http://mikepackdev.com/blog_posts/31-exhibit-vs-presenter

samuelmullen.com/2013/05/the-problem-with-rails-callbacks

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-

models/

http://rubysource.com/ddd-for-rails-developers-part-1-layered-architecture/

http://blog.plataformatec.com.br/2012/03/barebone-models-to-use-with-actionpack-

in-rails-4-0/

http://www.youtube.com/watch?v=DC-pQPq0acs

http://objectsonrails.com/