Tweetegy On the edge of chaos with Ruby, Rails, JavaScript and AngularJS.

| About | Search | Archive | Github | RSS |

Using Value Objects in ActiveRecord

I wanted to find out more about using Value Objects in ActiveRecord. Value objects are conserned with the value of their attributes over other identifiers (as opposed to Entitiy Objects which are concerned mainly about a unique ID). I decided to create a stand alone Active Record project (i.e. without using Rails). This became a sort of mini exercise in itself.

Structuring a standalone ActiveRecord project

I like structure, so the setup I settled with is this:

/db /models /config run.rb

Inside config I placed two files a config.rb and database.yml file:

config.rb

1 require 'rubygems'
2 require 'active_record'
3 require 'yaml'
4 require 'logger'
5 
6 DBCONFIG = YAML::load(File.open('config/database.yml'))
7 ActiveRecord::Base.establish_connection(DBCONFIG)
8 ActiveRecord::Base.logger = Logger.new(File.open('db/database.log', 'a'))

The database.yml file simply contains the details for my database connection. Inside my schema.rb I use ActiveRecord::Migration to up (create) and down (drop) my table in the database. All the details can be found in the sample project on Bitbucket.

Structuring Value Objects

The interesting part when concerning Value Objects are the two classes under the models directory person.rb and address.rb (shown below). Notice that since we want address data to be stored as a Value Object we do not inherit from ActiveRecord::Base. Instead, address is a plain Ruby object and is used in Person by using the composed_of class method. This allows for the attributes of the Value Object to be stored together in the database and composed_of allows us to use these together as one object.

person.rb

1 require 'models/address'
2 
3 class Person < ActiveRecord::Base
4   composed_of :address, :mapping => [%w(address_city city), %w(address_county county)]
5 end

address.rb

1 class Address
2   attr_reader :city, :county
3   def initialize(city, county)
4     @city, @county = city, county
5   end
6   def ==(other_address)
7     city == other_address.city && county == other_address.county
8   end
9 end

Using Value Objects

So how to use Value Objects? Below is a sample application run.rb that uses the above models and creates four records in the database and searches for using a Value Object for records that have a particular address. The output from this applicaition is not surprisingly: Darren, Michael, proving that it is possible to pass in a new instance of a Value Object (in this case Address) and ActiveRecord will break that down and search for the address in the database for us.

run.rb

 1 require 'config/config'
 2 require 'models/person'
 3 
 4 p1 = Person.create(:name => "Darren", :address_city => "Eastbourne", :address_county => "Sussex")
 5 p2 = Person.create(:name => "Paul", :address_city => "Nottingham", :address_county => "Nottinghamshire")
 6 p3 = Person.create(:name => "Simon", :address_city => "Dartford", :address_county => "Kent")
 7 p4 = Person.create(:name => "Michael", :address_city => "Eastbourne", :address_county => "Sussex")
 8 
 9 people_in_eastbourne_sussex = Person.find_all_by_address(Address.new("Eastbourne", "Sussex"))
10 puts people_in_eastbourne_sussex.map(&:name)

The SQL that is generated for find_all_by_address is not surprising:

SELECT "people".* FROM "people"
WHERE "people"."address_city" = 'Eastbourne'
AND "people"."address_county" = 'Sussex'

Another thing to note is that it is possible to reference either the Value Object via person.address or the attributes separately via person.address_city and person.address_county.