DEV Community

Cover image for The Decorator Pattern: A Ruby On Rails approach for data presentation logic
Henrique Caltram
Henrique Caltram

Posted on

The Decorator Pattern: A Ruby On Rails approach for data presentation logic

A bit of context

While working on a new Ruby on Rails pet project of my own, a very common decision-making situtation for projects like this one arose:

  • How should one deal with presentation-related logic of model data in a way that differs from the its standard persisted format?

In this case, the data to be presented was basically an automatically calculated field (for the sake of this article I will call it calculated <Float>) which depended on two other user-input fields which were also persisted into the DB: input_a <Float> and input_b <Float>.

Normalization

To go forward in common terms, let's assume for this article that:

  • We are dealing with a model called Generic;
  • This model has a GenericsController controller class;
  • The associated action view template is show.html.erb;

The problem

Interface-wise, the expected way to render calculated's value was in a 1/N ratio format, in which 1 was the common nominator while N was a variable denominator. As expected, this would require a bit of raw data presentation treatment logic put into calculated's value.

If we had, let's say, calculated = 0.075, then:

# object.calculated = 0.075 => object.presentable_calculated = 1/13
Enter fullscreen mode Exit fullscreen mode

The straight-forward possible solutions

The following list (not an exhaustive one) presents some of the most common solving possibilities for the problem:

  • Exposing a @presentable_calculated instance variable into the related action methods on GenericsController, so that they are available to the related action view templates;
class GenericsController < ApplicationController
  # ... Boilerplate code

  def show
    @generic = Generic.find(params[:generic_id])
    @presentable_calculated = begin do
      denominator = 1 / @generic.calculated

      "1/#{denominator}"
    end
  end
end
Enter fullscreen mode Exit fullscreen mode
  • Implementing a GenericsHelper module to center the formatting logic:
module GenericsHelper
  def presentable_calculated(calculated)
    denominator = 1 / calculated

    "1/#{denominator}"
  end
end
Enter fullscreen mode Exit fullscreen mode
  • Implementing the #presentable_calculated method into the Generic model itself:
class Generic < ApplicationRecord
  # ... Boilerplate code

  def presentable_calculated
    denominator = 1 / calculated

    "1/#{denominator}"
  end
end
Enter fullscreen mode Exit fullscreen mode

While functional at first glance, each of these has its own downsides:

The Controller solution:

Exposing a new @presentable_calculated instance variable into the controller means highly coupling view needs with controller action implementation.

A general action method (#show in this example) would then have and grant access to a view-specific data. This approach possibly breaks a number of clean code/design patterns good practices, the clearest of them being the single responsibility principle.

The Helper solution:

While indeed helpful, Rails Helpers tend to become cluttered over time if not used carefully for a simple reason: they start to feel like the go-to solution when a piece of code doesn't seem to fit into any other place in your codebase.

The anti-pattern can start to take its toll when we notice that, by default, Rails Controllers and view templates all have access to all of the existing Helper modules in your codebase.

The Model solution:

Close to what we discussed in The Helper Solution section, Rails models can also start to become cluttered with code that seems to belong but, in reality, don't.

In object oriented-programming, this is what we call The God class (god object) anti-pattern: one or more classes which hold too many responsibilities.

Take the current presentation context we're into, moving the presentation logic to a model class would mean centering view-specific needs into a class which might already know more than enough, mixing different domains in a single place. Yet another SOLID single responsibility violation (among other possible issues).

An alternate solution: The Decorator Pattern

This is where the Decorator Pattern (also known as Wrapper) comes in place.

The main idea behind the technique is:

  • Wrap your original object around a stack of wrappers, each of which implement the needed API (either with the same or different signatures), thus extending your original object with extra behaviors.

In terms of our Ruby code, that would mean something like the following:

# app/models/generics.rb
class Generic < ApplicationRecord
  # Inner implementations

  def decorator
    GenericsDecorator.new(self)
  end
end

# app/decorators/generics_decorator.rb
class GenericsDecorator
  attr_reader :object

  def initialize(object)
    @object = object
  end

  def presentable_calculated
    denominator = 1 / calculated

    "1/#{denominator}"
  end
end
Enter fullscreen mode Exit fullscreen mode

Then, in a hypothetical view:

<!--app/views/generics/show.html.erb-->
<div>
  <p class="bleus_class">
    <% = @generic.decorator.presentable_calculated %>
  </p>
</div>
Enter fullscreen mode Exit fullscreen mode

With this solution, we avoid the aforementioned pitfalls of different possibilities while respecting the open-closed principle: the Generic class was extended, not changed, to handle the view specific needs. It's also kept separate from the responsibility of implementing the #presentable_calculated logic, respecting the separate responsibilities principle.

As a result, GenericsController remains as a sole request handler class and the view's presentation logic is also kept lean.

The already invented wheel: the Draper gem

The ruby gems community already has an existing gem aimed at solving this same problem: Draper.

Take your time and explore the documentation in order to learn more about the topic.

Top comments (0)