Model of the colossus @ Rupy Brazil 2013

Post on 12-Sep-2014

918 views 2 download

Tags:

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

Model of the Colossus

Mauro quem...

RSpec Best Friends

maurogeorge.com.br

Seu model, um grande colosso

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

AR quebra o SRP

Alto acoplamentoCallbackObserverFinders

Falta de coesãoPersiste dados

Envia e-mailAcessa Api externas

Anti-pattern

Model gerando conteúdo para a view

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

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

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

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?

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

HelpersProcedurais

DecoratorsPara uma única entidade

PresentersPara multiplas entidades

Anti-pattern

Model Callbacks

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

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

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

Anti-pattern

Model salvando N models

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

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: "maurogot@gmail.com",

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

Anti-pattern

Scopes para um único problema

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

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)

Anti-pattern

ActiveSupport::Concerns

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

class Post < ActiveRecord::Base include Likeable

# ...end

app/models/post.rb

Anti-pattern: ActiveSupport::Concerns

Esconde responsabilidade

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

Bad Smells

Meu Model está virando um Colosso?

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

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

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

Bad Smell

Classe gigante(Provavelmente uma God Class)

Prefira N classes pequenas

Futuro

DCIFuncional

Conclusão

Crie classes

Quebre Model e Classes grandes em classes menores

Divida responsabilidades

Classes que façam apenas uma coisa bem feita

Obrigado

maurogeorge.com.br

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/