Ruby On Rails, Design, Simplicity, Web 2.0, Ajax, Mac and Tons of Pizza.

Oct 13

multi-table Inheritance with ActiveRecord

Posted by Sandro Paganotti in 2 comments digg this add to delicious

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:

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

    yeah.. switch to dotnet framework with nhibernate =D
  • Adam Sanderson

    Posted on October 14

    I don't know which database you're using, but you might want to give postgresql a look. It supports table based inheritance, so you can do single or multi table class hierarchies, and it's mostly invisible to rails.

Post a comment

Categories:

Tags:

Powered by Mephisto, Valid XHTML 1.1, Valid CSS - Supported by Wave Factory