Friday, March 16, 2007

Rails: Presenter Pattern

The default architecture for Ruby on Rails, Model View Controller, can begin to break down as Controllers become bloated and logic begins to creep into view templates. The Presenter pattern addresses this problem by adding another layer of abstraction: a class representation of the state of the view.

Presenter was inspired by the various GUI patterns documented by Martin Fowler.

How It Works
The Presenter pattern addresses bloated controllers and views containing logic in concert by creating a class representation of the state of the view. An architecture that uses the Presenter pattern provides view specific data as attributes of an instance of the Presenter. The Presenter's state is an aggregation of model and user entered data.

When To Use It
As non-trivial applications grow Controllers can approach sizes that compromise maintainability. Complex controller actions may require instantiation of multiple objects and take on the responsibility of aggregating data from various objects. A Presenter can encapsulate this aggregation behavior and leave the controller with more focused responsibilities. This also allows for testing of aggregation or calculation behavior without a dependency on setting up a Controller to a testable state.

Since views are templates they can also contain behavior. The most common behavior you encounter in a view is formatting; however, since the view is a template you will occasionally see computations or worse. Because this logic is stored in a template it can be problematic to test. Presenters address this problem by pulling all formatting, computation, and any additional behavior into a class that can be easily tested.

Presenters do add an additional layer, and thus more complexity. A presenter is not likely to be a automatic decision or standard. Presenters are generally introduced when actions are required to act upon various models or the data in the database needs to be manipulated in various ways before it is displayed in the view.

Example
The example used to demonstrate a usage of the Presenter pattern is a page that allows you to enter your address information, personal information, and user credentials.



To get to our example page (and the following page) we'll need to add a few routes to routes.rb.
ActionController::Routing::Routes.draw do |map|
map.with_options :controller => 'order' do |route|
route.complete "complete", :action => "complete"
route.thank_you "thank_you", :action => "thank_you"
end
...
end
The example relies on three different models: address, user_account, user_credential. The migration for creating these tables is straightforward.
class CreateModels < ActiveRecord::Migration
def self.up
create_table :user_accounts do |t|
t.column :name, :string
end
create_table :addresses do |t|
t.column :line_1, :string
t.column :line_2, :string
t.column :city, :string
t.column :state, :string
t.column :zip_code, :string
end
create_table :user_credentials do |t|
t.column :username, :string
t.column :password, :string
end
end

def self.down
drop_table :user_accounts
drop_table :addresses
drop_table :user_credentials
end
end
The models need not contain any behavior for our example.
class Address < ActiveRecord::Base
end

class UserAccount < ActiveRecord::Base
end

class UserCredential < ActiveRecord::Base
end
The template for our example view is also straightforward, and this is one of the large benefits for using a presenter.
<% form_for :presenter do |form| %>
<table>
<tr><td colspan="2">Billing Information:</td></tr>
<tr>
<td>Name</td>
<td><%= form.text_field :name %></td>
</tr>
<tr>
<td>Address Line 1</td>
<td><%= form.text_field :line_1 %></td>
</tr>
<tr>
<td>Address Line 2</td>
<td><%= form.text_field :line_2 %></td>
</tr>
<tr>
<td>City</td>
<td><%= form.text_field :city %></td>
</tr>
<tr>
<td>State</td>
<td><%= form.text_field :state %></td>
</tr>
<tr>
<td>Zip Code</td>
<td><%= form.text_field :zip_code %></td>
</tr>
<tr><td colspan="2">Account Information:</td></tr>
<tr>
<td>Username</td>
<td><%= form.text_field :username %></td>
</tr>
<tr>
<td>Password</td>
<td><%= form.text_field :password %></td>
</tr>
</table>
<%= submit_tag "Complete Order" %>
<% end %>
And, the last example before we dive into the Presenter will be the controller. The controller is also simple, thus maintainable, due to the usage of the Presenter.
class OrderController < ApplicationController
def complete
@presenter = CompletePresenter.new(params[:presenter])
redirect_to thank_you_url if request.post? && @presenter.save
end

def thank_you
end
end
Finally, the CompletePresenter aggregates all the data for the view.
class CompletePresenter < Presenter
def_delegators :user_account, :name, :name=
def_delegators :address, :line_1, :line_2, :city, :state, :zip_code
:line_1=, :line_2=, :city=, :state=, :zip_code=
def_delegators :user_credentials, :username, :password, :username=, :password=

def user_account
@user_account ||= UserAccount.new
end

def address
@address ||= Address.new
end

def user_credentials
@credentials ||= UserCredential.new
end

def save
user_account.save && address.save && user_credentials.save
end
end
The CompletePresenter does inherit from Presenter, but only to get Forwardable behavior and a constructor that allows you to create an instance with attributes set from a hash.
class Presenter
extend Forwardable

def initialize(params)
params.each_pair do |attribute, value|
self.send :"#{attribute}=", value
end unless params.nil?
end
end
By using the presenter an easily testable layer has been created. This additional layer can coordinate with the models that this view is responsible for. The added layer also allows the models to be tested independent of any controller behavior. The presenter also provides the ability for extension where other solutions prove inadequate. For example, in a real scenario, the models would also likely contain validations. The presenter provides a layer that can validate the various models and merge their errors collections to provide one error collection that the view can work with.

Used appropriately, Presenters greatly benefit an application's architecture and maintainability

20 comments:

  1. Great idea. It makes a lot of sense.

    Two thoughts:
    1. I think the saves should be wrapped in a transaction to avoid any dangling objects if the user doesn't complete the entry ?
    2. The use of delegators is a nice addition.. having to enumerate all the accessors is a bit of a nuisance, though, when you've got some big objects. I wonder if it would make sense to get all the accessors on the included objects and use a prefix to identify (as with the Forwardable prefix). In fact, taken to a conclusion perhaps a Presenter could automatically do all this w/ any objects sent to it? (Could even automate the saving & generation/aggregation of validation errors.. assuming there weren't any association links)

    ReplyDelete
  2. Anonymous3:01 PM

    Charles

    Thanks for the feedback.
    1) Absolutely. I left the example as simple as possible, but in realistic scenarios wrapping it in a transaction makes a lot of sense.

    2a(to address delegation) I've toyed with various delegation schemes during the life of my last 3 projects. Here's a version using some convention over configuration.

    http://pastie.caboo.se/47453

    I've also previously written another version that always splits on underscore and if it finds a defined method with a matching name then it delegates the rest of the missing method name to the return of the first part.

    http://pastie.caboo.se/47454

    I've found it's not a matter of how to delegate, but how your team chooses to delegate.

    2b(to address validation) I created Validatable to handle aggregation of errors and adding error checking to Presenters when it made sense. Validatable can be found at http://validatable.rubyforge.org

    ReplyDelete
  3. Anonymous8:21 AM

    Thanks for this post. I've read all your other Presenters posts and have been thinking a lot about it, but I think this summarises very nicely. I haven't yet used Presenters myself, but I have an application which might benefit from it - in order to handle multiple associations concisely I've been doing things like defining methods on the association.

    One thing somebody told me about the other night was the "Simply Presentable" plugin by Rich Collins: http://simply_presentable.richcollins.net/. This seems to be trying to achieve the same sort of goals, although it also acts as a sort of object orientated alternative to simply_helpful. I don't know if you have seen this plugin and what your opinions are?

    One big difference I see if that you name your Presenters based on the name of the action, whereas the examples for "Simply Presentable" use the name of the model. I guess you have to think about what part of the application Presenters are *really* working for. They are probably not working for a single model as they seem most useful when dealing with associations. You obviously see them as working for the action/view, but then you could need the same presenter over multiple actions. For instance #create and #update in a REST application. Perhaps you could say they don't work for the model, but for the resource. In which case maybe the Simply Presentable way is okay.

    ReplyDelete
  4. Anonymous4:32 PM

    Jon,
    The pattern will need to be customized for each application. The questions you're asking show that you get the concept and shouldn't have any problem working with the pattern.

    As for specific answers, I believe that they will depend so much on what application they are put in, that a general rule would be fairly worthless.

    ReplyDelete
  5. Jay: in case you're still checking this thread...

    I really like the Presenter pattern for a lot of reasons. But I'm trying to figure out how to be able to call ActionView helpers from within a Presenter method. This would allow for code like this in a view:

    @presenter.link_to_item

    instead of:

    link_to(@presenter.item.name, :controller => 'item', ...

    This would make for more readable views as well as the ability to unit test these helpers without having to render a template.

    For this should Presenter be subclassing ActionView? Should I be trying another path?

    thx!

    ps: Thanks for the great session at RailsConf!

    ReplyDelete
  6. Anonymous5:31 PM

    Hello dwfrank,

    If it's just a module that contains the methods you need, I'd mix that module into your presenter. For example, I've mixed in NumberHelper in the past and it worked fine.

    I might try to subclass ActionView, but I'd keep an eye to that adding unnecessary complexity. I wouldn't trade that complexity for the ability to use the helpers.

    Thanks for the kind words concerning RailsConf.

    Cheers, Jay

    ReplyDelete
  7. Anonymous8:11 AM

    In a recent project I have to implement four models in a single form, and the result was a practically unmaintainable code. At the end, thanks to red/green/refactor process I can be able to address this problem, but it can cost me more time than it worths.

    When search in my spare time for a better way to do this in a further situation I met an article from Dave Verwer. It is about Active Record Delegation plugin. And this plugin let us to make something like this:

    class Order < ActiveRecord::Base
    has_one :user_account
    has_one :user_credential
    has_one :address

    has_columns :from => :user_account, :prefix => 'ua_'
    has_columns :from => :user_credential, :prefix => 'uc_'
    has_columns :from => :address, :prefix => 'ad_'
    end

    Then we can do this inside a controller:

    @order = Order.new(params[:order]
    redirect_to thank_you_url if @order.save

    @order.save saves automatically all the delegated models.

    It is required to implement an extra model, but we should track the orders, isn't it?

    What do you think is best? Delegator or Presenter?

    (Please, forgive me if you see spelling mistakes here, I am not from a Englsh-speaking country)

    ReplyDelete
  8. Anonymous6:38 PM

    Markus, I agree with you. Your order example obscures the point a little bit, though, because most people would say "Yes, an Order is part of the business domain, so there should be a class in the model for it".

    I say that perhaps even a Registration is part of the business domain so there should be a class in the model for it. Even if none of its direct attributes are persisted, doesn't mean it shouldn't be in the model (refering to a comment I made on one of Jay's previous posts on this topic).

    I say that the Registration type is part of a business process. Just because that business process isn't core to the organisation's day-to-day operations doesn't mean it's not part of the business domain.

    Now, to be clearer, though, I'd say that Jay is talking more about the "code behind" type idea in the Model-View-Presenter style of ASP.NET where the View has no behaviour at all, and the Presenter does the work of validation and formating.

    Personally, I'd prefer the core rules of validating and formatting to be lodged in the domain model classes and utilised during instantiation, persistence, (if you have to ask) attribute reading, and (if you have to mutate) attribute writing.

    To save on the overhead of attempted instantiations or mutations with invalid values, the Presenter may have access to static/class methods in the model class that provide direct access to granular rules for each attribute. This saves on things like NumberHelper - which usually means the writer of Number needs help, not the user of Number ;-)

    Nonetheless, as Jay highlights, your circumstances may dictate that validation and formatting rules may be quite variable and so mixing in behaviour (or injecting the dependency) from auxilliary types is most reasonable.

    Whatever your preference - Fight anaemic models. Give blood now.

    ReplyDelete
  9. Anonymous4:45 PM

    Markus and Josh,

    I just spoke about the presenter pattern at RailsConf Europe. Much to the disappointment of the crowd, I admitted that it wasn't a silver bullet.

    Here's the thing, for a simple application, you may not need a presenter at all.

    For an application that starts to hurt when testing the controllers, it's probably time to create a presenter similar to the examples. Of course, as Josh points out, look first to move behavior into models of some kind.

    However, presenters can also begin to hurt, at that time it's time to move to full on services from Domain Driven Design.

    ReplyDelete
  10. After some head beating, and not wanting to define every single side of every attribute. Rein Henrich and myself came up with an updated Presenter which handles most of the heavy lifting for you. Yes it works with error_message_on and other railities. Very handy!

    You can find the code at
    http://pastie.textmate.org/pastes/134685

    Josh Martin

    ReplyDelete
  11. Thanks Jay for this useful idea and thanks Josh for the example: http://pastie.textmate.org/pastes/134685
    I've changed it a bit, so that all accessors could be prefixed by the model name: http://paste.pocoo.org/show/21334
    Because on my application i need to present several instances of the same class.

    ReplyDelete
  12. hi can u post it as a demo application

    ReplyDelete
  13. This idea is having a reborn through Mustache: http://github.com/defunkt/mustache

    ReplyDelete
  14. Thanks for this post. This is really useful. And I read it at just the right time. I might just be able to refactor a whole bunch of code on Monday with this.

    I agree that this really cleans up controller and view code back to a readable state. Especially in projects that have lots of multi-model forms as in your example.

    You should also add the additions you put in pastie to the original post they are very valid and make it even more useful.

    ReplyDelete
  15. I think you can DRY the Law of Demeter implementation using the demeter gem

    http://github.com/emerleite/demeter
    http://gemcutter.org/gems/demeter

    ReplyDelete
  16. Nice post, thanks.
    Just a simple question but one I've been thinking about: Where do you put the presenter.rb and complete_presenter.rb files? Thanks in advance.

    ReplyDelete
  17. @Emerson, please, stay away from Demeter gem. It gives me creeps every time I remember debugging errors because of ActiveRecord's lazy loading of DB fields, and attributes like "book.cover_url" being translated to "book.cover.url"...

    ReplyDelete
  18. In Complete_Presenter, how do you enforce mass assignment protection? How does def_delegators and attr_accessible interact?

    ReplyDelete
  19. In Complete_Presenter, how do you enforce mass assignment protection? How does def_delegators and attr_accessible interact?

    ReplyDelete

Note: Only a member of this blog may post a comment.