View Objects – The Way to Deal with Messy Rails Views

   Back to list

Why you should use View Objects?

As your view begins to contain more complex logic, you can encounter all kinds of problems. First, it can be problematic to test. Second, your code becomes less readable and harder to modify. Look at the example code below:

<table class="table table-hover">
  <thead>
    <tr>
      <th>Ordered date</th>
      <th>Items to order</th>
      <th>Vendor</th>
      <th>Term</th>
      <th>Discount</th>
      <th>Actions</th>
    </tr>
  </thead>
  <tbody>
    <% @orders.each do |order| %>
      <tr>
        <td><%= order.date.nil? ? "Not ordered" : order.date.strftime("%m/%d/%Y - %H:%M") %></td>
        <td><%= order.items.map(&:amount_ordered).sum %></td>
        <td><%= order.is_custom ? "Custom" : order.vendor.brand.name  %></td>
        <td><%= order.term %></td>
        <td><%= "#{(order.discount * 100).round(2)} %" %></td>
        <td>
          <%= link_to 'Show', order_path(order), class: 'btn btn-primary btn-xs' %>
          <% if has_order_permission? %>
            <%= link_to 'Destroy', order_path(order), class: 'btn btn-danger btn-xs', 
       method: :delete %>
          <% end %>
        </td>
      </tr>
    <% end %>
  </tbody>
</table>

It’s not so pretty, is it? There are several issues we can deal with.

At first glance there are too many if statements responsible for displaying either the model value or a default string. For instance, we sum values for an order’s associated model which is a rather bad solution. A view is not the proper place for any computations. The next thing is formatting data, which should be done first and then displayed in a view.

Common mistakes you should avoid to keep your views clean:

  • Putting computations in the views. This makes code non reusable and hard to debug. It also can cause performance issues. Templates should only display data.
  • Fetching data from a database.
  • Using complex nested data. You should avoid expressions that have more than one dot like order.vendor.brand.name.
  • Using many if statements that handle the logic for displaying values.

Don’t extract Mixins from bloated models

We can encapsulate all view related logic in models, however ideally, your Rails models shouldn’t include any code for presenting and displaying its attributes.

It’s a good idea to separate the logic that is only used in views to another class rather than messing up ActiveRecord models. We can define the logic inside modules and include them into a model. Sure, it looks cleaner, but we still have one giant monster full of dependencies, that are hard to identify.

Rails helpers

Rails has built-in mechanism to deal with view-related logic which is View Helpers (app/helpers/*_helper.rb). You can easily put methods in a global namespace and use them inside your views. Sounds great, doesn’t it? However, there’s nothing object-orientated about them. Moving that logic into helpers may be OK when your application isn’t too complex.

There are several problems with helpers:

  • Helpers are included into all views, so we have to pay attention to naming conflicts and unintended behaviour.
  • Helpers are modules and they don’t have access to an object. You’re forced to constantly pass objects to method (e.q formatted_ordered_date(order)).
  • Helper functions defined inside modules can’t be inherited.

As the view-related logic doesn’t exactly belongs to a model nor a helper, the view object comes in. They give you object oriented way to represent display logic for model by adding another layer of abstraction. This approach gives us much cleaner code and ensures maintainability, because the logic is now encapsulated.

There are many solutions that already exist for this problem.

Note: You can meet different names refering to View Objects such as Presenters or Decorators. Strictly speaking they are slightly different. As their responsibilities are quite similar, the names are often replaced with each other, but generally we can call them View Objects.

Let’s check out how we can implement view object pattern in our applications.

Decorators

Decorators allow you to separate presentation-related logic and add additional behaviour to an individual objects. It’s very handy, because we can add any behaviour we want to a model instance (order in our example), before passing it to a template from a controller. We can decide ourselves in which context the instance will use the added behaviour as a result of the decoration.

Draper is a popular gem that provides a well-defined decorator pattern. Let’s find out what features it offers.

According to Github source, decorators are the ideal place to:

  • format complex data for user display
  • define commonly-used representations of an object, like a name method that combines first_name and last_nameattributes
  • mark up attributes with a little semantic HTML, like turning a url field into a hyperlink

Add Draper to your Gemfile.

gem ‘draper’

Then install and run the generator for the model you’d like to decorate.

$ bundle install
$ rails generate decorator Order

It will automatically create app/decorators directory when you can find the newly created OrderDecorator file.

First, we will eliminate if statements from the view:

app/decorators/order_decorator.rb

class OrderDecorator < Draper::Decorator
  delegate_all

  def formatted_ordered_date
    object.date.nil? ? "Not ordered" : object.date.strftime("%m/%d/%Y - %H:%M")
  end

  def vendor_name
    object.is_custom ? "Custom" : object.vendor.brand.name
  end
end

All methods of the source Order are available on the decorator by the delegate_all call.

The next issue we met is a summing ordered items amount for each order. Any kind of computations used only in a template should be moved to a decorator class as well. Formatting the model’s attributes also doesn’t belong to either a model (it’s not business logic) or a view (it’s data sensitive). So let’s create separate methods for them.

class OrderDecorator < Draper::Decorator
  ...
  
  def ordered_items_amount
    object.items.map(&:amount_ordered).sum
  end

  def discount_percent
    "#{(object.discount * 100).round(2)} %"
  end
end

Draper gem provides an easy access to helpers within a decorator using h method.

	h.content_tag(:strong, "Hello there!")

Including Draper::LazyHelpers module inside your decorators gives you direct access to the helpers. After that we’re capable of adding links to the template with no need to add h before link_to expression. We get access to routes paths and a helper method that check a user permission.

class OrderDecorator < Draper::Decorator
  include Draper::LazyHelpers
  ...

  def link_show
    link_to 'Show', order_path(object), class: 'btn btn-primary btn-xs'
  end

  def link_destroy
    if has_order_permission?
      link_to 'Destroy', order_path(object), class: 'btn btn-danger btn-xs', method: :delete
    end
  end
end

Decorate anything, a collection or a single object

The last we have to do is to decorate orders collection in our controller:

class OrdersController < Admin::BaseController
  def index
    @orders = Order.all.includes(:items, :vendor, vendor: [:brand]).order('date desc')
    @orders = OrderDecorator.decorate_collection(@orders)
  end
end

You don’t have to decorate all collections at once. You can just decorate a single object:

@order = OrderDecorator.decorate(Order.first)

In the view, you use the decorator in exactly the same way as you would have used the model. Let’s look at result of using decorator pattern:

<table class="table table-hover">
  <thead>
    <tr>
      <th>Ordered date</th>
      <th>Items to order</th>
      <th>Vendor</th>
      <th>Term</th>
      <th>Discount</th>
      <th>Actions</th>
    </tr>
  </thead>
  <tbody>
    <% @orders.each do |order| %>
      <tr>
        <td><%= order.formatted_ordered_date %></td>
        <td><%= order.ordered_items_amount %></td>
        <td><%= order.vendor_name %></td>
        <td><%= order.term %></td>
        <td><%= order.discount_percent %></td>
        <td>
          <%= order.link_show %>
          <%= order.link_destroy %>
        </td>
      </tr>
    <% end %>
  </tbody>
</table>

Now, we can see got the markup is structured. Our view is definitely more readable and easier to understand.

SimpleDelegator

If you don’t want to add a gem to your app you can simply use SimpleDelegator class from Ruby’s standard library to implement the decorator pattern. The SimpleDelegator implements the method_missing method that ruby calls when couldn’t find a method name or a variable for delegated object.

Let’s try to decorate an object this way!

Inside your app/decorators folder create your decorator class that inherits from SimpleDelegator. In our case, it will be OrderDecorator (app/decorators/order_decorator.rb). Then add methods that will be accessible for the passed object.

class OrderDecorator < SimpleDelegator
  def formatted_ordered_date
    date.nil? ? "Not ordered" : date.strftime("%m/%d/%Y - %H:%M")
  end

  def vendor_name
    is_custom ? "Custom" : vendor.brand.name
  end

  def ordered_items_amount
    items.map(&:amount_ordered).sum
  end

  def discount_percent
    "#{(discount * 100).round(2)} %"
  end
end

Looks similar, doesn’t it? The difference is that now we’re able to invoke to model properties and methods directly. As we pass an object into the constructor, SimpleDelegator delegates all method calls to the object so we don’t have to worry about attributes.This means when we call discount in the OrderDelegator, SimpleDelegator class delegates the property to the order object.

But what if we need to use helpers inside our new decorator? The simple solution is to create ViewHelpers module…

module ViewHelpers
  def h
    ActionController::Base.helpers
  end

  def routes
    Rails.application.routes.url_helpers
  end 
end

..and then include the module inside OrderDecorator.

class OrderDecorator < SimpleDecorator
   include ViewHelpers
   …

    def link_show
       h.link_to 'Show', routes.order_path(self), class: 'btn btn-primary btn-xs'
    end
end

Often it turns out you need to decorate all collections of objects, not only one. Perhaps it will be useful many times, so let’s put the method inside newly created BaseDecorator and then allow our OrderDecorator inherit from it.

class BaseDecorator < SimpleDelegator
  def self.wrap_collection(collection)
    collection.map { |obj| new obj }
  end
  ...
end
class OrderDecorator < BaseDecorator
  ...
end

Back to Rails. That’s how controller calls look like:

# single object decoration
@order = OrderDecorator.new(order)
# collection decoration
@orders = OrderDecorator.wrap_collection(orders)

That’s it with SimpleDelegator!

Presenters

You’re likely to use presenters when you need to aggregate data from various models that are not necessarily connected to each other or you have to perform complex data manipulations before displaying a view. They are very powerful tool, and it’s worth devoting a separate article for them.

Why you should use View Object Pattern?

  • View Objects allow you to encapsulate all view related logic and keep both models and views tidy.
  • As View Objects are object-oriented you can use all OOP features that Ruby gives you e.q. inheritance.
  • Testing logic in views is really problematic. View Objects are just ruby classes so they’re easy to test.

Hopefully, you enjoyed this brief article and you’ve gained a better understanding of View Objects. If you have any questions or some advice don’t hesitate to contact me. Have a wonderful day full of clean code 😉

Send this to a friend