Ruby on Rails
SingleTableInheritance (Version #101)

See Also: ForumExample, ConcreteTableInheritance

Inheritance hierarchies can be represented in a relational database with at least three different forms. Martin Fowler describes the different forms with these short summaries in Patterns of Enterprise Application Architecture

  1. Class Table Inheritance: Represents an inheritance hierarchy of classes with one table for each class1.
  2. Single Table Inheritance: Represents an inheritance hierarchy of classes as a single table that has columns for all the fields of the various classes2.
  3. Concrete Table Inheritance: Represents an inheritance hierarchy of classes with one table per concrete class in the hierarchy3.

1 http://www.martinfowler.com/eaaCatalog/classTableInheritance.html

2 http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html

3 http://www.martinfowler.com/eaaCatalog/concreteTableInheritance.html

Active Record uses approach number two, which means that you have to add a string column — by convention named ‘type’, can be overridden using inheritance_column() — to the table where you want to keep the hierarchy. (Note that the column type must be a string type, a MySQL enum does not work.)

For more details see API docs: http://api.rubyonrails.org/classes/ActiveRecord/Base.html

Alternatively, some work on getting approach 1 working in Rails is here: ClassTableInheritanceInRails

If you have an “uninitialized constant” error:
Note: Rails must ‘see’ the file containing the STI classes before it can use them, otherwise you’ll end up with an “uninitialized constant” error. This can be a problem if the name of the class and the name of the file defining it are not the same, e.g. you have the model ‘Manager’ in the file ‘employees.rb’. Rails will not be able to divine the filename from the class name in this case.

An easy way to do fix this is to add “model :employees” to your application.rb controller, where ‘employees’ is the name of the file containing the STI class minus the extension (so in this case, the model would be contained in a file named ‘employees.rb’.) This forces Rails to load that file and ‘see’ all the models you have defined in it, and it will then know of your STI classes when you go to use them. {color:red} Using ‘model’ has been deprecated in Rails 1.2 (possibly earlier) use ‘require_dependency’ instead of ‘model’. An alternative to using “model :mymodel” is to just require the file defining the models used in the STI, eg. require ‘mymodel’

You may also get an “uninitialized constant” error if you have mistyped the name. For instance:


class Fastrabbit < Animal; end

# versus

class FastRabbit < Animal; end

Additional info & tips

  • Use Employee[:type] to access the subclass info, not Employee.type. type is a (deprecated) ruby method, and so this will throw up all sorts of strange results.
  • Note that the find methods do not necessarily find all descendant classes. For instance, if you have Manager < Employee < Person, then

    People.find(:all)

    will find all People, Employees, and Managers, while

    Employees.find(:all)

    will find only Employees, and not Managers.
  • When constructing a form that can be used for several subclasses the following may help (thanks to Chris Hall)

first, in your controller:


# simplified
def new
  case @params[:person_type]
    when "Manager"
      @person = Manager.new
    when "Slave"
      @person = Slave.new
  end
end

then in your view, do:


<%= hidden_field_tag "person_type", @person[:type] %>

|{background:#ddd}. How does the view know which persontype to send with the form data?
-JamesH_ |

then in your controller’s create method:


#again, simplified
def create
  case @params[:person_type]
    when "Manager"
      @person = Manager.new(@params[:person])
    when "Slave"
      @person = Slave.new(@params[:person])
  end
  if @person.save
    redirect_to :action => :list
  else
    render_action :new
  end
end

you could even go so far as to create a factory class method in your Person model


class Person < ActiveRecord::Base
  def self.factory(type, params = nil)
    case type
      when "Manager"
        return Manager.new(params)
      when "Slave"
        return Slave.new(params)
      else
        return nil
    end
  end
end

A more scalable version of the above could go something like below. The version below will raise an exception if the named subclass doesn’t exist (which could be considered a positive feature, depending on circumstance). You could also check for undefined subclasses and return Person if the subclass isn’t found:


class Person < ActiveRecord::Base
  def self.factory(type, params = nil)
    params[:type] ||= 'Person'
    class_name = params[:type]
    # if defined? class_name.constantize
    class_name.constantize.new(params)
    # else
    # Person.new(params) # or do what you will...
    # end
  end
end

then in your controller’s new and create methods, you can do


def new
  @person = Person.factory(@params[:person_type])
end

def create
  @person = Person.factory(@params[:person_type], @params[:person])
  if @person.save
    ...
  end
end

Q: I tried to find a way to redefine Person.new instead of having to remember to use Person.factory everywhere, but couldn’t find a way to do it. Anyone?
A: Sure. Just put it in the ‘initialize’ method of the class. Class.new creates the new object then Class#initialize is called on that object.

  • You can also create a subclass the following way (thanks to Sean Hussey/Ezra)

    person_type = "Manager" @person = eval(person_type + ".new")

    Or use constantize:

    person_type = "Manager" @person = person_type.constantize.new

WARNING: if person_type comes from an HTTP request, this is a security hole


person_type = "Manager" @person = person_type.constantize.new

  • You might also use this less dangerous way, which checks if the passed type is a child of a parent—or the parent itself:

    #Lets say :type contains the string "Manager" type = params[:type] || "Article" if (type.constantize.base_class) == Person @person = type.new end

Another way of doing the above factory that fixes the security problem is to follow the convention that all created classes be prefixed by a standard name. That way the system won’t be instantiating unintended classes. Also, you need to use rescue instead of defined? to handle the exception raised by constantization of undefined class types. Like this:


def self.create(params = nil)
  params[:person_type] ||= 'Person'
  class_name = "Person#{params[:person_type]}"
  begin
    class_name.constantize.new(params)
  rescue
    Person.new(params)
  end
end

A nice one-liner to check your constantization and return the model given you want your model’s superclass to be the passed in superclass is:


def check_valid_model(model_string, superclass)
  (_model = model_string.constantize).superclass == superclass && _model
rescue
  false
end

  • If the text in your “type” column doesn’t exactly match your class name, you can do this to make things more readable:

    class ETH < ListData; end Ethnicity = ETH

Or you could create some accessors to the :type attribute to use in your forms.


  def class_type=(value)
    self[:type] = value
  end
  
  def class_type
    return self[:type]
  end

This way you can assign the type using things like a radio button.


	radio_button("market", "class_type", "Exclusive")

Can someone post details on how to go about testing Single Table Inheritance models in Rails?

Ex: you have People, and Employee < People.

Do you create unit tests for both your People and Employee models? I’ve done this, have set the inheritance in the Employee model, as well as set_table_name “people”.

When I run ‘ruby unit/employee_test.rb’ it gives an error like:

‘myapp_test.employees’ doesn’t exist

How do I tell it to look at the People table instead?

To test STI classes, create fixtures in the baseclass fixture-file (’people’) and specify ‘type: Employee’ manually (note upper casing of ‘Employee’. The type column is case-sensitive, so make sure to use the UpperCase notation, not the rails_file notation when setting the class.)

I read that above statement about 10 times without getting the point: do not use fixtures :employees, :people or sth like that. There can’t be a fixture for employees; use fixtures :people, no more.

Also note that when you do the above, accessing fixtures’s methods will not work if you have cross-namespace STI. For example, if you have MyNameSpace::MyInheritingModel < MyModel


models.yml
--------
one:
  id: 1
  ...data...
  type: MyInheritingModel

You cannot use a method of models(:one), like models(:one).id


controller_test.rb
--------
fixtures: models

...

def test
  model_one_id = models(:one).id # gets you ActiveRecord::SubclassNotFound
  model_one_id = 1 # OK to do manual id
end

——

Gang, this’ll save you some hours of frustration. Your controllers may not see your STI subclasses unless you include the following in your controller files:

require_dependency ‘model’

…where ‘model’ is the name of the parent class.

——

I’ll add to the above – while refactoring to use STI, rails wouldn’t even start – turns out I had an observer looking for a class that didn’t have a corresponding file name (Host was defined inside ‘asset.rb’); adding

require_dependency ‘asset’

to to the top of the observer was what was needed (that was hours of fun).

——

Note also that Rails doesn’t set the [:type] field for the base class in Single Table Inheritance. Only subclasses of the base class have the type field filled in.

See Also: ForumExample, ConcreteTableInheritance

Inheritance hierarchies can be represented in a relational database with at least three different forms. Martin Fowler describes the different forms with these short summaries in Patterns of Enterprise Application Architecture

  1. Class Table Inheritance: Represents an inheritance hierarchy of classes with one table for each class1.
  2. Single Table Inheritance: Represents an inheritance hierarchy of classes as a single table that has columns for all the fields of the various classes2.
  3. Concrete Table Inheritance: Represents an inheritance hierarchy of classes with one table per concrete class in the hierarchy3.

1 http://www.martinfowler.com/eaaCatalog/classTableInheritance.html

2 http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html

3 http://www.martinfowler.com/eaaCatalog/concreteTableInheritance.html

Active Record uses approach number two, which means that you have to add a string column — by convention named ‘type’, can be overridden using inheritance_column() — to the table where you want to keep the hierarchy. (Note that the column type must be a string type, a MySQL enum does not work.)

For more details see API docs: http://api.rubyonrails.org/classes/ActiveRecord/Base.html

Alternatively, some work on getting approach 1 working in Rails is here: ClassTableInheritanceInRails

If you have an “uninitialized constant” error:
Note: Rails must ‘see’ the file containing the STI classes before it can use them, otherwise you’ll end up with an “uninitialized constant” error. This can be a problem if the name of the class and the name of the file defining it are not the same, e.g. you have the model ‘Manager’ in the file ‘employees.rb’. Rails will not be able to divine the filename from the class name in this case.

An easy way to do fix this is to add “model :employees” to your application.rb controller, where ‘employees’ is the name of the file containing the STI class minus the extension (so in this case, the model would be contained in a file named ‘employees.rb’.) This forces Rails to load that file and ‘see’ all the models you have defined in it, and it will then know of your STI classes when you go to use them. {color:red} Using ‘model’ has been deprecated in Rails 1.2 (possibly earlier) use ‘require_dependency’ instead of ‘model’. An alternative to using “model :mymodel” is to just require the file defining the models used in the STI, eg. require ‘mymodel’

You may also get an “uninitialized constant” error if you have mistyped the name. For instance:


class Fastrabbit < Animal; end

# versus

class FastRabbit < Animal; end

Additional info & tips

  • Use Employee[:type] to access the subclass info, not Employee.type. type is a (deprecated) ruby method, and so this will throw up all sorts of strange results.
  • Note that the find methods do not necessarily find all descendant classes. For instance, if you have Manager < Employee < Person, then

    People.find(:all)

    will find all People, Employees, and Managers, while

    Employees.find(:all)

    will find only Employees, and not Managers.
  • When constructing a form that can be used for several subclasses the following may help (thanks to Chris Hall)

first, in your controller:


# simplified
def new
  case @params[:person_type]
    when "Manager"
      @person = Manager.new
    when "Slave"
      @person = Slave.new
  end
end

then in your view, do:


<%= hidden_field_tag "person_type", @person[:type] %>

|{background:#ddd}. How does the view know which persontype to send with the form data?
-JamesH_ |

then in your controller’s create method:


#again, simplified
def create
  case @params[:person_type]
    when "Manager"
      @person = Manager.new(@params[:person])
    when "Slave"
      @person = Slave.new(@params[:person])
  end
  if @person.save
    redirect_to :action => :list
  else
    render_action :new
  end
end

you could even go so far as to create a factory class method in your Person model


class Person < ActiveRecord::Base
  def self.factory(type, params = nil)
    case type
      when "Manager"
        return Manager.new(params)
      when "Slave"
        return Slave.new(params)
      else
        return nil
    end
  end
end

A more scalable version of the above could go something like below. The version below will raise an exception if the named subclass doesn’t exist (which could be considered a positive feature, depending on circumstance). You could also check for undefined subclasses and return Person if the subclass isn’t found:


class Person < ActiveRecord::Base
  def self.factory(type, params = nil)
    params[:type] ||= 'Person'
    class_name = params[:type]
    # if defined? class_name.constantize
    class_name.constantize.new(params)
    # else
    # Person.new(params) # or do what you will...
    # end
  end
end

then in your controller’s new and create methods, you can do


def new
  @person = Person.factory(@params[:person_type])
end

def create
  @person = Person.factory(@params[:person_type], @params[:person])
  if @person.save
    ...
  end
end

Q: I tried to find a way to redefine Person.new instead of having to remember to use Person.factory everywhere, but couldn’t find a way to do it. Anyone?
A: Sure. Just put it in the ‘initialize’ method of the class. Class.new creates the new object then Class#initialize is called on that object.

  • You can also create a subclass the following way (thanks to Sean Hussey/Ezra)

    person_type = "Manager" @person = eval(person_type + ".new")

    Or use constantize:

    person_type = "Manager" @person = person_type.constantize.new

WARNING: if person_type comes from an HTTP request, this is a security hole


person_type = "Manager" @person = person_type.constantize.new

  • You might also use this less dangerous way, which checks if the passed type is a child of a parent—or the parent itself:

    #Lets say :type contains the string "Manager" type = params[:type] || "Article" if (type.constantize.base_class) == Person @person = type.new end

Another way of doing the above factory that fixes the security problem is to follow the convention that all created classes be prefixed by a standard name. That way the system won’t be instantiating unintended classes. Also, you need to use rescue instead of defined? to handle the exception raised by constantization of undefined class types. Like this:


def self.create(params = nil)
  params[:person_type] ||= 'Person'
  class_name = "Person#{params[:person_type]}"
  begin
    class_name.constantize.new(params)
  rescue
    Person.new(params)
  end
end

A nice one-liner to check your constantization and return the model given you want your model’s superclass to be the passed in superclass is:


def check_valid_model(model_string, superclass)
  (_model = model_string.constantize).superclass == superclass && _model
rescue
  false
end

  • If the text in your “type” column doesn’t exactly match your class name, you can do this to make things more readable:

    class ETH < ListData; end Ethnicity = ETH

Or you could create some accessors to the :type attribute to use in your forms.


  def class_type=(value)
    self[:type] = value
  end
  
  def class_type
    return self[:type]
  end

This way you can assign the type using things like a radio button.


	radio_button("market", "class_type", "Exclusive")

Can someone post details on how to go about testing Single Table Inheritance models in Rails?

Ex: you have People, and Employee < People.

Do you create unit tests for both your People and Employee models? I’ve done this, have set the inheritance in the Employee model, as well as set_table_name “people”.

When I run ‘ruby unit/employee_test.rb’ it gives an error like:

‘myapp_test.employees’ doesn’t exist

How do I tell it to look at the People table instead?

To test STI classes, create fixtures in the baseclass fixture-file (’people’) and specify ‘type: Employee’ manually (note upper casing of ‘Employee’. The type column is case-sensitive, so make sure to use the UpperCase notation, not the rails_file notation when setting the class.)

I read that above statement about 10 times without getting the point: do not use fixtures :employees, :people or sth like that. There can’t be a fixture for employees; use fixtures :people, no more.

Also note that when you do the above, accessing fixtures’s methods will not work if you have cross-namespace STI. For example, if you have MyNameSpace::MyInheritingModel < MyModel


models.yml
--------
one:
  id: 1
  ...data...
  type: MyInheritingModel

You cannot use a method of models(:one), like models(:one).id


controller_test.rb
--------
fixtures: models

...

def test
  model_one_id = models(:one).id # gets you ActiveRecord::SubclassNotFound
  model_one_id = 1 # OK to do manual id
end

——

Gang, this’ll save you some hours of frustration. Your controllers may not see your STI subclasses unless you include the following in your controller files:

require_dependency ‘model’

…where ‘model’ is the name of the parent class.

——

I’ll add to the above – while refactoring to use STI, rails wouldn’t even start – turns out I had an observer looking for a class that didn’t have a corresponding file name (Host was defined inside ‘asset.rb’); adding

require_dependency ‘asset’

to to the top of the observer was what was needed (that was hours of fun).

——

Note also that Rails doesn’t set the [:type] field for the base class in Single Table Inheritance. Only subclasses of the base class have the type field filled in.