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 employees.id, employees.name, employees.kind
# FROM employees

Manager.dataset.sql
# SELECT employees.id, employees.name, employees.kind, managers.num_staff
# FROM employees
# JOIN managers ON (managers.id = employees.id)

Executive.dataset.sql
# SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers
# FROM employees
# JOIN managers ON (managers.id = employees.id)
# JOIN executives ON (executives.id = managers.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

If you want to get all columns in a subclass instance after loading via the superclass, call Model#refresh.

a = Employee.first
a.values # {:id=>1, name=>'S', :kind=>'Executive'}
a.refresh.values # {:id=>1, name=>'S', :kind=>'Executive', :num_staff=>4, :num_managers=>2}

Usage:

# Set up class table inheritance in the parent class
# (Not in the subclasses)
class Employee < Sequel::Model
  plugin :class_table_inheritance
end

# Have subclasses inherit from the appropriate class
class Staff < Employee; end
class Manager < Employee; end
class Executive < Manager; end

# You can also set options when loading the plugin:
# :kind :: column to hold the class name
# :table_map :: map of class name symbols to table name symbols
# :model_map :: map of column values to class name symbols
Employee.plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff},
  :model_map=>{1=>:Employee, 2=>:Manager, 3=>:Executive, 4=>:Staff}

Defined Under Namespace

Modules: ClassMethods, InstanceMethods

Class Method Summary collapse

Class Method Details

.apply(model, opts = OPTS) ⇒ Object

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



96
97
98
# File 'lib/sequel/plugins/class_table_inheritance.rb', line 96

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

.configure(model, opts = 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

:model_map

Hash with keys being values of the cti_key column, and values being class name strings or symbols. Used if you don’t want to store class names in the database. If you use this option, you are responsible for setting the values of the cti_key column manually (usually in a before_create hook).



114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/sequel/plugins/class_table_inheritance.rb', line 114

def self.configure(model, opts=OPTS)
  model.instance_eval do
    @cti_base_model = self
    @cti_key = opts[:key] 
    @cti_tables = [table_name]
    @cti_columns = {table_name=>columns}
    @cti_table_map = opts[:table_map] || {}
    @cti_model_map = opts[:model_map]
    set_dataset_cti_row_proc
    set_dataset(dataset.select(*columns.map{|c| Sequel.qualify(table_name, Sequel.identifier(c))}))
  end
end