Model of the colossus @ Rupy Brazil 2013

1,425 views

Published on



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.

Published in: Technology, Lifestyle, Business
0 Comments
6 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,425
On SlideShare
0
From Embeds
0
Number of Embeds
20
Actions
Shares
0
Downloads
12
Comments
0
Likes
6
Embeds 0
No embeds

No notes for slide

Model of the colossus @ Rupy Brazil 2013

  1. 1. Model of the Colossus
  2. 2. Mauro quem...
  3. 3. RSpec Best Friends
  4. 4. maurogeorge.com.br
  5. 5. Seu model, um grande colosso
  6. 6. Seu model, um grande colosso Rails 15 minutes blog MVC Rails way Aplicações grandes 37 Signals stack ERB MySQL MiniTest Fat Models, Skinny Controllers Prime stack Haml PostgreSQL Rspec/Cucumber Skinny models, controllers, and a service layer
  7. 7. AR quebra o SRP Alto acoplamento Callback Observer Finders Falta de coesão Persiste dados Envia e-mail Acessa Api externas
  8. 8. Anti-pattern Model gerando conteúdo para a view
  9. 9. Anti-pattern: Model gerando conteudo para a view app/models/user.rb class User < ActiveRecord::Base Alto acoplamento # ... def info %Q{ <ul> <li>#{name}</li> <li>#{email}</li> </ul> } end end Falta de coesão
  10. 10. Solução: Decorator app/decorators/user_decorator.rb require 'delegate' class UserDecorator < SimpleDelegator def info %Q{ <ul> <li>#{name}</li> <li>#{email}</li> </ul> } end end # Exemplo user = User.find(1) user_decorator = UserDecorator.new(user) user_decorator.info user_decorator.name Baixo acoplamento Alta coesão
  11. 11. Anti-pattern: Model gerando conteudo para a view app/models/user.rb class User < ActiveRecord::Base # ... def wrote_post?(post) if post.user_id == id "<p>O post #{post.title} foi escrito por #{name}</p>" end end end Alto acoplamento Falta de coesão Método de Post ou User
  12. 12. Solução: Presenter app/presenters/writter_post_presenter.rb class WritterPostPresenter def initialize(user, post) @user, @post = user, post end Baixo acoplamento Alta coesão 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 # Exemplo def wrote_post? user == post.user user = User.find(1) post = Post.find(1) end writter_post_presenter = WritterPostPresenter.new(user, post) end writter_post_presenter.post_is_wrote_by_writter?
  13. 13. Presenters, decorators, exhibit, View Objects e helpers??? Helpers Procedurais Decorators Para uma única entidade Presenters Para multiplas entidades
  14. 14. Anti-pattern Model Callbacks
  15. 15. Anti-pattern: Model Callbacks app/models/post.rb class Post < ActiveRecord::Base Alto acoplamento # ... after_save :notify_users # ... private def notify_users NotifyMailer.delay.notify(self) end end Falta de coesão Testes lentos
  16. 16. Solução: PORO model app/models/post_creator.rb 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) end end Baixo acoplamento Alta coesão Testes rápidos
  17. 17. Solução: PORO model app/controllers/posts_controller.rb class PostsController < ApplicationController # .. def create @post = current_user.posts.new(post_params) if @post.save PostCreator.new(@post).save redirect_to posts_path, notice: "Post criado com sucesso!" else render 'new' end end end
  18. 18. Anti-pattern Model salvando N models
  19. 19. Anti-pattern: Model salvando N models app/models/user.rb class User < ActiveRecord::Base Alto acoplamento # ... accepts_nested_attributes_for :posts end Falta de coesão
  20. 20. Solução: Form Object app/models/app/models/user_with_post.rb 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 Baixo acoplamento Alta coesão end # Exemplo params = { 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
  21. 21. Anti-pattern Scopes para um único problema
  22. 22. Anti-pattern: Scopes para um único problema app/models/post.rb class Post < ActiveRecord::Base # ... scope scope scope scope # ... end :from, ->(user) { where(user_id: user.id) } :recents, -> { order(created_at: :asc) } :top_likeds, -> { order(like_count: :asc) } :top_from, ->(user) { from(user).recents.top_likeds } Falta de coesão
  23. 23. Solução: Query object app/queries/top_post_query.rb class TopPostQuery Alta coesão 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 # ... # Exemplo user = User.find(1) top_post_query = TopPostQuery.new top_post_query.top_from(user)
  24. 24. Anti-pattern ActiveSupport::Concerns
  25. 25. Anti-pattern: ActiveSupport::Concerns app/models/concerns/likeable.rb 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 # ... Alto acoplamento Falta de coesão
  26. 26. Anti-pattern: ActiveSupport::Concerns app/models/post.rb class Post < ActiveRecord::Base include Likeable # ... end Esconde responsabilidade
  27. 27. Solução: Service app/services/like_manager.rb 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 Baixo acoplamento Alta coesão Única responsabilidade Duck Typing def unlike return false unless user_already_liked? down_one_like remove_user_as_voted end # Exemplo user = User.find(1) private post = Post.find(1) like_manager = LikeManager.new(post, user) attr_reader :likeable, :user like_manager.like like_manager.unlike def up_one_like # ...
  28. 28. Bad Smells Meu Model está virando um Colosso?
  29. 29. Bad Smell: N métodos com nome de outra entidade app/models/post.rb class Post < ActiveRecord::Base # ... def popular_comments # ... end def most_viewed_comment # ... end def most_replied_comment # ... end end
  30. 30. Bad Smell: N métodos recebendo o mesmo paramêtro app/models/post.rb 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) # ... end end
  31. 31. Bad Smell: N métodos privados que são usados em apenas um método app/models/post.rb 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 # ... end end
  32. 32. Bad Smell Classe gigante (Provavelmente uma God Class) Prefira N classes pequenas
  33. 33. Futuro DCI Funcional
  34. 34. Conclusão Crie classes Quebre Model e Classes grandes em classes menores Divida responsabilidades Classes que façam apenas uma coisa bem feita
  35. 35. Obrigado
  36. 36. maurogeorge.com.br
  37. 37. Referências http://rubyweekly.com/archive/124.html http://rubyweekly.com/archive/126.html http://robots.thoughtbot.com/post/14825364877/evaluating-alternative-decoratorimplementations-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-activerecordmodels/ http://rubysource.com/ddd-for-rails-developers-part-1-layered-architecture/ http://blog.plataformatec.com.br/2012/03/barebone-models-to-use-with-actionpackin-rails-4-0/ http://www.youtube.com/watch?v=DC-pQPq0acs http://objectsonrails.com/

×