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.
@@ -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
- # The default primary key for tables, inherited by future subclasses
8
+ @allowed_columns = nil
9
+ @dataset_methods = {}
11
10
  @primary_key = :id
12
-
13
- # Whether to typecast attribute values on assignment, inherited by
14
- # future subclasses.
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
- metaattr_accessor :primary_key
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
- dataset.meta_def(args.first, &block)
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 typecast_on_assignment and primary_key
140
- # attributes from the parent class.
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
- subclass.instance_variable_set(:@typecast_on_assignment, sup_class.typecast_on_assignment) unless ivs.include?("@typecast_on_assignment")
145
- subclass.instance_variable_set(:@primary_key, sup_class.primary_key) unless ivs.include?("@primary_key")
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
- def_dataset_method(:destroy) do
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
- metaprivate :def_column_accessor, :get_db_schema, :set_columns
464
+ private_class_method :def_column_accessor, :get_db_schema, :set_columns
370
465
  end
371
466
  end
@@ -54,7 +54,7 @@ module Sequel
54
54
  obj
55
55
  end
56
56
 
57
- metaprivate :cache_delete, :cache_key, :cache_lookup
57
+ private_class_method :cache_delete, :cache_key, :cache_lookup
58
58
 
59
59
  ### Instance Methods ###
60
60
 
@@ -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
- current.instance_variable_set("@#{alias_map[ta]}", type_map[ta] == :many_to_one ? :null : []) unless current.instance_variable_get("@#{alias_map[ta]}")
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
- ivar = "@#{alias_map[ta]}"
293
+ assoc_name = alias_map[ta]
292
294
  case assoc_type = type_map[ta]
293
295
  when :many_to_one
294
- current.instance_variable_set(ivar, rec)
296
+ current.associations[assoc_name] = rec
295
297
  else
296
- list = current.instance_variable_get(ivar)
297
- list.push(rec)
298
- if (assoc_type == :one_to_many) && (reciprocal = reciprocal_map[ta])
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.instance_variable_set(assoc_iv, :null)
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 do |object|
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 do |object|
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.instance_variable_get(assoc_iv) << assoc_object
424
- assoc_object.instance_variable_set(reciprocal, object) if reciprocal
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
@@ -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
- metaprivate :add_hook, :all_hooks, :hooks, :run_hooks
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")
@@ -57,6 +57,6 @@ module Sequel
57
57
  Sequel::Plugins.const_get(module_name)
58
58
  end
59
59
 
60
- metaprivate :plugin_gem, :plugin_module
60
+ private_class_method :plugin_gem, :plugin_module
61
61
  end
62
62
  end
@@ -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
- # The hash of attribute values. Keys are symbols with the names of the
10
- # underlying database columns.
11
- attr_reader :values
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
- set_with_params(values)
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
- model.all_association_reflections.each do |r|
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 set_with_params instead.
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
- def setter_methods
301
- methods.grep(/=\z/)
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