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