Pretty Data, Pretty Code

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| %>

  

<%= f.text_field :name %>

<%= f.text_field :price_field %>

<%= f.text_area :features_field %>

<%= f.submit "Create Product" %>

<% 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| %>

  

<%= f.text_field :name %>

<%= f.text_field :price %>

<%= f.text_area :features %>

<%= f.submit "Create Product" %>

<% 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.