What’s the size of
user.rb file in your current project? The default Rails way model-view-controller architecture often leads to a bloated model layer. Presenter pattern is one of the more straightforward ways to slim down your Rails models. It also helps to reduce the logic in the view layer and makes testing easier. In this blog post, I’ll describe how to implement a presenter pattern without including additional gem dependencies.
How not to views
Before we jump into coding our presenters, let’s first quickly discuss the shortcoming of the default approach of model + view. For the sake of this tutorial, let’s assume we want to code a feature displaying a number of unread notifications.
Without implementing additional layers of abstraction, we can either put the related logic into the model or view files. Sample implementation in the view partial could look like that:
Despite the logic being relatively simple, there’s already a lot of the erb syntax overhead. Another downside of putting logic into views is that it’s challenging to test it in isolation. You must run slow integration tests or configure controller specs to render views. Unfortunately, it’s sometimes problematic to simulate different edge cases without resorting to hard-to-maintain stubbing techniques. If logic is complex, I usually prefer to implement a single “happy path” integration spec and test edge cases using low-level unit tests.
Testing would be simpler if we implemented the same logic directly in the model:
The logic is easier to grasp without all the noise added by the erb syntaxt. But the inclusion of
ActionView::Helpers::TextHelper in the model indicates that we’re mixing up unrelated layers. Writing code like that is a one-way ticket to bloating your models to an unmaintainable mess.
Why not helpers?
Another way to implement this logic in a classical Rails way would be to use a built-in Rails helper (files inheriting from
ApplicationHelper class). But, I usually try to avoid them. The same as in the case of views, they are challenging to test in isolation, and have a global scope. It means that you have to be careful to keep methods naming unique across ALL your helpers. From my experience refactoring legacy projects, putting logic into helpers can also lead to the terrible practice of including them in controllers to keep things DRY…
Presenters to the rescue
Presenters are a popular programming pattern, and there’s an abundance of gems that implement it. But, in this tutorial, I’ll describe a from-scratch approach.
Just one more gem?
Based on the experience from my Rails performance audits, memory issues are the most common obstacle for projects to achieve adequate performance and scalability. And overusing gem dependencies is a simple way to unnecessarily bloat a Rails app memory usage. I mention memory issues because a not-so-long-ago popular Draper gem is infamous for its elevated memory consumption. Apparently, it’s no longer maintained with the last commit older than a year ago. There are successors to Draper improving on its shorcommings. But, adding a gem is always a risky investment, so in this tutorial, we’ll focus on a custom solution.
Implementing Rails presenters from scratch
Let’s start by defining a module:
and now include it in your model:
Presentable module uses Ruby metaprogramming to dynamically define a presenter class. It enforces a naming convention for presenter files. Let’s now create one:
It inherits a
SimpleDelegator class and is instantiated with the model object itself. It means that all the method calls will well delegate to the original. Now inside the controller, your can use your presenter like that:
User model instances, you’re wrapping them inside a
UserPresenter class and extending their API. You can interact with the new objects like before but also use all the methods defined inside a presenter.
This approach allows encapsulating logic related to the view layer in a separate file and helps to keep your model files from bloating their size.
Compared to a dedicated gem, this method is lightweight on memory and complexity. Just a few lines of metaprogramming and Ruby stdlib in action.
As previously mentioned, since our presenters are POROs (Plain Old Ruby Objects), it’s straightforward to test them. Here’s a sample spec for our logic:
As you can see, we can test view-related logic without the usual overhead of view specs. There’s no need to authenticate the user, stub session state, render views, or launch a test browser. While high-level integration tests also have their value and place, testing more complex edge cases is usually an order of magnitude simpler with low-level unit tests.
Implementing presenters can be a relatively simple introduction to programming patterns that are not part of an orthodox Rails way. Organising codebase in a way that focuses on the logical layers of application is an approach that works for MVPs and also scales for more complex Rails projects.