/usr/lib/ruby/vendor_ruby/sequel/plugins/class_table_inheritance.rb is in ruby-sequel 3.33.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | module Sequel
module Plugins
# 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}
module ClassTableInheritance
# The class_table_inheritance plugin requires the lazy_attributes plugin
# to handle lazily-loaded attributes for subclass instances returned
# by superclass methods.
def self.apply(model, opts={})
model.plugin :lazy_attributes
end
# 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
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).call(r)}
else
model
end
end
end
module ClassMethods
# The parent/root/base model for this class table inheritance hierarchy.
# This is the only model in the hierarchy that load the
# class_table_inheritance plugin.
attr_reader :cti_base_model
# Hash with table name symbol keys and arrays of column symbol values,
# giving the columns to update in each backing database table.
attr_reader :cti_columns
# The column containing the class name as a string. Used to
# return instances of subclasses when calling the superclass's
# load method.
attr_reader :cti_key
# An array of table symbols that back this model. The first is
# cti_base_model table symbol, and the last is the current model
# table symbol.
attr_reader :cti_tables
# A hash with class name symbol keys and table name symbol values.
# Specified with the :table_map option to the plugin, and used if
# the implicit naming is incorrect.
attr_reader :cti_table_map
# Add the appropriate data structures to the subclass. Does not
# allow anonymous subclasses to be created, since they would not
# be mappable to a table.
def inherited(subclass)
cc = cti_columns
ck = cti_key
ct = cti_tables.dup
ctm = cti_table_map.dup
cbm = cti_base_model
pk = primary_key
ds = dataset
subclass.instance_eval do
raise(Error, "cannot create anonymous subclass for model class using class_table_inheritance") if !(n = name) || n.empty?
table = ctm[n.to_sym] || implicit_table_name
columns = db.from(table).columns
@cti_key = ck
@cti_tables = ct + [table]
@cti_columns = cc.merge(table=>columns)
@cti_table_map = ctm
@cti_base_model = cbm
# Need to set dataset and columns before calling super so that
# the main column accessor module is included in the class before any
# plugin accessor modules (such as the lazy attributes accessor module).
set_dataset(ds.join(table, [pk]))
set_columns(self.columns)
end
super
subclass.instance_eval do
m = method(:constantize)
dataset.row_proc = if cti_key
lambda{|r| (m.call(r[ck]) rescue subclass).call(r)}
else
subclass
end
(columns - [cbm.primary_key]).each{|a| define_lazy_attribute_getter(a)}
cti_tables.reverse.each do |table|
db.schema(table).each{|k,v| db_schema[k] = v}
end
end
end
# The primary key in the parent/base/root model, which should have a
# foreign key with the same name referencing it in each model subclass.
def primary_key
return super if self == cti_base_model
cti_base_model.primary_key
end
# The table name for the current model class's main table (not used
# by any superclasses).
def table_name
self == cti_base_model ? super : cti_tables.last
end
end
module InstanceMethods
# Set the cti_key column to the name of the model.
def before_create
send("#{model.cti_key}=", model.name.to_s) if model.cti_key
super
end
# Delete the row from all backing tables, starting from the
# most recent table and going through all superclasses.
def delete
m = model
m.cti_tables.reverse.each do |table|
m.db.from(table).filter(m.primary_key=>pk).delete
end
self
end
private
# Insert rows into all backing tables, using the columns
# in each table.
def _insert
return super if model == model.cti_base_model
iid = @values[primary_key]
m = model
m.cti_tables.each do |table|
h = {}
h[m.primary_key] ||= iid if iid
m.cti_columns[table].each{|c| h[c] = @values[c] if @values.include?(c)}
nid = m.db.from(table).insert(h)
iid ||= nid
end
@values[primary_key] = iid
end
# Update rows in all backing tables, using the columns in each table.
def _update(columns)
pkh = pk_hash
m = model
m.cti_tables.each do |table|
h = {}
m.cti_columns[table].each{|c| h[c] = columns[c] if columns.include?(c)}
m.db.from(table).filter(pkh).update(h) unless h.empty?
end
end
end
end
end
end
|