UTILIZANDO FORM OBJECTS PARA SIMPLIFICAR SEU CÓDIGO

WHERE DOES THE FAT GOES?
Guilherme Cavalcanti
github.com/guiocavalcanti
APLICAÇÕES MONOLÍTICAS

O Que São?

• Dependências compartilhadas
• Difícil de modificar
• Difícil de evoluir
• Mas o assunto ainda são
aplicações monolíticas

NÃO VOU FALAR DE REST

• Outras estratégias para
decompor

• Form Object
ROTEIRO
Sobre O Que Vamos
Falar?

• O problema
• Sintomas
• Form objects
O Problema
MV "F*" C

• Separação de concerns
• Baldes
• Views: apresentação
• Controller: Telefonista
• Model
•

Persistência

•

Do...
M

V

C
Código Inicial
APLICAÇÃO
E-Commerce

• Criação de usuário
• Criação de loja
• Envio de emails
• Auditoria
FAT CONTROLLER

	
  	
  def	
  create	
  
	
  	
  	
  	
  @user	
  =	
  User.new(user_params)	
  
	
  	
  	
  	
  @store	
...
SLIM MODEL
• Validação
• Relacionamentos

class	
  User	
  <	
  ActiveRecord::Base	
  
	
  	
  has_one	
  :store	
  
	
  	...
PROBLEMAS
Mas O Que Isso
Significa?

• E se precisássemos de mais de
um controller para criar conta?

• Vários pontos de s...
CODE SMELLS
Martin Fowler

Refactoring: Improving
The Design Of Existing
Code Ruby
CODE SMELLS

	
  	
  def	
  create	
  
	
  	
  	
  	
  @user	
  =	
  User.new(user_params)	
  
	
  	
  	
  	
  @store	
  =...
SANDI METZ'
RULES FOR
DEVELOPERS
Rubyrogues.Com

Poodr.Com
SANDI RULES

	
  	
  def	
  create	
  
	
  	
  	
  	
  @user	
  =	
  User.new(user_params)	
  
	
  	
  	
  	
  @store	
  =...
Refactor I
Fat Model, Slim Controller
SLIM CONTROLLER

• Inicialização
• Rendering/redirect

	
  	
  def	
  create	
  
	
  	
  	
  	
  @user	
  =	
  User.new(pa...
• Classes can be no longer
than one hundred lines
of code.

• Methods can be no

longer than five lines of
code.

• Pass n...
FAT MODEL

	
  	
  class	
  User	
  <	
  ActiveRecord::Base	
  
	
  	
  	
  attr_accessor	
  :remote_ip,	
  :captcha_id,	
...
CODE SMELLS
•

	
  	
  class	
  User	
  <	
  ActiveRecord::Base	
  
	
  	
  	
  attr_accessor	
  :remote_ip,	
  :captcha_i...
ACTIVE RECORD
Regras De Negócio No
Active Record?

• Precisa do ActiveRecord (specs)
• Acesso a métodos de baixo
nível

• ...
Refactor II
Um Passo A Frente
Form Objects
NOVOS BALDES
• Novas camadas
• Melhor separação
de concerns

• Por muito tempo o
Rails não
estimulava isso
FORM OBJECTS
•
• Realiza validações
• Dispara Callbacks
• app/forms

Delega persistência

module	
  Form	
  
	
  	
  exten...
FORM: O BÁSICO

• Provê accessors
• Delega

responsabilidades

• Infra de callbacks
• Realiza validações
• Inclusive
custo...
FORM: ATRIBUTOS

• Alguns são da class
• Alguns são

	
  	
  
attr_accessor	
  :captcha_id,	
  :captcha_answer	
  

delega...
FORM: VALIDAÇÃO

• Fácil de compor em

outros FormObjects

• Não modifica a
lógica do Form
Object

• Pode ser testada em
i...
FORM: CALLBACKS

• Dispara callbacks
• Callbacks

implementados em
classe a parte

• Reutilizáveis
• Pode ser testado em
i...
#	
  account_form.rb	
  

FORM: PERSISTÊNCIA

!

	
  	
  protected	
  
!

• Delega para os
models

• Precisa do

ActiveRec...
SLIM CONTROLLER

• Inicialização
• Rendering/redirect

	
  	
  def	
  create	
  
	
  	
  	
  	
  @form	
  =	
  AccountForm...
SLIM MODEL
• Apenas

relacionamentos

• Sem validações
• Sem callbacks

	
  	
  class	
  Store	
  <	
  ActiveRecord::Base	...
CODE SMELL
• Divergent change
• This smell refers to

making unrelated
changes in the same
location.

	
  	
  def	
  persi...
Perpetuity
Implementação
do DataMapper
Pattern
PERPETUITY
•

Desacopla
persistência de
lógica de domínio

• Funciona com

qualquer PORO

form	
  =	
  AccountForm.new	
  ...
Reform

Infraestrutura
para form objects
REFORM
• Desacopla

persistência de
lógica de domínio

• Nesting
• Relacionamentos
• Coerção (usando o
Virtus)

@form.save...
OBRIGADO!
guilherme@geteloquent.com
• http://pivotallabs.com/form-backing-objects-for-fun-and-profit/
• http://robots.thoughtbot.com/activemodel-form-objects
...
Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código
Upcoming SlideShare
Loading in...5
×

Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

148

Published on

Como adicionar novas camadas à sua aplicação MVC para ajudar a manutenção e evolução do código.

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
148
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
1
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

  1. 1. UTILIZANDO FORM OBJECTS PARA SIMPLIFICAR SEU CÓDIGO WHERE DOES THE FAT GOES?
  2. 2. Guilherme Cavalcanti github.com/guiocavalcanti
  3. 3. APLICAÇÕES MONOLÍTICAS O Que São? • Dependências compartilhadas • Difícil de modificar • Difícil de evoluir
  4. 4. • Mas o assunto ainda são aplicações monolíticas NÃO VOU FALAR DE REST • Outras estratégias para decompor • Form Object
  5. 5. ROTEIRO Sobre O Que Vamos Falar? • O problema • Sintomas • Form objects
  6. 6. O Problema
  7. 7. MV "F*" C • Separação de concerns • Baldes • Views: apresentação • Controller: Telefonista • Model • Persistência • Domain logic
  8. 8. M V C
  9. 9. Código Inicial
  10. 10. APLICAÇÃO E-Commerce • Criação de usuário • Criação de loja • Envio de emails • Auditoria
  11. 11. FAT CONTROLLER    def  create          @user  =  User.new(user_params)          @store  =  @user.build_store(store_params)   ! • Inicialização • Validação (humano) • Database stuff • Auditoria (IP) • Email • Rendering/redirect        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
  12. 12. 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
  13. 13. PROBLEMAS Mas O Que Isso Significa? • E se precisássemos de mais de um controller para criar conta? • Vários pontos de saída • Acoplamento entre modelos (user e store)
  14. 14. CODE SMELLS Martin Fowler
 Refactoring: Improving The Design Of Existing Code Ruby
  15. 15. CODE SMELLS    def  create          @user  =  User.new(user_params)          @store  =  @user.build_store(store_params)   ! • 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        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
  16. 16. SANDI METZ' RULES FOR DEVELOPERS Rubyrogues.Com
 Poodr.Com
  17. 17. SANDI RULES    def  create          @user  =  User.new(user_params)          @store  =  @user.build_store(store_params)   ! • 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.        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
  18. 18. Refactor I
  19. 19. Fat Model, Slim Controller
  20. 20. 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
  21. 21. • 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.
  22. 22. FAT MODEL    class  User  <  ActiveRecord::Base        attr_accessor  :remote_ip,  :captcha_id,  :captcha_answer   !        has_one  :store   ! • Criação de Store • Validação (humano) • Database stuff • Auditoria (IP) • Email        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
  23. 23. CODE SMELLS •    class  User  <  ActiveRecord::Base        attr_accessor  :remote_ip,  :captcha_id,  :captcha_answer   !        has_one  :store   ! 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.        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
  24. 24. ACTIVE RECORD Regras De Negócio No 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
  25. 25. Refactor II
  26. 26. Um Passo A Frente Form Objects
  27. 27. NOVOS BALDES • Novas camadas • Melhor separação de concerns • Por muito tempo o Rails não estimulava isso
  28. 28. FORM OBJECTS • • Realiza validações • Dispara Callbacks • app/forms Delega persistência 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
  29. 29. 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
  30. 30. FORM: ATRIBUTOS • Alguns são da class • Alguns são     attr_accessor  :captcha_id,  :captcha_answer   delegados • ! delegate_accessors ! delegate_accessors  :name,          :password,  :email,  to:  :user   delegate_accessors  :name,  :url,            to:  :store,  prefix:  true
  31. 31. 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.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  
  32. 32. 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
  33. 33. #  account_form.rb   FORM: PERSISTÊNCIA !    protected   ! • Delega para os models • Precisa do ActiveRecord :(    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
  34. 34. 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
  35. 35. 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
  36. 36. 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
  37. 37. Perpetuity Implementação do DataMapper Pattern
  38. 38. 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
  39. 39. Reform Infraestrutura para form objects
  40. 40. 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
  41. 41. OBRIGADO! guilherme@geteloquent.com
  42. 42. • 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
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×