Wednesday, March 12, 2008

Rails composed_of &block conversion

If you had read Domain Driven Design, you will remember the author, Eric Evans, talks about various types of model objects in your domain model. Two of which are Entity Objects and Value Objects.

Entity objects are those that we care about their identity. For example, on Amazon.com, two users obviously have different identities, despite the possibility that they have the same name. Value objects, on the other hand, are those that we only care about their internal states' equality. Two money objects each has amount 100 and currency 'US' are treated as equal in the entire application. These two types of objects are similar in that they can all be defined as a class, but their usage is quite different.

Usually, a big bulk of your domain objects will be Entity objects. Almost all of your ActiveRecord::Base subclasses belong to that category, since each object has a different 'id' to uniquely identify themselves.

Rails provides composed_of as a way for your Value Objects to be referenced by an ActiveRecord Entity object. Rails 2.0 even added some syntactic sugar to composed_of, allowing you to pass a block for value object conversion. For Rails 1.x users, you can achieve the same with the plugin called composed_of_conversion. Let's see how to beautify your Rails code.


class Account < ActiveRecord::Base
composed_of :balance, :class_name => 'Money', :mapping => [%w(balance amount), %w(currency currency)] do |params|
Money.new params[:balance], params[:currency]
end
end

class Money
attr_reader :amount, :currency

def initialize(amount, currency)
@amount, @currency = amount, currency
end
end

class AccountsController < ApplicationController
def create
@account = Account.new params[:account]

if @account.save
flash[:notice] = 'New account created...'
redirect_to @author
else
render :action => "new"
end
end
end


Notice how my @account creation does not require any additional Controller code for value object creation and assignment, keeping my controller away from snacks and candy (i.e. thin). But since I have redefined Account#balance and Account#balance= through the use of composed_of, what should my form_for fields in my view look like? The answer lies with a use of fields_for.

<% form_for(@account) do |f| %>

<% f.fields_for :balance, @account.balance do |ff| %
>
<p>
Balance: <%= ff.text_field :balance %>
</p>
<p>
Currency: <%
= ff.text_field :currency %>
</p
>
<% end %>

<p
><%= f.submit "Create" %></p>

<% end %>



POST Parameters:
{
"commit" => "Create",
"account" => { "balance" => { "balance"=>"400", "currency"=>"us" } }
}


By calling f.fields_for on the form builder (and not just fields_for), you are creating a new hash scope 'balance' under params[:account]. Since your composed_of is called balance, only the sub-hash 'balance' will be used in your composed_of block conversion. Then, your model will use the sub-hash to convert it into the Money object you want.

At this point, your Money value object need not be confined with a constructor that takes parameters by ordinal position anymore. You can have it take a hash just like any ActiveRecord::Base classes, so that any future addition of parameters to Money does not require changes to your conversion block. Just drop an html element using your fields_for form builder and it will just work.

Why do this? Now, you have a different class to handle money related operations, such as addition, subtraction, validations, money comparison, and even formatting your money amounts everywhere. Should you have another model that needs money, you know where to share code from.

Also, do not get into thinking that a value object must abstract at least 2 columns on a table. It doesn't have to be. A value object can abstract just one column but contains its own validation and formatting logic and just be fine.

Value objects are very important in any application, even though a lot of times they are simple. Make good use of them and your code quality will improve. Now it's time to roll up your sleeves and clean up those value object creation code inside your controllers!

3 comments:

Justin said...

Very helpful to see how fields_for and composed_of work together. You also allude to aggregate objects containing their 'own validation' code. I'd be curious to see a writeup on this, haven't been able to find much on the web.

Damon said...

Great article Stephen,

I have one question about when to use composed_of. I have Address that is an aggregation of Locations. The types of Locations depend on the type of address. For example, a US_Postal_Address is an aggregation of City, State, Zip, etc Locations. An English_Postal_Address is an aggregation of City, County, Postal Code, ... Locations.

Other Address types will added, so I don't want to store locations within the definition of the Addresses table.

My question is, I'm not particularly interested in the identity of the Locations, I'm only interested in their values. However, the values do need to persist. Can composed_of aggregate other ActiveRecord models?

Second question, does this approach make sense to you?

Thanks for a great post and your help.

Damon

Stephen Chu said...

@Justin:

Check this article out: Rails composed_of validation