Imagine being immersed in a complex Ruby on Rails project, where you find yourself navigating through models, controllers, and views like a seasoned developer. However, as the project evolves, so does the complexity of the codebase, making it increasingly challenging to maintain clarity and organization. This is precisely the scenario I encountered in a recent project. As the project expanded, the need for clean and understandable code became necessary. Amidst the need, I found a solution — the Presenter Pattern. Now, with this fresh understanding at hand, my aim is to contribute for fellow developers, highlighting the impact of the Presenter Pattern in Ruby on Rails.
Understanding the Presenter Pattern:
Before we dive into the nitty-gritty of implementation, let’s take a moment to understand what exactly the Presenter Pattern is all about. The Presenter Pattern is a structural design pattern that promotes the separation of concerns by extracting presentation logic from the models and controllers into separate presenter objects. This separation allows for a cleaner architecture where each component is responsible for a specific task, thus enhancing code readability and maintainability. It’s like giving each component of your application its own spotlight on the stage — models handle data, controllers orchestrate the flow, and presenters? Well, presenters take charge of how that data is presented to the user.
Implementation in Ruby on Rails:
Implementing the Presenter Pattern in Ruby on Rails is straightforward and can be achieved using plain Ruby classes or dedicated gems such as Draper or ActivePresenter. In this article I will go with plain ruby classes, let’s break down the implementation step by step, starting with the BasePresenter class, followed by the ProductPresenter, and finally, how they are utilized in views with the help of an application helper.
BasePresenter:
The BasePresenter class serves as the foundation for all other presenters in our application. It’s responsible for handling the common functionality and delegation of methods to the underlying model object.
class BasePresenter < SimpleDelegator
def initialize(model, view)
@model, @view = model, view
super(@model)
end
def method_missing(meth, *args, &block)
if @model.respond_to?(meth)
@model.send(meth, *args)
else
nil
end
end
def get_view
@view
end
end
We initialize the presenter with the model object (@model), the view context (@view), and super(@model) is used to initialize SimpleDelegator so that all of @model’s methods are available in the presenter.
The method_missing method dynamically delegates method calls to the model object if the method is not explicitly defined in the presenter.
ProductPresenter:
The ProductPresenter class is a specific presenter tailored for the Product model. It encapsulates the presentation logic related to products, such as formatting prices and determining availability status.
class ProductPresenter < BasePresenter
def formatted_price
get_view.number_to_currency(price)
end
def availability_status
available? ? "Available" : "Out of stock"
end
end
We define methods like formatted_price and availability_status to encapsulate presentation logic.
formatted_price method utilizes the get_view method inherited from BasePresenter to access view-related functionalities like number_to_currency for formatting prices.
View Integration:
In our view templates, we utilize the presenters to handle the presentation logic, ensuring separation of concerns and maintaining clean and readable views.
<% present(product, ProductPresenter) do |p| %>
<h2><%= p.name %></h2>
<p>Price: <%= p.formatted_price %></p>
<p>Status: <%= p.availability_status %></p>
<% end %>
We use the present helper method to instantiate a ProductPresenter for the product object.
Inside the block, we can access the presenter methods (name, formatted_price, availability_status) to display the product information.
Application Helper:
To streamline the usage of presenters in views, we define a helper method in the application helper to instantiate presenters easily.
module ApplicationHelper
def present(model, presenter_class)
presenter = presenter_class.new(model, self)
yield(presenter) if block_given?
end
end
yield(presenter) yields the presenter object to a block of code provided in the view. If a block is given when calling the present method in the view, the presenter object is passed to that block, allowing for custom presentation logic to be executed within the block.
By following this approach, we ensure a clear separation of concerns, with presentation logic encapsulated in presenters, leading to more maintainable and readable code in our Ruby on Rails application.
Pros of the Presenter Pattern:
Now, let’s shine the spotlight on the pros of the Presenter Pattern:
Separation of Concerns: By isolating presentation logic, presenters declutter your models and controllers, making your codebase a joy to navigate.
Testability: With presentation logic neatly packaged in presenters, unit testing becomes a breeze.
Reusability: Presenters aren’t just a one-hit wonder — they can be reused across different views and controllers, saving you time and effort in the long run.
Cons of the Presenter Pattern:
Of course, no solution is without its drawbacks. Here are a few cons to keep in mind:
Increased Complexity: Introducing presenters adds an extra layer of abstraction, which can be overwhelming for simpler applications.
Overhead: Implementing presenters for every model and view may introduce some overhead in terms of additional code and maintenance.
Potential for Over-Engineering: There’s a fine line between elegance and over-engineering — be wary of creating presenters for every piece of data, as it might lead to unnecessary complexity.
Conclusion:
As we wrap up our exploration of presenters, it’s evident that this pattern can significantly improve your Rails applications. By adopting the Presenter Pattern, you can say farewell to messy code and welcome a more manageable codebase.
So, what are your thoughts? How do you think presenters could enhance your projects? Share your ideas and let’s collaborate to improve our codebases!
Top comments (4)
Overriding method_missing to return nil looks a little weird - it should return super() in that case. And respond_to? needs overriding as well to be correct.
the flexibility of the presenter pattern is actually its beauty; we can accomplish things in many ways. I think if receiving nil is properly handled, where the presenter is called, then it won't create an issue, and for the overriding of respond_to? the default method was doing the job for me, so I avoided the further complexity. Again, thanks for sharing your thoughts.
Its good to unit test the presenter methods as well if they get complicated.
agreed, thanks for the suggestion.