aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel')
-rw-r--r--activemodel/CHANGELOG.md12
-rw-r--r--activemodel/README.rdoc8
-rw-r--r--activemodel/lib/active_model/callbacks.rb2
-rw-r--r--activemodel/lib/active_model/dirty.rb17
-rw-r--r--activemodel/lib/active_model/errors.rb53
-rw-r--r--activemodel/lib/active_model/locale/en.yml1
-rw-r--r--activemodel/lib/active_model/naming.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb33
-rw-r--r--activemodel/lib/active_model/validations/format.rb2
-rw-r--r--activemodel/lib/active_model/validations/length.rb10
-rw-r--r--activemodel/lib/active_model/validator.rb2
-rw-r--r--activemodel/test/cases/errors_test.rb64
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb4
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations_test.rb19
15 files changed, 178 insertions, 53 deletions
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index c303bf5684..80b42859e2 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,10 +1,20 @@
+* Deprecate `ActiveModel::Errors#add_on_empty` and `ActiveModel::Errors#add_on_blank`
+ with no replacement.
+
+ *Wojciech Wnętrzak*
+
+* Deprecate `ActiveModel::Errors#get`, `ActiveModel::Errors#set` and
+ `ActiveModel::Errors#[]=` methods that have inconsistent behaviour.
+
+ *Wojciech Wnętrzak*
+
* Allow symbol as values for `tokenize` of `LengthValidator`
*Kensuke Naito*
* Assigning an unknown attribute key to an `ActiveModel` instance during initialization
will now raise `ActiveModel::AttributeAssignment::UnknownAttributeError` instead of
- `NoMethodError`
+ `NoMethodError`.
Example:
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index f6beff14e1..4920666f27 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -49,7 +49,7 @@ behavior out of the box:
send("#{attr}=", nil)
end
end
-
+
person = Person.new
person.clear_name
person.clear_age
@@ -132,7 +132,7 @@ behavior out of the box:
"Name"
end
end
-
+
person = Person.new
person.name = nil
person.validate!
@@ -216,10 +216,10 @@ behavior out of the box:
{Learn more}[link:classes/ActiveModel/Validations.html]
* Custom validators
-
+
class HasNameValidator < ActiveModel::Validator
def validate(record)
- record.errors[:name] = "must exist" if record.name.blank?
+ record.errors.add(:name, "must exist") if record.name.blank?
end
end
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 6214802074..2cf39b68fb 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -49,7 +49,7 @@ module ActiveModel
# puts 'block successfully called.'
# end
#
- # You can choose not to have all three callbacks by passing a hash to the
+ # You can choose to have only specific callbacks by passing a hash to the
# +define_model_callbacks+ method.
#
# define_model_callbacks :create, only: [:after, :before]
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index ca5dac272f..c03e5fac79 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -118,7 +118,7 @@ module ActiveModel
attribute_method_affix prefix: 'restore_', suffix: '!'
end
- # Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
+ # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
#
# person.changed? # => false
# person.name = 'bob'
@@ -166,7 +166,7 @@ module ActiveModel
@changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
end
- # Handle <tt>*_changed?</tt> for +method_missing+.
+ # Handles <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr, options = {}) #:nodoc:
result = changes_include?(attr)
result &&= options[:to] == __send__(attr) if options.key?(:to)
@@ -174,7 +174,7 @@ module ActiveModel
result
end
- # Handle <tt>*_was</tt> for +method_missing+.
+ # Handles <tt>*_was</tt> for +method_missing+.
def attribute_was(attr) # :nodoc:
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
end
@@ -186,6 +186,7 @@ module ActiveModel
private
+ # Returns +true+ if attr_name is changed, +false+ otherwise.
def changes_include?(attr_name)
attributes_changed_by_setter.include?(attr_name)
end
@@ -197,18 +198,18 @@ module ActiveModel
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
- # Clear all dirty data: current changes and previous changes.
+ # Clears all dirty data: current changes and previous changes.
def clear_changes_information # :doc:
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
- # Handle <tt>*_change</tt> for +method_missing+.
+ # Handles <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
end
- # Handle <tt>*_will_change!</tt> for +method_missing+.
+ # Handles <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
return if attribute_changed?(attr)
@@ -221,7 +222,7 @@ module ActiveModel
set_attribute_was(attr, value)
end
- # Handle <tt>restore_*!</tt> for +method_missing+.
+ # Handles <tt>restore_*!</tt> for +method_missing+.
def restore_attribute!(attr)
if attribute_changed?(attr)
__send__("#{attr}=", changed_attributes[attr])
@@ -230,7 +231,7 @@ module ActiveModel
end
# This is necessary because `changed_attributes` might be overridden in
- # other implemntations (e.g. in `ActiveRecord`)
+ # other implementations (e.g. in `ActiveRecord`)
alias_method :attributes_changed_by_setter, :changed_attributes # :nodoc:
# Force an attribute to have a particular "before" value
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index a809c72ccd..8334747615 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -3,6 +3,7 @@
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/object/deep_dup'
+require 'active_support/deprecation'
module ActiveModel
# == Active \Model \Errors
@@ -72,7 +73,7 @@ module ActiveModel
# end
def initialize(base)
@base = base
- @messages = {}
+ @messages = Hash.new { |messages, attribute| messages[attribute] = [] }
@details = Hash.new { |details, attribute| details[attribute] = [] }
end
@@ -110,8 +111,14 @@ module ActiveModel
#
# person.errors.messages # => {:name=>["cannot be nil"]}
# person.errors.get(:name) # => ["cannot be nil"]
- # person.errors.get(:age) # => nil
+ # person.errors.get(:age) # => []
def get(key)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#get is deprecated and will be removed in Rails 5.1.
+
+ To achieve the same use model.errors[:#{key}].
+ MESSAGE
+
messages[key]
end
@@ -121,6 +128,12 @@ module ActiveModel
# person.errors.set(:name, ["can't be nil"])
# person.errors.get(:name) # => ["can't be nil"]
def set(key, value)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#set is deprecated and will be removed in Rails 5.1.
+
+ Use model.errors.add(:#{key}, #{value.inspect}) instead.
+ MESSAGE
+
messages[key] = value
end
@@ -128,7 +141,7 @@ module ActiveModel
#
# person.errors.get(:name) # => ["cannot be nil"]
# person.errors.delete(:name) # => ["cannot be nil"]
- # person.errors.get(:name) # => nil
+ # person.errors.get(:name) # => []
def delete(key)
messages.delete(key)
details.delete(key)
@@ -140,7 +153,7 @@ module ActiveModel
# person.errors[:name] # => ["cannot be nil"]
# person.errors['name'] # => ["cannot be nil"]
def [](attribute)
- get(attribute.to_sym) || set(attribute.to_sym, [])
+ messages[attribute.to_sym]
end
# Adds to the supplied attribute the supplied error message.
@@ -148,7 +161,13 @@ module ActiveModel
# person.errors[:name] = "must be set"
# person.errors[:name] # => ['must be set']
def []=(attribute, error)
- self[attribute] << error
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#[]= is deprecated and will be removed in Rails 5.1.
+
+ Use model.errors.add(:#{attribute}, #{error.inspect}) instead.
+ MESSAGE
+
+ messages[attribute.to_sym] << error
end
# Iterates through each error key, value pair in the error messages hash.
@@ -167,7 +186,7 @@ module ActiveModel
# end
def each
messages.each_key do |attribute|
- self[attribute].each { |error| yield attribute, error }
+ messages[attribute].each { |error| yield attribute, error }
end
end
@@ -317,8 +336,8 @@ module ActiveModel
raise exception, full_message(attribute, message)
end
- details[attribute.to_sym] << detail
- self[attribute] << message
+ details[attribute.to_sym] << detail
+ messages[attribute.to_sym] << message
end
# Will add an error message to each of the attributes in +attributes+
@@ -328,6 +347,14 @@ module ActiveModel
# person.errors.messages
# # => {:name=>["can't be empty"]}
def add_on_empty(attributes, options = {})
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#add_on_empty is deprecated and will be removed in Rails 5.1
+
+ To achieve the same use:
+
+ errors.add(attribute, :empty, options) if value.nil? || value.empty?
+ MESSAGE
+
Array(attributes).each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
is_empty = value.respond_to?(:empty?) ? value.empty? : false
@@ -342,6 +369,14 @@ module ActiveModel
# person.errors.messages
# # => {:name=>["can't be blank"]}
def add_on_blank(attributes, options = {})
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ ActiveModel::Errors#add_on_blank is deprecated and will be removed in Rails 5.1
+
+ To achieve the same use:
+
+ errors.add(attribute, :empty, options) if value.blank?
+ MESSAGE
+
Array(attributes).each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
add(attribute, :blank, options) if value.blank?
@@ -384,7 +419,7 @@ module ActiveModel
# person.errors.full_messages_for(:name)
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
def full_messages_for(attribute)
- (get(attribute) || []).map { |message| full_message(attribute, message) }
+ messages[attribute].map { |message| full_message(attribute, message) }
end
# Returns a full message for a given attribute.
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index b11c8f53b4..061e35dd1e 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -6,6 +6,7 @@ en:
# The values :model, :attribute and :value are always available for interpolation
# The value :count is available when applicable. Can be used for pluralization.
messages:
+ model_invalid: "Validation failed: %{errors}"
inclusion: "is not included in the list"
exclusion: "is reserved"
invalid: "is invalid"
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 2bc3eeaa19..22010b517c 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -130,7 +130,7 @@ module ActiveModel
#
# Equivalent to +to_s+.
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
- :to_str, to: :name
+ :to_str, :as_json, to: :name
# Returns a new ActiveModel::Name instance. By default, the +namespace+
# and +name+ option will take the namespace and name of the given class
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 6a2668b8f7..176d4c0607 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -371,6 +371,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
@@ -395,6 +404,30 @@ module ActiveModel
_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
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index 02478dd5b6..46a2e54fba 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -77,7 +77,7 @@ module ActiveModel
# with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
# end
#
- # Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the
+ # Note: use <tt>\A</tt> and <tt>\z</tt> to match the start and end of the
# string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
#
# Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index c63a9d74b3..23201b264a 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -112,10 +112,12 @@ module ActiveModel
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
- # * <tt>:tokenizer</tt> - Specifies a method, proc or string to how to split up the attribute string.
- # (e.g. <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> or <tt>tokenizer: :word_tokenizer</tt> to count words
- # as in above example). Defaults to <tt>->(value) { value.split(//) }</tt>
- # which counts individual characters.
+ # * <tt>:tokenizer</tt> - A method (as a symbol), proc or string to
+ # specify how to split up the attribute string. (e.g.
+ # <tt>tokenizer: :word_tokenizer</tt> to call the +word_tokenizer+ method
+ # or <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words as in
+ # above example). Defaults to <tt>->(value) { value.split(//) }</tt> which
+ # counts individual characters.
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+ and +:strict+.
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index b98585912e..1d2888a818 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -15,7 +15,7 @@ module ActiveModel
# class MyValidator < ActiveModel::Validator
# def validate(record)
# if some_complex_logic
- # record.errors[:base] = "This record is invalid"
+ # record.errors.add(:base, "This record is invalid")
# end
# end
#
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 17dbb817d0..da142ea2c0 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -29,28 +29,28 @@ class ErrorsTest < ActiveModel::TestCase
def test_delete
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'omg'
+ errors[:foo] << 'omg'
errors.delete(:foo)
assert_empty errors[:foo]
end
def test_include?
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'omg'
+ errors[:foo] << 'omg'
assert errors.include?(:foo), 'errors should include :foo'
end
def test_dup
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'bar'
+ errors[:foo] << 'bar'
errors_dup = errors.dup
- errors_dup[:bar] = 'omg'
+ errors_dup[:bar] << 'omg'
assert_not_same errors_dup.messages, errors.messages
end
def test_has_key?
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'omg'
+ errors[:foo] << 'omg'
assert_equal true, errors.has_key?(:foo), 'errors should have key :foo'
end
@@ -61,7 +61,7 @@ class ErrorsTest < ActiveModel::TestCase
def test_key?
errors = ActiveModel::Errors.new(self)
- errors[:foo] = 'omg'
+ errors[:foo] << 'omg'
assert_equal true, errors.key?(:foo), 'errors should have key :foo'
end
@@ -81,37 +81,41 @@ class ErrorsTest < ActiveModel::TestCase
test "get returns the errors for the provided key" do
errors = ActiveModel::Errors.new(self)
- errors[:foo] = "omg"
+ errors[:foo] << "omg"
- assert_equal ["omg"], errors.get(:foo)
+ assert_deprecated do
+ assert_equal ["omg"], errors.get(:foo)
+ end
end
test "sets the error with the provided key" do
errors = ActiveModel::Errors.new(self)
- errors.set(:foo, "omg")
+ assert_deprecated do
+ errors.set(:foo, "omg")
+ end
assert_equal({ foo: "omg" }, errors.messages)
end
test "error access is indifferent" do
errors = ActiveModel::Errors.new(self)
- errors[:foo] = "omg"
+ errors[:foo] << "omg"
assert_equal ["omg"], errors["foo"]
end
test "values returns an array of messages" do
errors = ActiveModel::Errors.new(self)
- errors.set(:foo, "omg")
- errors.set(:baz, "zomg")
+ errors.messages[:foo] = "omg"
+ errors.messages[:baz] = "zomg"
assert_equal ["omg", "zomg"], errors.values
end
test "keys returns the error keys" do
errors = ActiveModel::Errors.new(self)
- errors.set(:foo, "omg")
- errors.set(:baz, "zomg")
+ errors.messages[:foo] << "omg"
+ errors.messages[:baz] << "zomg"
assert_equal [:foo, :baz], errors.keys
end
@@ -133,7 +137,9 @@ class ErrorsTest < ActiveModel::TestCase
test "assign error" do
person = Person.new
- person.errors[:name] = 'should not be nil'
+ assert_deprecated do
+ person.errors[:name] = 'should not be nil'
+ end
assert_equal ["should not be nil"], person.errors[:name]
end
@@ -282,46 +288,60 @@ class ErrorsTest < ActiveModel::TestCase
test "add_on_empty generates message" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :empty, {})
- person.errors.add_on_empty :name
+ assert_deprecated do
+ person.errors.add_on_empty :name
+ end
end
test "add_on_empty generates message for multiple attributes" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :empty, {})
person.errors.expects(:generate_message).with(:age, :empty, {})
- person.errors.add_on_empty [:name, :age]
+ assert_deprecated do
+ person.errors.add_on_empty [:name, :age]
+ end
end
test "add_on_empty generates message with custom default message" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :empty, { message: 'custom' })
- person.errors.add_on_empty :name, message: 'custom'
+ assert_deprecated do
+ person.errors.add_on_empty :name, message: 'custom'
+ end
end
test "add_on_empty generates message with empty string value" do
person = Person.new
person.name = ''
person.errors.expects(:generate_message).with(:name, :empty, {})
- person.errors.add_on_empty :name
+ assert_deprecated do
+ person.errors.add_on_empty :name
+ end
end
test "add_on_blank generates message" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :blank, {})
- person.errors.add_on_blank :name
+ assert_deprecated do
+ person.errors.add_on_blank :name
+ end
end
test "add_on_blank generates message for multiple attributes" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :blank, {})
person.errors.expects(:generate_message).with(:age, :blank, {})
- person.errors.add_on_blank [:name, :age]
+ assert_deprecated do
+ person.errors.add_on_blank [:name, :age]
+ end
end
test "add_on_blank generates message with custom default message" do
person = Person.new
person.errors.expects(:generate_message).with(:name, :blank, { message: 'custom' })
- person.errors.add_on_blank :name, message: 'custom'
+ assert_deprecated do
+ person.errors.add_on_blank :name, message: 'custom'
+ end
end
test "details returns added error detail" do
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index e2eb91eeb0..d765a47636 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -195,4 +195,8 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_no_match %r{"awesome":}, json
assert_no_match %r{"preferences":}, json
end
+
+ test "Class.model_name should be json encodable" do
+ assert_match %r{"Contact"}, Contact.model_name.to_json
+ end
end
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
index 3eeb80a48b..da63df9152 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -62,7 +62,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
assert_equal 'custom message', @person.errors.generate_message(:title, :empty, message: 'custom message')
end
- # add_on_blank: generate_message(attr, :blank, message: custom_message)
+ # validates_presence_of: generate_message(attr, :blank, message: custom_message)
def test_generate_message_blank_with_default_message
assert_equal "can't be blank", @person.errors.generate_message(:title, :blank)
end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index dc125bc884..f0317ad219 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -351,6 +351,25 @@ class ValidationsTest < ActiveModel::TestCase
assert_not_empty topic.errors
end
+ def test_validate_with_bang
+ Topic.validates :title, presence: true
+
+ assert_raise(ActiveModel::ValidationError) do
+ Topic.new.validate!
+ end
+ end
+
+ def test_validate_with_bang_and_context
+ Topic.validates :title, presence: true, on: :context
+
+ assert_raise(ActiveModel::ValidationError) do
+ Topic.new.validate!(:context)
+ end
+
+ t = Topic.new(title: "Valid title")
+ assert t.validate!(:context)
+ end
+
def test_strict_validation_in_validates
Topic.validates :title, strict: true, presence: true
assert_raises ActiveModel::StrictValidationFailed do