This post originated from an RSS feed registered with Ruby Buzz
by Jay Fields.
Original Post: Rails: Presenter Pattern
Feed Title: Jay Fields Thoughts
Feed URL: http://feeds.feedburner.com/jayfields/mjKQ
Feed Description: Blog about Ruby, Agile, Testing and other topics related to software development.
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.
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.
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.
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