multi-table Inheritance with ActiveRecord
Posted by Sandro Paganotti in
2 comments
In the past few days I’ve come across a problem that is usually solved using a multi-table inheritance (also called Class Table Inheritance) approach, for those of you who don’t still know what ‘multi-table inheritance’ means here’s a small quote from Martin Fowler
[Class Table Inheritance] Represents an inheritance hierarchy of classes with one table for each class.
This pattern lead to a data aggregation problem; in fact you’ll need to mix data from two tables (one from the parent class and one containing the data of the current object). ActiveRecord actually does not offer a ‘out-of-the-box’ solution for this problem and the best way we can do is try to emulate this pattern.
On the web many posts have been written about this problem, each of them trying to figure out a solution that achieves the goal while keeping all the ActiveRecord features and maximizing elegance:
- How to model Hibernate Multi-Table inheritance with ActiveRecord
- Multi-Table inheritance in Rails, when two tables are one …
My personal approach
I’ve tried to figure out a possible ActiveRecord-friendly implementation for the common PartyRole architecture. In this schema you have a table called party which holds all the information about the user (name,e-mail, ...), a table called roles which contains all the possible roles a user could have (customer, seller, administrator, ...), a table that joins the two (called PartyRole) linking a user with its roles and a table with detailed data for each role (eg: ‘customer shipping address’ only for the customer role).
When you retrieve a PartyRole instance you need to mix the data of the user related to this partyrole with the information stored in the table named as the role this instance is using (eg: ‘Sandro as a Customer’ partyrole instance needed to retrieve data both from parties and customers tables).
As I’m writing this article I’ve only managed to flat the relationship between a PartyRole and its ‘role-dependent’ data; to do this I dynamically extend the PartyRole model creating (via STI) a dedicated PartyRole class for each of the roles stated in the roles table (eg: CustomerPartyRole, SellerPartyRole, ... ). In each of these classes then I create a ‘has_one :extra’ association that point to the table that holds the data related to this particular role:
Role.all.each do |r|
Object.const_set("#{r.title}#{PartyRole}".to_sym,Class.new(PartyRole))
Object.const_get("#{r.title}#{PartyRole}").class_eval do
has_one :extra, :class_name=>"#{r.title}", :foreign_key=>'party_role_id'
end
end
By this way you can retrieve the information related to a PartyRole simply invoking:
sandro = PartyRole.find(:include=>[:party],:conditions=>{:role=>Role.find_by_title('Customer').id, :party=>{:name=>'Sandro'}})
sandro.extra
# I'll get the information collected into the 'customers' table related to 'sandro'
sandro.party
# I'll get the information about 'sandro' stored into the parties table
The next step to achieve is to find a way to dynamically merge extra and party into the sandro instance, I’ve tried using Delegators but this does address the problems you face when trying to execute a query like this one:
CustomerRole.find(:all, :conditions=>{:'an attribute from the customers table'=>xxx })
So, if you have some ideas or suggestions, please share :)
Sandro


Comments
Me
Posted on October 13
Adam Sanderson
Posted on October 14