More on Single Table Inheritence

The previous post touched briefly on single table inheritance and gave a simple example of setting up the base model, Fruit, and two inherited models, Apple and Watermelon. Where the two kinds grow, on a vine or tree, has been defined. Say its time to harvest, so we’ll define a function to take care of that. We could define that function in the base class.

 def harvest
    plant.take_fruit
  end

This works if both sub models can be treated the same by the function. Now its time to prepare the fruit. Eat fruit is prepared differently; apple is sliced and the core removed, the wattermelon is cut into wedges. End users of the watermelon are responsible for dealing with the seeds (unless the watermelon is a seedless). The prepare function can be defined in the Apple and Watermelon models:

class Apple < Fruit
  ...
  def prepare
    #slice fruit
    #remove core
  end
  ...
end

class Watermelon < Fruit
  ...
  def prepare
    #Halve fruit
    #cut into wedges
  end
  ...
end

While the above could be done without STI, but might not be the cleanest thing possible. For example, lets give apples their own table and model, and watermelon their own table and model, and do away with the fruits table/model. This gives us two very similar tables in the database [apples, watermelons] where there would be just one [fruits] and a duplicated harvest method. Things continue to get complicated and duplicated if there arises a need to represent different types of fruit, like bananas, strawberries, etc, down the road with duplicate code and additional similar database tables.

Alternatively, keep the single fruits table and model, and use the fruits model to handle the different types of fruit by using the ‘kind’ column to figure out what fruit it is. The prepare function bloats, as it needs at least a case statement to determine how to prepare different fruit, which leads to a cluttered function. As different fruit are added, the case statement grows larger. On the plus side, code isn’t being duplicated, and the database isn’t flooded with similar tables.

With using STI and adding more fruit, its as simple as defining a new model with Fruit as the base class. In this way, where there are multiple models STI can save you code and tables when you have distinct objects with a similar/same table schema. Its also imporant to note that STI is not for everything, and careful analysis should be used before deciding if STI is right for you.

More on inherited functions

A model can also inherit from another model higher than the base class. For example, more specific varieties of fruit:

class Fiji < Apple
  ...
end

We don’t define the prepare method for this model directly. Consider:

f = Fiji.new
f.prepare

Since the ‘prepare’ function is not defined for Fiji, the parent model is checked next for the definition, and is used from there. This is useful, as there are many kinds of apples, but they can all be prepared the same, and we don’t need to add any function definitions. STI will start with the object model, and work its way too the root base model sequentially until it finds the definition for the called function (or errors if that function was never defined).