Abstracting Ruby on Rails View Logic with Presenters

Cluttering ActiveRecord models with methods intended for the views is bad practice and a code smell. Presenters introduce a layer of abstraction between our models and views to help create a more maintainable app.

As a Rails developer, you'll often find models with methods that seem to just do some formatting. Typically these methods are used in views to present the data in a format more familiar to the end users.

Models are representations of data and associations not a solution to every problem.

It can be very tempting to define these formatting and boolean methods in the model as we're used to the idea that models should be rendered in the views. 
If we stop and consider the role of each object in our app we can easily come to the conclusion that there's mixed responsibility happening when we define these methods. While I don't think mixing responsibilities should always be forbidden, I do think this problem can be solved easily and so, worth doing.

Presenters are a simple abstraction that solves our problem with low effort and high reward. Presenters make our apps much easier to maintain by reducing responsibilities which in turn helps to reduce clutter and isolates logic that only applies to one tiny piece of the application as a whole.

What is a presenter?


A presenter is an object that sits between the model and view to encapsulate formatting and other complex view logic.
Like glasses, presenters help give another perspective unintrusively.

For example if we have a BankCard but we want to mask the number we can create a BankCardPresenter and define a method masked_card_number

class BankCardPresenter
  def initialize(bank_card)
    @bank_card = bank_card
  end

  def masked_card_number
    "****-****-****-#{@bank_card.card_number[-4..-1]}"
  end
 
  def valid?
    Time.current.before?(@bank_card.expiration)
  end
 
  def bank_name
    return 'KBank' if @bank_card.bank_name == 'Kasikorn'
 
    @bank_card.bank_name
  end
end
BankCardPresenter.new(BankCard.first).masked_card_number
# => ****-****-****-4242

Or perhaps we want to display a QR code for our payment system. We can create a TransactionPresenter and define a method 'qr_code'

class TransactionPresenter
  def initialize(transaction)
    @transaction = transaction
  end

  def qr_code
    QRCode.new(transaction.transaction_identifier)
  end
end

Over time, these methods start to clutter our models. This is normally not an issue until you have hundreds of methods in your god models. We probably only need that qr_code for a single view; even if we need it for every single view we won't be needing it in our models or background jobs, etc. Our application logic should be designed to work with the data that we get directly from the database. We only need to format when we present data to end users so it's in a familiar form which is easier to consume.

Can we make it better?


Ruby has a nice class to help us create presenters - SimpleDelegator! Presenters are possibly one of the most fitting use cases for SimpleDelegator.

SimpleDelegator is a ruby core class that allows us to delegate all undefined methods in the current object to the object that has been wrapped. To wrap an object we just pass it in during initialization. 

This allows us to create presenters that don't require custom initialization logic and we can treat the presenter as if it is the presentee, calling transaction_identifier on the presenter itself instead of the @transaction object. This makes our presenters cleaner and simpler.

class BankCardPresenter < SimpleDelegator
  def masked_card_number
    "****-****-****-#{card_number[-4..-1]}"
  end
end
BankCardPresenter.new(BankCard.first).masked_card_number
# => ****-****-****-4242

Have a present, I wrapped it myself!

Sometimes you may want to override a method that exists on the wrapped object, SimpleDelegator allows us to call super to call the presentee e.g.

class BankCardPresenter < SimpleDelegator
  def card_number
    "****-****-****-#{super[-4..-1]}"
  end
end
BankCardPresenter.new(BankCard.first).card_number
# => ****-****-****-4242

When should you use a presenter?


I personally use them in views and serializers when the situation requires some attribute formatting. If I ever need an attribute that's not already on the model I will create a presenter.

Organizing presenters is a little trickier and something to be conscious of - you will need to experiment. For example, if you create a presenter called UserRegistrationPresenter and then use that in 4 different views, but one view requires another attribute you could either add that attribute which only applies to 1 in 4 of the views, or you could create a whole new presenter.

You can of course sub-class them so you can share methods, or even use concerns, PORO modules or other solutions to share between different presenters. In my experience, most of the time you won't need a huge amount of presenters to get the job done. I tend to favor just adding that extra method even if it only applies to a few views. It's still a big improvement over shoving these methods in the models.

One neat thing about presenters is that they present the object passed in so you could technically pass a presenter to another presenter. I wouldn't necessarily recommend this as I'm sure it would become difficult to follow exactly where the calls are going to at some level of inception.

Every tool has its place.

As always, the choices are up to you. Ruby gives us a tremendous amount of flexibility and power - be responsible! Presenters are just one more tool we can use to build maintainable applications - I hope you find as much use for them as I do.

Looking for a new challenge? Join Our Team

Like 5 likes
Joe Woodward
I'm Joe Woodward, a Ruby on Rails fanatic working with OOZOU in Bangkok, Thailand. I love Web Development, Software Design, Hardware Hacking.
Share:

Join the conversation

This will be shown public
All comments are moderated

Get our stories delivered

From us to your inbox weekly.