aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model/validations/validates.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/lib/active_model/validations/validates.rb')
-rw-r--r--activemodel/lib/active_model/validations/validates.rb171
1 files changed, 171 insertions, 0 deletions
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
new file mode 100644
index 0000000000..ae8d377fdf
--- /dev/null
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -0,0 +1,171 @@
+require 'active_support/core_ext/hash/slice'
+
+module ActiveModel
+ module Validations
+ module ClassMethods
+ # This method is a shortcut to all default validators and any custom
+ # validator classes ending in 'Validator'. Note that Rails default
+ # validators can be overridden inside specific classes by creating
+ # custom validator classes in their place such as PresenceValidator.
+ #
+ # Examples of using the default rails validators:
+ #
+ # validates :terms, acceptance: true
+ # validates :password, confirmation: true
+ # validates :username, exclusion: { in: %w(admin superuser) }
+ # validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }
+ # validates :age, inclusion: { in: 0..9 }
+ # validates :first_name, length: { maximum: 30 }
+ # validates :age, numericality: true
+ # validates :username, presence: true
+ # validates :username, uniqueness: true
+ #
+ # The power of the +validates+ method comes when using custom validators
+ # and default validators in one call for a given attribute.
+ #
+ # class EmailValidator < ActiveModel::EachValidator
+ # def validate_each(record, attribute, value)
+ # record.errors.add attribute, (options[:message] || "is not an email") unless
+ # value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
+ # end
+ # end
+ #
+ # class Person
+ # include ActiveModel::Validations
+ # attr_accessor :name, :email
+ #
+ # validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
+ # validates :email, presence: true, email: true
+ # end
+ #
+ # Validator classes may also exist within the class being validated
+ # allowing custom modules of validators to be included as needed.
+ #
+ # class Film
+ # include ActiveModel::Validations
+ #
+ # class TitleValidator < ActiveModel::EachValidator
+ # def validate_each(record, attribute, value)
+ # record.errors.add attribute, "must start with 'the'" unless value =~ /\Athe/i
+ # end
+ # end
+ #
+ # validates :name, title: true
+ # end
+ #
+ # Additionally validator classes may be in another namespace and still
+ # used within any class.
+ #
+ # validates :name, :'film/title' => true
+ #
+ # The validators hash can also handle regular expressions, ranges, arrays
+ # and strings in shortcut form.
+ #
+ # validates :email, format: /@/
+ # validates :gender, inclusion: %w(male female)
+ # validates :password, length: 6..20
+ #
+ # When using shortcut form, ranges and arrays are passed to your
+ # validator's initializer as <tt>options[:in]</tt> while other types
+ # including regular expressions and strings are passed as <tt>options[:with]</tt>.
+ #
+ # There is also a list of options that could be used along with validators:
+ #
+ # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
+ # validation contexts by default (+nil+), other options are <tt>:create</tt>
+ # and <tt>:update</tt>.
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
+ # proc or string should return or evaluate to a +true+ or +false+ value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # method, proc or string should return or evaluate to a +true+ or
+ # +false+ value.
+ # * <tt>:allow_nil</tt> - Skip validation if the attribute is +nil+.
+ # * <tt>:allow_blank</tt> - Skip validation if the attribute is blank.
+ # * <tt>:strict</tt> - If the <tt>:strict</tt> option is set to true
+ # will raise ActiveModel::StrictValidationFailed instead of adding the error.
+ # <tt>:strict</tt> option can also be set to any other exception.
+ #
+ # Example:
+ #
+ # validates :password, presence: true, confirmation: true, if: :password_required?
+ # validates :token, uniqueness: true, strict: TokenGenerationException
+ #
+ #
+ # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+
+ # and +:message+ can be given to one specific validator, as a hash:
+ #
+ # validates :password, presence: { if: :password_required?, message: 'is forgotten.' }, confirmation: true
+ def validates(*attributes)
+ defaults = attributes.extract_options!.dup
+ validations = defaults.slice!(*_validates_default_keys)
+
+ raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
+ raise ArgumentError, "You need to supply at least one validation" if validations.empty?
+
+ defaults[:attributes] = attributes
+
+ validations.each do |key, options|
+ next unless options
+ key = "#{key.to_s.camelize}Validator"
+
+ begin
+ validator = key.include?('::') ? key.constantize : const_get(key)
+ rescue NameError
+ raise ArgumentError, "Unknown validator: '#{key}'"
+ end
+
+ validates_with(validator, defaults.merge(_parse_validates_options(options)))
+ end
+ end
+
+ # This method is used to define validations that cannot be corrected by end
+ # users and are considered exceptional. So each validator defined with bang
+ # or <tt>:strict</tt> option set to <tt>true</tt> will always raise
+ # <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error
+ # when validation fails. See <tt>validates</tt> for more information about
+ # the validation itself.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates! :name, presence: true
+ # end
+ #
+ # person = Person.new
+ # person.name = ''
+ # person.valid?
+ # # => ActiveModel::StrictValidationFailed: Name can't be blank
+ def validates!(*attributes)
+ options = attributes.extract_options!
+ options[:strict] = true
+ validates(*(attributes << options))
+ end
+
+ protected
+
+ # When creating custom validators, it might be useful to be able to specify
+ # additional default keys. This can be done by overwriting this method.
+ def _validates_default_keys # :nodoc:
+ [:if, :unless, :on, :allow_blank, :allow_nil , :strict]
+ end
+
+ def _parse_validates_options(options) # :nodoc:
+ case options
+ when TrueClass
+ {}
+ when Hash
+ options
+ when Range, Array
+ { in: options }
+ else
+ { with: options }
+ end
+ end
+ end
+ end
+end