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| %>Given the above view, the controller could contain the following code.
...
<%= form.text_field :name %>
...
<%= form.text_field :email %>
...
<%= form.text_field :phone %>
<% end %>
def createUsing 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.
@person = UsersPresenter.new(params[:presenter]).create_person(current_account)
redirect_to person_url(@person)
end
class Person < ActiveRecord::BaseFinally, the Presenter brings all of this together.
has_one :email_address
has_one :phone_number
end
class UsersPresenterWhile 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.
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
class UsersPresnterTest < Test::Unit::TestCaseUsing Jamis' (admittedly simpler) solution would require saving data to the database and verifying it's existence.
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 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.
I dig this pattern a lot. Would love to see how you tie in validations though... ie: how are you returning the errors ?
ReplyDeleteIf 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 ?