In my last article I wrote about using data modeling to clean up form-related code and to take advantage of powerful helpers like form_for and error_messages_for. This solves the significant problem of isolating business logic into a model class, but another problem remains — how can we make our form data pretty without trashing our model's code with view logic?
Beautifying the Data
I have a simple Product model with rows for name, price and features. Setting and displaying the price field is tricky because I need to remove currency formatting before storing it in the database as a decimal, and I want to reformat it when displaying the current price in an 'Edit' form. The features field is also tricky. I want the user to enter each product feature ("Slices and Dices", "Purees Anything", etc.) on a separate line of the textarea and for that to be split into a serialized array that I will store in the database.
My first instinct is to create virtual attributes in my model to handle the logic of deformatting/reformatting this data. Here's how it looks:
class Product < ActiveRecord::Base serialize :features, Array validates_presence_of :name validates_numericality_of :price # need this for formatting def helpers ActionController::Base.helpers end def price_field=(p) # expecting p to be something like "$4,000.00", set price to 4000.00 p.gsub!(/[^0-9.]/, '') self.price = p.to_f end def price_field helpers.number_to_currency(self.price) end def features_field=(str) # expecting string with features separated by newlines self.features = str.split("\n").collect {|f| f.strip } end def features_field self.features.join("\n") end end
<%# the product form %> <% form_for :product, url => products_path do |f| %> <p class="form_item text_field"> <label for="product[name]">Name:</label> <%= f.text_field :name %> </p> <p class="form_item text_field"> <label for="product[price_field]">Price:</label> <%= f.text_field :price_field %> </p> <p class="form_item text_field"> <label for="product[features_field]">Features (1 per line):</label> <%= f.text_area :features_field %> </p> <p class="form_item submit_button"> <%= f.submit "Create Product" %> </p> <% end %>
The good news is that we have a very simple, straightforward looking view with no logic stuffed in it. Our controller is also completely vanilla, so I didn't bother to even show it.
Our model, however, is getting cluttered. What once was a haven for business logic is now filled with both business and view logic. I'm also a little concerned about having to use the #price_field and #features_field methods, since it adds complexity to what should be a simple object API. Will this cause confusion with my fellow programmers?
Beautifying the Code
What if we extracted the view logic into its own object? By using a presenter object as an adapter between our Product model and our form we can isolate the view logic from the business logic. Here's how it looks:
class Product < ActiveRecord::Base serialize :features, Array validates_presence_of :name validates_numericality_of :price end
class ProductPresenter attr_reader :product def initialize(product) @product = product end # need this for formatting def helpers ActionController::Base.helpers end # for mass assignment def attributes=(hash) hash.each_pair do |key, val| self.send("#{key}=".to_sym, val) end end def price=(p) # expecting p to be something like "$4,000.00", set price to 4000.00 p.gsub!(/[^0-9.]/, '') @product.price = p.to_f end def price helpers.number_to_currency(@product.price) end def features=(str) # expecting string with features separated by newlines @product.features = str.split("\n").collect {|f| f.strip } end def features @product.features.join("\n") end # proxy all other methods to @product def method_missing(method_name, *args, &block) @product.send(method_name, *args, &block) end end
class ProductsController < ApplicationController def new @product = ProductPresenter.new(Product.new) end def edit product = Product.find(params[:id]) @product = ProductPresenter.new(product) end def create @product = ProductPresenter.new(Product.new) @product.attributes = params[:product] if @product.save # success else render :action => 'new' end end def update p = Product.find(params[:id]) @product = ProductPresenter.new(p) @product.attributes = params[:product] if @product.save # success else render :action => 'new' end end end
<%# the product form %> <% form_for :product, url => products_path do |f| %> <p class="form_item text_field"> <label for="product[name]">Name:</label> <%= f.text_field :name %> </p> <p class="form_item text_field"> <label for="product[price]">Price:</label> <%= f.text_field :price %> </p> <p class="form_item text_field"> <label for="product[features]">Features (1 per line):</label> <%= f.text_area :features %> </p> <p class="form_item submit_button"> <%= f.submit "Create Product" %> </p> <% end %>
Our business and view logic have been effectively separated, our form code is clearer and the controller is only slightly more complicated. Because the ProductPresenter is acting as a proxy object to its Product object, we can simply treat it like a product thanks to 'duck typing'. While this example is a little contrived, using presenter classes can make or break your code base as you create complex model objects with equally complex visual representations.

