Friday, January 11, 2008

Format your data using Rails composed_of Value Objects

A requirement comes in as usual says, "I want prices to be formatted like '$2,000.00' on all rhtml's."

Your programming brain then associates the word "formatted" with "presentation". Afterall, the price column is simply stored in a decimal column in the database. And off you go, wrapping number_to_currency(@book.price) on all of your views, just like any tutorial says.

It's all good until the same formatting is required for all emails being sent, all web services requests being issued, all Excel/CSV reports being generated, all everything. Since you took advantage of formatting the prices with an ActionView helper method number_to_currency, you cannot do the same in places where ActionView helper methods are not available to you. You start creating wrapper classes around your Book class, maybe a presenter, to keep this "presentation" logic outside of your pristine business domain model.

class Book < ActiveRecord::Base
end

class BookPresenter < SimpleDelegator
include ActionView::Helpers::NumberHelper

def price
nummber_to_currency "#{__getobj__.price}"
end

def isbn
... ...
end
end

class BooksController < ApplicationController
def show
@book = BookPresenter.new Book.find(param[:id])
end

<p>
ISBN: <%= @book.isbn %>
Price: <%
= @book.price %>
</p
>


To present formatted data is extremely common on any applications, be it a price, date/time, name, address, SSN, ISBN, or even a baseball game score. A lot of these information are so simple that they can be stored in a single database column and be represented by a primitive type like String, DateTime, or Fixnum in Ruby. Since they are so simple in nature, programmers think that all it takes to format them is wrap a helper method around it on the view. But this approach may backfire like the example above.

Alternatively, one can create a value object that has a #to_s method that does all the formatting for you. That way, everywhere that needs formatting can ask a book object for it, and not have to wrap a presenter around it for something as formatting.

class Book < ActiveRecord::Base
composed_of :isbn, :class_name => 'ISBN'
composed_of :price, :class_name => 'Money'
end

class ISBN
def to_s
... ...
end
end

class Money
include ActionView::Helpers::NumberHelper

def to_s
nummber_to_currency @price
end
end

class BooksController < ApplicationController
def show
@book = Book.find param[:id]
end

<p>
ISBN: <%= @book.isbn %>
Price: <%
= @book.price %>
</p
>


So what's the difference? The primarily difference is, every Book object now knows how to format its information correctly, and now when a book needs to be used by various sources, be it on an rhtml page, a web service request, or an Excel/CSV report, those sources can interrogate a book object, but not "first fetch a book object, then wrap it with a presenter".

Some people will yell now, "but aren't you putting presentation logic into your domain model?" Yes, kinda. One of the toughest decisions that a developer always has to make is to distinguish between domain logic and non-domain logic. Now if I may ask, if my formatting is happening in multiple places, is this domain logic? I tend to say it is. Also, from experience, while it is extremely rare for me to put such presentation/formatting logic in an Entity Object domain model class (e.g. Book) because such classes are almost always complicated enough in just domain behaviors, having formatting logic in these usually less complicated Value Objects (e.g. Money) makes a lot of sense to me. And in Rails, composed_of provides me that convenience to me almost effortlessly.

The way Rails has these handy ActionView::Helpers is super useful, but every once a while we must step back and ask ourselves the question, "how can I use them beyond the tutorial way on my really complicated application?"

Tip #1: You can also have your #to_s methods to take an additional parameter to customize the formatting behavior. Take a look at how DATE_FORMATS are used in Time#to_s.

Tip #2: You may not need a new class for the sake of using composed_of every time you have a new formatting requirement either. Sometimes you may be able to get away with just extending the Ruby primitive types with a custom method/format, for example Price: <%= @book.price.to_s :money %>.

5 comments:

muness said...

Funnily enough, I needed this sort of thing just yesterday. With a subtle difference: I needed it on the input text fields instead... e.g. currency values should show as $1,000.00 in input fields.

Alas, when using the standard rails field_tag method, it short circuits all this. See action_view/helpers/form_helper.rb, line 45. It explicitly calls value_before_type_cast on objects. Looks like we'll have to do the formatting in Javascript on render.

Ajasja said...

Well, you could always check out this plugin:
http://brad.folkens.com/formatting-data-in-rails-models

Mars said...

I don't get this use of #composed_of here. The last example can be reduced to simple Ruby method definitions. There is no reason to use ActiveRecord's Aggregations here... it's not aggregating anything. If you just want a "value object", then #freeze the return value from each of the formatter methods.

Am I missing something?

Emma said...

Nice! One question, is it posible to apply composed_of to a list of Value Objects? i.e like a list of prices in your example.

marano said...

Hi! I did exactly as you said but now I got an error when I call @product.price:
wrong number of arguments (1 for 0)
any ideia?
thanks!