Module: Sequel::Plugins::ClassTableInheritance

Defined in:
lib/sequel/plugins/class_table_inheritance.rb

Overview

The class_table_inheritance plugin allows you to model inheritance in the database using a table per model class in the hierarchy, with only columns unique to that model class (or subclass hierarchy) being stored in the related table. For example, with this hierarchy:

    Employee
   /        \ 
Staff     Manager
             |
         Executive

the following database schema may be used (table - columns):

  • employees - id, name, kind

  • staff - id, manager_id

  • managers - id, num_staff

  • executives - id, num_managers

The class_table_inheritance plugin assumes that the main table (e.g. employees) has a primary key field (usually autoincrementing), and all other tables have a foreign key of the same name that points to the same key in their superclass’s table. For example:

  • employees.id - primary key, autoincrementing

  • staff.id - foreign key referencing employees(id)

  • managers.id - foreign key referencing employees(id)

  • executives.id - foreign key referencing managers(id)

When using the class_table_inheritance plugin, subclasses use joined datasets:

Employee.dataset.sql  # SELECT * FROM employees
Manager.dataset.sql   # SELECT * FROM employees
                      # INNER JOIN managers USING (id)
Executive.dataset.sql # SELECT * FROM employees 
                      # INNER JOIN managers USING (id)
                      # INNER JOIN executives USING (id)

This allows Executive.all to return instances with all attributes loaded. The plugin overrides the deleting, inserting, and updating in the model to work with multiple tables, by handling each table individually.

This plugin allows the use of a :key option when loading to mark a column holding a class name. This allows methods on the superclass to return instances of specific subclasses. This plugin also requires the lazy_attributes plugin and uses it to return subclass specific attributes that would not be loaded when calling superclass methods (since those wouldn’t join to the subclass tables). For example:

a = Employee.all # [<#Staff>, <#Manager>, <#Executive>]
a.first.values # {:id=>1, name=>'S', :kind=>'Staff'}
a.first.manager_id # Loads the manager_id attribute from the database

Usage:

# Set up class table inheritance in the parent class
# (Not in the subclasses)
Employee.plugin :class_table_inheritance

# Set the +kind+ column to hold the class name, and
# set the subclass table to map to for each subclass 
Employee.plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}

Defined Under Namespace

Modules: ClassMethods, InstanceMethods

Class Method Summary collapse

Class Method Details

.apply(model, opts = {}) ⇒ Object

The class_table_inheritance plugin requires the lazy_attributes plugin to handle lazily-loaded attributes for subclass instances returned by superclass methods.



71
72
73
# File 'lib/sequel/plugins/class_table_inheritance.rb', line 71

def self.apply(model, opts={})
  model.plugin :lazy_attributes
end

.configure(model, opts = {}) ⇒ Object

Initialize the per-model data structures and set the dataset’s row_proc to check for the :key option column for the type of class when loading objects. Options:

  • :key - The column symbol holding the name of the model class this is an instance of. Necessary if you want to call model methods using the superclass, but have them return subclass instances.

  • :table_map - Hash with class name symbol keys and table name symbol values. Necessary if the implicit table name for the model class does not match the database table name



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/sequel/plugins/class_table_inheritance.rb', line 84

def self.configure(model, opts={})
  model.instance_eval do
    m = method(:constantize)
    @cti_base_model = self
    @cti_key = key = opts[:key] 
    @cti_tables = [table_name]
    @cti_columns = {table_name=>columns}
    @cti_table_map = opts[:table_map] || {}
    dataset.row_proc = if key
      lambda{|r| (m.call(r[key]) rescue model).load(r)}
    else
      lambda{|r| model.load(r)}
    end
  end
end