SlideShare une entreprise Scribd logo
1  sur  75
Design Patterns em Ruby

Guilherme Garnier
@guilhermgarnier
blog.guilhermegarnier.com
rails new projeto
app
├── assets
│   ├── images
│   ├── javascripts
│   └── stylesheets
├── controllers
├── helpers
├── mailers
├── models
└── views
rails new projeto
app
├── assets
│   ├── images
│   ├── javascripts
│   └── stylesheets
├── controllers
├── helpers
├── mailers
├── models
└── views
●

Model
●
●

●

Regras de negócio
Persistência

View
●

●

Exibição de dados

Controller
●

Comunicação entre
model e view
# controller
@categoria = Categoria.find(params[:id])
# view
<img src="<%= @categoria.foto %>" />
...
<h2><%= @categoria.nome %></h2>
<p><%= @categoria.descricao %></p>
# view
<nav class="breadcrumb">
<a href="/receitas">receitas.com</a>
<span> › </span>
<a href="/receitas/massas">massas</a>
<span> › </span>
<span>receitas de lasanha</span>
</nav>
Onde colocar essa lógica?
No model?
●

●

Gerar markup não é responsabilidade do
model
“Single Responsibility Principle” (SRP)
No controller?
●

●

Controllers devem fazer a comunicação
entre models e views
Controllers devem ser “magros”
Na view?
●
●

Código complexo
Difícil de testar
HELPERS
module CategoriaHelper
def breadcrumb(categoria)
...
end
end
# view
<%= breadcrumb(@categoria) %>
module CategoriaHelper
def breadcrumb(categoria)
...
end
def links_subcategorias(categoria)
...
end
def descricao_resumida(categoria)
...
end
end
Será que ninguém passou por
esse problema antes?
DECORATOR
class CategoriaDecorator
attr_reader :categoria
def initialize(categoria)
@categoria = categoria
end
def breadcrumb
...
end
end
# controller
categoria = Categoria.find(params[:id])
@categoria_decorator =
CategoriaDecorator.new(categoria)
# view
<%= @categoria_decorator.breadcrumb %>
<%= @categoria_decorator.categoria.nome %>
module Decorator
attr_reader :model
def initialize(model)
@model = model
end
def method_missing(meth, *args)
if @model.respond_to?(meth)
@model.send(meth, *args)
else
super
end
end
def respond_to?(meth)
@model.respond_to?(meth)
end
end
class CategoriaDecorator
include Decorator
def breadcrumb
...
end
end
# view
<%= @categoria_decorator.breadcrumb %>
<%= @categoria_decorator.nome %>
Problemas com Decorator
Criar um único Decorator para
todas as views?
class CategoriaDecorator
def breadcrumb
…
end
def breadcrumb_admin
…
end
end
Criar um Decorator para
cada view?
class CategoriaDecorator
def breadcrumb
…
end
end
class CategoriaAdminDecorator
def breadcrumb
…
end
end
Criar um Decorator para
cada model?
class DestaquePrincipalDecorator; end
class DestaqueSecundarioDecorator; end
class TopReceitasDecorator; end
class TopChefsDecorator; end
class ReceitasEspeciaisDecorator; end
class CategoriaDestaqueDecorator; end
class HomeController
def show
@destaque_principal_decorator =
DestaquePrincipalDecorator.new(…)
@destaque_secundario_decorator =
DestaqueSecundarioDecorator.new(…)
@top_receitas_decorator =
TopReceitasDecorator.new(…)
@top_chefs_decorator =
TopChefsDecorator.new(…)
@receitas_especiais_decorator =
ReceitasEspeciaisDecorator.new(…)
@categorias_especiais_decorator =
CategoriasEspeciaisDecorator.new(…)
end
end
Criar um único Decorator
para vários models?
Presenter
Exhibit
Presenter
●
●

Um Decorator que decora vários objetos
Também conhecido por outros nomes, como
View Object
class Home
def initialize(destaques, top_receitas, top_chefs, categorias)
end
def top_receitas
# só exibe receitas com foto
end
end
# view
<%= @home.top_receitas.each do |top| %>
<%= render partial: "top_receita", locals: {receita:
top.receita, favoritos: top.favoritos} %>
<% end %>
Exhibit
●

Semelhante ao Presenter

●

Inverte a lógica de visualização

●

O Exhibit é responsável por renderizar a view
class Home
def initialize(..., context)
@context = context
end
def render_top_receitas
@top_receitas.each do |top|
@context.render partial: "top_receita", locals: {receita:
top.receita, favoritos: top.favoritos}
end
end
end
# view
<%= @home.render_top_receitas %>
# controller
@home = Home.new(..., view_context)
# helper
@home = Home.new(..., self)
Onde colocar meus
Design Patterns no projeto?
app
├── assets
├── controllers
├── decorators
├── helpers
├── jobs
├── models
├── presenters
├── services
└── views
Qual é a melhor opção?
●

Não existe bala de prata

●

Analisar a melhor solução para cada caso

●

Não abusar de Design Patterns

●

Questão de gosto pessoal
# app/models/receita.rb
class Receita
include Mongoid::Document
field :nome
field :descricao
... # outros campos da receita
after_save :aplicar_medalhas, :indexar!
after_destroy :aplicar_medalhas, :indexar!
def aplicar_medalhas
Medalhas.apply_to(self.usuario)
end
def indexar!
Sunspot.index!(self)
end
...
# app/models/receita.rb
class Receita
include Mongoid::Document
field :nome
field :descricao
... # outros campos da receita
after_save :aplicar_medalhas, :indexar!
after_destroy :aplicar_medalhas, :indexar!
def aplicar_medalhas
Medalhas.apply_to(self.usuario)
end
def indexar!
Sunspot.index!(self)
end
...
# app/models/receita.rb
class Receita
include Mongoid::Document
field :nome
field :descricao
... # outros campos da receita
after_save :aplicar_medalhas, :indexar!
after_destroy :aplicar_medalhas, :indexar!
def aplicar_medalhas
Medalhas.apply_to(self.usuario)
end
def indexar!
Sunspot.index!(self)
end
...
# app/models/receita.rb
class Receita
def dar_rating(rating)
inc :soma_ratings, rating
inc :total_ratings, 1
end
def rating
return 0 if total_ratings == 0
soma_ratings / total_ratings
end
def serializable_hash
{:id => id.to_s,
:nome => nome,
:descricao => descricao,
:data_envio => data_envio.try(:to_date),
...
}
end
...
# app/models/receita.rb
class Receita
def dar_rating(rating)
inc :soma_ratings, rating
inc :total_ratings, 1
end
def rating
return 0 if total_ratings == 0
soma_ratings / total_ratings
end
def serializable_hash
{:id => id.to_s,
:nome => nome,
:descricao => descricao,
:data_envio => data_envio.try(:to_date),
...
}
end
...
# app/models/receita.rb
class Receita
def dar_rating(rating)
inc :soma_ratings, rating
inc :total_ratings, 1
end
def rating
return 0 if total_ratings == 0
soma_ratings / total_ratings
end
def serializable_hash
{:id => id.to_s,
:nome => nome,
:descricao => descricao,
:data_envio => data_envio.try(:to_date),
...
}
end
...
# app/models/receita.rb
class Receita
def self.busca(opts)
opts[:pagina] = 1 if opts[:pagina].to_i < 1
opts[:por_pagina] = DEFAULT_RESULTS if
opts[:por_pagina].nil?
Sunspot.search(self) do
with(:tipo_prato, opts[:tipo_prato]) if opts[:tipo_prato].present?
without(:foto_url, nil) if opts[:so_com_foto]
with(:quarentena, opts[:quarentena])
with(:is_deleted, false)
paginate :page => opts[:pagina].to_i, :per_page =>
opts[:por_pagina].to_i
end
end
end
# app/models/receita.rb
Sunspot.setup(Receita) do
text :nome, :boost => 10.0
text :descricao
text :tipo_prato
boost {foto.nil? ? 1.0 : 2.5}
boolean(:is_deleted) { destroyed? }
string(:tipo_prato) {tipo_prato.try(:to_slug)}
string(:foto_url) {foto.nil? ? nil : foto.url}
string(:enviada_por) {usuario_id}
end
Responsabilidades da
classe Receita
1. Representar as regras de negócio da receita
2. Mapear os dados da receita no banco
3. Disparar eventos após salvar/excluir receita
(aplicar medalhas e reindexar)
Responsabilidades da
classe Receita
4. Armazenar e calcular avaliações de receitas
feitas pelos usuários
5. Representar uma receita em JSON
6. Executar uma busca de receitas no Solr
7. Configurar o índice de receitas no Solr
Classe Receita refatorada
# app/models/receita.rb
class Receita
include Mongoid::Document
include Rateable
include Searchable
include Receitas::Converters
extend Receitas::Buscas
field :nome
field :descricao
... # outros campos da receita
end
# app/observers/receita_observer.rb
class ReceitaObserver < Mongoid::Observer
def after_save(receita)
Receitas::SolrIndexer.indexar(receita)
Medalhas.aplicar(receita.usuario)
end
def after_destroy(receita)
Receitas::SolrIndexer.indexar(receita)
Medalhas.aplicar(receita.usuario)
end
end
# config/application.rb
module Receitas
class Application < Rails::Application
config.mongoid.observers = :receita_observer
end
end
# app/models/receita/rateable.rb
class Receita
field :soma_ratings, :default => 0
field :total_ratings, :default => 0
module Rateable
def dar_rating(rating)
inc :soma_ratings, rating
inc :total_ratings, 1
end
def rating
return 0 if total_ratings == 0
soma_ratings / total_ratings
end
end
end
# app/models/receitas/converters.rb
module Receitas::Converters
def serializable_hash
{
:id => id.to_s,
:nome => nome,
:descricao => descricao,
:data_envio => data_envio.try(:to_date),
...
}
end
# app/models/receitas/buscas.rb
module Receitas::Buscas
def busca(opts)
opts[:pagina] = 1 if opts[:pagina].to_i < 1
opts[:por_pagina] = DEFAULT_RESULTS if
opts[:por_pagina].nil?
Receita.solr_search do
with(:tipo_prato, opts[:tipo_prato]) if opts[:tipo_prato].present?
without(:foto_url, nil) if opts[:so_com_foto]
with(:quarentena, opts[:quarentena])
with(:is_deleted, false)
paginate :page => opts[:pagina].to_i, :per_page =>
opts[:por_pagina].to_i
end
end
end
# app/models/receita/searchable.rb
class Receita
include Sunspot::Mongoid
module Searchable
included do
searchable do
text :nome, :boost => 10.0
text :descricao
text :tipo_prato
boost {foto.nil? ? 1.0 : 2.5}
boolean(:is_deleted) { destroyed? }
string(:tipo_prato) {tipo_prato.try(:to_slug)}
string(:foto_url) {foto.nil? ? nil : foto.url}
string(:enviada_por) {usuario_id}
end
end
end
end
Como refatorar um “mega model”?
●

Separar responsabilidades
“Single Responsibility Principle”
● Uma classe/módulo para cada responsabilidade
Vantagens
●

●

●
●
●

Mais fácil de testar
Mais fácil de compreender
Mais fácil de manter
Como refatorar um “mega model”?
●

A solução apresentada não é ideal
●
●

●

Usar mixins == herança
A classe Receita continua tendo muitas
responsabilidades e violando o SRP
Baby steps
Referências
Referências
●

●

●

●

●

blog.guilhermegarnier.com/2013/04/design-patterns-emruby-decorators-presenters-e-exhibits/
blog.steveklabnik.com/posts/2011-09-09-better-rubypresenters
robots.thoughtbot.com/post/14825364877/evaluatingalternative-decorator-implementations-in
blog.codeclimate.com/blog/2012/10/17/7-ways-todecompose-fat-activerecord-models/
www.slideshare.net/maurogeorge/model-of-colossus
opensource.globo.com
facebook.com/GloboDev
globodev.tumblr.com
@GloboDev
@guilhermgarnier
blog.guilhermegarnier.com
Slides: goo.gl/6FJU3e

Contenu connexe

Similaire à Design Patterns Ruby

Programando para programadores: Desafios na evolução de um Framework
Programando para programadores: Desafios na evolução de um FrameworkProgramando para programadores: Desafios na evolução de um Framework
Programando para programadores: Desafios na evolução de um FrameworkPablo Dall'Oglio
 
Aplicações rápidas para a Web com Django
Aplicações rápidas para a Web com DjangoAplicações rápidas para a Web com Django
Aplicações rápidas para a Web com DjangoFreedom DayMS
 
Workshop Ruby on Rails dia 2 ruby-pt
Workshop Ruby on Rails dia 2  ruby-ptWorkshop Ruby on Rails dia 2  ruby-pt
Workshop Ruby on Rails dia 2 ruby-ptPedro Sousa
 
LambdaDay: Backbone.js
LambdaDay: Backbone.jsLambdaDay: Backbone.js
LambdaDay: Backbone.jsGiovanni Bassi
 
Case studies about Layout & View States & Scale in Windows 8 Store Apps
Case studies about Layout & View States & Scale in Windows 8 Store AppsCase studies about Layout & View States & Scale in Windows 8 Store Apps
Case studies about Layout & View States & Scale in Windows 8 Store AppsComunidade NetPonto
 
Google Analytics Reporting API: Bebendo água direto da fonte
Google Analytics Reporting API: Bebendo água direto da fonteGoogle Analytics Reporting API: Bebendo água direto da fonte
Google Analytics Reporting API: Bebendo água direto da fonteJohann Vivot
 
TDC2016 Boas Práticas SQL em Banco Relacional para Desenvolvedores
TDC2016 Boas Práticas SQL em Banco Relacional para DesenvolvedoresTDC2016 Boas Práticas SQL em Banco Relacional para Desenvolvedores
TDC2016 Boas Práticas SQL em Banco Relacional para DesenvolvedoresFernando Franquini
 
Django: Desenvolvendo uma aplicação web em minutos
Django: Desenvolvendo uma aplicação web em minutosDjango: Desenvolvendo uma aplicação web em minutos
Django: Desenvolvendo uma aplicação web em minutosRodrigo Nossal
 
Aula 1 view model livedata e databinding.pptx
Aula 1   view model livedata e databinding.pptxAula 1   view model livedata e databinding.pptx
Aula 1 view model livedata e databinding.pptxRicardo Ogliari
 
Documentação CakePHP - Português Br
Documentação CakePHP -  Português BrDocumentação CakePHP -  Português Br
Documentação CakePHP - Português BrLuiz Ladeira
 
RubyConfBr 2015 - Rails & Javascript: faça isso direito
RubyConfBr 2015 - Rails & Javascript: faça isso direitoRubyConfBr 2015 - Rails & Javascript: faça isso direito
RubyConfBr 2015 - Rails & Javascript: faça isso direitoCezinha Anjos
 
Boas práticas de django
Boas práticas de djangoBoas práticas de django
Boas práticas de djangoFilipe Ximenes
 
Treinamento Básico de Django
Treinamento Básico de DjangoTreinamento Básico de Django
Treinamento Básico de DjangoLeandro Zanuz
 
Pense no futuro: PHP com Zend Framework
Pense no futuro: PHP com Zend FrameworkPense no futuro: PHP com Zend Framework
Pense no futuro: PHP com Zend FrameworkFlávio Lisboa
 
Como Perder Peso (no browser)
Como Perder Peso (no browser)Como Perder Peso (no browser)
Como Perder Peso (no browser)Zeno Rocha
 
Desenvolvendo aplicações web com o framework cakephp
Desenvolvendo aplicações web com o framework cakephpDesenvolvendo aplicações web com o framework cakephp
Desenvolvendo aplicações web com o framework cakephpRodrigo Aramburu
 

Similaire à Design Patterns Ruby (20)

Asp.Net Mvc Dev Days09 V3 Pt
Asp.Net Mvc Dev Days09 V3 PtAsp.Net Mvc Dev Days09 V3 Pt
Asp.Net Mvc Dev Days09 V3 Pt
 
Zend Framework
Zend FrameworkZend Framework
Zend Framework
 
Programando para programadores: Desafios na evolução de um Framework
Programando para programadores: Desafios na evolução de um FrameworkProgramando para programadores: Desafios na evolução de um Framework
Programando para programadores: Desafios na evolução de um Framework
 
Aplicações rápidas para a Web com Django
Aplicações rápidas para a Web com DjangoAplicações rápidas para a Web com Django
Aplicações rápidas para a Web com Django
 
Workshop Ruby on Rails dia 2 ruby-pt
Workshop Ruby on Rails dia 2  ruby-ptWorkshop Ruby on Rails dia 2  ruby-pt
Workshop Ruby on Rails dia 2 ruby-pt
 
LambdaDay: Backbone.js
LambdaDay: Backbone.jsLambdaDay: Backbone.js
LambdaDay: Backbone.js
 
Rails na prática
Rails na práticaRails na prática
Rails na prática
 
Case studies about Layout & View States & Scale in Windows 8 Store Apps
Case studies about Layout & View States & Scale in Windows 8 Store AppsCase studies about Layout & View States & Scale in Windows 8 Store Apps
Case studies about Layout & View States & Scale in Windows 8 Store Apps
 
Google Analytics Reporting API: Bebendo água direto da fonte
Google Analytics Reporting API: Bebendo água direto da fonteGoogle Analytics Reporting API: Bebendo água direto da fonte
Google Analytics Reporting API: Bebendo água direto da fonte
 
TDC2016 Boas Práticas SQL em Banco Relacional para Desenvolvedores
TDC2016 Boas Práticas SQL em Banco Relacional para DesenvolvedoresTDC2016 Boas Práticas SQL em Banco Relacional para Desenvolvedores
TDC2016 Boas Práticas SQL em Banco Relacional para Desenvolvedores
 
Django: Desenvolvendo uma aplicação web em minutos
Django: Desenvolvendo uma aplicação web em minutosDjango: Desenvolvendo uma aplicação web em minutos
Django: Desenvolvendo uma aplicação web em minutos
 
Aula 1 view model livedata e databinding.pptx
Aula 1   view model livedata e databinding.pptxAula 1   view model livedata e databinding.pptx
Aula 1 view model livedata e databinding.pptx
 
Documentação CakePHP - Português Br
Documentação CakePHP -  Português BrDocumentação CakePHP -  Português Br
Documentação CakePHP - Português Br
 
RubyConfBr 2015 - Rails & Javascript: faça isso direito
RubyConfBr 2015 - Rails & Javascript: faça isso direitoRubyConfBr 2015 - Rails & Javascript: faça isso direito
RubyConfBr 2015 - Rails & Javascript: faça isso direito
 
Boas práticas de django
Boas práticas de djangoBoas práticas de django
Boas práticas de django
 
Treinamento Básico de Django
Treinamento Básico de DjangoTreinamento Básico de Django
Treinamento Básico de Django
 
Ruby On Rails Regis
Ruby On Rails RegisRuby On Rails Regis
Ruby On Rails Regis
 
Pense no futuro: PHP com Zend Framework
Pense no futuro: PHP com Zend FrameworkPense no futuro: PHP com Zend Framework
Pense no futuro: PHP com Zend Framework
 
Como Perder Peso (no browser)
Como Perder Peso (no browser)Como Perder Peso (no browser)
Como Perder Peso (no browser)
 
Desenvolvendo aplicações web com o framework cakephp
Desenvolvendo aplicações web com o framework cakephpDesenvolvendo aplicações web com o framework cakephp
Desenvolvendo aplicações web com o framework cakephp
 

Design Patterns Ruby

Notes de l'éditeur

  1. - http://www.slideshare.net/damiansromek/thin-controllers-fat-models-proper-code-structure-for-mvc - Controllers devem ser “magros”, somente uma fachada para traduzir requests num formato que o model entenda - Models devem conter toda a lógica de negócio