21 Jun 2014. comments
Sometimes when you’re working on an a Rails app you might be tempted to put conditional logic in your views or json-emitting code to control the display of certain things based on the model. This is pretty widely accepted as being a bad practice and it’s preferrable to instead push as much logic into the model layer as possible. This is a large part of why there was a rise of ‘logic-less’ templates like Mustache. As with anything in software development it’s a trade-off and in small doses a single ‘if’ statement might be ok depending on the circumstances, but in general you should avoid it.
If we accept the premise that the responsibility of display-specific logic should be isolated from the model what we end up with is the ‘presenter’ pattern. A presenter is essentially an object that uses an underlying domain model and exposes methods that expose information to the view or json in a way specific to the consumer (usually human-readable friendly representations of data).
Let’s say we had the following model/view:
class Person attr_accessor :first_name, :last_name end
<p> Full name: <% if model.last_name.present? %> <%= model.last_name %>, <%= model.first_name %> <% else > <%= model.first_name %> <% end %> </p>
That logic isn’t great. Lets put it in a presenter:
<p><%= presenter.full_name %></p>
class PersonPresenter def initialize(person) @person = person end def full_name [@person.last_name, @person.first_name].compact.join(", ") end end
This seems better. We don’t have logic littering the view so it’s easier to understand the presentation layer. And if we decide to change how full names are displayed (first, last) we don’t need to make any changes to the view. The view in this case isn’t even passed the model directly anymore so it doesn’t even need to know about first and last names. And the best benefit of all is that we can easily unit test the presenter object to assert the name is being displayed as we want.
Let’s say you still have a bunch of properties on your model that you do care about as-is. It’d be kind of a pain to delegate each and every one of those methods through the presenter. There is a way that ruby can help here:
class PersonPresenter < SimpleDelegator def full_name [@person.last_name, @person.first_name].compact.join(", ") end end
SimpleDelegator takes whatever object you pass into its constructor and exposes them and any new methods you layer on top. It’s basically composition with automatic delegation rather than inheritance.
presenter = PersonPresenter.new(person) presenter.first_name # "John" presenter.last_name # "Doe" presenter.full_name # "Doe, John"