Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add :allow_missing validation option, useful if the database provides…

… a good default

The RDoc has a good description for this option:

Whether to skip the validation if the attribute isn't a key in the
values hash.  This is different from allow_nil, because Sequel only
sends the attributes in the values when doing an insert or update.
If the attribute is not present, Sequel doesn't specify it, so the
database will use the table's default value.  This is different from
having an attribute in values with a value of nil, which Sequel will
send as NULL. If your database table has a non NULL default, this may
be a good option to use.  You don't want to use allow_nil, because if
the attribute is in values but has a value nil, Sequel will attempt
to insert a NULL value into the database, instead of using the
database's default.

This option is available for all of the standard validations,
and can be used by validates_each as well, so it works with custom
validations.

This commit also changes the :allow_nil and :allow_blank validation
options so they are applied at the validates_each level generically,
instead of having code for them in each of the validation methods.

I removed the duplicative RDoc for these options (and the :tag
option) from all of the validation methods, and added a class level
RDoc notice that all validation methods also accept the options that
validates_each accepts.
  • Loading branch information...
commit 1c46322e4400238ecb17948eea7449c2819f212d 1 parent 5f80e6d
@jeremyevans authored
View
2  CHANGELOG
@@ -1,5 +1,7 @@
=== HEAD
+* Add :allow_missing validation option, useful if the database provides a good default (jeremyevans)
+
* Fix literalization of SQL::Blobs in DataObjects and JDBC adapter's postgresql subadapters when ruby 1.9 is used (jeremyevans)
* When using standard strings in the postgres adapter with the postgres-pr driver, use custom string escaping to prevent errors (jeremyevans)
View
2  lib/sequel_model.rb
@@ -50,6 +50,8 @@ def self.Model(source)
# cache_store, cache_ttl, dataset_methods, primary_key, restricted_columns,
# sti_dataset, and sti_key. You should not usually need to
# access these directly.
+ # * All validation methods also accept the options specified in #validates_each,
+ # in addition to the options specified in the RDoc for that method.
# * The following class level attr_accessors are created: raise_on_typecast_failure,
# raise_on_save_failure, strict_param_setting, typecast_empty_string_to_nil,
# and typecast_on_assignment:
View
99 lib/sequel_model/validations.rb
@@ -104,26 +104,21 @@ def self.validate(o)
end
# Validates acceptance of an attribute. Just checks that the value
- # is equal to the :accept option.
+ # is equal to the :accept option. This method is unique in that
+ # :allow_nil is assumed to be true instead of false.
#
# Possible Options:
# * :accept - The value required for the object to be valid (default: '1')
- # * :allow_blank - Whether to skip the validation if the value is blank (default: false)
- # * :allow_nil - Whether to skip the validation if the value is nil (default: true)
- # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
- # skipping this validation if it returns nil or false.
# * :message - The message to use (default: 'is not accepted')
- # * :tag - The tag to use for this validation (default: :acceptance)
def self.validates_acceptance_of(*atts)
opts = {
:message => 'is not accepted',
:allow_nil => true,
- :accept => '1'
+ :accept => '1',
+ :tag => :acceptance,
}.merge!(atts.extract_options!)
-
- atts << {:if=>opts[:if], :tag=>opts[:tag]||:acceptance}
+ atts << opts
validates_each(*atts) do |o, a, v|
- next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
o.errors[a] << opts[:message] unless v == opts[:accept]
end
end
@@ -137,22 +132,15 @@ def self.validates_acceptance_of(*atts)
# or email addresses on web forms.
#
# Possible Options:
- # * :allow_false - Whether to skip the validation if the value is blank (default: false)
- # * :allow_nil - Whether to skip the validation if the value is nil (default: false)
- # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
- # skipping this validation if it returns nil or false.
# * :message - The message to use (default: 'is not confirmed')
- # * :tag - The tag to use for this validation (default: :confirmation)
def self.validates_confirmation_of(*atts)
opts = {
:message => 'is not confirmed',
+ :tag => :confirmation,
}.merge!(atts.extract_options!)
-
- atts << {:if=>opts[:if], :tag=>opts[:tag]||:confirmation}
+ atts << opts
validates_each(*atts) do |o, a, v|
- next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
- c = o.send(:"#{a}_confirmation")
- o.errors[a] << opts[:message] unless v == c
+ o.errors[a] << opts[:message] unless v == o.send(:"#{a}_confirmation")
end
end
@@ -165,13 +153,30 @@ def self.validates_confirmation_of(*atts)
# end
#
# Possible Options:
+ # * :allow_blank - Whether to skip the validation if the value is blank.
+ # * :allow_missing - Whether to skip the validation if the attribute isn't a key in the
+ # values hash. This is different from allow_nil, because Sequel only sends the attributes
+ # in the values when doing an insert or update. If the attribute is not present, Sequel
+ # doesn't specify it, so the database will use the table's default value. This is different
+ # from having an attribute in values with a value of nil, which Sequel will send as NULL.
+ # If your database table has a non NULL default, this may be a good option to use. You
+ # don't want to use allow_nil, because if the attribute is in values but has a value nil,
+ # Sequel will attempt to insert a NULL value into the database, instead of using the
+ # database's default.
+ # * :allow_nil - Whether to skip the validation if the value is nil.
# * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
# skipping this validation if it returns nil or false.
- # * :tag - The tag to use for this validation (default: nil)
+ # * :tag - The tag to use for this validation.
def self.validates_each(*atts, &block)
opts = atts.extract_options!
- blk = if opts[:if]
- proc{|o,a,v| block.call(o,a,v) if o.instance_eval(&if_proc(opts))}
+ blk = if (i = opts[:if]) || (am = opts[:allow_missing]) || (an = opts[:allow_nil]) || (ab = opts[:allow_blank])
+ proc do |o,a,v|
+ next if i && !o.instance_eval(&if_proc(opts))
+ next if an && Array(v).all?{|x| x.nil?}
+ next if ab && Array(v).all?{|x| x.blank?}
+ next if am && Array(a).all?{|x| !o.values.has_key?(x)}
+ block.call(o,a,v)
+ end
else
block
end
@@ -190,25 +195,20 @@ def self.validates_each(*atts, &block)
# value against the regular expression provided by the :with option.
#
# Possible Options:
- # * :allow_blank - Whether to skip the validation if the value is blank (default: false)
- # * :allow_nil - Whether to skip the validation if the value is nil (default: false)
- # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
- # skipping this validation if it returns nil or false.
# * :message - The message to use (default: 'is invalid')
- # * :tag - The tag to use for this validation (default: :format)
# * :with - The regular expression to validate the value with (required).
def self.validates_format_of(*atts)
opts = {
:message => 'is invalid',
+ :tag => :format,
}.merge!(atts.extract_options!)
unless opts[:with].is_a?(Regexp)
raise ArgumentError, "A regular expression must be supplied as the :with option of the options hash"
end
- atts << {:if=>opts[:if], :tag=>opts[:tag]||:format}
+ atts << opts
validates_each(*atts) do |o, a, v|
- next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
o.errors[a] << opts[:message] unless v.to_s =~ opts[:with]
end
end
@@ -216,16 +216,11 @@ def self.validates_format_of(*atts)
# Validates the length of an attribute.
#
# Possible Options:
- # * :allow_blank - Whether to skip the validation if the value is blank (default: false)
- # * :allow_nil - Whether to skip the validation if the value is nil (default: false)
- # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
- # skipping this validation if it returns nil or false.
# * :is - The exact size required for the value to be valid (no default)
# * :maximum - The maximum size allowed for the value (no default)
# * :message - The message to use (no default, overrides :too_long, :too_short, and :wrong_length
# options if present)
# * :minimum - The minimum size allowed for the value (no default)
- # * :tag - The tag to use for this validation (default: :length)
# * :too_long - The message to use use if it the value is too long (default: 'is too long')
# * :too_short - The message to use use if it the value is too short (default: 'is too short')
# * :with - The array/range that must include the size of the value for it to be valid (no default)
@@ -237,14 +232,9 @@ def self.validates_length_of(*atts)
:wrong_length => 'is the wrong length'
}.merge!(atts.extract_options!)
- tag = if opts[:tag]
- opts[:tag]
- else
- ([:length] + [:maximum, :minimum, :is, :within].reject{|x| !opts.include?(x)}).join('-').to_sym
- end
- atts << {:if=>opts[:if], :tag=>tag}
+ opts[:tag] ||= ([:length] + [:maximum, :minimum, :is, :within].reject{|x| !opts.include?(x)}).join('-').to_sym
+ atts << opts
validates_each(*atts) do |o, a, v|
- next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
if m = opts[:maximum]
o.errors[a] << (opts[:message] || opts[:too_long]) unless v && v.size <= m
end
@@ -263,21 +253,15 @@ def self.validates_length_of(*atts)
# Validates whether an attribute is a number.
#
# Possible Options:
- # * :allow_blank - Whether to skip the validation if the value is blank (default: false)
- # * :allow_nil - Whether to skip the validation if the value is nil (default: false)
- # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
- # skipping this validation if it returns nil or false.
# * :message - The message to use (default: 'is not a number')
- # * :tag - The tag to use for this validation (default: :numericality)
# * :only_integer - Whether only integers are valid values (default: false)
def self.validates_numericality_of(*atts)
opts = {
:message => 'is not a number',
+ :tag => :numericality,
}.merge!(atts.extract_options!)
-
- atts << {:if=>opts[:if], :tag=>opts[:tag]||:numericality}
+ atts << opts
validates_each(*atts) do |o, a, v|
- next if (v.nil? && opts[:allow_nil]) || (v.blank? && opts[:allow_blank])
begin
if opts[:only_integer]
Kernel.Integer(v.to_s)
@@ -294,16 +278,13 @@ def self.validates_numericality_of(*atts)
# with false considered present instead of absent.
#
# Possible Options:
- # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
- # skipping this validation if it returns nil or false.
# * :message - The message to use (default: 'is not present')
- # * :tag - The tag to use for this validation (default: :presence)
def self.validates_presence_of(*atts)
opts = {
:message => 'is not present',
+ :tag => :presence,
}.merge!(atts.extract_options!)
-
- atts << {:if=>opts[:if], :tag=>opts[:tag]||:presence}
+ atts << opts
validates_each(*atts) do |o, a, v|
o.errors[a] << opts[:message] if v.blank? && v != false
end
@@ -318,22 +299,18 @@ def self.validates_presence_of(*atts)
# database, as this suffers from a fairly obvious race condition.
#
# Possible Options:
- # * :allow_nil - Whether to skip the validation if the value(s) is/are nil (default: false)
- # * :if - A symbol (indicating an instance_method) or proc (which is instance_evaled)
- # skipping this validation if it returns nil or false.
# * :message - The message to use (default: 'is already taken')
- # * :tag - The tag to use for this validation (default: :uniqueness)
def self.validates_uniqueness_of(*atts)
opts = {
:message => 'is already taken',
+ :tag => :uniqueness,
}.merge!(atts.extract_options!)
- atts << {:if=>opts[:if], :tag=>opts[:tag]||:uniqueness}
+ atts << opts
validates_each(*atts) do |o, a, v|
error_field = a
a = Array(a)
v = Array(v)
- next unless v.any? or opts[:allow_nil] == false
ds = o.class.filter(a.zip(v))
num_dups = ds.count
allow = if num_dups == 0
View
90 spec/sequel_model/validations_spec.rb
@@ -120,6 +120,39 @@ def o.yy
atts.should == [:xx, :yy]
end
+ specify "should respect allow_missing option when using multiple attributes" do
+ o = @c.new
+ def o.xx
+ self[:xx]
+ end
+ def o.yy
+ self[:yy]
+ end
+ vals = nil
+ atts = nil
+ @c.validates_each([:xx, :yy], :allow_missing=>true){|obj,a,v| atts=a; vals=v}
+
+ o.values[:xx] = 1
+ o.valid?
+ vals.should == [1,nil]
+ atts.should == [:xx, :yy]
+
+ vals = nil
+ atts = nil
+ o.values.clear
+ o.values[:yy] = 2
+ o.valid?
+ vals.should == [nil, 2]
+ atts.should == [:xx, :yy]
+
+ vals = nil
+ atts = nil
+ o.values.clear
+ o.valid?.should == true
+ vals.should == nil
+ atts.should == nil
+ end
+
specify "should overwrite existing validation with the same tag and attribute" do
@c.validates_each(:xx, :xx, :tag=>:low) {|o, a, v| o.xxx; o.errors[a] << 'too low' if v < 50}
@c.validates_each(:yy, :yy) {|o, a, v| o.yyy; o.errors[a] << 'too low' if v < 50}
@@ -243,6 +276,18 @@ def dont_skip; true; end
@m.should_not be_valid
end
+ specify "should validate acceptance_of with allow_missing => true" do
+ @c.validates_acceptance_of :value, :allow_missing => true
+ @m.should be_valid
+ end
+
+ specify "should validate acceptance_of with allow_missing => true and allow_nil => false" do
+ @c.validates_acceptance_of :value, :allow_missing => true, :allow_nil => false
+ @m.should be_valid
+ @m.value = nil
+ @m.should_not be_valid
+ end
+
specify "should validate acceptance_of with if => true" do
@c.validates_acceptance_of :value, :if => :dont_skip
@m.value = '0'
@@ -300,6 +345,16 @@ def dont_skip; true; end
@m.should be_valid
end
+ specify "should validate confirmation_of with allow_missing => true" do
+ @c.send(:attr_accessor, :value_confirmation)
+ @c.validates_acceptance_of :value, :allow_missing => true
+ @m.should be_valid
+ @m.value_confirmation = 'blah'
+ @m.should be_valid
+ @m.value = nil
+ @m.should_not be_valid
+ end
+
specify "should validate format_of" do
@c.validates_format_of :value, :with => /.+_.+/
@m.value = 'abc_'
@@ -327,6 +382,13 @@ def dont_skip; true; end
@m.should be_valid
end
+ specify "should validate format_of with allow_missing => true" do
+ @c.validates_format_of :value, :allow_missing => true, :with=>/./
+ @m.should be_valid
+ @m.value = nil
+ @m.should_not be_valid
+ end
+
specify "should validate length_of with maximum" do
@c.validates_length_of :value, :maximum => 5
@m.should_not be_valid
@@ -386,6 +448,13 @@ def dont_skip; true; end
@m.should be_valid
end
+ specify "should validate length_of with allow_missing => true" do
+ @c.validates_length_of :value, :allow_missing => true, :minimum => 5
+ @m.should be_valid
+ @m.value = nil
+ @m.should_not be_valid
+ end
+
specify "should allow multiple calls to validates_length_of with different options without overwriting" do
@c.validates_length_of :value, :maximum => 5
@c.validates_length_of :value, :minimum => 5
@@ -450,6 +519,13 @@ def dont_skip; true; end
@m.should be_valid
end
+ specify "should validate numericality_of with allow_missing => true" do
+ @c.validates_numericality_of :value, :allow_missing => true
+ @m.should be_valid
+ @m.value = nil
+ @m.should_not be_valid
+ end
+
specify "should validate presence_of" do
@c.validates_presence_of :value
@m.should_not be_valid
@@ -475,6 +551,13 @@ def dont_skip; true; end
@m.should be_valid
end
+ specify "should validate presence_of with allow_missing => true" do
+ @c.validates_presence_of :value, :allow_missing => true
+ @m.should be_valid
+ @m.value = nil
+ @m.should_not be_valid
+ end
+
specify "should validate uniqueness_of with if => true" do
@c.validates_uniqueness_of :value, :if => :dont_skip
@@ -489,10 +572,11 @@ def dont_skip; true; end
@m.should be_valid
end
- specify "should validate with :if => block" do
- @c.validates_presence_of :value, :if => proc {false}
-
+ specify "should validate uniqueness_of with allow_missing => true" do
+ @c.validates_uniqueness_of :value, :allow_missing => true
@m.should be_valid
+ @m.value = nil
+ @m.should_not be_valid
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.