sequel 2.0.1 → 2.1.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.
- data/CHANGELOG +38 -0
- data/README +3 -4
- data/Rakefile +4 -4
- data/lib/sequel_model.rb +22 -2
- data/lib/sequel_model/association_reflection.rb +2 -9
- data/lib/sequel_model/associations.rb +184 -91
- data/lib/sequel_model/base.rb +117 -22
- data/lib/sequel_model/caching.rb +1 -1
- data/lib/sequel_model/dataset_methods.rb +26 -0
- data/lib/sequel_model/eager_loading.rb +16 -20
- data/lib/sequel_model/hooks.rb +1 -1
- data/lib/sequel_model/plugins.rb +1 -1
- data/lib/sequel_model/record.rb +125 -39
- data/lib/sequel_model/validations.rb +101 -115
- data/spec/association_reflection_spec.rb +6 -6
- data/spec/associations_spec.rb +205 -37
- data/spec/base_spec.rb +161 -1
- data/spec/dataset_methods_spec.rb +66 -0
- data/spec/eager_loading_spec.rb +36 -25
- data/spec/model_spec.rb +51 -6
- data/spec/record_spec.rb +172 -62
- data/spec/schema_spec.rb +7 -0
- data/spec/validations_spec.rb +152 -51
- metadata +5 -3
data/lib/sequel_model/base.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# This file holds general class methods for Sequel::Model
|
2
|
-
|
3
1
|
module Sequel
|
4
2
|
class Model
|
5
3
|
# Whether to lazily load the schema for future subclasses. Unless turned
|
@@ -7,15 +5,42 @@ module Sequel
|
|
7
5
|
# created
|
8
6
|
@@lazy_load_schema = false
|
9
7
|
|
10
|
-
|
8
|
+
@allowed_columns = nil
|
9
|
+
@dataset_methods = {}
|
11
10
|
@primary_key = :id
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
@restrict_primary_key = true
|
12
|
+
@restricted_columns = nil
|
13
|
+
@sti_dataset = nil
|
14
|
+
@sti_key = nil
|
15
|
+
@strict_param_setting = true
|
15
16
|
@typecast_on_assignment = true
|
16
17
|
|
18
|
+
# Which columns should be the only columns allowed in a call to set
|
19
|
+
# (default: all columns).
|
20
|
+
metaattr_reader :allowed_columns
|
21
|
+
|
22
|
+
# Hash of dataset methods to add to this class and subclasses when
|
23
|
+
# set_dataset is called.
|
24
|
+
metaattr_reader :dataset_methods
|
25
|
+
|
17
26
|
# The default primary key for classes (default: :id)
|
18
|
-
|
27
|
+
metaattr_reader :primary_key
|
28
|
+
|
29
|
+
# Which columns should not be update in a call to set
|
30
|
+
# (default: no columns).
|
31
|
+
metaattr_reader :restricted_columns
|
32
|
+
|
33
|
+
# The base dataset for STI, to which filters are added to get
|
34
|
+
# only the models for the specific STI subclass.
|
35
|
+
metaattr_reader :sti_dataset
|
36
|
+
|
37
|
+
# The column name holding the STI key for this model
|
38
|
+
metaattr_reader :sti_key
|
39
|
+
|
40
|
+
# Whether new/set/update and their variants should raise an error
|
41
|
+
# if an invalid key is used (either that doesn't exist or that
|
42
|
+
# access is restricted to it).
|
43
|
+
metaattr_accessor :strict_param_setting
|
19
44
|
|
20
45
|
# Whether to typecast attribute values on assignment (default: true)
|
21
46
|
metaattr_accessor :typecast_on_assignment
|
@@ -28,8 +53,14 @@ module Sequel
|
|
28
53
|
left_outer_join limit map multi_insert naked order order_by order_more
|
29
54
|
paginate print query range reverse_order right_outer_join select
|
30
55
|
select_all select_more set set_graph_aliases single_value size to_csv
|
31
|
-
transform union uniq unordered update where'
|
32
|
-
|
56
|
+
transform union uniq unordered update where'.map{|x| x.to_sym}
|
57
|
+
|
58
|
+
# Instance variables that are inherited in subclasses
|
59
|
+
INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup, :@dataset_methods=>:dup,
|
60
|
+
:@primary_key=>nil, :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
|
61
|
+
:@sti_dataset=>nil, :@sti_key=>nil, :@strict_param_setting=>nil,
|
62
|
+
:@typecast_on_assignment=>nil}
|
63
|
+
|
33
64
|
# Returns the first record from the database matching the conditions.
|
34
65
|
# If a hash is given, it is used as the conditions. If another
|
35
66
|
# object is given, it finds the first record whose primary key(s) match
|
@@ -102,7 +133,9 @@ module Sequel
|
|
102
133
|
raise(Error, "No arguments given") if args.empty?
|
103
134
|
if block_given?
|
104
135
|
raise(Error, "Defining a dataset method using a block requires only one argument") if args.length > 1
|
105
|
-
|
136
|
+
meth = args.first
|
137
|
+
@dataset_methods[meth] = block
|
138
|
+
dataset.meta_def(meth, &block)
|
106
139
|
end
|
107
140
|
args.each{|arg| instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__)}
|
108
141
|
end
|
@@ -136,19 +169,23 @@ module Sequel
|
|
136
169
|
end
|
137
170
|
|
138
171
|
# If possible, set the dataset for the model subclass as soon as it
|
139
|
-
# is created. Also, inherit the
|
140
|
-
#
|
172
|
+
# is created. Also, inherit the INHERITED_INSTANCE_VARIABLES
|
173
|
+
# from the parent class.
|
141
174
|
def self.inherited(subclass)
|
142
175
|
sup_class = subclass.superclass
|
143
176
|
ivs = subclass.instance_variables
|
144
|
-
|
145
|
-
|
177
|
+
INHERITED_INSTANCE_VARIABLES.each do |iv, dup|
|
178
|
+
next if ivs.include?(iv.to_s)
|
179
|
+
sup_class_value = sup_class.instance_variable_get(iv)
|
180
|
+
sup_class_value = sup_class_value.dup if dup == :dup && sup_class_value
|
181
|
+
subclass.instance_variable_set(iv, sup_class_value)
|
182
|
+
end
|
146
183
|
unless ivs.include?("@dataset")
|
147
184
|
begin
|
148
185
|
if sup_class == Model
|
149
186
|
subclass.set_dataset(Model.db[subclass.implicit_table_name]) unless subclass.name.empty?
|
150
187
|
elsif ds = sup_class.instance_variable_get(:@dataset)
|
151
|
-
subclass.set_dataset(ds.clone)
|
188
|
+
subclass.set_dataset(sup_class.sti_key ? sup_class.sti_dataset.filter(sup_class.sti_key=>subclass.name) : ds.clone)
|
152
189
|
end
|
153
190
|
rescue
|
154
191
|
end
|
@@ -199,6 +236,19 @@ module Sequel
|
|
199
236
|
end
|
200
237
|
end
|
201
238
|
|
239
|
+
# Restrict the setting of the primary key(s) inside new/set/update. Because
|
240
|
+
# this is the default, this only make sense to use in a subclass where the
|
241
|
+
# parent class has used unrestrict_primary_key.
|
242
|
+
def self.restrict_primary_key
|
243
|
+
@restrict_primary_key = true
|
244
|
+
end
|
245
|
+
|
246
|
+
# Whether or not setting the primary key inside new/set/update is
|
247
|
+
# restricted, true by default.
|
248
|
+
def self.restrict_primary_key?
|
249
|
+
@restrict_primary_key
|
250
|
+
end
|
251
|
+
|
202
252
|
# Serializes column with YAML or through marshalling. Arguments should be
|
203
253
|
# column symbols, with an optional trailing hash with a :format key
|
204
254
|
# set to :yaml or :marshal (:yaml is the default). Setting this adds
|
@@ -213,6 +263,17 @@ module Sequel
|
|
213
263
|
@dataset.transform(@transform) if @dataset
|
214
264
|
end
|
215
265
|
|
266
|
+
# Set the columns to allow in new/set/update. Using this means that
|
267
|
+
# any columns not listed here will not be modified. If you have any virtual
|
268
|
+
# setter methods (methods that end in =) that you want to be used in
|
269
|
+
# new/set/update, they need to be listed here as well (without the =).
|
270
|
+
#
|
271
|
+
# It may be better to use (set|update)_only instead of this in places where
|
272
|
+
# only certain columns may be allowed.
|
273
|
+
def self.set_allowed_columns(*cols)
|
274
|
+
@allowed_columns = cols
|
275
|
+
end
|
276
|
+
|
216
277
|
# Sets the dataset associated with the Model class. ds can be a Symbol
|
217
278
|
# (specifying a table name in the current database), or a Dataset.
|
218
279
|
# If a dataset is used, the model's database is changed to the given
|
@@ -236,14 +297,10 @@ module Sequel
|
|
236
297
|
raise(Error, "Model.set_dataset takes a Symbol or a Sequel::Dataset")
|
237
298
|
end
|
238
299
|
@dataset.set_model(self)
|
239
|
-
|
240
|
-
raise(Error, "No model associated with this dataset") unless @opts[:models]
|
241
|
-
count = 0
|
242
|
-
@db.transaction {each {|r| count += 1; r.destroy}}
|
243
|
-
count
|
244
|
-
end
|
300
|
+
@dataset.extend(DatasetMethods)
|
245
301
|
@dataset.extend(Associations::EagerLoading)
|
246
302
|
@dataset.transform(@transform) if @transform
|
303
|
+
@dataset_methods.each{|meth, block| @dataset.meta_def(meth, &block)} if @dataset_methods
|
247
304
|
begin
|
248
305
|
(@db_schema = get_db_schema) unless @@lazy_load_schema
|
249
306
|
rescue
|
@@ -271,6 +328,39 @@ module Sequel
|
|
271
328
|
@primary_key = (key.length == 1) ? key[0] : key.flatten
|
272
329
|
end
|
273
330
|
|
331
|
+
# Set the columns to restrict in new/set/update. Using this means that
|
332
|
+
# any columns listed here will not be modified. If you have any virtual
|
333
|
+
# setter methods (methods that end in =) that you want not to be used in
|
334
|
+
# new/set/update, they need to be listed here as well (without the =).
|
335
|
+
#
|
336
|
+
# It may be better to use (set|update)_except instead of this in places where
|
337
|
+
# only certain columns may be allowed.
|
338
|
+
def self.set_restricted_columns(*cols)
|
339
|
+
@restricted_columns = cols
|
340
|
+
end
|
341
|
+
|
342
|
+
# Makes this model a polymorphic model with the given key being a string
|
343
|
+
# field in the database holding the name of the class to use. If the
|
344
|
+
# key given has a NULL value or there are any problems looking up the
|
345
|
+
# class, uses the current class.
|
346
|
+
#
|
347
|
+
# This should be used to set up single table inheritance for the model,
|
348
|
+
# and it only makes sense to use this in the parent class.
|
349
|
+
#
|
350
|
+
# You should call sti_key after any calls to set_dataset in the model,
|
351
|
+
# otherwise subclasses might not have the filters set up correctly.
|
352
|
+
#
|
353
|
+
# The filters that sti_key sets up in subclasses will not work if
|
354
|
+
# those subclasses have further subclasses. For those middle subclasses,
|
355
|
+
# you will need to call set_dataset manually with the correct filter set.
|
356
|
+
def self.set_sti_key(key)
|
357
|
+
m = self
|
358
|
+
@sti_key = key
|
359
|
+
@sti_dataset = dataset
|
360
|
+
dataset.set_model(key, Hash.new{|h,k| h[k] = (k.constantize rescue m)})
|
361
|
+
before_create(:set_sti_key){send("#{key}=", model.name)}
|
362
|
+
end
|
363
|
+
|
274
364
|
# Returns the columns as a list of frozen strings instead
|
275
365
|
# of a list of symbols. This makes it possible to check
|
276
366
|
# whether a column exists without creating a symbol, which
|
@@ -302,6 +392,11 @@ module Sequel
|
|
302
392
|
dataset.opts[:from].first
|
303
393
|
end
|
304
394
|
|
395
|
+
# Allow the setting of the primary key(s) inside new/set/update.
|
396
|
+
def self.unrestrict_primary_key
|
397
|
+
@restrict_primary_key = false
|
398
|
+
end
|
399
|
+
|
305
400
|
# Add model methods that call dataset methods
|
306
401
|
def_dataset_method(*DATASET_METHODS)
|
307
402
|
|
@@ -366,6 +461,6 @@ module Sequel
|
|
366
461
|
@columns
|
367
462
|
end
|
368
463
|
|
369
|
-
|
464
|
+
private_class_method :def_column_accessor, :get_db_schema, :set_columns
|
370
465
|
end
|
371
466
|
end
|
data/lib/sequel_model/caching.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Dataset methods are methods that the model class extends its dataset with in
|
2
|
+
# the call to set_dataset.
|
3
|
+
module Sequel::Model::DatasetMethods
|
4
|
+
# Destroy each row in the dataset by instantiating it and then calling
|
5
|
+
# destroy on the resulting model object. This isn't as fast as deleting
|
6
|
+
# the object, which does a single SQL call, but this runs any destroy
|
7
|
+
# hooks.
|
8
|
+
def destroy
|
9
|
+
raise(Error, "No model associated with this dataset") unless @opts[:models]
|
10
|
+
count = 0
|
11
|
+
@db.transaction {each {|r| count += 1; r.destroy}}
|
12
|
+
count
|
13
|
+
end
|
14
|
+
|
15
|
+
# This allows you to call to_hash without any arguments, which will
|
16
|
+
# result in a hash with the primary key value being the key and the
|
17
|
+
# model object being the value.
|
18
|
+
def to_hash(key_column=nil, value_column=nil)
|
19
|
+
if key_column
|
20
|
+
super
|
21
|
+
else
|
22
|
+
raise(Sequel::Error, "No primary key for model") unless pk = @opts[:models][nil].primary_key
|
23
|
+
super(pk, value_column)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -132,17 +132,18 @@ module Sequel::Model::Associations::EagerLoading
|
|
132
132
|
assoc_table_alias = ds.eager_unique_table_alias(ds, assoc_name)
|
133
133
|
join_type = r[:graph_join_type]
|
134
134
|
conditions = r[:graph_conditions]
|
135
|
+
select = r[:graph_select]
|
135
136
|
ds = case assoc_type = r[:type]
|
136
137
|
when :many_to_one
|
137
|
-
ds.graph(klass, [[klass.primary_key, :"#{ta}__#{r[:key]}"]] + conditions, :table_alias=>assoc_table_alias, :join_type=>join_type)
|
138
|
+
ds.graph(klass, [[klass.primary_key, :"#{ta}__#{r[:key]}"]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type)
|
138
139
|
when :one_to_many
|
139
|
-
ds = ds.graph(klass, [[r[:key], :"#{ta}__#{model.primary_key}"]] + conditions, :table_alias=>assoc_table_alias, :join_type=>join_type)
|
140
|
+
ds = ds.graph(klass, [[r[:key], :"#{ta}__#{model.primary_key}"]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type)
|
140
141
|
# We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
|
141
142
|
ds.opts[:eager_graph][:reciprocals][assoc_table_alias] = r.reciprocal
|
142
143
|
ds
|
143
144
|
when :many_to_many
|
144
145
|
ds = ds.graph(r[:join_table], [[r[:left_key], :"#{ta}__#{model.primary_key}"]] + r[:graph_join_table_conditions], :select=>false, :table_alias=>ds.eager_unique_table_alias(ds, r[:join_table]), :join_type=>join_type)
|
145
|
-
ds.graph(klass, [[klass.primary_key, r[:right_key]]] + conditions, :table_alias=>assoc_table_alias, :join_type=>join_type)
|
146
|
+
ds.graph(klass, [[klass.primary_key, r[:right_key]]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type)
|
146
147
|
end
|
147
148
|
eager_graph = ds.opts[:eager_graph]
|
148
149
|
eager_graph[:requirements][assoc_table_alias] = requirements.dup
|
@@ -278,7 +279,8 @@ module Sequel::Model::Associations::EagerLoading
|
|
278
279
|
return if dependency_map.empty?
|
279
280
|
# Don't clobber the instance variable array for *_to_many associations if it has already been setup
|
280
281
|
dependency_map.keys.each do |ta|
|
281
|
-
|
282
|
+
assoc_name = alias_map[ta]
|
283
|
+
current.associations[assoc_name] = type_map[ta] == :many_to_one ? nil : [] unless current.associations.include?(assoc_name)
|
282
284
|
end
|
283
285
|
dependency_map.each do |ta, deps|
|
284
286
|
next unless rec = record_graph[ta]
|
@@ -288,15 +290,14 @@ module Sequel::Model::Associations::EagerLoading
|
|
288
290
|
else
|
289
291
|
records_map[ta][rec.pk] = rec
|
290
292
|
end
|
291
|
-
|
293
|
+
assoc_name = alias_map[ta]
|
292
294
|
case assoc_type = type_map[ta]
|
293
295
|
when :many_to_one
|
294
|
-
current.
|
296
|
+
current.associations[assoc_name] = rec
|
295
297
|
else
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
rec.instance_variable_set(reciprocal, current)
|
298
|
+
current.associations[assoc_name].push(rec)
|
299
|
+
if assoc_type == :one_to_many and reciprocal = reciprocal_map[ta]
|
300
|
+
rec.associations[reciprocal] = current
|
300
301
|
end
|
301
302
|
end
|
302
303
|
# Recurse into dependencies of the current object
|
@@ -362,7 +363,6 @@ module Sequel::Model::Associations::EagerLoading
|
|
362
363
|
reflections.each do |reflection|
|
363
364
|
assoc_class = reflection.associated_class
|
364
365
|
assoc_name = reflection[:name]
|
365
|
-
assoc_iv = :"@#{assoc_name}"
|
366
366
|
# Proc for setting cascaded eager loading
|
367
367
|
assoc_block = Proc.new do |d|
|
368
368
|
if order = reflection[:order]
|
@@ -389,13 +389,11 @@ module Sequel::Model::Associations::EagerLoading
|
|
389
389
|
# Set the instance variable to null by default, so records that
|
390
390
|
# don't have a associated records will cache the negative lookup.
|
391
391
|
a.each do |object|
|
392
|
-
object.
|
392
|
+
object.associations[assoc_name] = nil
|
393
393
|
end
|
394
394
|
assoc_block.call(assoc_class.select(*reflection.select).filter(assoc_class.primary_key=>keys)).all do |assoc_object|
|
395
395
|
next unless objects = h[assoc_object.pk]
|
396
|
-
objects.each
|
397
|
-
object.instance_variable_set(assoc_iv, assoc_object)
|
398
|
-
end
|
396
|
+
objects.each{|object| object.associations[assoc_name] = assoc_object}
|
399
397
|
end
|
400
398
|
when :one_to_many, :many_to_many
|
401
399
|
h = key_hash[model.primary_key]
|
@@ -408,9 +406,7 @@ module Sequel::Model::Associations::EagerLoading
|
|
408
406
|
assoc_class.select(*(Array(reflection.select)+Array(reflection[:left_key_select]))).inner_join(reflection[:join_table], [[reflection[:right_key], reflection.associated_primary_key], [reflection[:left_key], h.keys]])
|
409
407
|
end
|
410
408
|
h.values.each do |object_array|
|
411
|
-
object_array.each
|
412
|
-
object.instance_variable_set(assoc_iv, [])
|
413
|
-
end
|
409
|
+
object_array.each{|object| object.associations[assoc_name] = []}
|
414
410
|
end
|
415
411
|
assoc_block.call(ds).all do |assoc_object|
|
416
412
|
fk = if rtype == :many_to_many
|
@@ -420,8 +416,8 @@ module Sequel::Model::Associations::EagerLoading
|
|
420
416
|
end
|
421
417
|
next unless objects = h[fk]
|
422
418
|
objects.each do |object|
|
423
|
-
object.
|
424
|
-
assoc_object.
|
419
|
+
object.associations[assoc_name].push(assoc_object)
|
420
|
+
assoc_object.associations[reciprocal] = object if reciprocal
|
425
421
|
end
|
426
422
|
end
|
427
423
|
end
|
data/lib/sequel_model/hooks.rb
CHANGED
@@ -51,7 +51,7 @@ module Sequel
|
|
51
51
|
all_hooks(hook).each{|b| return false if object.instance_eval(&b) == false}
|
52
52
|
end
|
53
53
|
|
54
|
-
|
54
|
+
private_class_method :add_hook, :all_hooks, :hooks, :run_hooks
|
55
55
|
|
56
56
|
(HOOKS + PRIVATE_HOOKS).each do |hook|
|
57
57
|
instance_eval("def #{hook}(method = nil, &block); add_hook(:#{hook}, method, &block) end")
|
data/lib/sequel_model/plugins.rb
CHANGED
data/lib/sequel_model/record.rb
CHANGED
@@ -1,18 +1,30 @@
|
|
1
|
-
# This file holds general instance methods for Sequel::Model
|
2
|
-
|
3
1
|
module Sequel
|
4
2
|
class Model
|
3
|
+
# The setter methods (methods ending with =) that are never allowed
|
4
|
+
# to be called automatically via set.
|
5
|
+
RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_on_assignment="
|
6
|
+
|
7
|
+
# The current cached associations. A hash with the keys being the
|
8
|
+
# association name symbols and the values being the associated object
|
9
|
+
# or nil (many_to_one), or the array of associated objects (*_to_many).
|
10
|
+
attr_reader :associations
|
11
|
+
|
5
12
|
# The columns that have been updated. This isn't completely accurate,
|
6
13
|
# see Model#[]=.
|
7
14
|
attr_reader :changed_columns
|
8
15
|
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
16
|
+
# Whether this model instance should raise an error if attempting
|
17
|
+
# to call a method through set/update and their variants that either
|
18
|
+
# doesn't exist or access to it is denied.
|
19
|
+
attr_writer :strict_param_setting
|
12
20
|
|
13
21
|
# Whether this model instance should typecast on attribute assignment
|
14
22
|
attr_writer :typecast_on_assignment
|
15
23
|
|
24
|
+
# The hash of attribute values. Keys are symbols with the names of the
|
25
|
+
# underlying database columns.
|
26
|
+
attr_reader :values
|
27
|
+
|
16
28
|
class_attr_reader :columns, :dataset, :db, :primary_key, :str_columns
|
17
29
|
|
18
30
|
# Creates new instance with values set to passed-in Hash.
|
@@ -27,16 +39,18 @@ module Sequel
|
|
27
39
|
# exists.
|
28
40
|
def initialize(values = nil, from_db = false, &block)
|
29
41
|
values ||= {}
|
42
|
+
@associations = {}
|
43
|
+
@db_schema = model.db_schema
|
30
44
|
@changed_columns = []
|
45
|
+
@strict_param_setting = model.strict_param_setting
|
31
46
|
@typecast_on_assignment = model.typecast_on_assignment
|
32
|
-
@db_schema = model.db_schema
|
33
47
|
if from_db
|
34
48
|
@new = false
|
35
49
|
@values = values
|
36
50
|
else
|
37
51
|
@values = {}
|
38
52
|
@new = true
|
39
|
-
|
53
|
+
set(values)
|
40
54
|
end
|
41
55
|
@changed_columns.clear
|
42
56
|
|
@@ -167,9 +181,7 @@ module Sequel
|
|
167
181
|
# exists in the database.
|
168
182
|
def refresh
|
169
183
|
@values = this.first || raise(Error, "Record not found")
|
170
|
-
|
171
|
-
instance_variable_set("@#{r[:name]}", nil)
|
172
|
-
end
|
184
|
+
@associations.clear
|
173
185
|
self
|
174
186
|
end
|
175
187
|
alias_method :reload, :refresh
|
@@ -227,11 +239,40 @@ module Sequel
|
|
227
239
|
save(*@changed_columns) unless @changed_columns.empty?
|
228
240
|
end
|
229
241
|
|
242
|
+
# Updates the instance with the supplied values with support for virtual
|
243
|
+
# attributes, ignoring any values for which no setter method is available.
|
244
|
+
# Does not save the record.
|
245
|
+
#
|
246
|
+
# If no columns have been set for this model (very unlikely), assume symbol
|
247
|
+
# keys are valid column names, and assign the column value based on that.
|
248
|
+
def set(hash)
|
249
|
+
set_restricted(hash, nil, nil)
|
250
|
+
end
|
251
|
+
alias_method :set_with_params, :set
|
252
|
+
|
253
|
+
# Set all values using the entries in the hash, ignoring any setting of
|
254
|
+
# allowed_columns or restricted columns in the model.
|
255
|
+
def set_all(hash)
|
256
|
+
set_restricted(hash, false, false)
|
257
|
+
end
|
258
|
+
|
259
|
+
# Set all values using the entries in the hash, except for the keys
|
260
|
+
# given in except.
|
261
|
+
def set_except(hash, *except)
|
262
|
+
set_restricted(hash, false, except.flatten)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Set the values using the entries in the hash, only if the key
|
266
|
+
# is included in only.
|
267
|
+
def set_only(hash, *only)
|
268
|
+
set_restricted(hash, only.flatten, false)
|
269
|
+
end
|
270
|
+
|
230
271
|
# Sets the value attributes without saving the record. Returns
|
231
272
|
# the values changed. Raises an error if the keys are not symbols
|
232
273
|
# or strings or a string key was passed that was not a valid column.
|
233
274
|
# This is a low level method that does not respect virtual attributes. It
|
234
|
-
# should probably be avoided. Look into using
|
275
|
+
# should probably be avoided. Look into using set instead.
|
235
276
|
def set_values(values)
|
236
277
|
s = str_columns
|
237
278
|
vals = values.inject({}) do |m, kv|
|
@@ -254,30 +295,35 @@ module Sequel
|
|
254
295
|
vals
|
255
296
|
end
|
256
297
|
|
257
|
-
# Updates the instance with the supplied values with support for virtual
|
258
|
-
# attributes, ignoring any values for which no setter method is available.
|
259
|
-
# Does not save the record.
|
260
|
-
#
|
261
|
-
# If no columns have been set for this model (very unlikely), assume symbol
|
262
|
-
# keys are valid column names, and assign the column value based on that.
|
263
|
-
def set_with_params(hash)
|
264
|
-
columns_not_set = model.instance_variable_get(:@columns).blank?
|
265
|
-
meths = setter_methods
|
266
|
-
hash.each do |k,v|
|
267
|
-
m = "#{k}="
|
268
|
-
if meths.include?(m)
|
269
|
-
send(m, v)
|
270
|
-
elsif columns_not_set && (Symbol === k)
|
271
|
-
self[k] = v
|
272
|
-
end
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
298
|
# Returns (naked) dataset that should return only this instance.
|
277
299
|
def this
|
278
300
|
@this ||= dataset.filter(pk_hash).limit(1).naked
|
279
301
|
end
|
280
302
|
|
303
|
+
# Runs set with the passed hash and runs save_changes (which runs any callback methods).
|
304
|
+
def update(hash)
|
305
|
+
update_restricted(hash, nil, nil)
|
306
|
+
end
|
307
|
+
alias_method :update_with_params, :update
|
308
|
+
|
309
|
+
# Update all values using the entries in the hash, ignoring any setting of
|
310
|
+
# allowed_columns or restricted columns in the model.
|
311
|
+
def update_all(hash)
|
312
|
+
update_restricted(hash, false, false)
|
313
|
+
end
|
314
|
+
|
315
|
+
# Update all values using the entries in the hash, except for the keys
|
316
|
+
# given in except.
|
317
|
+
def update_except(hash, *except)
|
318
|
+
update_restricted(hash, false, except.flatten)
|
319
|
+
end
|
320
|
+
|
321
|
+
# Update the values using the entries in the hash, only if the key
|
322
|
+
# is included in only.
|
323
|
+
def update_only(hash, *only)
|
324
|
+
update_restricted(hash, only.flatten, false)
|
325
|
+
end
|
326
|
+
|
281
327
|
# Sets the values attributes with set_values and then updates
|
282
328
|
# the record in the database using those values. This is a
|
283
329
|
# low level method that does not run the usual save callbacks.
|
@@ -287,18 +333,52 @@ module Sequel
|
|
287
333
|
this.update(set_values(values))
|
288
334
|
end
|
289
335
|
|
290
|
-
# Runs set_with_params and runs save_changes (which runs any callback methods).
|
291
|
-
def update_with_params(values)
|
292
|
-
set_with_params(values)
|
293
|
-
save_changes
|
294
|
-
end
|
295
|
-
|
296
336
|
private
|
297
337
|
|
338
|
+
# Set the columns, filtered by the only and except arrays.
|
339
|
+
def set_restricted(hash, only, except)
|
340
|
+
columns_not_set = model.instance_variable_get(:@columns).blank?
|
341
|
+
meths = setter_methods(only, except)
|
342
|
+
strict_param_setting = @strict_param_setting
|
343
|
+
hash.each do |k,v|
|
344
|
+
m = "#{k}="
|
345
|
+
if meths.include?(m)
|
346
|
+
send(m, v)
|
347
|
+
elsif columns_not_set && (Symbol === k)
|
348
|
+
self[k] = v
|
349
|
+
elsif strict_param_setting
|
350
|
+
raise Error, "method #{m} doesn't exist or access is restricted to it"
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
298
355
|
# Returns all methods that can be used for attribute
|
299
|
-
# assignment (those that end with =)
|
300
|
-
|
301
|
-
|
356
|
+
# assignment (those that end with =), modified by the only
|
357
|
+
# and except arguments:
|
358
|
+
#
|
359
|
+
# * only
|
360
|
+
# * false - Don't modify the results
|
361
|
+
# * nil - if the model has allowed_columns, use only these, otherwise, don't modify
|
362
|
+
# * Array - allow only the given methods to be used
|
363
|
+
# * except
|
364
|
+
# * false - Don't modify the results
|
365
|
+
# * nil - if the model has restricted_columns, remove these, otherwise, don't modify
|
366
|
+
# * Array - remove the given methods
|
367
|
+
#
|
368
|
+
# only takes precedence over except, and if only is not used, certain methods are always
|
369
|
+
# restricted (RESTRICTED_SETTER_METHODS). The primary key is restricted by default as
|
370
|
+
# well, see Model.unrestrict_primary_key to change this.
|
371
|
+
def setter_methods(only, except)
|
372
|
+
only = only.nil? ? model.allowed_columns : only
|
373
|
+
except = except.nil? ? model.restricted_columns : except
|
374
|
+
if only
|
375
|
+
only.map{|x| "#{x}="}
|
376
|
+
else
|
377
|
+
meths = methods.grep(/=\z/) - RESTRICTED_SETTER_METHODS
|
378
|
+
meths -= Array(primary_key).map{|x| "#{x}="} if primary_key && model.restrict_primary_key?
|
379
|
+
meths -= except.map{|x| "#{x}="} if except
|
380
|
+
meths
|
381
|
+
end
|
302
382
|
end
|
303
383
|
|
304
384
|
# Typecast the value to the column's type if typecasting. Calls the database's
|
@@ -309,5 +389,11 @@ module Sequel
|
|
309
389
|
raise(Error, "nil/NULL is not allowed for the #{column} column") if value.nil? && (col_schema[:allow_null] == false)
|
310
390
|
model.db.typecast_value(col_schema[:type], value)
|
311
391
|
end
|
392
|
+
|
393
|
+
# Set the columns, filtered by the only and except arrays.
|
394
|
+
def update_restricted(hash, only, except)
|
395
|
+
set_restricted(hash, only, except)
|
396
|
+
save_changes
|
397
|
+
end
|
312
398
|
end
|
313
399
|
end
|