Presenter from Railscasts

Posted Almost 8 years ago. Visible to the public.

Let's say we have a user page with different textual and graphical information. Usually it means there will be a lot of Ruby code with conditional logic inside .erb template. Sure it is not a good thing and will create a significant overhead during future application changes if we want to maintain our application. One of the good solutions is to use presenters. Now we are going to build such presenter from scratch without a help of gems which are quite good (e.g. Draper)

Let's start from the controller:

class UsersController < ApplicationController
  def index
    @users = User.all
  end

  def show
    @user = User.find params[:id]
  end
end

As you can see this is the simple well known controller. It has two actions to show users list and to render personal details for the specific user. The next thing is the show.erb template:

<% present @user do |user_presenter| %>
  <div id="profile">
    <%= user_presenter.avatar %>
    <h1><%= user_presenter.linked_name %></h1>
    <dl>
      <dt>Username:</dt>
      <dd><%= user_presenter.username %></dd>
      <dt>Member Since:</dt>
      <dd><%= user_presenter.member_since %></dd>
      <dt>Website URL:</dt>
      <dd><%= user_presenter.website %></dd>
      <dt>Twitter Name:</dt>
      <dd><%= user_presenter.twitter %></dd>
      <dt>Bio:</dt>
      <dd><%= user_presenter.bio %></dd>
    </dl>
  </div>
<% end %>

Wow! That's it? Yep. All the logic and rendering of the user data are now inside the user presenter. What is present method? This is a helper method located in ApplicationController in order to be accessible from all views.

def present(object, presenter_class_name = nil)
  presenter_class_name ||= "#{object.class}Presenter".constantize
  presenter = presenter_class_name.new(object, self)
  yield presenter if block_given?
  presenter
end

The code is straight forward and speaks for itself but why this method is here? Shouldn't be better to create a presenter in UsersController#show action? We could do it and it a quite popular approach. With Draper and other presenter libraries it’s common to do this in a controller action, but we’re not going to take that approach here as it’s arguable the controllers shouldn’t be aware of presenters at all.
Ok, we understand the code from the above but what about the presenter itself? How does it look? What are the methods and what is their output?

class UserPresenter < BasePresenter
  presents :user
  delegate :username, to: :user

  def avatar
    link_to_if user.url.present?, image_tag("avatars/#{avatar_name}", class: "avatar"), user.url
  end

  def linked_name
    site_link(user.full_name.present? ? user.full_name : user.username)
  end

  def member_since
    user.created_at.strftime("%B %e, %Y")
  end

  def website
    handle_none user.url do
      link_to user.url, user.url
    end
  end

  def twitter
    handle_none user.twitter_name do
      link_to user.twitter_name, "http://twitter.com/#{user.twitter_name}"
    end
  end

  def bio
    handle_none user.bio do
      markdown user.bio
    end
  end

  private

  def avatar_name
    if user.avatar_image_name.present?
      user.avatar_image_name
    else
      'default.png'
    end
  end

  def handle_none(value)
    if value.present?
      yield
    else
      content_tag :span, 'None given', class: 'none'
    end
  end

  def site_link(content)
    link_to_if(user.url.present?, content, user.url)
  end
end

Do you see BasePresenter class? It includes methods that are common and should be inherited by all presenters we are going to add. For example presents method or initializer. Let's see how does it look.

class BasePresenter
  class << self
    def presents(name)
      define_method(name) do
        @object
      end
    end
  end

  def initialize(object, template)
    @object   = object
    @template = template
  end

  def markdown(text)
    Redcarpet.new(text, :hard_wrap, :filter_html, :autolink).to_html.html_safe
  end

  def method_missing(*args, &block)
    @template.send(*args, &block)
  end
end

First of all its initialize method gets two arguments: an instance of a class we want to "decorate" or to "present" and a template which gives us an access to helper methods such as link_to. Do you remember presents :user we have seen earlier in the user presenter? Well now you see how this class method looks and it simply creates a method in run time based on the name of the presented instance which in our case @user. Besides that we have method_missing method and it is responsible to handle all helper methods like link_to, content_tag etc. Basically that is it.

The one thing although that might be important. If we want to use our presenter not only from views but in controller as well we need to add something to Applicationcontroller.

class ApplicationController < ActionController::Base
# ...
protected

def present(object, presenter_class_name = nil)
  presenter_class_name ||= "#{object.class}Presenter".constantize
  presenter_class_name.new(view_context, object)
end
# ...
Alexander M
Last edit
Over 7 years ago
Alexander M
Posted by Alexander M to Ruby and RoR knowledge base (2016-06-09 12:39)