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 – 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.
Hi Gerry.
I just wanted to let you know, that I’ve implemented your idea, in this article, as a gem.
It’s freely available on rubygems and the source-code is available on github:
https://github.com/BenjaminMedia/Heritage
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).
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 :)
My apologies, I recently updated my github username to PeterHamilton so the gem can be found at http://peterhamilton.github.com/citier/ what an idiot…
Aaaannnd he’s apparently changed it again:
https://github.com/petehamilton/citier
quit changing your name..
https://github.com/petehamilton/citier
Pingback: Single Table Inheritance or Class Table Inheritance? - Programmers Goodies
Where are you getting :biz from? The example doesn’t make sense.
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.
” 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
This pattern is called Class Table Inheritance. http://martinfowler.com/eaaCatalog/classTableInheritance.html
Very, very nice page! :)
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.
Pingback: Creating View-Centric Interfaces with the Decorator Pattern » Big Nerd Ranch Blog
Pingback: Common behaviour for ActiveRecord models | Reliqs
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.
Not perfect, but decent.
How do you have additional belongs_to associations in say business?
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?
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
i am getting “stack level too deep” error