Rails Decorators using the Draper gem

Rails models are getting fatter and fatter and the complexity of helper classes increases.
train rails countryside

You might have experienced this yourself: Over time, Rails models are getting fatter and fatter and the complexity of helper classes increases.

It is good practice to keep complicated logic away from your views. But dealing with a lot of helper methods is also tedious and adding view-related logic to the model is not an option either.

The Decorator design pattern helps to solve this problem.


Draper decorators

There is a nice gem called Draper that facilitates this.

In our example, a decorator comes as another level of abstraction between the model and the view.

class PersonDecorator < Draper::Decorator
  delegate_all

  def full_name
    "#{firstname} #{lastname}"
  end
end

@person = Person.new(firstname: 'John', lastname: 'Doe').decorate

@person.full_name # 'John Doe'
@person.firstname # 'John'

Calling #decorate on a model returns a decorated instance. We now have access to the #full_name method.

Delegation

The delegate_all call ensures that model methods are available in an instance of the decorator as well. You can use a decorated object the same way as a model.

Draper also takes care that the class of a decorated instance is set to the class of the model. That way, all Rails helpers like e.g. form_for work as expected when you pass a decorated instance. In fact the decorated instance behaves and looks like a regular model instance.

@person = Person.first
@decorated_person = @person.decorate

@decorated_person.is_a?(Person) # true
@decorated_person.is_a?(PersonDecorator) # true

@person == @decorated_person # true

Accessing helper modules

Additionally, a decorator has access to Rails helper modules via the helper proxy method. This is useful when you want to hide complex logic from the templates.

class PersonDecorator < Draper::Decorator
  delegate_all

  def author_headline
    if author == helper.current_user
      h.content_tag(:h3, 'You')
    else
      h.content_tag(:h3, author.name)
    end
  end
end

Now in your template you just call @person.author_headline and you are done. No conditions in the template. Your designers will be thankful!

More on decorators

To find out more about alternative implementations and internals of creating decorators in Ruby, I recommend to read Dan Croak’s article at Giant robots smashing into other giant robots

Photo by Tiago Gerken on Unsplash

Want to join our Engineering team?
Apply today!
Share: