Design Patterns on
       Rails
      Thiago Pradi
Thiago Pradi

• Desenvolvedor Web na ZInfinit
• Ruby/Rails a 3 anos
• Participante do Ruby Summer of Code
• Sócio-Fundador da THP Informática
Padrões no Rails

• No inicio, era só trevas...
• Rails, nascido com o MVC
• Simplicidade ao máximo
Primórdios

• Muitos desenvolvedores vindo de .net / java
• Atraídos por simplicidade e produtividade
• Nascem aplicações cada vez mais
  complexas
• Surgindo os plugins...
Plugins

• Padrão act_as_.....
• Instalação simples
• Convenção sobre configuração
• Porém...
Problemas..

• Alta utilização de monkey patch
• Nasce o alias_method_chain
• OMG! SO EVIL!
alias_method_chain
Caso Octopus

• Abusa de Monkey patch.
• Parecia legal, no ínicio
• as coisas foram mudando e...
• terá que ser totalmente refatorado para
  Rails 3.1 =/
Discussões sobre
         código

• Vários blogs criados
• Discussões sobre códigos
• Padrões surgindo...
Fat Model, Skinny
        Controller

• Introduzido pelo blog Rails best practices
• Mover código do controller para o model
• Favorecendo testabilidade
Parece bom, porém...

• Alto acoplamento no model
• Model com 1001 utilidades
• e 328193892189 linhas de código! ZONG!
• 1325 Callbacks, com zilhões de ifs.
User.rb
Responsabilidades

• Envio de e-mail
• Processamento de CSV
• Upload de Arquivos
• Geração de HTML
Responsabilidades II

• Cálculos complexos
• Geração de PDF
• Processamento de Imagens
• Liste a sua!
Resultado disso..

• Testes lentos
• Altamente acoplados
• Manutenção horrível!
Design patterns!

• Não realmente
• Falarei sobre experiências de trabalho
• Coisas que podem melhorar seu dia a dia
• Design Patterns existem vários livros!
SOLID

• Single Responsability
• Open-closed
• Liskov Substitution
• Interface Segregation
• Dependecy Inversion
ActionController::Base


ree-1.8.7-2011.03 :017 >
ActionController::Base.instance_methods(false).count
 => 721
Upload de Arquivos
Péssimo - FileColumn

  class Entry < ActiveRecord::Base
    file_column :image
  end
Regular - paperclip
class User < ActiveRecord::Base
  has_attached_file :avatar,
                    :styles => {
                      :medium => "300x300>",
                      :thumb => "100x100>"
                    }
end
Bom - Carrierwave
class AvatarUploader < CarrierWave::Uploader::Base
  storage :file

  def store_dir
    'public/my/upload/directory'
  end

  def extension_white_list
    %w(jpg jpeg gif png)
  end
end

class User
  mount_uploader :avatar, AvatarUploader
end
POROs

• Plain Old Ruby Object
• Simplesmente um Objeto Limpo Ruby.
• Não tenha medo de escrever uma nova
  classe!
• Culpa do famigerado lib/ ...
Exemplo: filters
class UsersController < ApplicationController
  before_filter :my_complex_filter

  def my_complex_filter
    # Code, Code and code...
  end
end
Problemas

• Como testar esse filtro de forma isolada?
• Ao invés de unitários, escrevemos testes de
  integração!
• POROs para salvar o dia!
Filters
class UsersController < ApplicationController
  before_filter ComplexFilter.new
end

class ComplexFilter
  def filter(controller)
    # Agora sim!
  end
end
Presenters


• Extração de lógica da view.
• Camada extra entre o Controller e a View.
Código
 class UsersController < ApplicationController
   def show
     @user = User.find(params[:id])
   end
 end

<% if @user.active? %>
  <%= @user.activated_at.strftime("%d/%m/%y") %>
<% else %>
  <%= @user.created_at.strftime("%d/%m/%y") %>
<% end %>
Presenter
class UserPresenter
  attr_accessor :user

  def initialize(user)
    @user = user
  end

  def activity_date
    if @user.active?
      @user.activated_at.strftime("%d/%m/%y")
    else
      @user.created_at.strftime("%d/%m/%y")
    end
  end
end
Refatorando
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    @user_presentter = UserPresenter.new(@user)
  end
end

     <%= @user_presentter.activity_date %>
Mas, e os Helpers?

• Helpers são somente funções
• Presenters são “Helpers” orientados a
  objeto!
• Responsabilidades extras: controle de
  cache, etc.
Break Out Method
        Object
• Extraído do Livro: Working Effectively With
  Legacy Code
• A idéia é simples, transformar métodos
  complexos em objetos.
• Crie um novo objeto, passando os
  parametros necessários para o construtor,
  e copiando o corpo do método
Exemplo..
class Order
  def calculate_shipping
    # blablabla, method with 50 lines.
  end
end
Refatorando
class ShippingCalculator
  attr_accessor :order

  def initialize(order)
    @order = order
  end

  def value
    # copy and paste
  end
end
Model

class Order
  def calculate_shipping
    ShippingCalculator.new(self).value
  end
end
Pontos Positivos

• Removendo responsabilidades do model
• Método gigante pode ser quebrado em
  métodos menores
• Testado de forma isolada
Dependency Injection
• Reduz acoplamente entre objetos
• Objeto devem focar em suas
  responsabilidades, tendo suas dependências
  bem definidas.
• Aplicado extensivamente em Java/C#
• Por que não usar em Ruby?!
Exemplo
class UserUpdaterService
  attr_accessor :user

  def initialize(user_id)
    @user = User.find(user_id)
  end

  def update
    # code for my update method.
  end
end

UserUpdaterService.new(params[:id])
Problemas

• o UserUpdater depende do usuário
• para testar, teriamos que criar um novo
  usuário no banco, e passar a id dele para o
  UserUpdater
• Ruim e lento :(
Refatorando
class UserUpdaterService
  attr_accessor :user

  def initialize(user)
    @user = user
  end

  def update
    # code for my update method.
  end
end

UserUpdaterService.new(User.find(params[:id]))
# ou para testar
UserUpdaterService.new(mock_model(User))
mas, mocks?!

• Eu ouvi falar que mocks são do mau!
• O problema não são os mocks
• São VOCÊ!
Opiniões

• To be a successful mockist, you must dislike
  mocks.
• Isole os testes unitários, porém cubra eles
  com integração!
Vantagens

• Facilita testabilidade
• Feature testada de forma isolada
• Reuso de forma inteligente
• Testes voando!
#FicaADica

• Railers acabaram esquecendo de OO
• A beleza do Ruby está na sua OO
• Não seja bitolado, pense diferente :)
• Tente, experimente, pense.. e veja o que
  funciona para você!
Leituras Recomendadas
• Blog do Steve Klabnik - http://
  blog.steveklabnik.com/
• Blog do Cássio Marques - http://
  cassiomarques.wordpress.com/
• Working Effectively with Legacy Code -
  http://goo.gl/DKrF0
• James on Software - http://jamesgolick.com/
Extra #1

  Três Regras para escrever callbacks:
1. Somente escreva um callback quando você
   souber o que está fazendo
2. Você não sabe o que está fazendo
3. Veja regra 1.
Extra #2
 class User < ActiveRecord::Base
   def after_save
     Log.create(:class => "User", :attrs => attributes)
   end
 end

 @user = User.find(1)
 @user.save



Será mesmo que está claro para o programador
  que após salvar o usuário, o log será criado?
Obrigado!

• thiago.pradi@gmail.com
• www.thiagopradi.net
• http://www.github.com/tchandy
• http://www.twitter.com/thiagopradi

Design Patterns on Rails

  • 1.
    Design Patterns on Rails Thiago Pradi
  • 2.
    Thiago Pradi • DesenvolvedorWeb na ZInfinit • Ruby/Rails a 3 anos • Participante do Ruby Summer of Code • Sócio-Fundador da THP Informática
  • 3.
    Padrões no Rails •No inicio, era só trevas... • Rails, nascido com o MVC • Simplicidade ao máximo
  • 4.
    Primórdios • Muitos desenvolvedoresvindo de .net / java • Atraídos por simplicidade e produtividade • Nascem aplicações cada vez mais complexas • Surgindo os plugins...
  • 5.
    Plugins • Padrão act_as_..... •Instalação simples • Convenção sobre configuração • Porém...
  • 6.
    Problemas.. • Alta utilizaçãode monkey patch • Nasce o alias_method_chain • OMG! SO EVIL!
  • 7.
  • 8.
    Caso Octopus • Abusade Monkey patch. • Parecia legal, no ínicio • as coisas foram mudando e... • terá que ser totalmente refatorado para Rails 3.1 =/
  • 9.
    Discussões sobre código • Vários blogs criados • Discussões sobre códigos • Padrões surgindo...
  • 10.
    Fat Model, Skinny Controller • Introduzido pelo blog Rails best practices • Mover código do controller para o model • Favorecendo testabilidade
  • 11.
    Parece bom, porém... •Alto acoplamento no model • Model com 1001 utilidades • e 328193892189 linhas de código! ZONG! • 1325 Callbacks, com zilhões de ifs.
  • 12.
  • 13.
    Responsabilidades • Envio dee-mail • Processamento de CSV • Upload de Arquivos • Geração de HTML
  • 14.
    Responsabilidades II • Cálculoscomplexos • Geração de PDF • Processamento de Imagens • Liste a sua!
  • 15.
    Resultado disso.. • Testeslentos • Altamente acoplados • Manutenção horrível!
  • 16.
    Design patterns! • Nãorealmente • Falarei sobre experiências de trabalho • Coisas que podem melhorar seu dia a dia • Design Patterns existem vários livros!
  • 17.
    SOLID • Single Responsability •Open-closed • Liskov Substitution • Interface Segregation • Dependecy Inversion
  • 18.
  • 19.
  • 20.
    Péssimo - FileColumn class Entry < ActiveRecord::Base file_column :image end
  • 21.
    Regular - paperclip classUser < ActiveRecord::Base has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" } end
  • 22.
    Bom - Carrierwave classAvatarUploader < CarrierWave::Uploader::Base storage :file def store_dir 'public/my/upload/directory' end def extension_white_list %w(jpg jpeg gif png) end end class User mount_uploader :avatar, AvatarUploader end
  • 23.
    POROs • Plain OldRuby Object • Simplesmente um Objeto Limpo Ruby. • Não tenha medo de escrever uma nova classe! • Culpa do famigerado lib/ ...
  • 24.
    Exemplo: filters class UsersController< ApplicationController before_filter :my_complex_filter def my_complex_filter # Code, Code and code... end end
  • 25.
    Problemas • Como testaresse filtro de forma isolada? • Ao invés de unitários, escrevemos testes de integração! • POROs para salvar o dia!
  • 26.
    Filters class UsersController <ApplicationController before_filter ComplexFilter.new end class ComplexFilter def filter(controller) # Agora sim! end end
  • 27.
    Presenters • Extração delógica da view. • Camada extra entre o Controller e a View.
  • 28.
    Código class UsersController< ApplicationController def show @user = User.find(params[:id]) end end <% if @user.active? %> <%= @user.activated_at.strftime("%d/%m/%y") %> <% else %> <%= @user.created_at.strftime("%d/%m/%y") %> <% end %>
  • 29.
    Presenter class UserPresenter attr_accessor :user def initialize(user) @user = user end def activity_date if @user.active? @user.activated_at.strftime("%d/%m/%y") else @user.created_at.strftime("%d/%m/%y") end end end
  • 30.
    Refatorando class UsersController <ApplicationController def show @user = User.find(params[:id]) @user_presentter = UserPresenter.new(@user) end end <%= @user_presentter.activity_date %>
  • 31.
    Mas, e osHelpers? • Helpers são somente funções • Presenters são “Helpers” orientados a objeto! • Responsabilidades extras: controle de cache, etc.
  • 32.
    Break Out Method Object • Extraído do Livro: Working Effectively With Legacy Code • A idéia é simples, transformar métodos complexos em objetos. • Crie um novo objeto, passando os parametros necessários para o construtor, e copiando o corpo do método
  • 33.
    Exemplo.. class Order def calculate_shipping # blablabla, method with 50 lines. end end
  • 34.
    Refatorando class ShippingCalculator attr_accessor :order def initialize(order) @order = order end def value # copy and paste end end
  • 35.
    Model class Order def calculate_shipping ShippingCalculator.new(self).value end end
  • 36.
    Pontos Positivos • Removendoresponsabilidades do model • Método gigante pode ser quebrado em métodos menores • Testado de forma isolada
  • 37.
    Dependency Injection • Reduzacoplamente entre objetos • Objeto devem focar em suas responsabilidades, tendo suas dependências bem definidas. • Aplicado extensivamente em Java/C# • Por que não usar em Ruby?!
  • 38.
    Exemplo class UserUpdaterService attr_accessor :user def initialize(user_id) @user = User.find(user_id) end def update # code for my update method. end end UserUpdaterService.new(params[:id])
  • 39.
    Problemas • o UserUpdaterdepende do usuário • para testar, teriamos que criar um novo usuário no banco, e passar a id dele para o UserUpdater • Ruim e lento :(
  • 40.
    Refatorando class UserUpdaterService attr_accessor :user def initialize(user) @user = user end def update # code for my update method. end end UserUpdaterService.new(User.find(params[:id])) # ou para testar UserUpdaterService.new(mock_model(User))
  • 41.
    mas, mocks?! • Euouvi falar que mocks são do mau! • O problema não são os mocks • São VOCÊ!
  • 42.
    Opiniões • To bea successful mockist, you must dislike mocks. • Isole os testes unitários, porém cubra eles com integração!
  • 43.
    Vantagens • Facilita testabilidade •Feature testada de forma isolada • Reuso de forma inteligente • Testes voando!
  • 44.
    #FicaADica • Railers acabaramesquecendo de OO • A beleza do Ruby está na sua OO • Não seja bitolado, pense diferente :) • Tente, experimente, pense.. e veja o que funciona para você!
  • 45.
    Leituras Recomendadas • Blogdo Steve Klabnik - http:// blog.steveklabnik.com/ • Blog do Cássio Marques - http:// cassiomarques.wordpress.com/ • Working Effectively with Legacy Code - http://goo.gl/DKrF0 • James on Software - http://jamesgolick.com/
  • 46.
    Extra #1 Três Regras para escrever callbacks: 1. Somente escreva um callback quando você souber o que está fazendo 2. Você não sabe o que está fazendo 3. Veja regra 1.
  • 47.
    Extra #2 classUser < ActiveRecord::Base def after_save Log.create(:class => "User", :attrs => attributes) end end @user = User.find(1) @user.save Será mesmo que está claro para o programador que após salvar o usuário, o log será criado?
  • 48.
    Obrigado! • thiago.pradi@gmail.com • www.thiagopradi.net •http://www.github.com/tchandy • http://www.twitter.com/thiagopradi