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

50 %
50 %
Information about Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu...
Technology

Published on February 23, 2014

Author: guiocavalcanti

Source: slideshare.net

Description

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

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 • Domain logic

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  =  @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

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

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)

CODE SMELLS Martin Fowler
 Refactoring: Improving The Design Of Existing Code Ruby

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

SANDI METZ' RULES FOR DEVELOPERS Rubyrogues.Com
 Poodr.Com

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

Refactor I

Fat Model, Slim Controller

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

• 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.

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

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

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

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      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

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

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

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  

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

#  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

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

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

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

Perpetuity Implementação do DataMapper Pattern

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

Reform Infraestrutura para form objects

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

OBRIGADO! guilherme@geteloquent.com

• 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

Add a comment

Related presentations

Related pages

50 tons de cache – estratégias de cache do HTTP ao ...

Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código. Feb 22, 2014 by Guilherme Cavalcanti. Speaker Details. View Speaker Details.
Read more

FrevoOnRails

Utilizando Form Objects para simplificar seu ... 9h30 - Where does the fat goes? Utilizando Form Objects para ... Form Objects para simplificar seu código.
Read more

db:: 4.15::Consulta NOT IN para LINQ 8x - hivmr.com

Widget settings form goes here ... perdida.Para simplificar, ... o countNão esqueça de usar o componente / na barra para posta seu código.
Read more

db:: 4.79::Provocar Keypress en Control importado desde Vb ...

Widget settings form goes here ... one Button to the Form and the KeyPress event doesn't respond ... y luego editas el codigo para cambiarlo y ...
Read more

Livro Passo a Passo C# - pt.scribd.com

Seu código irá rodar em todos os dispositivos com poucas ... Para simplificar, altere o código, ... utilizando um único fio para transferência de ...
Read more

GRAMÁTICA DE INGLÊS - Scribd

Os estudantes foram para casa mais cedo. My wife goes to ... Procurando simplificar essas ... nome o avô tinha dado para seu cão.O objeto indireto com ...
Read more