making unrelated changes in the same location. • Feature Envy • a method that seems more interested in a class other than the one it actually is in def create @user = User.new(user_params) @store = @user.build_store(store_params) ! captcha = CaptchaQuestion.find(captcha_id) unless captcha.valid?(captcha_answer) flash[:error] = 'Captcha answer is invalid' render :new and return end ! ActiveRecord::Base.transaction do @user.save! @store.save! @user.store = @store end ! IpLogger.log(request.remote_ip) SignupEmail.deliver(@user) ! redirect_to accounts_path ! rescue ActiveRecord::RecordInvalid render :new end
hundred lines of code. • Methods can be no longer than five lines of code. • Pass no more than four parameters into a method. • Controllers can instantiate only one object. def create @user = User.new(user_params) @store = @user.build_store(store_params) ! captcha = CaptchaQuestion.find(captcha_id) unless captcha.valid?(captcha_answer) flash[:error] = 'Captcha answer is invalid' render :new and return end ! ActiveRecord::Base.transaction do @user.save! @store.save! @user.store = @store end ! IpLogger.log(request.remote_ip) SignupEmail.deliver(@user) ! redirect_to accounts_path ! rescue ActiveRecord::RecordInvalid render :new end
of code. • Methods can be no longer than five lines of code. • Pass no more than four parameters into a method. • Controllers can instantiate only one object.
making unrelated changes in the same location. • Feature Envy • a method that seems more interested in a class other than the one it actually is in • Inappropriate Intimacy • too much intimate knowledge of another class or method's inner workings, inner data, etc. class User < ActiveRecord::Base attr_accessor :remote_ip, :captcha_id, :captcha_answer ! has_one :store ! validates :name, presence: true validate :ensure_captcha_answered, on: :create accepts_nested_attributes_for :store ! after_create :deliver_email after_create :log_ip ! protected ! def deliver_email SignupEmail.deliver(@user) end ! def log_ip IpLogger.log(self.remote_ip) end ! def ensure_captcha_answered captcha = CaptchaQuestion.find(self.captcha_id) ! unless captcha.valid?(self.captcha_answer) errors.add(:captcha_answer, :invalid) end end end
Callbacks • app/forms module Form extend ActiveSupport::Concern include ActiveModel::Model include DelegateAccessors ! included do define_model_callbacks :persist end ! def submit return false unless valid? run_callbacks(:persist) { persist! } true end ! def transaction(&block) ActiveRecord::Base.transaction(&block) end end
Não modifica a lógica do Form Object • Pode ser testada em isolamento # account_form.rb validates :captcha_answer, captcha: true ! # captcha_validator.rb class CaptchaValidator def validate_each(r, attr, val) captcha = CaptchaQuestion.find(r) ! unless captcha.valid?(val) r.errors.add(attr, :invalid) end end end
a parte • Reutilizáveis • Pode ser testado em isolamento # account_form.rb after_persist SendSignupEmail, LogIp ! ! ! class SendSignupEmail class << self def after_persist(form) SignupEmail.deliver(form.user) end end end ! class LogIp class << self def after_persist(form) IpLogger.log(form.remote_ip) end end end
ActiveRecord :( # account_form.rb ! protected ! def store @store ||= Store.new end ! def user @user ||= User.new end ! def persist! transaction do user.save store.save user.store = store end end
• Relacionamentos • Coerção (usando o Virtus) @form.save do |data, nested| u = User.create(nested[:user]) s = Store.create(nested[:store]) u.stores = s end