Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TDC 2014 - Combata a obesidade: Técnicas para thin models

TDC 2014 - Combata a obesidade: Técnicas para thin models

Ao iniciamos nossa jornada com o Rails, lemos o guides, alguns blog posts e começamos a nos divertir. Porém, após caminharmos um pouco seguindo as old conventions que ficaram enraizadas na cultura do framework, acabamos deixando de lado conceitos básicos que ajudam na sanidade e manutenabilidade de nossa
codebase por comodidade.

Quando vemos nossos projetos crescerem, toda a diversão do início se torna um peso. Para que ninguém culpe o framework, quero mostrar que existem maneiras de desenvolvermos nossas aplicações desde o início seguindo práticas que vão suportar aplicações de porte médio e grande.

Veremos como escrever models enxutos e de única responsabilidade, passando toda a gordura de validações, callbacks, nested_attributes e regras de negócios complexas, queries complexas e etc para quem realmente deve tratar disso.

Referências:
http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
http://blog.sensible.io/2014/04/19/don-t-just-dump-code-into-your-models.html

Carlos Eduardo L. Lopes

August 07, 2014
Tweet

More Decks by Carlos Eduardo L. Lopes

Other Decks in Programming

Transcript

  1. create create README.rdoc create Rakefile create config.ru create .gitignore create

    Gemfile create app create app/assets/javascripts/application.js create app/assets/stylesheets/application.css create app/controllers/application_controller.rb create app/helpers/application_helper.rb create app/views/layouts/application.html.erb create app/assets/images/.keep create app/mailers/.keep create app/models/.keep create app/controllers/concerns/.keep create app/models/concerns/.keep create bin create bin/bundle create bin/rails create bin/rake create config create config/routes.rb rails new nice_app Projects ~>
  2. Model Validações   Callbacks   Regras  de  negócio   Queries

     complexas   Regras  de  apresentação   Relacionamento   Persistência
  3. Model Validações   Callbacks   Regras  de  negócio   Queries

     complexas   Regras  de  apresentação   Relacionamento   Persistência Fat Models
  4. class User < ActiveRecord::Base
 validates :email, presence: true, 
 email:

    true
 validates :secondary_email, allow_blank: true, 
 email: true
 validates :name, presence: true
 validates :cpf, presence: true, 
 format: %r(\A\d+\z)
 validates :birth_date, presence: true
 end
  5. class User < ActiveRecord::Base
 validates :email, presence: true, 
 email:

    true
 validates :secondary_email, allow_blank: true, 
 email: true
 validates :name, presence: true
 validates :cpf, presence: true, 
 format: %r(\A\d+\z)
 validates :birth_date, presence: true
 
 has_one :address
 end
  6. class User < ActiveRecord::Base
 validates :email, presence: true, 
 email:

    true
 validates :secondary_email, allow_blank: true, 
 email: true
 validates :name, presence: true
 validates :cpf, presence: true, 
 format: %r(\A\d+\z)
 validates :birth_date, presence: true
 
 has_one :address
 
 accept_nested_attributes_for :address
 end
  7. class User < ActiveRecord::Base
 validates :email, presence: true, 
 email:

    true
 validates :secondary_email, allow_blank: true, 
 email: true
 validates :name, presence: true
 validates :cpf, presence: true, 
 format: %r(\A\d+\z)
 validates :birth_date, presence: true
 
 has_one :address
 
 accept_nested_attributes_for :address
 
 after_destroy :remove_picture
 
 private
 def remove_picture
 # Code to remove profile picture
 end
 end
  8. class User < ActiveRecord::Base
 validates :email, presence: true, 
 email:

    true
 validates :secondary_email, allow_blank: true, 
 email: true
 validates :name, presence: true
 validates :cpf, presence: true, 
 format: %r(\A\d+\z)
 validates :birth_date, presence: true
 
 has_one :address
 has_many :orders
 
 accept_nested_attributes_for :address
 
 after_destroy :remove_picture
 
 def special?
 last_login_at > 15.days.ago &&
 orders.last.created_at > 5.days.ago
 end
 
 private
 def remove_picture
 # Code to remove profile picture
 end

  9. accept_nested_attributes_for :address
 
 after_destroy :remove_picture
 
 def special?
 last_login_at >

    15.days.ago &&
 orders.last.created_at > 5.days.ago
 end
 
 def formatted_name
 if gender == 'male'
 "Derp #{name}"
 else
 "Derpina #{name}"
 end
 end
 
 def formatted_birth_date
 I18n.l(birth_date, format: :short)
 end
 
 private
 def remove_picture
 # Code to remove profile picture
 end
 end
  10. if gender == 'male'
 "Derp #{name}"
 else
 "Derpina #{name}"
 end


    end
 
 def formatted_birth_date
 I18n.l(birth_date, format: :short)
 end
 
 def self.apple_buyers
 apple_orders = Order.where(provider: 'Apple')
 where(id: apple_orders.select(:user_id))
 end
 
 private
 def remove_picture
 # Code to remove profile picture
 end
 end
  11. class User < ActiveRecord::Base
 validates :email, presence: true, 
 email:

    true
 validates :secondary_email, allow_blank: true,
 email: true
 validates :name, presence: true
 validates :cpf, presence: true,
 format: %r(\A\d+\z)
 validates :birth_date, presence: true
 
 has_one :address
 has_many :orders
 
 accept_nested_attributes_for :address
 
 after_destroy :remove_picture
 
 def special?
 last_login_at > 15.days.ago &&
 orders.last.created_at > 5.days.ago
 end
 
 def formatted_name
 if gender == 'male'
 "Derp #{name}"
 else
 "Derpina #{name}"
 end
 end
 
 def formatted_birth_date
 I18n.l(birth_date, format: :short)
 end
 
 def self.apple_buyers
 apple_orders = Order.where(provider: 'Apple')
 where(id: apple_orders.select(:user_id))
 end
 
 private
 def remove_picture
 # Code to remove profile picture
 end
 end
  12. class SignupForm
 include Virtus
 include ActiveModel::Model
 
 attr_reader :user, :address


    
 attribute :name, String
 # other attributes ...
 
 validates :email, presence: true
 # other validations
 
 def persisted?; false; end
 
 def save
 if valid?; persist!; true; else; false; end
 end
 
 private
 def persist!
 @user = User.create!(name: name ...)
 @address = @user.create_address!(street: street ...)
 end
 end

  13. # Antes
 def create
 @user = User.new(params[:user]) # Rails 3+


    @user = User.new(user_params) # Rails 4+ 
 if @user.save
 # Success
 else
 # Error
 end
 end
 
 # Depois
 def create
 @form = SignupForm.new(params[:signup_form])
 
 if @form.save
 # Success
 else
 # Error
 end
 end
  14. # Antes
 def create
 @user = User.new(params[:user]) # Rails 3+


    @user = User.new(user_params) # Rails 4+ 
 if @user.save
 # Success
 else
 # Error
 end
 end
 
 # Depois
 def create
 @form = SignupForm.new(params[:signup_form])
 
 if @form.save
 # Success
 else
 # Error
 end
 end
  15. class User < ActiveRecord::Base
 validates :email, presence: true, 
 email:

    true
 validates :secondary_email, allow_blank: true,
 email: true
 validates :name, presence: true
 validates :cpf, presence: true,
 format: %r(\A\d+\z)
 validates :birth_date, presence: true
 
 has_one :address
 has_many :orders
 
 accept_nested_attributes_for :address
 
 after_destroy :remove_picture
 
 def special?
 last_login_at > 15.days.ago &&
 orders.last.created_at > 5.days.ago
 end
 
 def formatted_name
 if gender == 'male'
 "Derp #{name}"
 else
 "Derpina #{name}"
 end
 end
 
 def formatted_birth_date
 I18n.l(birth_date, format: :short)
 end
 
 def self.apple_buyers
 apple_orders = Order.where(provider: 'Apple')
 where(id: apple_orders.select(:user_id))
 end
 
 private
 def remove_picture
 # Code to remove profile picture
 end
 end
  16. Forms Validações Model Callbacks   Regras  de  negócio   Queries

     complexas   Regras  de  apresentação   Relacionamento   Persistência
  17. class UserDestroyer
 def initialize(user)
 @user = user
 end
 
 def

    remove
 # Code to remove profile picture
 @user.destroy
 end
 end

  18. # Antes
 def destroy
 @user = User.find(params[:id])
 @user.destroy
 redirect_to users_path


    end
 
 # Depois
 def destroy
 @user = User.find(params[:id])
 UserDestroyer.new(@user).destroy
 redirect_to users_path
 end
  19. # Antes
 def destroy
 @user = User.find(params[:id])
 @user.destroy
 redirect_to users_path


    end
 
 # Depois
 def destroy
 @user = User.find(params[:id])
 UserDestroyer.new(@user).destroy
 redirect_to users_path
 end
  20. class User < ActiveRecord::Base
 has_one :address
 has_many :orders
 
 after_destroy

    :remove_picture
 
 def special?
 last_login_at > 15.days.ago &&
 orders.last.created_at > 5.days.ago
 end
 
 def formatted_name
 if gender == 'male'
 "Derp #{name}"
 else
 "Derpina #{name}"
 end
 end
 
 def formatted_birth_date
 I18n.l(birth_date, format: :short)
 end
 
 def self.apple_buyers
 apple_orders = Order.where(provider: 'Apple')
 where(id: apple_orders.select(:user_id))
 end
 
 private
 def remove_picture
 # Code to remove profile picture
 end
 end
  21. Model Regras  de  negócio   Queries  complexas   Regras  de

     apresentação   Relacionamento   Persistência Services Callbacks Forms Validações
  22. class SpecialUserPolicy
 def initialize(user)
 @user = user
 end
 
 def

    special?
 @user.last_login_at > 15.days.ago &&
 @user.orders.last > 5.days.ago
 end
 end
  23. # Controller antes
 def index
 end # Controller depois
 def

    index
 @special_user_policy = SpecialUserPolicy.new(current_user)
 end <%# View antes %>
 <% if current_user.special? %>
 <% end %> <%# View depois %>
 <% if @special_user_policy.special? %>
 <% end %>
  24. # Controller antes
 def index
 end # Controller depois
 def

    index
 @special_user_policy = SpecialUserPolicy.new(current_user)
 end <%# View antes %>
 <% if current_user.special? %>
 <% end %> <%# View depois %>
 <% if @special_user_policy.special? %>
 <% end %>
  25. class User < ActiveRecord::Base
 has_one :address
 has_many :orders
 
 def

    special?
 last_login_at > 15.days.ago &&
 orders.last.created_at > 5.days.ago
 end
 
 def formatted_name
 if gender == 'male'
 "Derp #{name}"
 else
 "Derpina #{name}"
 end
 end
 
 def formatted_birth_date
 I18n.l(birth_date, format: :short)
 end
 
 def self.apple_buyers
 apple_orders = Order.where(provider: 'Apple')
 where(id: apple_orders.select(:user_id))
 end
 end
  26. Model Queries  complexas   Regras  de  apresentação   Relacionamento  

    Persistência Services Callbacks Forms Validações Policies Regras  de  negócio
  27. class AppleBuyers
 def initialize(relation = User.scoped)
 @relation = relation
 end


    
 def find_each(&block)
 @relation.
 where(id: apple_orders.select(:user_id)).
 find_each(&block)
 end
 
 private
 def apple_orders
 Order.where(provider: 'Apple')
 end
 end
  28. # Antes
 def send
 User.apple_buyers.find_each do |user|
 MarketingMailer.apple_email(user).deliver
 end
 end


    
 # Depois
 def send
 AppleBuyers.new.find_each do |user|
 MarketingMailer.apple_email(user).deliver
 end
 end
  29. # Antes
 def send
 User.apple_buyers.find_each do |user|
 MarketingMailer.apple_email(user).deliver
 end
 end


    
 # Depois
 def send
 AppleBuyers.new.find_each do |user|
 MarketingMailer.apple_email(user).deliver
 end
 end
  30. class User < ActiveRecord::Base
 has_one :address
 has_many :orders
 
 def

    formatted_name
 if gender == 'male'
 "Derp #{name}"
 else
 "Derpina #{name}"
 end
 end
 
 def formatted_birth_date
 I18n.l(birth_date, format: :short)
 end
 
 def self.apple_buyers
 apple_orders = Order.where(provider: 'Apple')
 where(id: apple_orders.select(:user_id))
 end
 end
  31. Model Regras  de  apresentação   Relacionamento   Persistência Services Callbacks

    Forms Validações Policies Regras  de  negócio Queries Queries  complexas
  32. class UserDecorator < SimpleDelegator
 def initialize(user)
 @user = user
 super(user)


    end
 
 def name
 if gender == 'male'
 "Derp #{user.name}"
 else
 "Derpina #{user.name}"
 end
 end
 
 def birth_date
 h.l(user.birth_date, format: :short)
 end
 
 private
 attr_reader :user
 end
  33. # Antes
 def show
 @user = User.find(params[:id])
 end
 
 #

    Depois
 def show
 @user = User.find(params[:id])
 @decorated_user = UserDecorator.new(@user)
 end
  34. # Antes
 def show
 @user = User.find(params[:id])
 end
 
 #

    Depois
 def show
 @user = User.find(params[:id])
 @decorated_user = UserDecorator.new(@user)
 end
  35. class User < ActiveRecord::Base
 has_one :address
 has_many :orders
 
 def

    formatted_name
 if gender == 'male'
 "Derp #{name}"
 else
 "Derpina #{name}"
 end
 end
 
 def formatted_birth_date
 I18n.l(birth_date, format: :short)
 end
 end
  36. Model Relacionamento   Persistência Services Callbacks Forms Validações Policies Regras

     de  negócio Queries Queries  complexas Decorators Regras  de  apresentação
  37. Model Validações   Callbacks   Regras  de  negócio   Queries

     complexas   Regras  de  apresentação   Relacionamento   Persistência
  38. Model Relacionamento   Persistência Services Callbacks Forms Validações Policies Regras

     de  negócio Queries Queries  complexas Decorators Regras  de  apresentação