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
|
# frozen-string-literal: true
require 'delegate'
module Sequel
module Plugins
# The defaults_setter plugin makes the column getter methods return the default
# values for new objects, if the values have not already been set. Example:
#
# # column a default NULL
# # column b default 2
# album = Album.new
# album.a # => nil
# album.b # => 2
# album = Album.new(a: 1, b: 3)
# album.a # => 1
# album.b # => 3
#
# You can manually set default values as well:
#
# Album.default_values[:a] = 4
# Album.new.a # => 4
#
# You can also provide procs to set default values:
#
# Album.default_values[:a] = lambda{Date.today}
# Album.new.a # => Date.today
#
# If the proc accepts a single argument, it is passed the instance, allowing
# default values to depend on instance-specific state:
#
# Album.default_values[:a] = lambda{|album| album.b + 1}
# Album.new(b: 10).a # => 11
#
# By default, default values returned are not cached:
#
# Album.new.a.equal?(Album.new.a) # => false
#
# However, you can turn on caching of default values:
#
# Album.plugin :defaults_setter, cache: true
# Album.new.a.equal?(Album.new.a) # => false
#
# Note that if the cache is turned on, the cached values are stored in
# the values hash:
#
# Album.plugin :defaults_setter, cache: true
# album = Album.new
# album.values # => {}
# album.a
# album.values # => {:a => Date.today}
#
# Usage:
#
# # Make all model subclass instances set defaults (called before loading subclasses)
# Sequel::Model.plugin :defaults_setter
#
# # Make the Album class set defaults
# Album.plugin :defaults_setter
module DefaultsSetter
# Set the default values based on the model schema. Options:
# :cache :: Cache default values returned in the model's values hash.
def self.configure(model, opts=OPTS)
model.instance_exec do
set_default_values
@cache_default_values = opts[:cache] if opts.has_key?(:cache)
end
end
module ClassMethods
# The default values to use for this model. A hash with column symbol
# keys and default values. If the default values respond to +call+, it will be called
# to get the value, otherwise the value will be used directly. You can manually modify
# this hash to set specific default values, by default the ones will be parsed from the database.
attr_reader :default_values
Plugins.after_set_dataset(self, :set_default_values)
Plugins.inherited_instance_variables(self, :@default_values=>:dup, :@cache_default_values=>nil)
# Whether default values should be cached in the values hash after being retrieved.
def cache_default_values?
@cache_default_values
end
# Freeze default values when freezing model class
def freeze
@default_values.freeze
super
end
private
# Parse the cached database schema for this model and set the default values appropriately.
def set_default_values
h = {}
if @db_schema
@db_schema.each do |k, v|
if v[:callable_default]
h[k] = v[:callable_default]
elsif !v[:ruby_default].nil?
h[k] = convert_default_value(v[:ruby_default])
end
end
end
@default_values = h.merge!(@default_values || OPTS)
end
# Handle the CURRENT_DATE and CURRENT_TIMESTAMP values specially by returning an appropriate Date or
# Time/DateTime value.
def convert_default_value(v)
case v
when Sequel::CURRENT_DATE
lambda{Date.today}
when Sequel::CURRENT_TIMESTAMP
lambda{dataset.current_datetime}
when Hash, Array
v = Marshal.dump(v).freeze
lambda{Marshal.load(v)}
when Delegator
# DelegateClass returns an anonymous case, which cannot be marshalled, so marshal the
# underlying object and create a new instance of the class with the unmarshalled object.
klass = v.class
case o = v.__getobj__
when Hash, Array
v = Marshal.dump(o).freeze
lambda{klass.new(Marshal.load(v))}
else
v
end
else
v
end
end
end
module InstanceMethods
# Use default value for a new record if values doesn't already contain an entry for it.
def [](k)
if new? && !values.has_key?(k)
v = model.default_values.fetch(k){return}
if v.respond_to?(:call)
v = if v.respond_to?(:arity) && v.arity == 1
v.call(self)
else
v.call
end
end
values[k] = v if model.cache_default_values?
v
else
super
end
end
end
end
end
end
|