Single table inheritance is a software pattern described by Martin Fowler. STI is basically an idea of using a single table(colection in case of mongo) to reflect multiple models that inherit from a base model.
We use STI pattern when we are dealing with classes that have same attributes and behaviour. Rather than duplicate the same code over and over, STI helps us to use a common base model and write specific behaviours in its inherited class while keeeping data on a same table.
Mongoid supports inheritance in both root and embedded documents. In scenarios where documents are inherited from their fields, relations, validations and scopes get copied down into their child documents, but not vice-versa.
A very simple example:
class Employee include Mongoid::Document field :name, type: String field :employee_code, type: Integer end class FullTimeEmployee < Employee field status, type: String, default: "Temporary" end class InternEmployee < Employee field intern_period, type: Integer end
In the above example, both FullTimeEmployee and InternEmployee will be saved in the Employee collection. An additional attribute _type is automatically stored in order to make sure when loaded from the database the correct document is returned.
Following behaviour can be seen, much similar to Single Table Inheritance in ActiveRecord.
emp1 = Employee.new emp2 = FullTimeEmployee.new emp3 = InternEmployee.new Employee.count #=> 3 FullTimeEmployee.count #=> 1 InternEmployee.count #=> 1 emp1._type #=> "Employee" emp2._type #=> "FullTimeEmployee" emp3._type #=> "InternEmployee" Employee.where(name: "...") #=> Returns Employee documents and subclasses FullTimeEmployee.where(name: "...") #=> returns only FullTimeEmployee documents
An advance example:
class Canvas include Mongoid::Document field :name, type: String embeds_many :shapes end class Browser < Canvas field :version, type: Integer scope :recent, where(:version.gt => 3) end class Firefox < Browser end class Shape include Mongoid::Document field :x, type: Integer field :y, type: Integer embedded_in :canvas end class Circle < Shape field :radius, type: Float end class Rectangle < Shape field :width, type: Float field :height, type: Float end
Canvas, Browser and Firefox will all save in the canvases collection.This also holds true for the embedded documents Circle, Rectangle, and Shape.
To query for subclasses within an embedded collection you need to leverage the _type attribute in each subclassed object. Canvas and Shape documents, would not have it, but Browser, Firefox, Circle, and Rectangle would. Keep in mind that _type is a string that stores the name of the document’s class, and as such can only be used to query for a specific subclass, and not anything it is a subclass of.
If, for example, Rectangle was a subclass of Parallelogram which was in turn a subclass of Shape, you could search the Canvas’s shapes collection for objects with a _type of “Parallelogram” but it would never return a Rectangle object, and vice-versa.
# Returns all the Rectangle shapes in a previously found Canvas my_canvas.shapes.where(_type: "Rectangle") # Returns no entries (see above) my_canvas.shapes.where(_type: "Shape") # Returns all the Canvasas that have Circles Canvas.where("shapes._type"=>"Circle") # Returns no entries (see above) Canvas.where("shapes._type'=>"Shape")