sequel 4.18.0 → 4.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +18 -0
- data/MIT-LICENSE +1 -1
- data/doc/advanced_associations.rdoc +1 -1
- data/doc/association_basics.rdoc +55 -34
- data/doc/model_hooks.rdoc +7 -5
- data/doc/release_notes/4.19.0.txt +45 -0
- data/doc/validations.rdoc +4 -0
- data/lib/sequel/adapters/shared/mysql.rb +5 -3
- data/lib/sequel/adapters/shared/sqlanywhere.rb +3 -2
- data/lib/sequel/dataset/sql.rb +1 -1
- data/lib/sequel/extensions/migration.rb +12 -8
- data/lib/sequel/model/associations.rb +24 -24
- data/lib/sequel/model/base.rb +39 -8
- data/lib/sequel/plugins/accessed_columns.rb +61 -0
- data/lib/sequel/plugins/association_pks.rb +4 -4
- data/lib/sequel/plugins/boolean_readers.rb +1 -1
- data/lib/sequel/plugins/class_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/column_conflicts.rb +93 -0
- data/lib/sequel/plugins/composition.rb +3 -3
- data/lib/sequel/plugins/dirty.rb +6 -6
- data/lib/sequel/plugins/json_serializer.rb +1 -1
- data/lib/sequel/plugins/list.rb +4 -4
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +2 -2
- data/lib/sequel/plugins/optimistic_locking.rb +3 -3
- data/lib/sequel/plugins/pg_array_associations.rb +23 -23
- data/lib/sequel/plugins/prepared_statements_associations.rb +1 -1
- data/lib/sequel/plugins/rcte_tree.rb +2 -2
- data/lib/sequel/plugins/serialization.rb +1 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/timestamps.rb +2 -2
- data/lib/sequel/plugins/typecast_on_load.rb +1 -1
- data/lib/sequel/plugins/validation_class_methods.rb +4 -4
- data/lib/sequel/plugins/validation_helpers.rb +2 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/core/dataset_spec.rb +7 -0
- data/spec/extensions/accessed_columns_spec.rb +51 -0
- data/spec/extensions/column_conflicts_spec.rb +55 -0
- data/spec/extensions/hook_class_methods_spec.rb +18 -5
- data/spec/extensions/migration_spec.rb +4 -1
- data/spec/extensions/static_cache_spec.rb +3 -3
- data/spec/model/hooks_spec.rb +76 -9
- data/spec/model/record_spec.rb +43 -2
- metadata +8 -2
data/lib/sequel/model/base.rb
CHANGED
@@ -1143,6 +1143,20 @@ module Sequel
|
|
1143
1143
|
attr_reader :values
|
1144
1144
|
alias to_hash values
|
1145
1145
|
|
1146
|
+
# Get the value of the column. Takes a single symbol or string argument.
|
1147
|
+
# By default it calls send with the argument to get the value. This can
|
1148
|
+
# be overridden if you have columns that conflict with existing
|
1149
|
+
# method names.
|
1150
|
+
alias get_column_value send
|
1151
|
+
|
1152
|
+
# Set the value of the column. Takes two argument. The first is a
|
1153
|
+
# symbol or string argument for the column name, suffixed with =. The
|
1154
|
+
# second is the value to set for the column. By default it calls send
|
1155
|
+
# with the argument to set the value. This can be overridden if you have
|
1156
|
+
# columns that conflict with existing method names (unlikely for setter
|
1157
|
+
# methods, but possible).
|
1158
|
+
alias set_column_value send
|
1159
|
+
|
1146
1160
|
# Creates new instance and passes the given values to set.
|
1147
1161
|
# If a block is given, yield the instance to the block.
|
1148
1162
|
#
|
@@ -1219,6 +1233,14 @@ module Sequel
|
|
1219
1233
|
def autoincrementing_primary_key
|
1220
1234
|
primary_key
|
1221
1235
|
end
|
1236
|
+
|
1237
|
+
# Cancel the current action. Should be called in before hooks to halt
|
1238
|
+
# the processing of the action. If a +msg+ argument is given and
|
1239
|
+
# the model instance is configured to raise exceptions on failure,
|
1240
|
+
# sets the message to use for the raised HookFailed exception.
|
1241
|
+
def cancel_action(msg=nil)
|
1242
|
+
raise_hook_failure(msg)
|
1243
|
+
end
|
1222
1244
|
|
1223
1245
|
# The columns that have been updated. This isn't completely accurate,
|
1224
1246
|
# as it could contain columns whose values have not changed.
|
@@ -1597,23 +1619,23 @@ module Sequel
|
|
1597
1619
|
when :skip
|
1598
1620
|
fields.each do |f|
|
1599
1621
|
if hash.has_key?(f)
|
1600
|
-
|
1622
|
+
set_column_value("#{f}=", hash[f])
|
1601
1623
|
elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
|
1602
|
-
|
1624
|
+
set_column_value("#{sf}=", hash[sf])
|
1603
1625
|
end
|
1604
1626
|
end
|
1605
1627
|
when :raise
|
1606
1628
|
fields.each do |f|
|
1607
1629
|
if hash.has_key?(f)
|
1608
|
-
|
1630
|
+
set_column_value("#{f}=", hash[f])
|
1609
1631
|
elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
|
1610
|
-
|
1632
|
+
set_column_value("#{sf}=", hash[sf])
|
1611
1633
|
else
|
1612
1634
|
raise(Sequel::Error, "missing field in hash: #{f.inspect} not in #{hash.inspect}")
|
1613
1635
|
end
|
1614
1636
|
end
|
1615
1637
|
else
|
1616
|
-
fields.each{|f|
|
1638
|
+
fields.each{|f| set_column_value("#{f}=", hash[f])}
|
1617
1639
|
end
|
1618
1640
|
self
|
1619
1641
|
end
|
@@ -2073,8 +2095,17 @@ module Sequel
|
|
2073
2095
|
|
2074
2096
|
# Raise an error appropriate to the hook type. May be swallowed by
|
2075
2097
|
# checked_save_failure depending on the raise_on_failure? setting.
|
2076
|
-
def raise_hook_failure(type)
|
2077
|
-
|
2098
|
+
def raise_hook_failure(type=nil)
|
2099
|
+
msg = case type
|
2100
|
+
when String
|
2101
|
+
type
|
2102
|
+
when Symbol
|
2103
|
+
"the #{type} hook failed"
|
2104
|
+
else
|
2105
|
+
"a hook failed"
|
2106
|
+
end
|
2107
|
+
|
2108
|
+
raise HookFailed.new(msg, self)
|
2078
2109
|
end
|
2079
2110
|
|
2080
2111
|
# Get the ruby class or classes related to the given column's type.
|
@@ -2093,7 +2124,7 @@ module Sequel
|
|
2093
2124
|
hash.each do |k,v|
|
2094
2125
|
m = "#{k}="
|
2095
2126
|
if meths.include?(m)
|
2096
|
-
|
2127
|
+
set_column_value(m, v)
|
2097
2128
|
elsif strict
|
2098
2129
|
# Avoid using respond_to? or creating symbols from user input
|
2099
2130
|
if public_methods.map{|s| s.to_s}.include?(m)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# The accessed_columns plugin records which columns have been
|
4
|
+
# accessed for a model instance. This is useful if you are
|
5
|
+
# looking to remove other columns from being SELECTed by the
|
6
|
+
# dataset that retrieved the instance, which can significantly
|
7
|
+
# improve performance:
|
8
|
+
#
|
9
|
+
# a = Album[1]
|
10
|
+
# a.accessed_columns # []
|
11
|
+
# a.name
|
12
|
+
# a.accessed_columns # [:name]
|
13
|
+
# a.artist_id
|
14
|
+
# a.accessed_columns # [:name, :artist_id]
|
15
|
+
#
|
16
|
+
# Note that this plugin should probably not be used in production,
|
17
|
+
# as it causes a performance hit.
|
18
|
+
#
|
19
|
+
# Usage:
|
20
|
+
#
|
21
|
+
# # Make all model subclass instances record accessed columns (called before loading subclasses)
|
22
|
+
# Sequel::Model.plugin :accessed_columns
|
23
|
+
#
|
24
|
+
# # Make the Album instances record accessed columns
|
25
|
+
# Album.plugin :accessed_columns
|
26
|
+
module AccessedColumns
|
27
|
+
module InstanceMethods
|
28
|
+
# Record the column access before retrieving the value.
|
29
|
+
def [](c)
|
30
|
+
(@accessed_columns ||= {})[c] = true unless frozen?
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
# Clear the accessed columns when saving.
|
35
|
+
def after_save
|
36
|
+
super
|
37
|
+
@accessed_columns = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# The columns that have been accessed.
|
41
|
+
def accessed_columns
|
42
|
+
@accessed_columns ? @accessed_columns.keys : []
|
43
|
+
end
|
44
|
+
|
45
|
+
# Copy the accessed columns when duping and cloning.
|
46
|
+
def initialize_copy(other)
|
47
|
+
other.instance_variable_set(:@accessed_columns, @accessed_columns.dup) if @accessed_columns
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Clear the accessed columns when refreshing.
|
54
|
+
def _refresh(_)
|
55
|
+
@accessed_columns = nil
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -61,12 +61,12 @@ module Sequel
|
|
61
61
|
if clpk
|
62
62
|
def_association_pks_getter(opts) do
|
63
63
|
h = {}
|
64
|
-
lk.zip(lpk).each{|k, pk| h[k] =
|
64
|
+
lk.zip(lpk).each{|k, pk| h[k] = get_column_value(pk)}
|
65
65
|
_join_table_dataset(opts).filter(h).select_map(rk)
|
66
66
|
end
|
67
67
|
else
|
68
68
|
def_association_pks_getter(opts) do
|
69
|
-
_join_table_dataset(opts).filter(lk=>
|
69
|
+
_join_table_dataset(opts).filter(lk=>get_column_value(lpk)).select_map(rk)
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
@@ -74,10 +74,10 @@ module Sequel
|
|
74
74
|
pks = send(crk ? :convert_cpk_array : :convert_pk_array, opts, pks)
|
75
75
|
checked_transaction do
|
76
76
|
if clpk
|
77
|
-
lpkv = lpk.map{|k|
|
77
|
+
lpkv = lpk.map{|k| get_column_value(k)}
|
78
78
|
cond = lk.zip(lpkv)
|
79
79
|
else
|
80
|
-
lpkv =
|
80
|
+
lpkv = get_column_value(lpk)
|
81
81
|
cond = {lk=>lpkv}
|
82
82
|
end
|
83
83
|
ds = _join_table_dataset(opts).filter(cond)
|
@@ -40,7 +40,7 @@ module Sequel
|
|
40
40
|
# Add a attribute? method for the column to a module included in the class.
|
41
41
|
def create_boolean_reader(column)
|
42
42
|
overridable_methods_module.module_eval do
|
43
|
-
define_method("#{column}?"){model.db.typecast_value(:boolean,
|
43
|
+
define_method("#{column}?"){model.db.typecast_value(:boolean, get_column_value(column))}
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -255,7 +255,7 @@ module Sequel
|
|
255
255
|
# Set the cti_key column to the name of the model.
|
256
256
|
def _before_validation
|
257
257
|
if new? && model.cti_key && !model.cti_model_map
|
258
|
-
|
258
|
+
set_column_value("#{model.cti_key}=", model.name.to_s)
|
259
259
|
end
|
260
260
|
super
|
261
261
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# The column_conflicts plugin overrides Model#get_column_value and #set_column_value
|
4
|
+
# to automatically handle column names that conflict with Ruby/Sequel method names.
|
5
|
+
#
|
6
|
+
# By default, Model#get_column_value and #set_column_value just call send, this
|
7
|
+
# plugin overrides the methods and gets/sets the value directly in the values
|
8
|
+
# hash if the column name conflicts with an existing Sequel::Model instance
|
9
|
+
# method name.
|
10
|
+
#
|
11
|
+
# Checking for column conflicts causes a performance hit, which is why Sequel
|
12
|
+
# does not enable such checks by default.
|
13
|
+
#
|
14
|
+
# When using this plugin, you can manually update the columns used. This may be useful if
|
15
|
+
# the columns conflict with one of your custom methods, instead of a method defined in
|
16
|
+
# Sequel::Model:
|
17
|
+
#
|
18
|
+
# Album.plugin :column_conflicts
|
19
|
+
# Album.get_column_conflict!(:column)
|
20
|
+
# Album.set_column_conflict!(:other_column)
|
21
|
+
#
|
22
|
+
# Usage:
|
23
|
+
#
|
24
|
+
# # Make all model's handle column conflicts automatically (called before loading subclasses)
|
25
|
+
# Sequel::Model.plugin :column_conflicts
|
26
|
+
#
|
27
|
+
# # Make the Album class handle column conflicts automatically
|
28
|
+
# Album.plugin :column_conflicts
|
29
|
+
module ColumnConflicts
|
30
|
+
# Check for column conflicts on the current model if the model has a dataset.
|
31
|
+
def self.configure(model)
|
32
|
+
model.instance_eval do
|
33
|
+
@get_column_conflicts = {}
|
34
|
+
@set_column_conflicts = {}
|
35
|
+
check_column_conflicts if @dataset
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module ClassMethods
|
40
|
+
Plugins.after_set_dataset(self, :check_column_conflicts)
|
41
|
+
Plugins.inherited_instance_variables(self, :@get_column_conflicts=>:dup, :@set_column_conflicts=>:dup)
|
42
|
+
|
43
|
+
# Hash for columns where the getter method already exists. keys are column symbols/strings that
|
44
|
+
# conflict with method names and should be looked up directly instead of calling a method,
|
45
|
+
# values are the column symbol to lookup in the values hash.
|
46
|
+
attr_reader :get_column_conflicts
|
47
|
+
|
48
|
+
# Hash for columns where the setter method already exists. keys are column symbols/strings suffixed
|
49
|
+
# with = that conflict with method names and should be set directly in the values hash,
|
50
|
+
# values are the column symbol to set in the values hash.
|
51
|
+
attr_reader :set_column_conflicts
|
52
|
+
|
53
|
+
# Compare the column names for the model with the methods defined on Sequel::Model, and automatically
|
54
|
+
# setup the column conflicts.
|
55
|
+
def check_column_conflicts
|
56
|
+
mod = Sequel::Model
|
57
|
+
columns.find_all{|c| mod.method_defined?(c)}.each{|c| get_column_conflict!(c)}
|
58
|
+
columns.find_all{|c| mod.method_defined?("#{c}=")}.each{|c| set_column_conflict!(c)}
|
59
|
+
end
|
60
|
+
|
61
|
+
# Set the given column as one with a getter method conflict.
|
62
|
+
def get_column_conflict!(column)
|
63
|
+
@get_column_conflicts[column.to_sym] = @get_column_conflicts[column.to_s] = column.to_sym
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set the given column as one with a setter method conflict.
|
67
|
+
def set_column_conflict!(column)
|
68
|
+
@set_column_conflicts[:"#{column}="] = @set_column_conflicts["#{column}="] = column.to_sym
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
module InstanceMethods
|
73
|
+
# If the given column has a getter method conflict, lookup the value directly in the values hash.
|
74
|
+
def get_column_value(c)
|
75
|
+
if col = model.get_column_conflicts[c]
|
76
|
+
self[col]
|
77
|
+
else
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# If the given column has a setter method conflict, set the value directly in the values hash.
|
83
|
+
def set_column_value(c, v)
|
84
|
+
if col = model.set_column_conflicts[c]
|
85
|
+
self[col] = v
|
86
|
+
else
|
87
|
+
super
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -100,7 +100,7 @@ module Sequel
|
|
100
100
|
klass = opts[:class]
|
101
101
|
class_proc = proc{klass || constantize(opts[:class_name])}
|
102
102
|
opts[:composer] = proc do
|
103
|
-
if values = keys.map{|k|
|
103
|
+
if values = keys.map{|k| get_column_value(k)} and values.any?{|v| !v.nil?}
|
104
104
|
class_proc.call.new(*values)
|
105
105
|
else
|
106
106
|
nil
|
@@ -113,9 +113,9 @@ module Sequel
|
|
113
113
|
setters = setter_meths.zip(cov_methods)
|
114
114
|
opts[:decomposer] = proc do
|
115
115
|
if (o = compositions[name]).nil?
|
116
|
-
setter_meths.each{|sm|
|
116
|
+
setter_meths.each{|sm| get_column_value(sm, nil)}
|
117
117
|
else
|
118
|
-
setters.each{|sm, cm|
|
118
|
+
setters.each{|sm, cm| get_column_value(sm, o.send(cm))}
|
119
119
|
end
|
120
120
|
end
|
121
121
|
end
|
data/lib/sequel/plugins/dirty.rb
CHANGED
@@ -60,7 +60,7 @@ module Sequel
|
|
60
60
|
#
|
61
61
|
# column_change(:name) # => ['Initial', 'Current']
|
62
62
|
def column_change(column)
|
63
|
-
[initial_value(column),
|
63
|
+
[initial_value(column), get_column_value(column)] if column_changed?(column)
|
64
64
|
end
|
65
65
|
|
66
66
|
# A hash with column symbol keys and pairs of initial and
|
@@ -70,7 +70,7 @@ module Sequel
|
|
70
70
|
def column_changes
|
71
71
|
h = {}
|
72
72
|
initial_values.each do |column, value|
|
73
|
-
h[column] = [value,
|
73
|
+
h[column] = [value, get_column_value(column)]
|
74
74
|
end
|
75
75
|
h
|
76
76
|
end
|
@@ -99,7 +99,7 @@ module Sequel
|
|
99
99
|
#
|
100
100
|
# initial_value(:name) # => 'Initial'
|
101
101
|
def initial_value(column)
|
102
|
-
initial_values.fetch(column){
|
102
|
+
initial_values.fetch(column){get_column_value(column)}
|
103
103
|
end
|
104
104
|
|
105
105
|
# A hash with column symbol keys and initial values.
|
@@ -116,7 +116,7 @@ module Sequel
|
|
116
116
|
# name # => 'Initial'
|
117
117
|
def reset_column(column)
|
118
118
|
if initial_values.has_key?(column)
|
119
|
-
|
119
|
+
set_column_value(:"#{column}=", initial_values[column])
|
120
120
|
end
|
121
121
|
if missing_initial_values.include?(column)
|
122
122
|
values.delete(column)
|
@@ -136,7 +136,7 @@ module Sequel
|
|
136
136
|
value = if initial_values.has_key?(column)
|
137
137
|
initial_values[column]
|
138
138
|
else
|
139
|
-
|
139
|
+
get_column_value(column)
|
140
140
|
end
|
141
141
|
|
142
142
|
initial_values[column] = if value && value != true && value.respond_to?(:clone)
|
@@ -184,7 +184,7 @@ module Sequel
|
|
184
184
|
end
|
185
185
|
else
|
186
186
|
check_missing_initial_value(column)
|
187
|
-
iv[column] =
|
187
|
+
iv[column] = get_column_value(column)
|
188
188
|
super
|
189
189
|
end
|
190
190
|
end
|
data/lib/sequel/plugins/list.rb
CHANGED
@@ -63,7 +63,7 @@ module Sequel
|
|
63
63
|
proc{|obj| obj.model.filter(scope=>obj.send(scope))}
|
64
64
|
when Array
|
65
65
|
model.dataset = model.dataset.order_prepend(*scope)
|
66
|
-
proc{|obj| obj.model.filter(scope.map{|s| [s, obj.
|
66
|
+
proc{|obj| obj.model.filter(scope.map{|s| [s, obj.get_column_value(s)]})}
|
67
67
|
else
|
68
68
|
scope
|
69
69
|
end
|
@@ -90,8 +90,8 @@ module Sequel
|
|
90
90
|
# Set the value of the position_field to the maximum value plus 1 unless the
|
91
91
|
# position field already has a value.
|
92
92
|
def before_create
|
93
|
-
unless
|
94
|
-
|
93
|
+
unless get_column_value(position_field)
|
94
|
+
set_column_value("#{position_field}=", list_dataset.max(position_field).to_i+1)
|
95
95
|
end
|
96
96
|
super
|
97
97
|
end
|
@@ -168,7 +168,7 @@ module Sequel
|
|
168
168
|
|
169
169
|
# The value of the model's position field for this instance.
|
170
170
|
def position_value
|
171
|
-
|
171
|
+
get_column_value(position_field)
|
172
172
|
end
|
173
173
|
|
174
174
|
# The model instance the given number of places below this model instance
|
@@ -57,7 +57,7 @@ module Sequel
|
|
57
57
|
# Add the lock column instance filter to the object.
|
58
58
|
def lock_column_instance_filter
|
59
59
|
lc = model.lock_column
|
60
|
-
instance_filter(lc=>Sequel.blob(
|
60
|
+
instance_filter(lc=>Sequel.blob(get_column_value(lc)))
|
61
61
|
end
|
62
62
|
|
63
63
|
# Clear the instance filters when refreshing, so that attempting to
|
@@ -152,9 +152,9 @@ module Sequel
|
|
152
152
|
# not use an ensure block, so callers should be careful.
|
153
153
|
def nested_attributes_check_key_modifications(meta, obj)
|
154
154
|
reflection = meta[:reflection]
|
155
|
-
keys = reflection.associated_object_keys.map{|x| obj.
|
155
|
+
keys = reflection.associated_object_keys.map{|x| obj.get_column_value(x)}
|
156
156
|
yield
|
157
|
-
unless keys == reflection.associated_object_keys.map{|x| obj.
|
157
|
+
unless keys == reflection.associated_object_keys.map{|x| obj.get_column_value(x)}
|
158
158
|
raise(Error, "Modifying association dependent key(s) when updating associated objects is not allowed")
|
159
159
|
end
|
160
160
|
end
|
@@ -57,7 +57,7 @@ module Sequel
|
|
57
57
|
# Add the lock column instance filter to the object.
|
58
58
|
def lock_column_instance_filter
|
59
59
|
lc = model.lock_column
|
60
|
-
instance_filter(lc=>
|
60
|
+
instance_filter(lc=>get_column_value(lc))
|
61
61
|
end
|
62
62
|
|
63
63
|
# Clear the instance filters when refreshing, so that attempting to
|
@@ -72,10 +72,10 @@ module Sequel
|
|
72
72
|
# lock version.
|
73
73
|
def _update_columns(columns)
|
74
74
|
lc = model.lock_column
|
75
|
-
lcv =
|
75
|
+
lcv = get_column_value(lc)
|
76
76
|
columns[lc] = lcv + 1
|
77
77
|
super
|
78
|
-
|
78
|
+
set_column_value("#{lc}=", lcv + 1)
|
79
79
|
end
|
80
80
|
end
|
81
81
|
end
|