where does the fat goes? utilizando form objects para simplificar seu código
DESCRIPTION
Como adicionar novas camadas à sua aplicação MVC para ajudar a manutenção e evolução do código.TRANSCRIPT
![Page 1: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/1.jpg)
WHERE DOES THE FAT GOES?UTILIZANDO FORM OBJECTS PARA SIMPLIFICAR SEU CÓDIGO
![Page 2: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/2.jpg)
Guilherme Cavalcanti
github.com/guiocavalcanti
![Page 3: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/3.jpg)
![Page 4: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/4.jpg)
APLICAÇÕES MONOLÍTICAS
• Dependências compartilhadas
• Difícil de modificar
• Difícil de evoluirO Que São?
![Page 5: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/5.jpg)
NÃO VOU FALAR DE REST
• Mas o assunto ainda são aplicações monolíticas
• Outras estratégias para decompor
• Form Object
![Page 6: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/6.jpg)
ROTEIRO • O problema
• Sintomas
• Form objectsSobre O Que Vamos Falar?
![Page 7: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/7.jpg)
O Problema
![Page 8: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/8.jpg)
MV "F*" C
• Separação de concerns
• Baldes
• Views: apresentação
• Controller: Telefonista
• Model
• Persistência
• Domain logic
![Page 9: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/9.jpg)
M V C
![Page 10: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/10.jpg)
Código Inicial
![Page 11: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/11.jpg)
APLICAÇÃO
• Criação de usuário
• Criação de loja
• Envio de emails
• Auditoria
E-Commerce
![Page 12: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/12.jpg)
FAT CONTROLLER
• Inicialização
• Validação (humano)
• Database stuff
• Auditoria (IP)
• Rendering/redirect
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
![Page 13: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/13.jpg)
SLIM MODEL
• Validação
• Relacionamentos
class User < ActiveRecord::Base has_one :store validates :name, presence: true ! accepts_nested_attributes_for :store end
class Store < ActiveRecord::Base belongs_to :user validates :url, presence: true end
![Page 14: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/14.jpg)
PROBLEMAS
• E se precisássemos de mais de um controller para criar conta?
• Vários pontos de saída
• Acoplamento entre modelos (user e store)
Mas O Que Isso Significa?
![Page 15: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/15.jpg)
CODE SMELLSMartin FowlerRefactoring: Improving The Design Of Existing Code Ruby
![Page 16: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/16.jpg)
CODE SMELLS
• Divergent change
• This smell refers to 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
![Page 18: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/18.jpg)
SANDI RULES
• Classes can be no longer than one 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
![Page 19: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/19.jpg)
Refactor I
![Page 20: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/20.jpg)
Fat Model, Slim Controller
![Page 21: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/21.jpg)
SLIM CONTROLLER
• Inicialização
• Rendering/redirect def create @user = User.new(params) @user.remote_ip = request.remote_ip @user.save ! respond_with(@user, location: accounts_path) end
![Page 22: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/22.jpg)
• Classes can be no longer than one 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.
![Page 23: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/23.jpg)
FAT MODEL
• Criação de Store
• Validação (humano)
• Database stuff
• Auditoria (IP)
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
![Page 24: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/24.jpg)
CODE SMELLS• Divergent change
• This smell refers to 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
![Page 25: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/25.jpg)
ACTIVE RECORD
• Precisa do ActiveRecord (specs)
• Acesso a métodos de baixo nível
• update_attributes
• A instância valida a sí mesma
• Difícil de testar
Regras De Negócio No Active Record?
![Page 26: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/26.jpg)
Refactor II
![Page 27: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/27.jpg)
Form Objects
Um Passo A Frente
![Page 28: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/28.jpg)
NOVOS BALDES• Novas camadas
• Melhor separação de concerns
• Por muito tempo o Rails não estimulava isso
![Page 29: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/29.jpg)
FORM OBJECTS
• Delega persistência
• Realiza validações
• Dispara 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
![Page 30: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/30.jpg)
FORM: O BÁSICO
• Provê accessors
• Delega responsabilidades
• Infra de callbacks
• Realiza validações
• Inclusive customizadas
class AccountForm include Form ! attr_accessor :captcha_id, :captcha_answer ! delegate_accessors :name, :password, :email, to: :user ! delegate_accessors :name, :url, to: :store, prefix: true ! validates :captcha_answer, captcha: true validates :name, :store_url, presence: true end
![Page 31: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/31.jpg)
FORM: ATRIBUTOS
• Alguns são da class
• Alguns são delegados
• delegate_accessors
attr_accessor :captcha_id, :captcha_answer !delegate_accessors :name, :password, :email, to: :user !delegate_accessors :name, :url, to: :store, prefix: true
![Page 32: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/32.jpg)
FORM: VALIDAÇÃO
• Fácil de compor em outros FormObjects
• Não modifica a lógica do Form Object
• Pode ser testada em isolamento
# account_form.rb validates :captcha_answer, captcha: true
!# captcha_validator.rbclass CaptchaValidator def validate_each(r, attr, val) captcha = CaptchaQuestion.find(r) ! unless captcha.valid?(val) r.errors.add(attr, :invalid) end end end
![Page 33: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/33.jpg)
FORM: CALLBACKS
• Dispara callbacks
• Callbacks implementados em classe 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
![Page 34: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/34.jpg)
FORM: PERSISTÊNCIA
• Delega para os models
• Precisa do 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
![Page 35: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/35.jpg)
SLIM CONTROLLER
• Inicialização
• Rendering/redirect def create @form = AccountForm.new(accout_params) @form.remote_ip = request.remote_ip @form.submit ! respond_with(@form, location: accounts_path) end
![Page 36: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/36.jpg)
SLIM MODEL
• Apenas relacionamentos
• Sem validações
• Sem callbacks
class Store < ActiveRecord::Base belongs_to :user end ! class User < ActiveRecord::Base has_one :store end
![Page 37: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/37.jpg)
CODE SMELL
• Divergent change
• This smell refers to making unrelated changes in the same location.
def persist! transaction do user.save store.save user.store = store end end
![Page 39: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/39.jpg)
PERPETUITY
• Desacopla persistência de lógica de domínio
• Funciona com qualquer PORO
form = AccountForm.new form.name = ‘Guilherme' form.store_url = ‘http://...’ !Perpetuity[Account].insert account
![Page 41: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/41.jpg)
REFORM
• Desacopla persistência de lógica de domínio
• Nesting
• 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
![Page 42: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/42.jpg)
OBRIGADO! [email protected]
![Page 43: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código](https://reader033.vdocuments.net/reader033/viewer/2022060106/54bd17314a7959f95e8b4593/html5/thumbnails/43.jpg)
• http://pivotallabs.com/form-backing-objects-for-fun-and-profit/
• http://robots.thoughtbot.com/activemodel-form-objects
• http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
• http://www.reddit.com/r/ruby/comments/1qbiwr/any_form_object_fans_out_there_who_might_want_to/
• http://panthersoftware.com/blog/2013/05/13/user-registration-using-form-objects-in-rails/
• http://reinteractive.net/posts/158-form-objects-in-rails
• https://docs.djangoproject.com/en/dev/topics/forms/#form-objects
• http://engineering.nulogy.com/posts/building-rich-domain-models-in-rails-separating-persistence/
• http://robots.thoughtbot.com/sandi-metz-rules-for-developers
• https://github.com/brycesenz/freeform
• http://nicksda.apotomo.de/2013/05/reform-decouple-your-forms-from-your-models/
• http://joncairns.com/2013/04/fat-model-skinny-controller-is-a-load-of-rubbish/
• http://engineering.nulogy.com/posts/building-rich-domain-models-in-rails-separating-persistence/
• https://www.youtube.com/watch?v=jk8FEssfc90