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
|
# frozen-string-literal: true
module Sequel
module Plugins
# The subclasses plugin keeps track of all subclasses of the
# current model class. Direct subclasses are available via the
# subclasses method, and all descendent classes are available via the
# descendants method:
#
# c = Class.new(Sequel::Model)
# c.plugin :subclasses
# sc1 = Class.new(c)
# sc2 = Class.new(c)
# ssc1 = Class.new(sc1)
# c.subclasses # [sc1, sc2]
# sc1.subclasses # [ssc1]
# sc2.subclasses # []
# ssc1.subclasses # []
# c.descendants # [sc1, ssc1, sc2]
#
# You can also finalize the associations and then freeze the classes
# in all descendent classes. Doing so is a recommended practice after
# all models have been defined in production and testing, and this makes
# it easier than keeping track of the classes to finalize and freeze
# manually:
#
# c.freeze_descendants
#
# You can provide a block when loading the plugin, and it will be called
# with each subclass created:
#
# a = []
# Sequel::Model.plugin(:subclasses){|sc| a << sc}
# class A < Sequel::Model; end
# class B < Sequel::Model; end
# a # => [A, B]
module Subclasses
NEED_SUBCLASSES = !Object.respond_to?(:subclasses) || Object.method(:subclasses).source_location
private_constant :NEED_SUBCLASSES
# Initialize the subclasses instance variable for the model.
def self.apply(model, &block)
# :nocov:
model.instance_variable_set(:@subclasses, []) if NEED_SUBCLASSES
# :nocov:
model.instance_variable_set(:@on_subclass, block)
end
module ClassMethods
# Callable object that should be called with every descendent
# class created.
attr_reader :on_subclass
# :nocov:
if NEED_SUBCLASSES
# All subclasses for the current model. Does not
# include the model itself.
attr_reader :subclasses
end
# :nocov:
# All descendent classes of this model.
def descendants
Sequel.synchronize{subclasses.dup}.map{|x| [x] + x.send(:descendants)}.flatten
end
# SEQUEL6: Remove
alias descendents descendants
# Freeze all descendent classes. This also finalizes the associations for those
# classes before freezing.
def freeze_descendants
descendants.each(&:finalize_associations).each(&:freeze)
end
# SEQUEL6: Remove
alias freeze_descendents freeze_descendants
Plugins.inherited_instance_variables(self, :@subclasses=>lambda{|v| []}, :@on_subclass=>nil)
private
# Add the subclass to this model's current subclasses,
# and initialize a new subclasses instance variable
# in the subclass.
def inherited(subclass)
super
# :nocov:
Sequel.synchronize{subclasses << subclass} if NEED_SUBCLASSES
# :nocov:
on_subclass.call(subclass) if on_subclass
end
end
end
end
end
|