Sunday, January 14, 2007

Another Rails Presenter Example

Jamis Buck recently posted an entry about Moving associated creations to the model. His solution is cool and probably sufficient for the needs of most; however, if you don't fall into that group you may be interested in an alternative solution using a Presenter.

In the example the view contains fields that collect person, email, and phone number information. Jamis shows what the html would actually look like, but I'm going to show what I would put in the view (rhtml) for my example.
<% form_for :presenter do |form| %>
...
<%= form.text_field :name %>
...
<%= form.text_field :email %>
...
<%= form.text_field :phone %>
<% end %>
Given the above view, the controller could contain the following code.
def create
@person = UsersPresenter.new(params[:presenter]).create_person(current_account)
redirect_to person_url(@person)
end
Using a Presenter limits the responsibilities of the controller without requiring that the model take on those responsibilities. For our example we'll assume the same Person class that Jamis already defined, except ours only needs the associations defined.
class Person < ActiveRecord::Base
has_one :email_address
has_one :phone_number
end
Finally, the Presenter brings all of this together.
class UsersPresenter
attr_accessor :name, :email, :phone

def create_person(account)
person = account.people.create(:name => name)
person.create_email_address(:address => email) unless email.nil?
person.create_phone_number(:number => phone) unless phone.nil?
end

def initialize(hash={})
hash.each_pair { |key, value| self.send :"#{key}=", value }
end
end
While the UsersPresenter class is fairly straightforward it does require the effort to create the additional class. I don't believe that it is worth the trouble for strictly academic reasons (e.g. a separation of concerns debate). However, I do believe that the resulting presenter class may be more easily testable. For example, testing the presenter can be done using a mocking framework such as Mocha.
class UsersPresnterTest < Test::Unit::TestCase
test "a person is successfully initialized from create_person" do
account=mock
account.expects(:people).returns(people=mock)
people.expects(:create).with(:name => "Jay").returns(person=mock)
person.expects(:create_email_address).with(:address => "j@j.com")
person.expects(:create_phone_number).with(:number => "2125551212")
presenter = UsersPresenter.new(:name => "Jay", :email => "j@j.com", :phone => "2125551212")
presenter.create_person(account)
end
end
Using Jamis' (admittedly simpler) solution would require saving data to the database and verifying it's existence.

Using Presenters has additional advantages such as their ability to easily integrate with ActiveRecord validations, separation of view behavior from models (such as formatting a phone number), and allowing you to put validation error messages somewhere other than in a model. I'll address each of these scenarios in upcoming blog entries.

1 comment:

  1. Anonymous8:52 AM

    I dig this pattern a lot. Would love to see how you tie in validations though... ie: how are you returning the errors ?

    If you were to include Validatable, calling self.valid? on the presenter would bomb in this example.

    Creating model instances within your presenter is an option, but doesn't really help present the errors in the view.

    Ideas ?

    ReplyDelete

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