Presenters in Rails
When your models are bloated with methods that are only used in views, it might be a good time to refactor them. Moving that logic into helper modules might be OK in some cases, but as the complexity of your views grows, you might want to look at presenters.
Presenters give you an object oriented way to approach view helpers. In this post, I will walk through how we refactored our views to use presenters instead of helper methods or even methods in the model.
Ryan Bates’ excellent Railscasts has an episode on writing presenters from scratch that guided me through this refactor. It’s dives a bit deeper than what I’m covering here, so go check it out after you’ve read this.
Refactoring the views
We have a HAML view where we display the title and publication status of a post. The title is an ActiveRecord field and the publication status is a method in the Post model.
publication_status is shown as the publication date if it is already published. Otherwise we show the string ‘Draft’.
This isn’t too bad. But what if we need to change this to show the publication date in an “X hours ago” format instead? We would love to use Rails’ time_ago_in_words helper method, but it’s not available in the model. The simplest approach here would be to move this into a helper module.
This solves our problem, but these view helpers have the disadvantage of putting all helper methods into a single namespace. It would be nice to have a class that contains post related helper methods.
Creating our first presenter
We can create a
PostPresenter class that implements the logic that we have in the helper.
The view_context here represents an instance of
ActionView, which is where we get view helpers like time_ago_in_words. Since
PostPresenter does not contain these helper methods, we need to explicitly pass in the view_context from the controller.
Now we run into the problem of needing a
#title method since that is also needed in the view. We can avoid this by passing through method calls to the post object if they are not defined in the presenter. Let’s create a
BasePresenter class that takes care of this, and inherit all our presenters from this class.
By inheriting from Ruby’s builtin
SimpleDelegator class and calling
super in the initialize method, we make sure that if we call any method that is not defined in the presenter, it passes it on to the post object.
I have also included a handy method
#h that simply returns the view context. (This name is used in both Draper and the presenters Railscast, so let’s follow the same convention.) Our presenter, after inheriting from
BasePresenter, now looks like this:
We can instantiate the presenter objects in the controller:
Moving presenters out of controllers
To simplify things further, we could avoid instantiating the presenter object in the controller and instead add a helper method that allows us to wrap the ActiveRecord object inside a presenter directly in the view.
This infers the presenter class from the object being passed in and passes the presenter object into a block that we pass in from the view. It allows us to write code that looks like this:
The above code assumes that all our presenter classes follow the convention of appending ‘Presenter’ to the model name. I sometimes split presenters into smaller ones that are used in different places. For instance, I could have an
AdminPostPresenter which contains helper methods for showing a post in the admin dashboard.
For these cases, I tweaked the
present method to accept an optional second argument.
This allows me to call
present(@post, AdminPostPresenter) in the view where I use the admin-specific presenter, while continuing to use the
present(@post) format elsewhere.
Using presenters has been a great win for us in maintaining the views. They also have the added benefit of being easier to test. We did try using draper, but given how easy it is to roll our own presenters, we chose the latter option. If maintaining your Rails views is giving you problems, presenters could be a great way to clean things up.
- Ryan Bates’ Presenters from Scratch Railscast
- Draper gem
- Jay Fields’ article about presenters was one of the early mentions of presenters in the Rails community
- In my previous post, I wrote about the decorator pattern in Ruby. Presenters can be seen as a kind of decorator that resides closer to the view layer.
- Slides from my Decorators and Presenters talk at the Kochi Ruby meetup
- This pull request on Github by Justin Gordon has some interesting discussion on presenters. His Railsconf talk also covers decorators and presenters using Draper.