lib/sequel/plugins/validation_helpers.rb in sequel-3.46.0 vs lib/sequel/plugins/validation_helpers.rb in sequel-3.47.0

- old
+ new

@@ -4,39 +4,40 @@ # class-level validations. The names and APIs are different, though. Example: # # Sequel::Model.plugin :validation_helpers # class Album < Sequel::Model # def validate + # super # validates_min_length 1, :num_tracks # end # end # - # The validates_unique validation has a unique API, but the other validations have - # the API explained here: + # The validates_unique and validates_schema_types methods have a unique API, but the other + # validations have the API explained here: # # Arguments: - # * atts - Single attribute symbol or an array of attribute symbols specifying the - # attribute(s) to validate. + # atts :: Single attribute symbol or an array of attribute symbols specifying the + # attribute(s) to validate. # Options: - # * :allow_blank - Whether to skip the validation if the value is blank. You should - # make sure all objects respond to blank if you use this option, which you can do by: + # :allow_blank :: Whether to skip the validation if the value is blank. You should + # make sure all objects respond to blank if you use this option, which you can do by: # Sequel.extension :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. - # * :message - The message to use. Can be a string which is used directly, or a - # proc which is called. If the validation method takes a argument before the array of attributes, - # that argument is passed as an argument to the proc. The exception is the - # validates_not_string method, which doesn't take an argument, but passes - # the schema type symbol as the argument to the proc. + # :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. + # :message :: The message to use. Can be a string which is used directly, or a + # proc which is called. If the validation method takes a argument before the array of attributes, + # that argument is passed as an argument to the proc. The exception is the + # validates_not_string method, which doesn't take an argument, but passes + # the schema type symbol as the argument to the proc. # # The default validation options for all models can be modified by # changing the values of the Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS hash. You # change change the default options on a per model basis # by overriding a private instance method default_validation_helpers_options. @@ -81,17 +82,18 @@ :includes=>{:message=>lambda{|set| "is not in range or set: #{set.inspect}"}}, :integer=>{:message=>lambda{"is not a number"}}, :length_range=>{:message=>lambda{|range| "is too short or too long"}}, :max_length=>{:message=>lambda{|max| "is longer than #{max} characters"}, :nil_message=>lambda{"is not present"}}, :min_length=>{:message=>lambda{|min| "is shorter than #{min} characters"}}, + :not_null=>{:message=>lambda{"is not present"}}, :not_string=>{:message=>lambda{|type| type ? "is not a valid #{type}" : "is a string"}}, :numeric=>{:message=>lambda{"is not a number"}}, - :type=>{:message=>lambda{|klass| "is not a #{klass}"}}, + :type=>{:message=>lambda{|klass| klass.is_a?(Array) ? "is not a valid #{klass.join(" or ").downcase}" : "is not a valid #{klass.to_s.downcase}"}}, :presence=>{:message=>lambda{"is not present"}}, :unique=>{:message=>lambda{'is already taken'}} } - + module InstanceMethods # Check that the attribute values are the given exact length. def validates_exact_length(exact, atts, opts={}) validatable_attributes_for_type(:exact_length, atts, opts){|a,v,m| validation_error_message(m, exact) unless v && v.length == exact} end @@ -134,10 +136,15 @@ # Check that the attribute values are not shorter than the given min length. def validates_min_length(min, atts, opts={}) validatable_attributes_for_type(:min_length, atts, opts){|a,v,m| validation_error_message(m, min) unless v && v.length >= min} end + # Check attribute value(s) are not NULL/nil. + def validates_not_null(atts, opts={}) + validatable_attributes_for_type(:not_null, atts, opts){|a,v,m| validation_error_message(m) if v.nil?} + end + # Check that the attribute value(s) is not a string. This is generally useful # in conjunction with raise_on_typecast_failure = false, where you are # passing in string values for non-string attributes (such as numbers and dates). # If typecasting fails (invalid number or date), the value of the attribute will # be a string in an invalid format, and if typecasting succeeds, the value will @@ -156,16 +163,32 @@ validation_error_message(m) end end end - # Check if value is an instance of a class + # Validates for all of the model columns (or just the given columns) + # that the column value is an instance of the expected class based on + # the column's schema type. + def validates_schema_types(atts=keys) + Array(atts).each do |k| + next unless type = schema_type_class(k) + validates_type(type, k) + end + end + + # Check if value is an instance of a class. If +klass+ is an array, + # the value must be an instance of one of the classes in the array. def validates_type(klass, atts, opts={}) klass = klass.to_s.constantize if klass.is_a?(String) || klass.is_a?(Symbol) - validatable_attributes_for_type(:type, atts, opts){|a,v,m| validation_error_message(m, klass) if !v.nil? && !v.is_a?(klass)} + validatable_attributes_for_type(:type, atts, opts) do |a,v,m| + next if v.nil? + if klass.is_a?(Array) ? !klass.any?{|kls| v.is_a?(kls)} : !v.is_a?(klass) + validation_error_message(m, klass) + end + end end - + # Check attribute value(s) is not considered blank by the database, but allow false values. def validates_presence(atts, opts={}) validatable_attributes_for_type(:presence, atts, opts){|a,v,m| validation_error_message(m) if model.db.send(:blank_object?, v) && v != false} end @@ -218,9 +241,10 @@ end message = validation_error_message(opts[:message]) where = opts[:where] atts.each do |a| arr = Array(a) + next if arr.any?{|x| errors.on(x)} next if opts[:only_if_modified] && !new? && !arr.any?{|x| changed_columns.include?(x)} ds = if where where.call(model.dataset, self, arr) else vals = arr.map{|x| send(x)}