sequel 4.18.0 → 4.19.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|