Views Orientadas a Objeto com Decorators

Você já se pegou criando muitos helpers em aplicações Rails?

Os helpers nos ajudam oferecendo um recurso extra para trabalharmos a lógica da apresentação de dados. Porém, eles não são orientados a objeto, isso significa que podemos estar escrevendo código desnecessariamente e de maneira não elegante neles.

Case

Hoje estava trabalhando num projeto e me deparei com o seguinte cenário: eventos podem ter um preço mínimo e um preço máximo, ou apenas um dos dois. Como lidar com essa lógica na apresentação de dados?

Utilizar Helpers (wrong way)

A primeira (e talvez) mais comum solução seja criar uma função em um helper para cuidar disso:

# app/helpers/events_helper.rb
module EventsHelper
  def price(event)
    [event.min_price, event.max_price].map do |c| 
      number_to_currency(c) if c.present?
    end
    .reject(&:blank?).join(' - ')
  end
end

# app/views/whatever/view.haml
= price(@event)

Aqui, recebemos a instância do evento como parâmetro e retornamos uma string com os preços presentes formatados e separados por -.

Mas, se essa lógica de apresentação faz parte de todos eventos, porque devo ficar passando toda hora o evento que desejo como parâmetro? Isso cheira mal.

Utilizar Decorators

Em programação orientada a objetos, existe um design pattern chamado decorator, ele serve para adicionar responsabilidades há um objeto, extendendo assim suas capacidades.

Existe uma outra nomenclatura também, chamada presenter, ela nada mais é do que um decorator, só que unicamente responsável por extender funcionalidades de lógica de apresentação.

Mas independente do nome que você chamar, já sabemos a sua função e daqui pra frente irei chamar de decorators.

Por padrão, o Rails não vem com nada pronto para trabalhar com esse design, mas existem várias gemas criadas pela comunidade que dão conta do recado.

Segundo o site "The Ruby Toolbox", a gema Draper é a mais utilizada para isso (inclusive o episódio #286 do Railscasts é sobre ela), porém, eu não gostei de ter que chamar o método decorate toda vez que estiver instanciando o objeto no controller.

Preferi a ActiveDecorator, que foi criada pelo Akira Matsuda (mesmo criador do Kaminari) e irei mostrar agora como utilizar:

# app/decorators/event_decorator.rb
module EventDecorator
  def price
    [min_price, max_price].map do |c| 
      number_to_currency(c) if c.present?
    end
    .reject(&:blank?).join(' - ')
  end
end

# app/views/whatever/view.haml
= @event.price

Veja a diferença! Não precisamos passar nenhum parâmetro para o método price, apenas chamamos os atributos min_price e max_price e o decorator já sabe que se trata de atributos do model Event.

Use o comando rails g decorator model (substituindo "model" pelo nome de seu modelo) para criar o decorator dentro da pasta app/decorators.

Conclusão

  • Use decorator quando helpers ou métodos do model dizerem respeito há um comportamento de apresentação de dados (criar tags HTML, utilizar helpers do Rails..) relacionados há um ou mais objetos.
  • Mantenha a lógica de apresentação em um único lugar
  • Utilize helpers apenas para lógicas que não dizem respeito há um objeto
  • Siga o SRP (Single Responsible Principle), cada um faz seu papel

Essa foi dica de hoje, deixe sua opinião nos comentários.

Abraços.

Written on January 29, 2014

Share: