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
-
.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.
-
.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.
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 |