The Presenter Pattern With POROs for Rails Applications
When I first started making Rails applications, I ended up with views as shown in the below User Report Page code block after a while.
%h1 User Report Page
- @user.each do |user|
- if user.exists?
%p User exists
- else
%p User does not exist
I would check whether a user exists or not. If a user existed, I would display certain HTML markup. If you start trying to scale this type of logic over multiple lines, it becomes quite a mess to maintain.
Then I found out about the presenter pattern with POROs (Plain Old Ruby Objects).
What is a presenter pattern and why use it?
A presenter pattern takes your logic out of the views. It helps make your Rails views more maintainable.
Imagine if we started doing something more complicated such as displaying a different message if a user had an attached image file.
%h1 User Report Page
- @user.each do |user|
- if user.exists? && user.has_image?
%p User exists
- elsif user.exists? && !user.has_image?
%p User exists with no image
- else
%p User does not exist
Hopefully, you are starting to see how messy the above can be to maintain. If we used a presenter pattern, our view code might look something like this:
%h1 User Report Page
- present(@user) do |user|
%p= user.user_report
Notice how much cleaner the view looks! No conditional logic is needed in the view. Because of this, it will be easier to maintain.
Step 1 – Make a BasePresenter class
As a first step, let’s make a BasePresenter class that will house common behavior.
class BasePresenter < SimpleDelegator
include ActionView::Helpers
def initialize(model, view)
@model, @view = model, view
super(@model)
end
def h
@view
end
end
Step 2 - Make a Presenter
Next, we make a presenter class that inherits from BasePresenter to encapsulate our “view” logic. In this example, I define a method called user_exists_message.
This method returns a message with some HTML markup regarding the existence of a User object.
class UserPresenter < BasePresenter
def user_exists_message
if @model.user_exists?
h.content_tag(:h3, "User does not exist")
else
h.content_tag(:h3, "User exists")
end
end
end
Step 3 - Testing Your Presenter
Finally, if you want to add tests in RSpec, you need to instantiate a view instance of ActionController::Base.new.view_context.
require 'spec_helper'
RSpec.describe UserPresenter do
let(:view) { ActionController::Base.new.view_context }
let(:user) { FactoryGirl.create(:user) }
describe '#user_exists_message' do
let(:user_presenter) { UserPresenter.new(user, view) }
it "should say User exists" do
expect(user_presenter.user_exists_message).to include("User exists")
end
end
end
Summary
Use the Ruby language to create presenter patterns to simplify code in your views. The presenter pattern will help you maintain your views over the life of a code base.