On my previous project, my team developed a pattern for Rails presenters that I think is worth sharing. We used this pattern to a great deal of success and I will be advocating for its use in future Rails projects.

The Presenter pattern is what I call a projection of a model. That is: given a model A which contains data Z, a presenter is a derivation of model A and data Z. We can represent this as A' (Presenter class) and Z' (Presenter data). For any given model A, there are an unlimited number of projections and, thus, presenters.

Let's do something more concrete: we have an object of class User with data {first_name: 'Derek', last_name: 'Hammer', gender: 'M'}. A presenter might be called UserSalutations and is a derivation of the User model. The data might be {greeting: 'Hello, Mr. D. Hammer'}.

The benefits of the Presenter pattern is to separate domain logic (the changing of a person's name) and the presentation logic (formatting the person's name as a salutation). I will be quick to point out that the pattern is not always applicable and should be used as a conscious choice rather than as a matter of course.

We have User and we have UserSalutation. Both of these are a part of our system. How might the code that uses this look?

class MessagesController < ApplicationController
    def new
        @message = Message.new
        @user = User.find(params[:id])
        @salutations = UserSalutations.new(@user.first_name, @user.last_name, @user.gender, @user.id)
    end
end

class UserSalutations
    attr_reader :last_name, :gender, :first_name, :user_id

    def initialize first_name, last_name, gender, user_id
        @first_name = first_name
        @last_name = last_name
        @gender = gender
        @user_id = user_id
    end

    def greeting
        "Hello, #{honorific} #{first_initial}. #{last_name}"
    end

    private

    def honorific
        case gender
        when 'M' then 'Mr.'
        when 'F' then ms_or_mrs
        else then 'Mr.'
        end
    end

    def first_initial
        first_name.first
    end

    def ms_or_mrs
        if Marriage.where(person_one_id: user_id).or.where(person_two_id: user_id).any?(&:active?)
            "Mrs."
        else
            "Ms."
        end
    end
end

This method works, but we are doing some database access in our presenter. This makes the presenter difficult to test and require outside dependencies. One solution is to push the code "up" into the controller.

class MessagesController < ApplicationController
    def new
        @message = Message.new
        @user = User.find(params[:id])
        @salutations = UserSalutations.new(@user.first_name, @user.last_name, honorific)
    end

    def honorific
        case gender
        when 'M' then 'Mr.'
        when 'F' then ms_or_mrs
        else then 'Mr.'
        end
    end

    def ms_or_mrs
        if Marriage.where(person_one_id: user_id).or.where(person_two_id: user_id).any?(&:active?)
            "Mrs."
        else
            "Ms."
        end
    end
end

This solution is actually worse, though! We are creating a fat controller and controllers are notoriously difficult to test and maintain (hence the skinny controllers mantra espoused by the Rails community). The solution that my team came up with was the following:

class MessagesController < ApplicationController
    def new
        @message = Message.new
        @user = User.find(params[:id])
        @salutations = UserSalutations.from_user(@user)
    end
end


class UserSalutations
    attr_reader :last_name, :first_name, :honorific
    
    def self.from_user user
        self.new user.first_name, user.last_name, honorific(user)
    end

    def self.honorific user
        case user.gender
        when 'M' then 'Mr.'
        when 'F' then ms_or_mrs(user)
        else then 'Mr.'
        end
    end

    def self.ms_or_mrs user
        if Marriage.where(person_one_id: user_id).or.where(person_two_id: user_id).any?(&:active?)
            "Mrs."
        else
            "Ms."
        end
    end

    def initialize first_name, last_name, honorific
        @first_name = first_name
        @last_name = last_name
        @honorific = honorific
    end

    
end

Here, we have a testable, dependency free UserSalutations object. The dependencies are encapsulated by the factory method on the UserSalutations class object. Our Rails controller is still skinny (actually, its skinnier than before!). The downside is that the factory methods need to talk to external dependencies. This is okay, I think, because we are reducing the complexity here down to its bare minimum and testing in isolation.

So, in short, use a combination of factory methods and PROs in order to keep your presenters testable, maintainable and your controllers skinny!