We build Web & Mobile Applications.

< All Articles

Giving composed_of some much needed lovin'

UPDATE! Great news - my patch made it into core I updated the original patch with additional documentation and also took the opportunity to tweak some of the existing documentation too, so hopefully the options for composed_of are now a little bit clearer. Perhaps now composed_of can enjoy a little more popularity.

Did you know ActiveRecord includes support for aggregations? If you did, have you ever used them? Despite being part of Rails from the start composed_of tends to lurk in the shadows while newer features like named_scope steal the limelight. It’s time to give composed_of some love again!

Aggregation helps to make your models richer: for example if you’re dealing with money why deal with integer and decimal attributes when you can use something like the Money class? Thanks to various patches the shortcomings of the original composed_of implementation have been addressed, but there is still one that for some reason remains to be fixed: aggregate class constructors.

The problem is simple: composed_of assumes that the aggregate class can be instantiated using a simple new constructor that accepts arguments in the order defined by the :mapping option. In many cases this works, but if your aggregate class doesn’t conform then you’re left to manually initialise your aggregations (in an after_initialize callback for example). Not very elegant.

Here is an example from a recent project I’ve been working on:

class Visitor < ActiveRecord::Base

  composed_of :ip_addr,
              :class_name => 'IPAddr',
              :mapping => %w(ip to_i)


Here the composed_of aggregation maps the ip attribute (an integer) to the IPAddr class from the Ruby standard library. Much of the code that uses this attribute wants to treat it as an IP address rather than a raw integer so it makes sense to represent it as one in the model. Unfortunately this won’t work properly as the IPAddr constructor requires a second parameter to be passed to the constructor as shown below:

=> ArgumentError: unsupported address family

IPAddr.new(1024768100, Socket::AF_INET)
=> #<IPAddr: IPv4:>

Sadly previous attempts to allow composed_of to be more flexible with constructors have failed to make it into core. Personally I don’t think that too few people requesting an enhancement is a good enough reason not to apply it anyway. Perhaps if composed_of was more flexible in the first place more people would use it!

Hopefully though it’ll be third time lucky as I’ve just submitted my own patch to improve composed_of. If the core team still don’t feel motivated to apply it then I guess I’ll just release it as a plugin instead. It takes a slightly different approach then the previous patches:

  1. It adds a new :constructor option that takes a symbol or a proc that will be used to instantiate the aggregate object. It is optional and if not used then the existing behaviour of calling new with the mapped attributes will be used.

  2. It deprecates the use of a block to provide a method of converting values assigned to the aggregate attribute. The use of a block didn’t feel particularly consistent with the rest of Rails where typically symbols or procs are passed as options (a good example being the :if and :unless options for validations). Of course passing a block will still function, so existing code won’t break, but it will raise a deprecation warning. This change leads on to…

  3. It adds a new :converter option that also takes a symbol or proc that will be used when the aggregate attribute is assigned a value that is not an instance of the aggregate class. This replaces the old block syntax and makes it easier to do things like calling composed_of from within a with_options block. If both the :converter option and a block are passed to composed_of then the :converter option will take precedence.

Here’s the IP address example from earlier updated to take advantage of the new options:

class Visitor < ActiveRecord::Base

  composed_of :ip_addr,
              :class_name => 'IPAddr',
              :mapping => %w(ip to_i),
              :constructor => Proc.new { |value| IPAddr.new(value, Socket::AF_INET) },
              :converter => Proc.new { |value| value.is_a?(Integer) ? IPAddr.new(value, Socket::AF_INET) : IPAddr.new(value.to_s) }


Passing a symbol to either the :constructor or :converter options provides an easy way to use a class where the constructor is not named new but still expects arguments in the order defined by the :mapping option. For example if your aggregate class uses a parse method for instantiation you could do this:

class MyClass < ActiveRecord::Base

  composed_of :aggregate_value,
              :class_name => 'AggregateClass',
              :mapping => %w(value to_s),
              :constructor => :parse,
              :converter => :parse


I think these simple changes make aggregation with composed_of much more powerful without breaking existing code. If you’d like to see composed_of get the love it deserves, take a look at my patch and give it your support.

Update: A better example

Here’s another example to more fully illustrate why there is a need for separate constructors and converters. To clarify:

Here’s an example that hopefully makes this difference more obvious:

class NetworkResource < ActiveRecord::Base
  composed_of :cidr,
              :class_name => 'NetAddr::CIDR',
              :mapping => [ %w(network_address network), %w(cidr_range bits) ],
              :allow_nil => true,
              :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
              :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }

# Calls the :constructor proc
n = NetworkResource.new(:network_address => '', :cidr_range => 24)

# Calls the :converter proc
n.cidr = [ '', 8 ]
n.cidr = ''

# Doesn't call the :converter proc as the class matches the aggregate class
n.cidr = NetAddr::CIDR.create('')

# Save and reload - uses the :constructor proc on reload
Updated on 07 February 2019
First published by Rob Anderton on 24 August 2008
© Rob Anderton 2019
"Giving composed_of some much needed lovin'" by Rob Anderton at TheWebFellas is licensed under a Creative Commons Attribution 4.0 International License.