Improve typography of user facing validation messages

With the universal adoption of UTF-8 in browsers, user facing text can
use more optimal Unicode typography. In digital and print design, using
RIGHT SINGLE QUOTATION MARK (U+2019) is normally preferred over
APOSTROPHE (U+0027) in contractions.

For details, see the Unicode Standard Section 6.2:
https://www.unicode.org/versions/Unicode13.0.0/ch06.pdf

> Punctuation Apostrophe. U+2019 right single quotation mark is
> preferred where the character is to represent a punctuation mark, as
> for contractions: “We’ve been here before.” In this latter case,
> U+2019 is also referred to as a punctuation apostrophe.
This commit is contained in:
Jon Dufresne 2022-06-26 06:11:27 -07:00
parent 93b1d3998c
commit da82e587f2
31 changed files with 96 additions and 90 deletions

@ -1347,7 +1347,7 @@ def test_partial_with_form_builder_and_invalid_model_custom_rendering_field_erro
get :partial_with_form_builder_and_invalid_model
assert_equal <<~HTML.strip, @response.body.strip
<div class="field_with_errors"><label for="post_title">Title</label> <span class="error">can&#39;t be blank</span></div>
<div class="field_with_errors"><label for="post_title">Title</label> <span class="error">cant be blank</span></div>
HTML
ensure
ActionView::Base.field_error_proc = old_proc if old_proc

@ -20,7 +20,7 @@ def setup
super
@post = Post.new
@post.errors.add(:author_name, "can't be empty")
@post.errors.add(:author_name, "cant be empty")
@post.errors.add(:body, "foo")
@post.errors.add(:category, "must exist")
@post.errors.add(:published, "must be accepted")
@ -163,7 +163,7 @@ def test_field_error_proc
end
assert_dom_equal(
%(<div class="field_with_errors"><input id="post_author_name" name="post[author_name]" type="text" value="" /> <span class="error">can't be empty</span></div>),
%(<div class="field_with_errors"><input id="post_author_name" name="post[author_name]" type="text" value="" /> <span class="error">cant be empty</span></div>),
text_field("post", "author_name")
)
ensure

@ -276,10 +276,10 @@ def form_with(*, **)
@comment = Comment.new
def @post.errors
Class.new {
def [](field); field == "author_name" ? ["can't be empty"] : [] end
def [](field); field == "author_name" ? ["cant be empty"] : [] end
def empty?() false end
def count() 1 end
def full_messages() ["Author name can't be empty"] end
def full_messages() ["Author name cant be empty"] end
}.new
end
def @post.to_key; [123]; end

@ -110,10 +110,10 @@ def form_for(*)
@comment = Comment.new
def @post.errors
Class.new {
def [](field); field == "author_name" ? ["can't be empty"] : [] end
def [](field); field == "author_name" ? ["cant be empty"] : [] end
def empty?() false end
def count() 1 end
def full_messages() ["Author name can't be empty"] end
def full_messages() ["Author name cant be empty"] end
}.new
end
def @post.to_key; [123]; end

@ -1,3 +1,9 @@
* Improve typography of user facing error messages. In English contractions,
the Unicode APOSTROPHE (U+0027) is now RIGHT SINGLE QUOTATION MARK
(U+2019). For example, “can't be blank” is now “cant be blank”.
*Jon Dufresne*
* Raise `NoMethodError` in `ActiveModel::Type::Value#as_json` to avoid unpredictable
results.

@ -285,7 +285,7 @@ def group_by_attribute
#
# person.errors.add(:name, :blank)
# person.errors.messages
# # => {:name=>["can't be blank"]}
# # => {:name=>["cant be blank"]}
#
# person.errors.add(:name, :too_long, count: 25)
# person.errors.messages
@ -333,7 +333,7 @@ def add(attribute, type = :invalid, **options)
#
# person.errors.add :name, :blank
# person.errors.added? :name, :blank # => true
# person.errors.added? :name, "can't be blank" # => true
# person.errors.added? :name, "cant be blank" # => true
#
# If the error requires options, then it returns +true+ with
# the correct options, or +false+ with incorrect or missing options.
@ -386,7 +386,7 @@ def of_kind?(attribute, type = :invalid)
#
# person = Person.create(address: '123 First St.')
# person.errors.full_messages
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
# # => ["Name is too short (minimum is 5 characters)", "Name cant be blank", "Email cant be blank"]
def full_messages
@errors.map(&:full_message)
end
@ -401,7 +401,7 @@ def full_messages
#
# person = Person.create()
# person.errors.full_messages_for(:name)
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
# # => ["Name is too short (minimum is 5 characters)", "Name cant be blank"]
def full_messages_for(attribute)
where(attribute).map(&:full_message).freeze
end
@ -415,7 +415,7 @@ def full_messages_for(attribute)
#
# person = Person.create()
# person.errors.messages_for(:name)
# # => ["is too short (minimum is 5 characters)", "can't be blank"]
# # => ["is too short (minimum is 5 characters)", "cant be blank"]
def messages_for(attribute)
where(attribute).map(&:message)
end
@ -486,7 +486,7 @@ def normalize_arguments(attribute, type, **options)
# person = Person.new
# person.name = nil
# person.valid?
# # => ActiveModel::StrictValidationFailed: Name can't be blank
# # => ActiveModel::StrictValidationFailed: Name cant be blank
class StrictValidationFailed < StandardError
end

@ -10,10 +10,10 @@ en:
inclusion: "is not included in the list"
exclusion: "is reserved"
invalid: "is invalid"
confirmation: "doesn't match %{attribute}"
confirmation: "doesnt match %{attribute}"
accepted: "must be accepted"
empty: "can't be empty"
blank: "can't be blank"
empty: "cant be empty"
blank: "cant be blank"
present: "must be blank"
too_long:
one: "is too long (maximum is 1 character)"

@ -57,7 +57,7 @@ module ClassMethods
#
# user.save # => false, password required
# user.password = "vr00m"
# user.save # => false, confirmation doesn't match
# user.save # => false, confirmation doesnt match
# user.password_confirmation = "vr00m"
# user.save # => true
#

@ -326,7 +326,7 @@ def initialize_dup(other) # :nodoc:
#
# person = Person.new
# person.valid? # => false
# person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
# person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["cant be blank"]}>
def errors
@errors ||= Errors.new(self)
end

@ -64,7 +64,7 @@ module HelperMethods
# validates_presence_of :password_confirmation, if: :password_changed?
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
# * <tt>:message</tt> - A custom error message (default is: "doesnt match
# <tt>%{translated_attribute_name}</tt>").
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
# non-text columns (+true+ by default).

@ -26,7 +26,7 @@ module HelperMethods
# <tt>false.blank? # => true</tt>.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
# * <tt>:message</tt> - A custom error message (default is: "cant be blank").
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.

@ -144,7 +144,7 @@ def validates(*attributes)
# person = Person.new
# person.name = ''
# person.valid?
# # => ActiveModel::StrictValidationFailed: Name can't be blank
# # => ActiveModel::StrictValidationFailed: Name cant be blank
def validates!(*attributes)
options = attributes.extract_options!
options[:strict] = true

@ -89,7 +89,7 @@ def test_initialize
test "message with type as a symbol" do
error = ActiveModel::Error.new(Person.new, :name, :blank)
assert_equal "can't be blank", error.message
assert_equal "cant be blank", error.message
end
test "message with custom interpolation" do
@ -178,15 +178,15 @@ def test_initialize
test "full_message returns the given message with the attribute name included" do
error = ActiveModel::Error.new(Person.new, :name, :blank)
assert_equal "name can't be blank", error.full_message
assert_equal "name cant be blank", error.full_message
end
test "full_message uses default format" do
error = ActiveModel::Error.new(Person.new, :name, message: "can't be blank")
error = ActiveModel::Error.new(Person.new, :name, message: "cant be blank")
# Use a locale without errors.format
I18n.with_locale(:unknown) {
assert_equal "name can't be blank", error.full_message
assert_equal "name cant be blank", error.full_message
}
end

@ -179,7 +179,7 @@ def test_no_key
person.errors.add(:name, :blank)
assert_equal :blank, person.errors.objects.first.type
assert_equal ["can't be blank"], person.errors[:name]
assert_equal ["cant be blank"], person.errors[:name]
end
test "add, with type as String" do
@ -216,7 +216,7 @@ def test_no_key
person.errors.add(:name, type)
assert_equal :blank, person.errors.objects.first.type
assert_equal ["can't be blank"], person.errors[:name]
assert_equal ["cant be blank"], person.errors[:name]
end
test "add an error message on a specific attribute with a defined type" do

@ -52,14 +52,14 @@ class SecurePasswordTest < ActiveModel::TestCase
@user.password = ""
assert_not @user.valid?(:create), "user should be invalid"
assert_equal 1, @user.errors.count
assert_equal ["can't be blank"], @user.errors[:password]
assert_equal ["cant be blank"], @user.errors[:password]
end
test "create a new user with validation and a nil password" do
@user.password = nil
assert_not @user.valid?(:create), "user should be invalid"
assert_equal 1, @user.errors.count
assert_equal ["can't be blank"], @user.errors[:password]
assert_equal ["cant be blank"], @user.errors[:password]
end
test "create a new user with validation and password length greater than 72" do
@ -75,7 +75,7 @@ class SecurePasswordTest < ActiveModel::TestCase
@user.password_confirmation = ""
assert_not @user.valid?(:create), "user should be invalid"
assert_equal 1, @user.errors.count
assert_equal ["doesn't match Password"], @user.errors[:password_confirmation]
assert_equal ["doesnt match Password"], @user.errors[:password_confirmation]
end
test "create a new user with validation and a nil password confirmation" do
@ -89,7 +89,7 @@ class SecurePasswordTest < ActiveModel::TestCase
@user.password_confirmation = "something else"
assert_not @user.valid?(:create), "user should be invalid"
assert_equal 1, @user.errors.count
assert_equal ["doesn't match Password"], @user.errors[:password_confirmation]
assert_equal ["doesnt match Password"], @user.errors[:password_confirmation]
end
test "resetting password to nil clears the password cache" do
@ -134,7 +134,7 @@ class SecurePasswordTest < ActiveModel::TestCase
@existing_user.password = nil
assert_not @existing_user.valid?(:update), "user should be invalid"
assert_equal 1, @existing_user.errors.count
assert_equal ["can't be blank"], @existing_user.errors[:password]
assert_equal ["cant be blank"], @existing_user.errors[:password]
end
test "updating an existing user with validation and password length greater than 72" do
@ -150,7 +150,7 @@ class SecurePasswordTest < ActiveModel::TestCase
@existing_user.password_confirmation = ""
assert_not @existing_user.valid?(:update), "user should be invalid"
assert_equal 1, @existing_user.errors.count
assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation]
assert_equal ["doesnt match Password"], @existing_user.errors[:password_confirmation]
end
test "updating an existing user with validation and a nil password confirmation" do
@ -164,7 +164,7 @@ class SecurePasswordTest < ActiveModel::TestCase
@existing_user.password_confirmation = "something else"
assert_not @existing_user.valid?(:update), "user should be invalid"
assert_equal 1, @existing_user.errors.count
assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation]
assert_equal ["doesnt match Password"], @existing_user.errors[:password_confirmation]
end
test "updating an existing user with validation and a correct password challenge" do
@ -212,14 +212,14 @@ class SecurePasswordTest < ActiveModel::TestCase
@existing_user.password_digest = ""
assert_not @existing_user.valid?(:update), "user should be invalid"
assert_equal 1, @existing_user.errors.count
assert_equal ["can't be blank"], @existing_user.errors[:password]
assert_equal ["cant be blank"], @existing_user.errors[:password]
end
test "updating an existing user with validation and a nil password digest" do
@existing_user.password_digest = nil
assert_not @existing_user.valid?(:update), "user should be invalid"
assert_equal 1, @existing_user.errors.count
assert_equal ["can't be blank"], @existing_user.errors[:password]
assert_equal ["cant be blank"], @existing_user.errors[:password]
end
test "setting a blank password should not change an existing password" do

@ -109,12 +109,12 @@ def @contact.favorite_quote; "Constraints are liberating"; end
test "should return Hash for errors" do
contact = Contact.new
contact.errors.add :name, "can't be blank"
contact.errors.add :name, "cant be blank"
contact.errors.add :name, "is too short (minimum is 2 characters)"
contact.errors.add :age, "must be 16 or over"
hash = {}
hash[:name] = ["can't be blank", "is too short (minimum is 2 characters)"]
hash[:name] = ["cant be blank", "is too short (minimum is 2 characters)"]
hash[:age] = ["must be 16 or over"]
assert_equal hash.to_json, contact.errors.to_json
end

@ -57,7 +57,7 @@ def test_validates_confirmation_of_for_ruby_class
p.karma_confirmation = "None"
assert_predicate p, :invalid?
assert_equal ["doesn't match Karma"], p.errors[:karma_confirmation]
assert_equal ["doesnt match Karma"], p.errors[:karma_confirmation]
p.karma = "None"
assert_predicate p, :valid?
@ -70,14 +70,14 @@ def test_title_confirmation_with_i18n_attribute
I18n.load_path.clear
I18n.backend = I18n::Backend::Simple.new
I18n.backend.store_translations("en",
errors: { messages: { confirmation: "doesn't match %{attribute}" } },
errors: { messages: { confirmation: "doesnt match %{attribute}" } },
activemodel: { attributes: { topic: { title: "Test Title" } } })
Topic.validates_confirmation_of(:title)
t = Topic.new("title" => "We should be confirmed", "title_confirmation" => "")
assert_predicate t, :invalid?
assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation]
assert_equal ["doesnt match Test Title"], t.errors[:title_confirmation]
ensure
I18n.load_path.replace @old_load_path
I18n.backend = @old_backend

@ -39,7 +39,7 @@ def test_generate_message_invalid_with_custom_message
# validates_confirmation_of: generate_message(attr_name, :confirmation, message: custom_message)
def test_generate_message_confirmation_with_default_message
assert_equal "doesn't match Title", @person.errors.generate_message(:title, :confirmation)
assert_equal "doesnt match Title", @person.errors.generate_message(:title, :confirmation)
end
def test_generate_message_confirmation_with_custom_message
@ -57,7 +57,7 @@ def test_generate_message_accepted_with_custom_message
# add_on_empty: generate_message(attr, :empty, message: custom_message)
def test_generate_message_empty_with_default_message
assert_equal "can't be empty", @person.errors.generate_message(:title, :empty)
assert_equal "cant be empty", @person.errors.generate_message(:title, :empty)
end
def test_generate_message_empty_with_custom_message
@ -66,7 +66,7 @@ def test_generate_message_empty_with_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)
assert_equal "cant be blank", @person.errors.generate_message(:title, :blank)
end
def test_generate_message_blank_with_custom_message

@ -67,9 +67,9 @@ def test_errors_full_messages_on_nested_error_uses_attribute_format
})
person = person_class.new
error = ActiveModel::Error.new(person, :gender, "can't be blank")
error = ActiveModel::Error.new(person, :gender, "cant be blank")
person.errors.import(error, attribute: "person[0].contacts.gender")
assert_equal ["Gender can't be blank"], person.errors.full_messages
assert_equal ["Gender cant be blank"], person.errors.full_messages
end
def test_errors_full_messages_uses_attribute_format

@ -18,14 +18,14 @@ def test_validate_presences
t = Topic.new
assert_predicate t, :invalid?
assert_equal ["can't be blank"], t.errors[:title]
assert_equal ["can't be blank"], t.errors[:content]
assert_equal ["cant be blank"], t.errors[:title]
assert_equal ["cant be blank"], t.errors[:content]
t.title = "something"
t.content = " "
assert_predicate t, :invalid?
assert_equal ["can't be blank"], t.errors[:content]
assert_equal ["cant be blank"], t.errors[:content]
t.content = "like stuff"
@ -36,8 +36,8 @@ def test_accepts_array_arguments
Topic.validates_presence_of %w(title content)
t = Topic.new
assert_predicate t, :invalid?
assert_equal ["can't be blank"], t.errors[:title]
assert_equal ["can't be blank"], t.errors[:content]
assert_equal ["cant be blank"], t.errors[:title]
assert_equal ["cant be blank"], t.errors[:content]
end
def test_validates_acceptance_of_with_custom_error_using_quotes
@ -53,7 +53,7 @@ def test_validates_presence_of_for_ruby_class
p = Person.new
assert_predicate p, :invalid?
assert_equal ["can't be blank"], p.errors[:karma]
assert_equal ["cant be blank"], p.errors[:karma]
p.karma = "Cold"
assert_predicate p, :valid?
@ -65,7 +65,7 @@ def test_validates_presence_of_for_ruby_class_with_custom_reader
p = CustomReader.new
assert_predicate p, :invalid?
assert_equal ["can't be blank"], p.errors[:karma]
assert_equal ["cant be blank"], p.errors[:karma]
p[:karma] = "Cold"
assert_predicate p, :valid?
@ -79,11 +79,11 @@ def test_validates_presence_of_with_allow_nil_option
t.title = ""
assert_predicate t, :invalid?
assert_equal ["can't be blank"], t.errors[:title]
assert_equal ["cant be blank"], t.errors[:title]
t.title = " "
assert_predicate t, :invalid?
assert_equal ["can't be blank"], t.errors[:title]
assert_equal ["cant be blank"], t.errors[:title]
t.title = nil
assert_predicate t, :valid?

@ -65,7 +65,7 @@ def test_validates_with_if_as_local_conditions
Person.validates :karma, presence: true, email: { if: :condition_is_false }
person = Person.new
person.valid?
assert_equal ["can't be blank"], person.errors[:karma]
assert_equal ["cant be blank"], person.errors[:karma]
end
def test_validates_with_if_as_shared_conditions
@ -78,7 +78,7 @@ def test_validates_with_unless_as_local_conditions
Person.validates :karma, presence: true, email: { unless: :condition_is_true }
person = Person.new
person.valid?
assert_equal ["can't be blank"], person.errors[:karma]
assert_equal ["cant be blank"], person.errors[:karma]
end
def test_validates_with_unless_shared_conditions

@ -64,8 +64,8 @@ def test_multiple_errors_per_attr_iteration_with_full_error_composition
def test_errors_on_nested_attributes_expands_name
t = Topic.new
t.errors.add("replies.name", "can't be blank")
assert_equal ["Replies name can't be blank"], t.errors.full_messages
t.errors.add("replies.name", "cant be blank")
assert_equal ["Replies name cant be blank"], t.errors.full_messages
end
def test_errors_on_base
@ -213,8 +213,8 @@ def test_errors_to_json
assert_predicate t, :invalid?
hash = {}
hash[:title] = ["can't be blank"]
hash[:content] = ["can't be blank"]
hash[:title] = ["cant be blank"]
hash[:content] = ["cant be blank"]
assert_equal t.errors.to_json, hash.to_json
end
@ -224,7 +224,7 @@ def test_validation_order
t = Topic.new("title" => "")
assert_predicate t, :invalid?
assert_equal "can't be blank", t.errors["title"].first
assert_equal "cant be blank", t.errors["title"].first
Topic.validates_presence_of :title, :author_name
Topic.validate { errors.add("author_email_address", "will never be valid") }
Topic.validates_length_of :title, :content, minimum: 2
@ -233,10 +233,10 @@ def test_validation_order
assert_predicate t, :invalid?
assert_equal :title, key = t.errors.attribute_names[0]
assert_equal "can't be blank", t.errors[key][0]
assert_equal "cant be blank", t.errors[key][0]
assert_equal "is too short (minimum is 2 characters)", t.errors[key][1]
assert_equal :author_name, key = t.errors.attribute_names[1]
assert_equal "can't be blank", t.errors[key][0]
assert_equal "cant be blank", t.errors[key][0]
assert_equal :author_email_address, key = t.errors.attribute_names[2]
assert_equal "will never be valid", t.errors[key][0]
assert_equal :content, key = t.errors.attribute_names[3]
@ -414,7 +414,7 @@ def test_strict_validation_error_message
exception = assert_raises(ActiveModel::StrictValidationFailed) do
Topic.new.valid?
end
assert_equal "Title can't be blank", exception.message
assert_equal "Title cant be blank", exception.message
end
def test_does_not_modify_options_argument

@ -359,7 +359,7 @@ def create(attributes = {}, &block)
# end
#
# person.pets.create!(name: nil)
# # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
# # => ActiveRecord::RecordInvalid: Validation failed: Name cant be blank
def create!(attributes = {}, &block)
@association.create!(attributes, &block)
end

@ -2828,7 +2828,7 @@ def test_association_with_rewhere_doesnt_set_inverse_instance_key
assert_predicate pirate, :valid?
assert_not pirate.valid?(:conference)
assert_equal "can't be blank", ship.errors[:name].first
assert_equal "cant be blank", ship.errors[:name].first
end
test "association with instance dependent scope" do

@ -529,8 +529,8 @@ def test_errors_should_be_indexed_when_global_flag_is_set
assert_not_predicate invalid_electron, :valid?
assert_predicate valid_electron, :valid?
assert_not_predicate molecule, :valid?
assert_equal ["can't be blank"], molecule.errors["electrons[1].name"]
assert_not_equal ["can't be blank"], molecule.errors["electrons.name"]
assert_equal ["cant be blank"], molecule.errors["electrons[1].name"]
assert_not_equal ["cant be blank"], molecule.errors["electrons.name"]
ensure
ActiveRecord.index_nested_attribute_errors = old_attribute_config
end
@ -1339,7 +1339,7 @@ def test_should_not_ignore_different_error_messages_on_the_same_attribute
@pirate.ship.name = ""
@pirate.catchphrase = nil
assert_predicate @pirate, :invalid?
assert_equal ["can't be blank", "is invalid"], @pirate.errors[:"ship.name"]
assert_equal ["cant be blank", "is invalid"], @pirate.errors[:"ship.name"]
ensure
Ship._validators = old_validators if old_validators
Ship._validate_callbacks = old_callbacks if old_callbacks
@ -1638,7 +1638,7 @@ def test_should_automatically_validate_the_associated_models
@pirate.public_send(@association_name).each { |child| child.name = "" }
assert_not_predicate @pirate, :valid?
assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
assert_equal ["cant be blank"], @pirate.errors["#{@association_name}.name"]
assert_empty @pirate.errors[@association_name]
end
@ -1646,7 +1646,7 @@ def test_should_not_use_default_invalid_error_on_associated_models
@pirate.public_send(@association_name).build(name: "")
assert_not_predicate @pirate, :valid?
assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
assert_equal ["cant be blank"], @pirate.errors["#{@association_name}.name"]
assert_empty @pirate.errors[@association_name]
end
@ -1670,7 +1670,7 @@ def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it
@pirate.catchphrase = nil
assert_not_predicate @pirate, :valid?
assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
assert_equal ["cant be blank"], @pirate.errors["#{@association_name}.name"]
assert_predicate @pirate.errors[:catchphrase], :any?
end

@ -1104,7 +1104,7 @@ def setup
part = ShipPart.new(name: "Stern", ship_attributes: { name: nil })
assert_not_predicate part, :valid?
assert_equal ["Ship name can't be blank"], part.errors.full_messages
assert_equal ["Ship name cant be blank"], part.errors.full_messages
end
end

@ -48,7 +48,7 @@ def test_generate_message_taken_with_custom_message
topic = Topic.new
topic.errors.add(:title, :invalid)
topic.errors.add(:title, :blank)
assert_equal "Validation failed: Title is invalid, Title can't be blank", ActiveRecord::RecordInvalid.new(topic).message
assert_equal "Validation failed: Title is invalid, Title cant be blank", ActiveRecord::RecordInvalid.new(topic).message
end
test "RecordInvalid exception translation falls back to the :errors namespace" do

@ -364,7 +364,7 @@ irb> user = User.new
irb> user.save
=> false
irb> user.save!
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
ActiveRecord::RecordInvalid: Validation failed: Name cant be blank
```
You can learn more about validations in the [Active Record Validations

@ -2088,7 +2088,7 @@ to your `Customer` model. If you try to create a new `Customer` without passing
```irb
irb> Customer.find_or_create_by!(first_name: 'Andy')
ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank
ActiveRecord::RecordInvalid: Validation failed: Orders count cant be blank
```
[`find_or_create_by!`]: https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-find_or_create_by-21

@ -205,21 +205,21 @@ irb> p.errors.size
irb> p.valid?
=> false
irb> p.errors.objects.first.full_message
=> "Name can't be blank"
=> "Name cant be blank"
irb> p = Person.create
=> #<Person id: nil, name: nil>
irb> p.errors.objects.first.full_message
=> "Name can't be blank"
=> "Name cant be blank"
irb> p.save
=> false
irb> p.save!
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
ActiveRecord::RecordInvalid: Validation failed: Name cant be blank
irb> Person.create!
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
ActiveRecord::RecordInvalid: Validation failed: Name cant be blank
```
[`invalid?`][] is the inverse of `valid?`. It triggers your validations,
@ -648,7 +648,7 @@ validates :boolean_field_name, exclusion: [nil]
By using one of these validations, you will ensure the value will NOT be `nil`
which would result in a `NULL` value in most cases.
The default error message is _"can't be blank"_.
The default error message is _"cant be blank"_.
[`Object#blank?`]: https://api.rubyonrails.org/classes/Object.html#method-i-blank-3F
@ -1086,7 +1086,7 @@ irb> book.valid?
irb> book.valid?(:ensure_title)
=> false
irb> book.errors.messages
=> {:title=>["can't be blank"]}
=> {:title=>["cant be blank"]}
```
When triggered by an explicit context, validations are run for that context,
@ -1105,7 +1105,7 @@ irb> person = Person.new
irb> person.valid?(:account_setup)
=> false
irb> person.errors.messages
=> {:email=>["has already been taken"], :age=>["is not a number"], :name=>["can't be blank"]}
=> {:email=>["has already been taken"], :age=>["is not a number"], :name=>["cant be blank"]}
```
We will cover more use-cases for `on:` in the [callbacks guide](active_record_callbacks.html).
@ -1124,7 +1124,7 @@ end
```irb
irb> Person.new.valid?
ActiveModel::StrictValidationFailed: Name can't be blank
ActiveModel::StrictValidationFailed: Name cant be blank
```
There is also the ability to pass a custom exception to the `:strict` option.
@ -1137,7 +1137,7 @@ end
```irb
irb> Person.new.valid?
TokenGenerationException: Token can't be blank
TokenGenerationException: Token cant be blank
```
Conditional Validation
@ -1394,7 +1394,7 @@ irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors.full_messages
=> ["Name can't be blank", "Name is too short (minimum is 3 characters)"]
=> ["Name cant be blank", "Name is too short (minimum is 3 characters)"]
irb> person = Person.new(name: "John Doe")
irb> person.valid?
@ -1441,7 +1441,7 @@ irb> person = Person.new
irb> person.valid?
=> false
irb> person.errors[:name]
=> ["can't be blank", "is too short (minimum is 3 characters)"]
=> ["cant be blank", "is too short (minimum is 3 characters)"]
```
### `errors.where` and Error Object

@ -931,13 +931,13 @@ irb> person = Person.new.tap(&:valid?)
irb> person.errors.full_messages
=> [
"Invalid Name (can't be blank)",
"Invalid Name (cant be blank)",
"Please fill in your Age"
]
irb> person.errors.messages
=> {
:name => ["can't be blank"],
:name => ["cant be blank"],
:age => ["Please fill in your Age"]
}
```