Multiple / Class Table Inheritance in Rails

Surely many people face a problem when there are several models, say, Client, Manager and User - which have a number of fields - for example, name, email, position - are the same. Moreover, each of the models also has unique fields and methods. In this case (arguing abstractly) it would be logical to place common fields with the corresponding validations in a separate table people (the Person model), leaving only specifics in Client, Manager and User.

A number of examples can be continued: Product - Fridge, Phone, Toaster; Vehicle - Car, Truck, Motorcycle and so on. The problem is pretty general - what are the solution options for Rails?

Options


Single Table Inheritance (STI)

Much has been written about this, in particular in Rails Guides and on Habré here and here . The bottom line is that we put the entries for Client, Manager and User in the same people table, using a special type field to make it clear "who is who." Here is an example; it is conditional, general fields are marked with an asterisk, private fields with capital letters of model names:

type    | name*          | email*  | position (M)    | company (C)           | hobby (U)
-----------------------------------------------------------------------------------------
manager | Slartibartfast | a@b.com | Planet designer | NULL                  | NULL
client  | Ford           | c@d.com |                 | Megadodo Publications | NULL
user    | Arthur         | e@f.com | NULL            | NULL                  | Travelling

However, in this case, Manager acquires an unusual hobby property from User. Also, the hobby managers field will always be empty. User, Client and Manager in this case are subclasses of Person that do not have their own tables, and each unique property needs to be declared in the parent table / model.

In principle, one could close his eyes to this, but what if Manager requires the creation of 42 own fields that have nothing to do with Client and User? In this case, it would be more logical to transfer specific fields to separate clients, users and managers tables, leaving only common fields in people, as well as type and id to build the necessary links. Such a scheme, as Google suggests, is called Multiple Table Inheritance, but, unfortunately, Rails does not know anything about it yet, and, as a quick search on the developer forum shows, it is not going to be in the foreseeable future.

Nested attributes + Delegation

The problem can be solved as follows:

class Manager < ActiveRecord::Base
  belongs_to :person
  # общее
  accepts_nested_attributes_for :person
  delegate :name, :email, to: :person
  # частное
  validates_presence_of :position
end
Company.find(42).managers.create(position: 'Paranoid android', person_attributes: {
  name: 'Marvin', email: 'whats_the@point_to.be'
})

In principle, it’s an option, but in this case you’ll have to separate the common attributes all the time in a separate hash when creating or modifying the record, as well as use the fields_for helper when displaying forms, and also do additional squats in the controller and model, which is described in detail in the same Rails Guides.

I would like to have a “seamless” merge between both models and completely isolate the particularities of the implementation of the common fields Manager, User and Client from the point of view of other application classes.

Own garden

For obvious reasons, I don’t feel like fooling around with it, although if you look at the combination of “rails multiple table inheritance” or “rails class table inheritance”, you will find many options for implementing MTI yourself.

Gems

It was logical from the very beginning to assume that everything had already been done before us, and that’s what I managed to find. A not very detailed study of the repositories on Github shows that only active_record-acts_as lives and develops from them . I will not duplicate Readme, it clearly describes how to use the gem. A quick look at issues shows that the project is still in the initial phase, but IMO is quite applicable with proper test coverage.

Have you encountered a similar problem? Are you aware of other solutions? I will be glad to hear your opinion in the comments.

Also popular now: