sequel 2.1.0 → 2.2.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.
@@ -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]