Introduction

In this article we will show you how to use the presenter model in Ruby on Rails. This article is also the first article to contain a screencast. Feel free to leave feedback on the video below. There is no audio with this first screencast. I need a good quiet setup to do audio. If you WANT audio, please be sure to leave a comment so I know what you guys are looking for. Otherwise I will play around with audio in the near future.

Introducing Presenters

Wondering what presenters are? Presenters are simple classes that sit between the model and the view and provide provide a nice, clean, object oriented way of handling complex display logic. For example, in this article, we will cover a simple scenario. You have a model called Post that has a column called published_at which contains a date. We use certain display logic for the published_at column that is determined by whether it's null or not. If the date is null, you want to consider it to still be in 'draft' form and not ready for public use. However, if published_at isn't null, you want to display that date in a certain way. Presenters are perfect for this.

Rails Application Setup

The first thing we need to do is create the Post model mentioned earlier. To do this, first, either create a new Rails application or open an existing one. Then open a terminal and run the command listed below to create the post model.

Terminal Commands:

rails g model post title:string body:text published_at:timestamp

Now run a rake db:migrate to migrate the database.

Terminal Commands:

rake db:migrate

Next, we need to create a controller. For this example we will have a posts controller with an index action. Run the command below to make the controller now.

Terminal Commands:

rails g controller posts index

Now let's add some code to the controller to query the database and return all of the posts. Modify the PostsController so that it looks like the code listed below.

app/controllers/posts_controller.rb:

class PostsController < ApplicationController
  def index
    @posts = Post.order(published_at: :desc)
  end
end

Now that we are done with the controller, we need to make sure we set a site root so that the page loads when we start the Rails server. Modify your routes.rb file to look like the code listed below.

config/routes.rb:

Rails.application.routes.draw do
  get 'posts/index'

  root to: "posts#index"
end

Next, create a folder called presenters inside your app folder. The presenters folder will store your presenters.

Terminal Commands:

mkdir app/presenters

Next, we need to create a new class called BasePresenter. The BasePresenter class contains some code that will help us tie the presenter to the model and the view. Create a new file called base_presenter.rb inside the app/presenters and add in the code listed below.

app/presenters/base_presenter.rb:

class BasePresenter < SimpleDelegator
  def initialize(model, view)
    @model, @view = model, view
    super(@model)
  end

  def h
    @view
  end
end

Now we can create a presenter for the post model. Create a new file called post_presenter.rb inside the app/presenters folder and add in the code listed below.

app/presenters/post_presenter.rb:

class PostPresenter < BasePresenter
  def title
    @model.title.titleize
  end
  
  def publication_status
    if @model.published_at?
        h.time_ago_in_words(@model.published_at)
    else
      'Draft'
    end
  end
end

You'll notice that there are 2 methods. The first, title, takes the title and formats it to Proper Case. The second, publication_status, takes the published_at field and formats the date. If published_at is null it returns 'Draft' instead. That's the idea behind presenters, they allow you to pull the display logic out of the model and place it in the presenter, making things much cleaner.

Next we will create a helper method called present. The present method makes it easy for us to 'present' our presenter's methods to the view. Open up app/helpers/application_helper.rb and modify it so that it looks like the code listed below.

app/helpers/application_helper.rb:

module ApplicationHelper
  def present(model, presenter_class=nil)
    klass = presenter_class || "#{model.class}Presenter".constantize
    presenter = klass.new(model, self)
    yield(presenter) if block_given?
  end
end

Great, now finally, let's create our view. Open up the index view for your PostsController and modify it so that it looks like the code listed below.

app/views/posts/index.html.erb:

<h1>Posts</h1>
<table>
	<thead>
		<tr>
			<th>Id</th>
			<th>Title</th>
			<th>Body</th>
			<th>Published On</th>
		</tr>
	</thead>
	<tbody>
		<%- @posts.each do |p| %>
			<% present(p) do |post| %>
				<tr>
					<td><%= post.id %></td>
					<td><%= post.title %></td>
					<td><%= post.body %></td>
					<td><%= post.publication_status %></td>
				</tr>
			<% end %>
		<% end %>
	</tbody>
</table>

You'll notice the present method being used here. The present method automatically makes those 2 methods we created available to us in addition to our model's existing attributes. Pretty easy to use, right?

Now, before we start our Rails server, let's add some seed data to our database so that we have something to work with. Open up your db/seeds.rb file and modify it so that it looks like the code listed below.

db/seeds.rb:

Post.delete_all
Post.create!(id: 1, title: "Welcome to my site", body: "Welcome!  This is an example post", published_at: Time.now - 2.hours)
Post.create!(id: 2, title: "My Second Post", body: "Welcome!  This is another example post", published_at: Time.now - 1.hours)
Post.create!(id: 3, title: "Welcome to my site", body: "My third post", published_at: nil)

Now run rake db:seed to seed the database.

Terminal Commands:

rake db:seed

Now if you start a rails server and navigate to your development environment's web page, you'll see that the titles are properly formatted along with the dates. That's all there is to it! Presenters are a very powerful tool that can help you make nice, clean code. It's well worth using them in your Rails applications.

Credit for the original code goes to nithinbekal.com, who's code helped reduce the amount of time it took to get this article out the door. Look for more videos and articles coming soon! Please leave feedback listed below. Please note that I have only a limited amount of time to answer questions, so if you have a technical question, the best place to ask is stack overflow (but try and link to your Stack Overflow post from here so we can find it.)

Thanks for reading!