Posted almost 6 years ago. Visible to the public.

Presenter from Railscasts

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:

Copy
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:

Copy
<% 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.

Copy
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?

Copy
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.

Copy
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.

Copy
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 # ...

Owner of this card:

Avatar
Alexander M
Last edit:
almost 6 years ago
by Alexander M
Tags:
Software-Architecture
Posted by Alexander M to Ruby and RoR knowledge base
This website uses short-lived cookies to improve usability.
Accept or learn more