Rails Decorators for Views

Ademar Tutor | Aug 17, 2013




What are Decorators? Decorators help extract view-specific business logic from models into decorator class. Here is an example code that puts view-specific business logic in the model:

# app/models/user.rb

class User  
  include Mongoid::Document

  def is_top_twenty?
    User.desc(:points).limit(20).include? self
  end
end

# app/controllers/profiles_controllers.rb

class ProfilesController < ApplicationController  
  def show
    @user = User.find(params[:id])
  end
end

/* app/views/profiles/show.html.erb *.

<% if @user.is_top_twenty? %>  
  <%= "#{@user.name} is in the top twenty" %>
<% end %>

The problem with this code is that the method is_top_twenty?, which is only being used in the view is being declared in the User model. The User model is polluted with view-related logic.

One way to refactor this is to use decorators. By using decorators. we can remove the is_top_twenty? method out of the model User and create a UserDecorator class.

# app/decorators/user_decorator.rb
class UserDecorator  
  attr_reader :user

  def initialize(user)
    @user = user
  end

  def is_top_twenty?
     User.desc(:points).limit(20).include? user
  end
end

Instead creating an instance variable of the User model in the controllers, we create an instance of UserDecorator class.

# app/controllers/profiles_controllers.rb

class ProfilesController < ApplicationController  
  def show
    user = User.find(params[:id])
    @user_decorator = UserDecorator.new(user)
  end
end  

Now let’s see this in action on the view.

/* app/views/profiles/show.html.erb */

<% if @user_decorator.is_top_twenty? %>  
  <%= "#{@user_decorator.name} is in the top twenty" %>
<% end %>  

The code above will return an error since we don’t have the method for name yet. To fix this, you need to implement ‘method_missing’ function.

# app/decorators/user_decorator.rb

class UserDecorator  
  attr_reader :user

  def initialize(user)
    @user = user
  end

  def is_top_twenty?
     User.desc(:points).limit(20).include? user
  end

  def method_missing(method_name, *args, &block)
    post.send(method_name, *args, &block)
  end
end  

That’s it! Good luck on cleaning up your models.