50% de descuento en nuestra anualidad Premium: Canjear promo No me interesa

notifications Notificaciones

Marcar todas como leídas

Ver más

lightbulb_outline

Presenters en Rails con Decorators, qué, por qué y cómo

timer 4 Min.

remove_red_eye 3127

calendar_today 01/02/15

Antes de empezar a describir qué es lo qué es lo que haremos y cómo, voy a dar un escenario en el que vamos a usar este patrón, como muchos saben la página en la que están (Código Facilito) está construida con Ruby on Rails, así que podemos utilizar algún ejemplo de la página, ¿no? ¿Dónde usamos presenters? Bien, aquí un screenshot:

La parte importante aquí es notar como en los cursos premium se despliega el mensaje que indica que son premium, mientras que en los que no, se muestra la cantidad de alumnos inscritos. Todo bien hasta ahora, empecemos a implementarlo, la forma más común y simple es hacerlo desde la vista:

<% if curso.premium? %>
  Curso Premium
<%else%>
  <%= curso.alumnos %> alumnos
<%end%>

No parece ser cosa del otro mundo, y hasta ahora pareciera que todo está bien funcionando así... pero, aquí viene otro screenshot:

Digamos que se demuestra que esta lógica se encuentra en diferentes lugares en nuestras vistas. Un ejemplo más general que el de Código Facilito es el siguiente:

<% if articulo.published? %>
  Publicado <%= articulo.published_at.strftime('%m/%d/%Y') %>
<%else%>
  Borrador
<%end%>

Hasta ahora algunos ejemplos pero, ¿cuál es el problema con ellos? Simple, ¿qué pasaría si en Código Facilito decidieran que hay que cambiar y en lugar de alumnos serán suscritos? Bien, tendríamos que buscar todos los lugares en los que hemos colocado esa lógica y sustituir la palabra alumnos por suscritos, ignorando por completo el patrón DRY (Don't Repeat Yourself) .

Ahora, ok, creo entender cuál es el problema, ¿cómo se soluciona? Bueno, generalmente, sobre todo cuando estamos iniciando a resolver estos problemas, podríamos hacer lo siguiente:

# models/curso.rb
class Curso < ActiveRecord::Base
  has_many :videos
  def alumnos_value
    if curso.premium?
      "Curso Premium"
    else
      "#{curso.alumnos} alumnos"
    end
  end
end

Y sustituimos esto:

<% if curso.premium? %>
  Curso Premium
<%else%>
  <%= curso.alumnos %> alumnos
<%end%>

Por esto:

<%= curso.alumnos_value %>

Que se ve mucho más limpio y además la lógica no está repetida, por lo que si se decidiera cambiar alumnos por suscritos, sólo tendríamos que cambiarlo en la llamada al método alumnos_value y listo, se actualizan las vistas.

Genial, ¡listo!, en realidad... no. El enfoque de un método en el modelo está mal porque rompe el MVC la lógica de la vista no debería estar en el modelo.

Y aquí, luego de tanta descripción es cuando viene al rescate el patrón de los Presenters. Un presenter es un objeto que extiende al objeto del modelo con los métodos relativos a la vista, de esta manera respetamos el MVC y encapsulamos la lógica de la vista en un objeto.

Y bien, ¿cómo funciona? En Ruby existe algo que se conoce como Decorators cuya funcionalidad es decorar un objeto, veamos cómo.

require 'delegate'

class CursoDecorator < SimpleDelegator
 def alumnos_value
  if curso.premium?
   "Curso Premium"
  else
    "#{curso.alumnos} alumnos"
  end
 end
 def curso
  __getobj__
 end
end

Simplemente heredamos de SimpleDelegator, y definimos los métodos que queramos que son relativos de la vista, nota cómo accedemos al objeto *decorado con getobj, por motivos de legibilidad creamos un método curso que retorna el objeto.

Genial, todo bien hasta ahora, la pregunta es ¿cómo lo integro con Ruby on Rails? Aquí la respuesta, a partir de Rails 4 uno puede crear folders dentro de /app, y utilizarlos en nuestra aplicación. Así que por qué no creamos un folder decorators dentro de app, para tener algo así:

En este nuevo folder colocamos un archivo curso_decorator.rb y ahí colocamos el código que mostramos arriba. Ya casi está todo listo, sólo necesitamos abrir nuestro archivo cursos_controller.rb y sustituimos lo siguiente:

#/app/controllers/cursos_controller.rb
class CursosController < ApplicationController
  def show
    curso = Curso.find(params[:id])
    @curso = CursoPresenter.new(curso)
  end
end

Y en todas las vistas donde vayamos a colocar a los alumnos definimos lo siguiente:

<%= curso.alumnos_value %>

Lo cool de los decorators es que podemos mandar a llamar cualquier método del objeto original, ya que si el nuevo objeto no implementa el método, va a buscarlo en el objeto que está decorando.

¡Genial! Todo listo. Sólo... una cosa más

Draper

Draper es una gema de Ruby on Rails que hace todo lo que hicimos anteriormente, pueden revisar la gema aqui. Básicamente el decorator se crea de la siguiente manera:

# app/decorators/course_decorator.rb
class CursoDecorator < Draper::Decorator
  delegate_all

  def alumnos_value
    if curso.premium?
      "Curso Premium"
    else
      "#{curso.alumnos} alumnos"
    end
  end
end

La principal diferencia con Draper es que en el controlador tendríamos algo así:

#/app/controllers/cursos_controller.rb
class CursosController < ApplicationController
  def show
    @curso = Curso.find(params[:id]).decorate
  end
end

Para usar Drape no hay que olvidar agregarlo al Gemfile:

gem 'draper', '~> 1.3'

Y hacer bundle install en la consola.

Conclusión.

  • Un presenter es un objeto con métodos que contienen lógica de la vista.
  • Una manera de implementar un presenter es utilizando los decorators de Ruby.
  • Alternativamente, puedes utilizar Draper, una gema que implementa los decorators.
  • Todo esto, nos permite encapsular la lógica de la vista en un objeto de modo que si necesitamos hacer cambios en la presentación sólo se hacen en un lugar, y no tenemos que estar buscando en todas los archivos de la vista para actualizar.

Por último, no olviden dejar sus comentarios aquí abajo con dudas, o sugerencias. Cambios posibles en el artículo, etc. :)

Otros artículos del blog

Comunidad