aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model/validations.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/lib/active_model/validations.rb')
-rw-r--r--activemodel/lib/active_model/validations.rb81
1 files changed, 69 insertions, 12 deletions
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 31c2245265..f23c920d87 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -4,7 +4,7 @@ require 'active_support/core_ext/hash/except'
module ActiveModel
- # == Active \Model Validations
+ # == Active \Model \Validations
#
# Provides a full validation framework to your objects.
#
@@ -39,6 +39,7 @@ module ActiveModel
extend ActiveSupport::Concern
included do
+ extend ActiveModel::Naming
extend ActiveModel::Callbacks
extend ActiveModel::Translation
@@ -66,8 +67,11 @@ module ActiveModel
# end
#
# Options:
- # * <tt>:on</tt> - Specifies the context where this validation is active
- # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
+ # Runs in all validation contexts by default (nil). You can pass a symbol
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
+ # <tt>on: :custom_validation_context</tt> or
+ # <tt>on: [:create, :custom_validation_context]</tt>)
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
@@ -83,6 +87,8 @@ module ActiveModel
validates_with BlockValidator, _merge_attributes(attr_names), &block
end
+ VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc:
+
# Adds a validation method or block to the class. This is useful when
# overriding the +validate+ instance method becomes too unwieldy and
# you're looking for more descriptive declaration of your validations.
@@ -123,11 +129,15 @@ module ActiveModel
# end
# end
#
+ # Note that the return value of validation methods is not relevant.
+ # It's not possible to halt the validate callback chain.
+ #
# Options:
- # * <tt>:on</tt> - Specifies the context where this validation is active
- # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
- # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
+ # Runs in all validation contexts by default (nil). You can pass a symbol
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
+ # <tt>on: :custom_validation_context</tt> or
+ # <tt>on: [:create, :custom_validation_context]</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,
@@ -139,13 +149,23 @@ module ActiveModel
# value.
def validate(*args, &block)
options = args.extract_options!
+
+ if args.all? { |arg| arg.is_a?(Symbol) }
+ options.each_key do |k|
+ unless VALID_OPTIONS_FOR_VALIDATE.include?(k)
+ raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?")
+ end
+ end
+ end
+
if options.key?(:on)
options = options.dup
options[:if] = Array(options[:if])
- options[:if].unshift lambda { |o|
- o.validation_context == options[:on]
+ options[:if].unshift ->(o) {
+ !(Array(options[:on]) & Array(o.validation_context)).empty?
}
end
+
args << options
set_callback(:validate, *args, &block)
end
@@ -199,12 +219,12 @@ module ActiveModel
# # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
# # ]
#
- # If one runs Person.clear_validators! and then checks to see what
+ # If one runs <tt>Person.clear_validators!</tt> and then checks to see what
# validators this class has, you would obtain:
#
# Person.validators # => []
#
- # Also, the callback set by +validate :cannot_be_robot+ will be erased
+ # Also, the callback set by <tt>validate :cannot_be_robot</tt> will be erased
# so that:
#
# Person._validate_callbacks.empty? # => true
@@ -283,6 +303,8 @@ module ActiveModel
# Runs all the specified validations and returns +true+ if no errors were
# added otherwise +false+.
#
+ # Aliased as validate.
+ #
# class Person
# include ActiveModel::Validations
#
@@ -317,6 +339,8 @@ module ActiveModel
self.validation_context = current_context
end
+ alias_method :validate, :valid?
+
# Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
# added, +false+ otherwise.
#
@@ -350,6 +374,15 @@ module ActiveModel
!valid?(context)
end
+ # Runs all the validations within the specified context. Returns +true+ if
+ # no errors are found, raises +ValidationError+ otherwise.
+ #
+ # Validations with no <tt>:on</tt> option will run no matter the context. Validations with
+ # some <tt>:on</tt> option will only run in the specified context.
+ def validate!(context = nil)
+ valid?(context) || raise_validation_error
+ end
+
# Hook method defining how an attribute value should be retrieved. By default
# this is assumed to be an instance named after the attribute. Override this
# method in subclasses should you need to retrieve the value for a given
@@ -371,9 +404,33 @@ module ActiveModel
protected
def run_validations! #:nodoc:
- run_callbacks :validate
+ _run_validate_callbacks
errors.empty?
end
+
+ def raise_validation_error
+ raise(ValidationError.new(self))
+ end
+ end
+
+ # = Active Model ValidationError
+ #
+ # Raised by <tt>validate!</tt> when the model is invalid. Use the
+ # +model+ method to retrieve the record which did not validate.
+ #
+ # begin
+ # complex_operation_that_internally_calls_validate!
+ # rescue ActiveModel::ValidationError => invalid
+ # puts invalid.model.errors
+ # end
+ class ValidationError < StandardError
+ attr_reader :model
+
+ def initialize(model)
+ @model = model
+ errors = @model.errors.full_messages.join(", ")
+ super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid"))
+ end
end
end