How to Use Multiple Table Inheritance in Rails [Solved]

How to Use Multiple Table Inheritance in Rails

Ruby-on-RailsIt 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

Multiple Table Inheritance – Models

We will create a base model

  1. classBusiness<ActiveRecord::Base
  2. # ASSOCIATIONS ——————————————————–
  3. belongs_to :biz,:polymorphic =>true
  4. 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

  1. classRestaurant<ActiveRecord::Base
  2. acts_as_biz
  3. end
  1. classBar<ActiveRecord::Base
  2. acts_as_biz
  3. 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.

  1. bar =Bar.new
  2. # => #<Bar id: nil, music: nil, best_nights: nil, dresscode: nil>
  3. bar.name
  4. # => 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:

  1. moduleBiz
  2. def acts_as_biz
  3. include InstanceMethods
  4. ############################ Class methods ################################
  5. has_one :business,:as=>:biz,:autosave =>true,:dependent =>:destroy
  6. alias_method_chain :business,:build
  7. business_attributes =Business.content_columns.map(&:name)#<– gives access to all columns of Business
  8. # define the attribute accessor method
  9. def biz_attr_accessor(*attribute_array)
  10. attribute_array.each do|att|
  11. define_method(att)do
  12. business.send(att)
  13. end
  14. define_method(“#{att}=”)do|val|
  15. business.send(“#{att}=”,val)
  16. end
  17. end
  18. end
  19. biz_attr_accessor *business_attributes #<- delegating the attributes
  20. end
  21.  
  22. moduleInstanceMethods
  23. def business_with_build
  24. business_without_build || build_business
  25. end
  26. end
  27. end

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

  1. 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

  1. alias_method :business_without_build,:business
  2. alias_method :business,:business_with_build

Combining this with the method

  1. def business_with_build
  2. business_without_build || build_business
  3. 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.

  1. 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.

Credit: http://techspry.com/ruby_and_rails/multiple-table-inheritance-in-rails-3/

Leave a Reply

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