Making #composed_of more useful
Update: my patch finally got added to edge rails.
Active Record allows you to abstract fields into an aggregate object by using the composed\_of
declaration. This is handy, but the current implementation can be a real pain.
The first and somewhat trivial issue is the composed_of
declaration builds a string and evals it to define the attribute accessors. It’s not a big deal, but it’s dirty. The second issue is that aggregate objects are not easy to manipulate in Rails, especially in forms.
Fixing it
So, I’ve written a plugin that overrides the Active Record implementation of composed\_of
, which allows you to specify a block to convert incoming parameters to the correct type.
Personally, this has been most helpful when using the Money gem:
class Account < ActiveRecord::Base
composed_of :balance, :class_name => "Money", :mapping => %w(cents cents) do |amount|
amount.to_money
end
end
If \#balance=
receives anything besides a Money
object, it will call the block to try to convert the parameter to a Money
object.
>> account = Account.new :balance => 100
>> account.balance
=> #<Money:0x2612770 @cents=10000, @currency="USD">
And now it can transparently be used in forms:
<%= text_field :account, :balance %>
This can even be used for more advanced aggregations:
class User < ActiveRecord::Base
composed_of :address, :class_name => "Address"
:mapping => [%w(street street), %w(city city), %w(state state), %w(zip zip)] do |addr|
Address.new(addr[:street], addr[:city], addr[:state], addr[:zip])
end
end
A user can now be created from a hash:
User.new(:address => {:street => "123 A Street", :city => "Somewhere", :state => "NO", :zip => 12345})
I didn’t quite realize all the implications of this extension until I was writing up the docs for this plugin. Active Record magically does type casting for a limited set of types, namely dates and numbers. But this essentially allows you to have a form of type casting for any attribute. Interesting…
Installation
This has also been submitted as a patch to the Rails trac but hasn’t been accepted yet.
script/plugin install http://source.collectiveidea.com/public/rails/plugins/composed_of_conversion