Dovadi

None of us is as smart as all of us.

Gem Released: Has_attributes_from

My first little gem has_attributes_from for merging attributes from one ActiveRecord Class to another individual STI subclass.

So why, do we want to do that, you might ask? Well, I was working on a rails project where clients take care of the administration (planning and billing) for child daycare centres. In this project I have all kinds of people objects, like a child, a contactperson, a father, mother, caretaker etc. etc. So, the ideal casus for a Single Table Inheritance implementation. So I implemented a ‘classic’ Person Class as follows:

1
2
3
4
5
6
7
8
9
  create_table :people, :force => true do |t|
    t.string   :firstname
    t.string   :lastname
    t.string   :initials
    t.string   :type
    t.string   :social_security_number
    t.string   :gender
    t.datetime :date_of_birth
  end

However, I like to add certain extra attributes to a Child, like for example its nickname and information about its allergies. So I introduce another class which I call ChildDetail. Of course I can add these attributes to the people table as well, but in this project I had several more fields to add and some other attributes for a father., which would lead to a lot of columns for only two subclasses (of the five in total).

1
2
3
4
5
6
create_table :child_details, :force => true do |t|
    t.string   :nickname
    t.string   :vaccination
    t.string   :allergy
    t.integer  :child_id #belongs_to relationship with Child
end

Ok, now I can access the extra attributes

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person < ActiveRecord::Base
end
class Child < Person
  has_one :child_detail
end
class ChildDetail < ActiveRecord::Base
  belongs_to :child
end

child = Child.first
=>#<Child id: 2, firstname: "William", lastname: "Oxener", type: "Child", social_security_number: "123456789", gender: "m", date_of_birth: "2005-12-02 00:00:00">
puts child.child_detail.nickname
=>"Bill"

This is not really the way I want it, I like to ask directly for the nickname of a child without going through a child_detail. So to solve this ‘problem’ I wrote the has_attributes_from gem. Add the following line to your environment.rb file

1
config.gem 'dovadi-has_attributes_from', :lib => 'has_attributes_from', :version => '>=0.1.1', :source => 'http://gems.github.com'

Install and unpack this gem to your vendor directory or install as a plugin

1
./script/plugin install git://github.com/dovadi/has_attributes_from.git

Now we can do the following:

1
2
3
4
5
6
7
8
class Person < ActiveRecord::Base
end
class Child < Person
  has_attributes_from :child_detail
  validates_presence_of :nickname
end
class ChildDetail < ActiveRecord::Base
end

With has_attributes_from the attributes from ChidDetail are merged with Child. A child object acts as one single object and I can even do validation on nickname directly (or the other attributes from ChildDetail).

1
2
3
4
5
6
7
8
9
10
11
12
child = Child.first
=>#<Child id: 2, firstname: "William", lastname: "Oxener", type: "Child", social_security_number: "123456789", gender: "m", date_of_birth: "2005-12-02 00:00:00">
puts child.nickname
=>"Bill"
child.update_attributes(:nickname=>nil)
=>false
child.errors.full_messages
=> ["Nickname must be present"]
child.nickname="Daam"
=>"Daam"
child.save
=>true

I think this is much nicer, besides it was fun to make and a good exercise to put some Ruby meta programming into practice.