/usr/lib/ruby/vendor_ruby/sequel/plugins/composition.rb is in ruby-sequel 4.15.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 | module Sequel
module Plugins
# The composition plugin allows you to easily define a virtual
# attribute where the backing data is composed of other columns.
#
# There are two ways to use the plugin. One way is with the
# :mapping option. A simple example of this is when you have a
# database table with separate columns for year, month, and day,
# but where you want to deal with Date objects in your ruby code.
# This can be handled with:
#
# Album.plugin :composition
# Album.composition :date, :mapping=>[:year, :month, :day]
#
# With the :mapping option, you can provide a :class option
# that gives the class to use, but if that is not provided, it
# is inferred from the name of the composition (e.g. :date -> Date).
# When the <tt>date</tt> method is called, it will return a
# Date object by calling:
#
# Date.new(year, month, day)
#
# When saving the object, if the date composition has been used
# (by calling either the getter or setter method), it will
# populate the related columns of the object before saving:
#
# self.year = date.year
# self.month = date.month
# self.day = date.day
#
# The :mapping option is just a shortcut that works in particular
# cases. To handle any case, you can define a custom :composer
# and :decomposer procs. The :composer proc will be instance_evaled
# the first time the getter is called, and the :decomposer proc
# will be instance_evaled before saving. The above example could
# also be implemented as:
#
# Album.composition :date,
# :composer=>proc{Date.new(year, month, day) if year || month || day},
# :decomposer=>(proc do
# if d = compositions[:date]
# self.year = d.year
# self.month = d.month
# self.day = d.day
# else
# self.year = nil
# self.month = nil
# self.day = nil
# end
# end)
#
# Note that when using the composition object, you should not
# modify the underlying columns if you are also instantiating
# the composition, as otherwise the composition object values
# will override any underlying columns when the object is saved.
module Composition
# Define the necessary class instance variables.
def self.apply(model)
model.instance_eval{@compositions = {}}
end
module ClassMethods
# A hash with composition name keys and composition reflection
# hash values.
attr_reader :compositions
# A module included in the class holding the composition
# getter and setter methods.
attr_reader :composition_module
# Define a composition for this model, with name being the name of the composition.
# You must provide either a :mapping option or both the :composer and :decomposer options.
#
# Options:
# :class :: if using the :mapping option, the class to use, as a Class, String or Symbol.
# :composer :: A proc that is instance evaled when the composition getter method is called
# to create the composition.
# :decomposer :: A proc that is instance evaled before saving the model object,
# if the composition object exists, which sets the columns in the model object
# based on the value of the composition object.
# :mapping :: An array where each element is either a symbol or an array of two symbols.
# A symbol is treated like an array of two symbols where both symbols are the same.
# The first symbol represents the getter method in the model, and the second symbol
# represents the getter method in the composition object. Example:
# # Uses columns year, month, and day in the current model
# # Uses year, month, and day methods in the composition object
# {:mapping=>[:year, :month, :day]}
# # Uses columns year, month, and day in the current model
# # Uses y, m, and d methods in the composition object where
# # for example y in the composition object represents year
# # in the model object.
# {:mapping=>[[:year, :y], [:month, :m], [:day, :d]]}
def composition(name, opts=OPTS)
opts = opts.dup
compositions[name] = opts
if mapping = opts[:mapping]
keys = mapping.map{|k| k.is_a?(Array) ? k.first : k}
if !opts[:composer]
late_binding_class_option(opts, name)
klass = opts[:class]
class_proc = proc{klass || constantize(opts[:class_name])}
opts[:composer] = proc do
if values = keys.map{|k| send(k)} and values.any?{|v| !v.nil?}
class_proc.call.new(*values)
else
nil
end
end
end
if !opts[:decomposer]
setter_meths = keys.map{|k| :"#{k}="}
cov_methods = mapping.map{|k| k.is_a?(Array) ? k.last : k}
setters = setter_meths.zip(cov_methods)
opts[:decomposer] = proc do
if (o = compositions[name]).nil?
setter_meths.each{|sm| send(sm, nil)}
else
setters.each{|sm, cm| send(sm, o.send(cm))}
end
end
end
end
raise(Error, "Must provide :composer and :decomposer options, or :mapping option") unless opts[:composer] && opts[:decomposer]
define_composition_accessor(name, opts)
end
Plugins.inherited_instance_variables(self, :@compositions=>:dup)
# Define getter and setter methods for the composition object.
def define_composition_accessor(name, opts=OPTS)
include(@composition_module ||= Module.new) unless composition_module
composer = opts[:composer]
composition_module.class_eval do
define_method(name) do
if compositions.has_key?(name)
compositions[name]
elsif frozen?
instance_eval(&composer)
else
compositions[name] = instance_eval(&composer)
end
end
define_method("#{name}=") do |v|
modified!
compositions[name] = v
end
end
end
end
module InstanceMethods
# Cache of composition objects for this class.
def compositions
@compositions ||= {}
end
# Freeze compositions hash when freezing model instance.
def freeze
compositions.freeze
super
end
private
# For each composition, set the columns in the model class based
# on the composition object.
def _before_validation
@compositions.keys.each{|n| instance_eval(&model.compositions[n][:decomposer])} if @compositions
super
end
# Clear the cached compositions when manually refreshing.
def _refresh_set_values(hash)
@compositions.clear if @compositions
super
end
# Duplicate compositions hash when duplicating model instance.
def initialize_copy(other)
super
@compositions = other.compositions.dup
self
end
end
end
end
end
|