sequel 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,6 +8,7 @@ module Sequel
8
8
  @allowed_columns = nil
9
9
  @dataset_methods = {}
10
10
  @primary_key = :id
11
+ @raise_on_save_failure = true
11
12
  @restrict_primary_key = true
12
13
  @restricted_columns = nil
13
14
  @sti_dataset = nil
@@ -26,6 +27,10 @@ module Sequel
26
27
  # The default primary key for classes (default: :id)
27
28
  metaattr_reader :primary_key
28
29
 
30
+ # Whether to raise an error instead of returning nil on a failure
31
+ # to save/create/save_changes/etc.
32
+ metaattr_accessor :raise_on_save_failure
33
+
29
34
  # Which columns should not be update in a call to set
30
35
  # (default: no columns).
31
36
  metaattr_reader :restricted_columns
@@ -52,12 +57,13 @@ module Sequel
52
57
  insert_multiple intersect interval invert_order join join_table last
53
58
  left_outer_join limit map multi_insert naked order order_by order_more
54
59
  paginate print query range reverse_order right_outer_join select
55
- select_all select_more set set_graph_aliases single_value size to_csv
56
- transform union uniq unordered update where'.map{|x| x.to_sym}
60
+ select_all select_more set set_graph_aliases single_value size to_csv to_hash
61
+ transform union uniq unfiltered unordered update where'.map{|x| x.to_sym}
57
62
 
58
63
  # 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,
64
+ INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup, :@cache_store=>nil,
65
+ :@cache_ttl=>nil, :@dataset_methods=>:dup, :@primary_key=>nil,
66
+ :@raise_on_save_failure=>nil, :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
61
67
  :@sti_dataset=>nil, :@sti_key=>nil, :@strict_param_setting=>nil,
62
68
  :@typecast_on_assignment=>nil}
63
69
 
@@ -83,7 +89,7 @@ module Sequel
83
89
  # dataset) it will use Dataset#columns to find the columns, which
84
90
  # may be empty if the Dataset has no records.
85
91
  def self.columns
86
- @columns || set_columns(dataset.naked.columns || raise(Error, "Could not fetch columns for #{self}"))
92
+ @columns || set_columns(dataset.naked.columns)
87
93
  end
88
94
 
89
95
  # Creates new instance with values set to passed-in Hash, saves it
@@ -92,7 +98,7 @@ module Sequel
92
98
  # returns false.
93
99
  def self.create(values = {}, &block)
94
100
  obj = new(values, &block)
95
- return false if obj.save == false
101
+ return unless obj.save
96
102
  obj
97
103
  end
98
104
 
@@ -155,6 +161,17 @@ module Sequel
155
161
  db.fetch(*args).set_model(self)
156
162
  end
157
163
 
164
+ # Modify and return eager loading dataset based on association options
165
+ def self.eager_loading_dataset(opts, ds, select, associations)
166
+ ds = ds.select(*select) if select
167
+ ds = ds.order(*opts[:order]) if opts[:order]
168
+ ds = ds.eager(opts[:eager]) if opts[:eager]
169
+ ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph]
170
+ ds = ds.eager(associations) unless associations.blank?
171
+ ds = opts[:eager_block].call(ds) if opts[:eager_block]
172
+ ds
173
+ end
174
+
158
175
  # Finds a single record according to the supplied filter, e.g.:
159
176
  #
160
177
  # Ticket.find :author => 'Sharon' # => record
@@ -441,7 +458,11 @@ module Sequel
441
458
  # Dataset is for a single table with all columns,
442
459
  # so set the columns based on the order they were
443
460
  # returned by the schema.
444
- set_columns(schema_array.collect{|k,v| k})
461
+ cols = schema_array.collect{|k,v| k}
462
+ set_columns(cols)
463
+ # Also set the columns for the dataset, so the dataset
464
+ # doesn't have to do a query to get them.
465
+ dataset.instance_variable_set(:@columns, cols)
445
466
  end
446
467
  else
447
468
  # If the dataset uses multiple tables or custom sql or getting
@@ -56,8 +56,8 @@ module Sequel::Model::Associations::EagerLoading
56
56
  # or join the tables you need to filter on manually.
57
57
  #
58
58
  # Each association's order, if definied, is respected. Eager also works
59
- # on a limited dataset. If the association uses a block or has an :eager_block
60
- # argument, it is used.
59
+ # on a limited dataset, but does not use any :limit options for associations.
60
+ # If the association uses a block or has an :eager_block argument, it is used.
61
61
  def eager(*associations)
62
62
  model = check_model
63
63
  opt = @opts[:eager]
@@ -90,7 +90,7 @@ module Sequel::Model::Associations::EagerLoading
90
90
  # This does not respect each association's order, as all associations are loaded in
91
91
  # a single query. If you want to order the results, you must manually call .order.
92
92
  #
93
- # #eager_graph probably won't work the way you suspect with limit, unless you are
93
+ # #eager_graph probably won't work correctly on a limited dataset, unless you are
94
94
  # only graphing many_to_one associations.
95
95
  #
96
96
  # Does not use the block defined for the association, since it does a single query for
@@ -132,18 +132,22 @@ 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
+ use_only_conditions = r.include?(:graph_only_conditions)
136
+ only_conditions = r[:graph_only_conditions]
135
137
  select = r[:graph_select]
138
+ graph_block = r[:graph_block]
136
139
  ds = case assoc_type = r[:type]
137
140
  when :many_to_one
138
- ds.graph(klass, [[klass.primary_key, :"#{ta}__#{r[:key]}"]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type)
141
+ ds.graph(klass, use_only_conditions ? only_conditions : [[klass.primary_key, r[:key].qualify(ta)]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type, &graph_block)
139
142
  when :one_to_many
140
- ds = ds.graph(klass, [[r[:key], :"#{ta}__#{model.primary_key}"]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type)
143
+ ds = ds.graph(klass, use_only_conditions ? only_conditions : [[r[:key], model.primary_key.qualify(ta)]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type, &graph_block)
141
144
  # We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
142
145
  ds.opts[:eager_graph][:reciprocals][assoc_table_alias] = r.reciprocal
143
146
  ds
144
147
  when :many_to_many
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)
146
- ds.graph(klass, [[klass.primary_key, r[:right_key]]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type)
148
+ use_jt_only_conditions = r.include?(:graph_join_table_only_conditions)
149
+ ds = ds.graph(r[:join_table], use_jt_only_conditions ? r[:graph_join_table_only_conditions] : [[r[:left_key], model.primary_key.qualify(ta)]] + r[:graph_join_table_conditions], :select=>false, :table_alias=>ds.eager_unique_table_alias(ds, r[:join_table]), :join_type=>r[:graph_join_table_join_type], &r[:graph_join_table_block])
150
+ ds.graph(klass, use_only_conditions ? only_conditions : [[klass.primary_key, r[:right_key]]] + conditions, :select=>select, :table_alias=>assoc_table_alias, :join_type=>join_type, &graph_block)
147
151
  end
148
152
  eager_graph = ds.opts[:eager_graph]
149
153
  eager_graph[:requirements][assoc_table_alias] = requirements.dup
@@ -358,70 +362,7 @@ module Sequel::Model::Associations::EagerLoading
358
362
  end
359
363
  end
360
364
 
361
- # Iterate through eager associations and assign instance variables
362
- # for the association for all model objects
363
- reflections.each do |reflection|
364
- assoc_class = reflection.associated_class
365
- assoc_name = reflection[:name]
366
- # Proc for setting cascaded eager loading
367
- assoc_block = Proc.new do |d|
368
- if order = reflection[:order]
369
- d = d.order(*order)
370
- end
371
- if c = eager_assoc[assoc_name]
372
- d = d.eager(c)
373
- end
374
- if c = reflection[:eager]
375
- d = d.eager(c)
376
- end
377
- if b = reflection[:eager_block]
378
- d = b.call(d)
379
- end
380
- d
381
- end
382
- case rtype = reflection[:type]
383
- when :many_to_one
384
- key = reflection[:key]
385
- h = key_hash[key]
386
- keys = h.keys
387
- # No records have the foreign key set for this association, so skip it
388
- next unless keys.length > 0
389
- # Set the instance variable to null by default, so records that
390
- # don't have a associated records will cache the negative lookup.
391
- a.each do |object|
392
- object.associations[assoc_name] = nil
393
- end
394
- assoc_block.call(assoc_class.select(*reflection.select).filter(assoc_class.primary_key=>keys)).all do |assoc_object|
395
- next unless objects = h[assoc_object.pk]
396
- objects.each{|object| object.associations[assoc_name] = assoc_object}
397
- end
398
- when :one_to_many, :many_to_many
399
- h = key_hash[model.primary_key]
400
- ds = if rtype == :one_to_many
401
- fkey = reflection[:key]
402
- reciprocal = reflection.reciprocal
403
- assoc_class.select(*reflection.select).filter(fkey=>h.keys)
404
- else
405
- fkey = reflection[:left_key_alias]
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]])
407
- end
408
- h.values.each do |object_array|
409
- object_array.each{|object| object.associations[assoc_name] = []}
410
- end
411
- assoc_block.call(ds).all do |assoc_object|
412
- fk = if rtype == :many_to_many
413
- assoc_object.values.delete(fkey)
414
- else
415
- assoc_object[fkey]
416
- end
417
- next unless objects = h[fk]
418
- objects.each do |object|
419
- object.associations[assoc_name].push(assoc_object)
420
- assoc_object.associations[reciprocal] = object if reciprocal
421
- end
422
- end
423
- end
424
- end
365
+ reflections.each{|r| r[:eager_loader].call(key_hash, a, eager_assoc[r[:name]])}
425
366
  end
426
367
 
427
368
  # Build associations from the graph if #eager_graph was used,
@@ -2,7 +2,7 @@ module Sequel
2
2
  class Model
3
3
  # The setter methods (methods ending with =) that are never allowed
4
4
  # to be called automatically via set.
5
- RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_on_assignment="
5
+ RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_on_assignment= strict_param_setting= raise_on_save_failure="
6
6
 
7
7
  # The current cached associations. A hash with the keys being the
8
8
  # association name symbols and the values being the associated object
@@ -13,6 +13,10 @@ module Sequel
13
13
  # see Model#[]=.
14
14
  attr_reader :changed_columns
15
15
 
16
+ # Whether this model instance should raise an exception instead of
17
+ # returning nil on a failure to save/save_changes/etc.
18
+ attr_writer :raise_on_save_failure
19
+
16
20
  # Whether this model instance should raise an error if attempting
17
21
  # to call a method through set/update and their variants that either
18
22
  # doesn't exist or access to it is denied.
@@ -42,6 +46,7 @@ module Sequel
42
46
  @associations = {}
43
47
  @db_schema = model.db_schema
44
48
  @changed_columns = []
49
+ @raise_on_save_failure = model.raise_on_save_failure
45
50
  @strict_param_setting = model.strict_param_setting
46
51
  @typecast_on_assignment = model.typecast_on_assignment
47
52
  if from_db
@@ -108,7 +113,7 @@ module Sequel
108
113
  # the item from the database and returns self.
109
114
  def destroy
110
115
  db.transaction do
111
- return false if before_destroy == false
116
+ return save_failure(:destroy) if before_destroy == false
112
117
  delete
113
118
  after_destroy
114
119
  end
@@ -187,12 +192,13 @@ module Sequel
187
192
  alias_method :reload, :refresh
188
193
 
189
194
  # Creates or updates the record, after making sure the record
190
- # is valid. If the record is not valid, returns false.
191
- # If before_save, before_create (if new?), or before_update
192
- # (if !new?) return false, returns false. Otherwise,
193
- # returns self.
195
+ # is valid. If the record is not valid, or before_save,
196
+ # before_create (if new?), or before_update (if !new?) return
197
+ # false, returns nil unless raise_on_save_failure is true.
198
+ # Otherwise, returns self. You can provide an optional list of
199
+ # columns to update, in which case it only updates those columns.
194
200
  def save(*columns)
195
- return false unless valid?
201
+ return save_failure(:save) unless valid?
196
202
  save!(*columns)
197
203
  end
198
204
 
@@ -200,12 +206,12 @@ module Sequel
200
206
  # it first. You can provide an optional list of columns to update,
201
207
  # in which case it only updates those columns.
202
208
  # If before_save, before_create (if new?), or before_update
203
- # (if !new?) return false, returns false. Otherwise,
204
- # returns self.
209
+ # (if !new?) return false, returns nil unless raise_on_save_failure
210
+ # is true. Otherwise, returns self.
205
211
  def save!(*columns)
206
- return false if before_save == false
212
+ return save_failure(:save) if before_save == false
207
213
  if @new
208
- return false if before_create == false
214
+ return save_failure(:create) if before_create == false
209
215
  iid = model.dataset.insert(@values)
210
216
  # if we have a regular primary key and it's not set in @values,
211
217
  # we assume it's the last inserted id
@@ -219,7 +225,7 @@ module Sequel
219
225
  @new = false
220
226
  after_create
221
227
  else
222
- return false if before_update == false
228
+ return save_failure(:update) if before_update == false
223
229
  if columns.empty?
224
230
  this.update(@values)
225
231
  @changed_columns = []
@@ -234,9 +240,10 @@ module Sequel
234
240
  end
235
241
 
236
242
  # Saves only changed columns or does nothing if no columns are marked as
237
- # chanaged.
243
+ # chanaged. If no columns have been changed, returns nil. If unable to
244
+ # save, returns false unless raise_on_save_failure is true.
238
245
  def save_changes
239
- save(*@changed_columns) unless @changed_columns.empty?
246
+ save(*@changed_columns) || false unless @changed_columns.empty?
240
247
  end
241
248
 
242
249
  # Updates the instance with the supplied values with support for virtual
@@ -335,6 +342,128 @@ module Sequel
335
342
 
336
343
  private
337
344
 
345
+ # Backbone behind association_dataset
346
+ def _dataset(opts)
347
+ raise(Sequel::Error, 'model object does not have a primary key') if opts.dataset_need_primary_key? && !pk
348
+ ds = send(opts._dataset_method)
349
+ opts[:extend].each{|m| ds.extend(m)}
350
+ ds = ds.select(*opts.select) if opts.select
351
+ ds = ds.order(*opts[:order]) if opts[:order]
352
+ ds = ds.limit(*opts[:limit]) if opts[:limit]
353
+ ds = ds.eager(*opts[:eager]) if opts[:eager]
354
+ ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
355
+ ds = send(opts.dataset_helper_method, ds) if opts[:block]
356
+ ds
357
+ end
358
+
359
+ # Add the given associated object to the given association
360
+ def add_associated_object(opts, o)
361
+ raise(Sequel::Error, 'model object does not have a primary key') unless pk
362
+ raise(Sequel::Error, 'associated object does not have a primary key') if opts.need_associated_primary_key? && !o.pk
363
+ return if run_association_callbacks(opts, :before_add, o) == false
364
+ send(opts._add_method, o)
365
+ @associations[opts[:name]].push(o) if @associations.include?(opts[:name])
366
+ add_reciprocal_object(opts, o)
367
+ run_association_callbacks(opts, :after_add, o)
368
+ o
369
+ end
370
+
371
+ # Add/Set the current object to/as the given object's reciprocal association.
372
+ def add_reciprocal_object(opts, o)
373
+ return unless reciprocal = opts.reciprocal
374
+ case opts[:type]
375
+ when :many_to_many, :many_to_one
376
+ if array = o.associations[reciprocal] and !array.include?(self)
377
+ array.push(self)
378
+ end
379
+ when :one_to_many
380
+ o.associations[reciprocal] = self
381
+ end
382
+ end
383
+
384
+ # Load the associated objects using the dataset
385
+ def load_associated_objects(opts, reload=false)
386
+ name = opts[:name]
387
+ if @associations.include?(name) and !reload
388
+ @associations[name]
389
+ else
390
+ objs = if opts.single_associated_object?
391
+ if !opts[:key]
392
+ send(opts.dataset_method).all.first
393
+ elsif send(opts[:key])
394
+ send(opts.dataset_method).first
395
+ end
396
+ else
397
+ objs = send(opts.dataset_method).all
398
+ end
399
+ run_association_callbacks(opts, :after_load, objs)
400
+ # Only one_to_many associations should set the reciprocal object
401
+ objs.each{|o| add_reciprocal_object(opts, o)} if opts.set_reciprocal_to_self?
402
+ @associations[name] = objs
403
+ end
404
+ end
405
+
406
+ # Remove all associated objects from the given association
407
+ def remove_all_associated_objects(opts)
408
+ raise(Sequel::Error, 'model object does not have a primary key') unless pk
409
+ send(opts._remove_all_method)
410
+ ret = @associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if @associations.include?(opts[:name])
411
+ @associations[opts[:name]] = []
412
+ ret
413
+ end
414
+
415
+ # Remove the given associated object from the given association
416
+ def remove_associated_object(opts, o)
417
+ raise(Sequel::Error, 'model object does not have a primary key') unless pk
418
+ raise(Sequel::Error, 'associated object does not have a primary key') if opts.need_associated_primary_key? && !o.pk
419
+ return if run_association_callbacks(opts, :before_remove, o) == false
420
+ send(opts._remove_method, o)
421
+ @associations[opts[:name]].delete_if{|x| o === x} if @associations.include?(opts[:name])
422
+ remove_reciprocal_object(opts, o)
423
+ run_association_callbacks(opts, :after_remove, o)
424
+ o
425
+ end
426
+
427
+ # Remove/unset the current object from/as the given object's reciprocal association.
428
+ def remove_reciprocal_object(opts, o)
429
+ return unless reciprocal = opts.reciprocal
430
+ case opts[:type]
431
+ when :many_to_many, :many_to_one
432
+ if array = o.associations[reciprocal]
433
+ array.delete_if{|x| self === x}
434
+ end
435
+ when :one_to_many
436
+ o.associations[reciprocal] = nil
437
+ end
438
+ end
439
+
440
+ # Run the callback for the association with the object.
441
+ def run_association_callbacks(reflection, callback_type, object)
442
+ raise_error = @raise_on_save_failure
443
+ raise_error = true if reflection[:type] == :many_to_one
444
+ stop_on_false = true if [:before_add, :before_remove].include?(callback_type)
445
+ reflection[callback_type].each do |cb|
446
+ res = case cb
447
+ when Symbol
448
+ send(cb, object)
449
+ when Proc
450
+ cb.call(self, object)
451
+ else
452
+ raise Error, "callbacks should either be Procs or Symbols"
453
+ end
454
+ if res == false and stop_on_false
455
+ save_failure("modify association for", raise_error)
456
+ return false
457
+ end
458
+ end
459
+ end
460
+
461
+ # Raise an error if raise_on_save_failure is true
462
+ def save_failure(action, raise_error = nil)
463
+ raise_error = @raise_on_save_failure if raise_error.nil?
464
+ raise(Error, "unable to #{action} record") if raise_error
465
+ end
466
+
338
467
  # Set the columns, filtered by the only and except arrays.
339
468
  def set_restricted(hash, only, except)
340
469
  columns_not_set = model.instance_variable_get(:@columns).blank?
@@ -1,6 +1,6 @@
1
1
  module Sequel
2
2
  class Model
3
- # The Validation module houses a couple of subclasses used by Sequel's
3
+ # The Validation module houses a couple of classes used by Sequel's
4
4
  # validation code.
5
5
  module Validation
6
6
  # Validation::Errors represents validation errors, a simple hash subclass
@@ -85,11 +85,21 @@ module Sequel
85
85
  end
86
86
  validations.each do |att, procs|
87
87
  v = o.send(att)
88
- procs.each {|p| p[o, att, v]}
88
+ procs.each {|tag, p| p.call(o, att, v)}
89
89
  end
90
90
  end
91
91
 
92
- # Validates acceptance of an attribute.
92
+ # Validates acceptance of an attribute. Just checks that the value
93
+ # is equal to the :accept option.
94
+ #
95
+ # Possible Options:
96
+ # * :accept - The value required for the object to be valid (default: '1')
97
+ # * :allow_blank - Whether to skip the validation if the value is blank (default: false)
98
+ # * :allow_nil - Whether to skip the validation if the value is nil (default: true)
99
+ # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
100
+ # skipping this validation if it returns nil or false.
101
+ # * :message - The message to use (default: 'is not accepted')
102
+ # * :tag - The tag to use for this validation (default: :acceptance)
93
103
  def self.validates_acceptance_of(*atts)
94
104
  opts = {
95
105
  :message => 'is not accepted',
@@ -97,21 +107,35 @@ module Sequel
97
107
  :accept => '1'
98
108
  }.merge!(atts.extract_options!)
99
109
 
110
+ atts << {:if=>opts[:if], :tag=>opts[:tag]||:acceptance}
100
111
  validates_each(*atts) do |o, a, v|
101
- next unless o.instance_eval(&if_proc(opts))
102
112
  next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
103
113
  o.errors[a] << opts[:message] unless v == opts[:accept]
104
114
  end
105
115
  end
106
116
 
107
- # Validates confirmation of an attribute.
117
+ # Validates confirmation of an attribute. Checks that the object has
118
+ # a _confirmation value matching the current value. For example:
119
+ #
120
+ # validates_confirmation_of :blah
121
+ #
122
+ # Just makes sure that object.blah = object.blah_confirmation. Often used for passwords
123
+ # or email addresses on web forms.
124
+ #
125
+ # Possible Options:
126
+ # * :allow_false - Whether to skip the validation if the value is blank (default: false)
127
+ # * :allow_nil - Whether to skip the validation if the value is nil (default: false)
128
+ # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
129
+ # skipping this validation if it returns nil or false.
130
+ # * :message - The message to use (default: 'is not confirmed')
131
+ # * :tag - The tag to use for this validation (default: :confirmation)
108
132
  def self.validates_confirmation_of(*atts)
109
133
  opts = {
110
134
  :message => 'is not confirmed',
111
135
  }.merge!(atts.extract_options!)
112
-
136
+
137
+ atts << {:if=>opts[:if], :tag=>opts[:tag]||:confirmation}
113
138
  validates_each(*atts) do |o, a, v|
114
- next unless o.instance_eval(&if_proc(opts))
115
139
  next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
116
140
  c = o.send(:"#{a}_confirmation")
117
141
  o.errors[a] << opts[:message] unless v == c
@@ -125,11 +149,40 @@ module Sequel
125
149
  # validates_each :name, :password do |object, attribute, value|
126
150
  # object.errors[attribute] << 'is not nice' unless value.nice?
127
151
  # end
152
+ #
153
+ # Possible Options:
154
+ # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
155
+ # skipping this validation if it returns nil or false.
156
+ # * :tag - The tag to use for this validation (default: nil)
128
157
  def self.validates_each(*atts, &block)
129
- atts.each{|a| validations[a] << block}
158
+ opts = atts.extract_options!
159
+ blk = if opts[:if]
160
+ proc{|o,a,v| block.call(o,a,v) if o.instance_eval(&if_proc(opts))}
161
+ else
162
+ block
163
+ end
164
+ tag = opts[:tag]
165
+ atts.each do |a|
166
+ a_vals = validations[a]
167
+ if tag && (old = a_vals.find{|x| x[0] == tag})
168
+ old[1] = blk
169
+ else
170
+ a_vals << [tag, blk]
171
+ end
172
+ end
130
173
  end
131
174
 
132
- # Validates the format of an attribute.
175
+ # Validates the format of an attribute, checking the string representation of the
176
+ # value against the regular expression provided by the :with option.
177
+ #
178
+ # Possible Options:
179
+ # * :allow_blank - Whether to skip the validation if the value is blank (default: false)
180
+ # * :allow_nil - Whether to skip the validation if the value is nil (default: false)
181
+ # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
182
+ # skipping this validation if it returns nil or false.
183
+ # * :message - The message to use (default: 'is invalid')
184
+ # * :tag - The tag to use for this validation (default: :format)
185
+ # * :with - The regular expression to validate the value with (required).
133
186
  def self.validates_format_of(*atts)
134
187
  opts = {
135
188
  :message => 'is invalid',
@@ -139,14 +192,30 @@ module Sequel
139
192
  raise ArgumentError, "A regular expression must be supplied as the :with option of the options hash"
140
193
  end
141
194
 
195
+ atts << {:if=>opts[:if], :tag=>opts[:tag]||:format}
142
196
  validates_each(*atts) do |o, a, v|
143
- next unless o.instance_eval(&if_proc(opts))
144
197
  next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
145
198
  o.errors[a] << opts[:message] unless v.to_s =~ opts[:with]
146
199
  end
147
200
  end
148
201
 
149
202
  # Validates the length of an attribute.
203
+ #
204
+ # Possible Options:
205
+ # * :allow_blank - Whether to skip the validation if the value is blank (default: false)
206
+ # * :allow_nil - Whether to skip the validation if the value is nil (default: false)
207
+ # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
208
+ # skipping this validation if it returns nil or false.
209
+ # * :is - The exact size required for the value to be valid (no default)
210
+ # * :maximum - The maximum size allowed for the value (no default)
211
+ # * :message - The message to use (no default, overrides :too_long, :too_short, and :wrong_length
212
+ # options if present)
213
+ # * :minimum - The minimum size allowed for the value (no default)
214
+ # * :tag - The tag to use for this validation (default: :length)
215
+ # * :too_long - The message to use use if it the value is too long (default: 'is too long')
216
+ # * :too_short - The message to use use if it the value is too short (default: 'is too short')
217
+ # * :with - The array/range that must include the size of the value for it to be valid (no default)
218
+ # * :wrong_length - The message to use use if it the value is not valid (default: 'is the wrong length')
150
219
  def self.validates_length_of(*atts)
151
220
  opts = {
152
221
  :too_long => 'is too long',
@@ -154,8 +223,8 @@ module Sequel
154
223
  :wrong_length => 'is the wrong length'
155
224
  }.merge!(atts.extract_options!)
156
225
 
226
+ atts << {:if=>opts[:if], :tag=>opts[:tag]||:length}
157
227
  validates_each(*atts) do |o, a, v|
158
- next unless o.instance_eval(&if_proc(opts))
159
228
  next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
160
229
  if m = opts[:maximum]
161
230
  o.errors[a] << (opts[:message] || opts[:too_long]) unless v && v.size <= m
@@ -173,13 +242,22 @@ module Sequel
173
242
  end
174
243
 
175
244
  # Validates whether an attribute is a number.
245
+ #
246
+ # Possible Options:
247
+ # * :allow_blank - Whether to skip the validation if the value is blank (default: false)
248
+ # * :allow_nil - Whether to skip the validation if the value is nil (default: false)
249
+ # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
250
+ # skipping this validation if it returns nil or false.
251
+ # * :message - The message to use (default: 'is not a number')
252
+ # * :tag - The tag to use for this validation (default: :numericality)
253
+ # * :only_integer - Whether only integers are valid values (default: false)
176
254
  def self.validates_numericality_of(*atts)
177
255
  opts = {
178
256
  :message => 'is not a number',
179
257
  }.merge!(atts.extract_options!)
180
258
 
259
+ atts << {:if=>opts[:if], :tag=>opts[:tag]||:numericality}
181
260
  validates_each(*atts) do |o, a, v|
182
- next unless o.instance_eval(&if_proc(opts))
183
261
  next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
184
262
  begin
185
263
  if opts[:only_integer]
@@ -193,28 +271,41 @@ module Sequel
193
271
  end
194
272
  end
195
273
 
196
- # Validates the presence of an attribute.
274
+ # Validates the presence of an attribute. Requires the value not be blank,
275
+ # with false considered present instead of absent.
276
+ #
277
+ # Possible Options:
278
+ # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
279
+ # skipping this validation if it returns nil or false.
280
+ # * :message - The message to use (default: 'is not present')
281
+ # * :tag - The tag to use for this validation (default: :presence)
197
282
  def self.validates_presence_of(*atts)
198
283
  opts = {
199
284
  :message => 'is not present',
200
285
  }.merge!(atts.extract_options!)
201
286
 
287
+ atts << {:if=>opts[:if], :tag=>opts[:tag]||:presence}
202
288
  validates_each(*atts) do |o, a, v|
203
- next unless o.instance_eval(&if_proc(opts))
204
- o.errors[a] << opts[:message] unless v && !v.blank?
289
+ o.errors[a] << opts[:message] if v.blank? && v != false
205
290
  end
206
291
  end
207
292
 
208
293
  # Validates only if the fields in the model (specified by atts) are
209
294
  # unique in the database. You should also add a unique index in the
210
295
  # database, as this suffers from a fairly obvious race condition.
296
+ #
297
+ # Possible Options:
298
+ # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
299
+ # skipping this validation if it returns nil or false.
300
+ # * :message - The message to use (default: 'is already taken')
301
+ # * :tag - The tag to use for this validation (default: :uniqueness)
211
302
  def self.validates_uniqueness_of(*atts)
212
303
  opts = {
213
304
  :message => 'is already taken',
214
305
  }.merge!(atts.extract_options!)
215
306
 
307
+ atts << {:if=>opts[:if], :tag=>opts[:tag]||:uniqueness}
216
308
  validates_each(*atts) do |o, a, v|
217
- next unless o.instance_eval(&if_proc(opts))
218
309
  next if v.blank?
219
310
  num_dups = o.class.filter(a => v).count
220
311
  allow = if num_dups == 0
@@ -244,7 +335,7 @@ module Sequel
244
335
 
245
336
  ### Private Class Methods ###
246
337
 
247
- def self.if_proc(opts)
338
+ def self.if_proc(opts) # :nodoc:
248
339
  case opts[:if]
249
340
  when Symbol then proc{send opts[:if]}
250
341
  when Proc then opts[:if]