Multiple Table Inheritance in Rails 3

Multiple Table Inheritance – Introduction

It is a fact that sometimes we want to achieve a database design similar to the design of classes and subclasses in OOP. This design is what we call Multiple Table Inheritance (MTI). The idea behind it is to have a main table which will hold all the basic attributes of the underlying models and separate tables for the underlying models each of them will hold specific attributes special for each model.

Multiple Table Inheritance – Rails 3 Implementation

Enough of the theory, let’s get our hands dirty and make it work in the Rails framework. For our purposes let us suppose that we have two (for simplicity) different types of businesses: restaurants and bars. Both are businesses and both share same attributes such as: name, address, phone number, etc. But if you look closely you will see that a restaurant and a bar have also many different attributes (has waiter, has wifi, has kids area etc. for the restaurant, music type, best nights, dresscode etc. for the bar). So it is best to have a common table that holds all the common attributes and separate tables to hold all the different ones i.e. Multiple Table Inheritance. Following this design you remove duplication from the database and at the same you keep tables clean (without many many NULL values).

multiple table inheritance rails 3

Database Design

Multiple Table Inheritance – Models

We will create a base model

class Business < ActiveRecord::Base
  # ASSOCIATIONS --------------------------------------------------------
  belongs_to  :biz, :polymorphic => true
end

that it will hold all of the common attributes and it will be polymorphic as it will contain content from different models (Restaurant, Bar etc). Next we define the other two models

class Restaurant < ActiveRecord::Base
  acts_as_biz
end

class Bar < ActiveRecord::Base
  acts_as_biz
end

which both contain only the acts_as_biz class method. We intend to use this method in order to give to models some shared functionality. First we want to make the Business model transparent (as it is now an intermediate model) i.e. we want to call the Business attributes directly on the Restaurant / Bar instance e.g.

bar = Bar.new
# => #<Bar id: nil, music: nil, best_nights: nil, dresscode: nil>
bar.name
# => nil

Multiple Table Inheritance – Mixins

To accomplish that we have to do two basic things. Firstly we have to define attribute accessors (of the Business class) inside the acts_as_biz method and secondly we have to autobuild the association between Business and Restaurant / Bar whenever we initialize any child class. We do all that inside the following module:

module Biz
  def acts_as_biz
    include InstanceMethods
    ############################ Class methods ################################
    has_one :business, :as => :biz, :autosave => true, :dependent => :destroy
    alias_method_chain :business, :build
    
    business_attributes = Business.content_columns.map(&:name) #<-- gives access to all columns of Business
    # define the attribute accessor method
    def biz_attr_accessor(*attribute_array)
      attribute_array.each do |att|
        define_method(att) do
          business.send(att)
        end
        define_method("#{att}=") do |val|
          business.send("#{att}=",val)
        end
      end
    end
    biz_attr_accessor *business_attributes #<- delegating the attributes
  end

  module InstanceMethods
    def business_with_build
      business_without_build || build_business
    end
  end
end

Now, let us explain a little bit what’s going on here. The line

has_one :business, :as => :biz, :autosave => true, :dependent => :destroy

is self explanatory. Every Bar / Restaurant will have only one entry in the businesses table and when we destroy a Bar / Restaurant the entry in the businesses table must disappear too. Also we want to autosave the association for apparent reasons (that is why we have set autosave to true). Next we define the custom attribute accessor biz_attr_accessor that does the job of delegating the attribute accessors of the Business class to the Bar / Restaurant class. Now, as we said earlier, we have to autobuild the association and to do that we have to use the alias_method_chain approach as we have no control over the ActiveRecord::Base class. If you are not familiar of what the alias_method_chain does it is a shorter syntax of

alias_method :business_without_build, :business
alias_method :business, :business_with_build

Combining this with the method

def business_with_build
   business_without_build || build_business
end

we ensure the autobuilding of the association. Now that we have build the Biz module (and saved it into the /lib directory) we have to create an initializer, say biz_init.rb, and inside extend the ActiveRecord::Base i.e.

ActiveRecord::Base.extend Biz

Multiple Table Inheritance – Conclusion

Closing I have to point out that it is best to follow the above approach when

  • Your models physically follow inheritance (you should NOT use it otherwise)
  • Your models have plenty common attributes but at the same time plenty uncommon. If it is not the case, you should probably have them in the same table (have little uncommon attributes) or in separate tables (have little common attributes).

Note: I want to say to those alias_method_chain haters and to those who prefer to use super instead, that despite the fact that using super is preferable, sometimes (when you have no control over some class) you have no alternative. I strongly advise you to read the best article I have found so far on the subject.

20 Comments Multiple Table Inheritance in Rails 3

    1. Gerry

      Hello Thomas. It is good to hear that you have started building a gem over this idea! Consider though that you may not want to delegate all the attributes of the parent Model (it is good to have an :except => [:attr_1, :attr_2] option in your acts_as_predecessor method).

      Reply
  1. Peter Hamilton

    Hi,

    I recently forked a promising gem to implement multiple table inheritance and class inheritance in Rails. I have spent a few days subjecting it to rapid development, fixes, commenting and documentation and have re-released it as CITIER Class Inheritance and Table Inheritance Embeddings for Rails.

    Consider giving it a look: http://peterejhamilton.github.com/citier/index.html

    Has a really clean usage and seems really nice so far, really pleased with it :)

    Reply
  2. Pingback: Single Table Inheritance or Class Table Inheritance? - Programmers Goodies

  3. Jonathan

    I’m getting /home/user/myapp/config/initializers/dets_init.rb:1:in `’: uninitialized constant Dets (NameError)

    Where do you place the module file in lib, I also have assets and tasks in there. But I put it in just lib/. I tried putting it into assets no difference.

    Reply
  4. Jonathan

    ” ActiveRecord::Base.extend Biz ” – didn’t work for me. I had to put it at the end of the module file as ” ActiveRecord::Base.send :include, Biz ” as ” require ‘biz.rb’ ” in the initializer file.
    With Rails 3.2.1

    Reply
  5. Scott Brickner

    One major problem I see with this is that other tables can’t effectively refer to a “Business”.

    class Receipt < ActiveRecord::Base
    belongs_to :business
    end

    Receipt.first.business … ?

    The Business instance doesn't know which table it should use to find the rest of the instance data.

    Reply
  6. Pingback: Creating View-Centric Interfaces with the Decorator Pattern » Big Nerd Ranch Blog

  7. Pingback: Common behaviour for ActiveRecord models | Reliqs

  8. Scott W

    Thank you very much for this discussion and code, Gerry. It was extremely useful for me, and I now have Class Table Inheritance up and running. I’m somewhat surprised that this isn’t supported directly in the Rails core.

    The need to call self.super_class.attribute to set and get isn’t quite true polymorphism, but that can be hacked into sub-classes easily enough.

    
    attribute() self.super_class.attribute; end
    attribute=(val) self.super_class = val; end
    

    Not perfect, but decent.

    Reply
  9. Ant

    Hi, it works great and it solves the table per class wonderfully. I have a question though.
    If i pick a record from the db like this: Restaurant.all.first it works great and I can access to Business related attributes.
    If i pick a record from the db like this:
    Business.all.first I don’t get the related “restaurant” eager loaded attrubutes.

    I guess it’s because that’s the way polymorphism works in activerecord… anyway is there any way to achieve this behavior?

    Reply
  10. joaoCunha

    But after all that and I want a form to create a Restaurant how do i do it?

    I’m a bit confused.

    In the controller do i do Business.new or Restaurant.new? I’m confused

    Reply

Leave a Reply to Anon Cancel reply

Your email address will not be published. Required fields are marked *


*