Skip to content

Commit

Permalink
Revert typography change in user facing errors
Browse files Browse the repository at this point in the history
This change would force a lot of existing applications and libraries
to update their tests.

We included it in the beta to collect feedback from the community and
we had some comments about how negative this change would be.

Developers that care about the typography of their error messages
can easily change it in their applications using the translation
files, so there is no need to inflict pain in the upgrade process
by changing the default in the framework.

Revert "Merge PR #45463"

This reverts commit 9f60cd8, reversing
changes made to 35d574d.
  • Loading branch information
rafaelfranca committed Sep 26, 2023
1 parent 99cf11c commit acfa045
Show file tree
Hide file tree
Showing 31 changed files with 96 additions and 90 deletions.
2 changes: 1 addition & 1 deletion actionview/test/actionpack/controller/render_test.rb
Expand Up @@ -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">cant be blank</span></div>
<div class="field_with_errors"><label for="post_title">Title</label> <span class="error">can&#39;t be blank</span></div>
HTML
ensure
ActionView::Base.field_error_proc = old_proc if old_proc
Expand Down
4 changes: 2 additions & 2 deletions actionview/test/template/active_model_helper_test.rb
Expand Up @@ -20,7 +20,7 @@ def setup
super

@post = Post.new
@post.errors.add(:author_name, "cant be empty")
@post.errors.add(:author_name, "can't be empty")
@post.errors.add(:body, "foo")
@post.errors.add(:category, "must exist")
@post.errors.add(:published, "must be accepted")
Expand Down Expand Up @@ -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">cant be empty</span></div>),
%(<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>),
text_field("post", "author_name")
)
ensure
Expand Down
4 changes: 2 additions & 2 deletions actionview/test/template/form_helper/form_with_test.rb
Expand Up @@ -276,10 +276,10 @@ def form_with(*, **)
@comment = Comment.new
def @post.errors
Class.new {
def [](field); field == "author_name" ? ["cant be empty"] : [] end
def [](field); field == "author_name" ? ["can't be empty"] : [] end
def empty?() false end
def count() 1 end
def full_messages() ["Author name cant be empty"] end
def full_messages() ["Author name can't be empty"] end
}.new
end
def @post.to_key; [123]; end
Expand Down
4 changes: 2 additions & 2 deletions actionview/test/template/form_helper_test.rb
Expand Up @@ -110,10 +110,10 @@ def form_for(*)
@comment = Comment.new
def @post.errors
Class.new {
def [](field); field == "author_name" ? ["cant be empty"] : [] end
def [](field); field == "author_name" ? ["can't be empty"] : [] end
def empty?() false end
def count() 1 end
def full_messages() ["Author name cant be empty"] end
def full_messages() ["Author name can't be empty"] end
}.new
end
def @post.to_key; [123]; end
Expand Down
6 changes: 6 additions & 0 deletions activemodel/CHANGELOG.md
@@ -1,3 +1,9 @@
* Remove change in the typography of user facing error messages.
For example, “can’t be blank” is again “can't be blank”.

*Rafael Mendonça França*


## Rails 7.1.0.beta1 (September 13, 2023) ##

* Support composite identifiers in `to_key`
Expand Down
12 changes: 6 additions & 6 deletions activemodel/lib/active_model/errors.rb
Expand Up @@ -284,7 +284,7 @@ def group_by_attribute
#
# person.errors.add(:name, :blank)
# person.errors.messages
# # => {:name=>["cant be blank"]}
# # => {:name=>["can't be blank"]}
#
# person.errors.add(:name, :too_long, count: 25)
# person.errors.messages
Expand Down Expand Up @@ -332,7 +332,7 @@ def add(attribute, type = :invalid, **options)
#
# person.errors.add :name, :blank
# person.errors.added? :name, :blank # => true
# person.errors.added? :name, "cant be blank" # => true
# person.errors.added? :name, "can't be blank" # => true
#
# If the error requires options, then it returns +true+ with
# the correct options, or +false+ with incorrect or missing options.
Expand Down Expand Up @@ -385,7 +385,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 cant be blank", "Email cant be blank"]
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
def full_messages
@errors.map(&:full_message)
end
Expand All @@ -400,7 +400,7 @@ def full_messages
#
# person = Person.create()
# person.errors.full_messages_for(:name)
# # => ["Name is too short (minimum is 5 characters)", "Name cant be blank"]
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
def full_messages_for(attribute)
where(attribute).map(&:full_message).freeze
end
Expand All @@ -414,7 +414,7 @@ def full_messages_for(attribute)
#
# person = Person.create()
# person.errors.messages_for(:name)
# # => ["is too short (minimum is 5 characters)", "cant be blank"]
# # => ["is too short (minimum is 5 characters)", "can't be blank"]
def messages_for(attribute)
where(attribute).map(&:message)
end
Expand Down Expand Up @@ -487,7 +487,7 @@ def normalize_arguments(attribute, type, **options)
# person = Person.new
# person.name = nil
# person.valid?
# # => ActiveModel::StrictValidationFailed: Name cant be blank
# # => ActiveModel::StrictValidationFailed: Name can't be blank
class StrictValidationFailed < StandardError
end

Expand Down
6 changes: 3 additions & 3 deletions activemodel/lib/active_model/locale/en.yml
Expand Up @@ -10,10 +10,10 @@ en:
inclusion: "is not included in the list"
exclusion: "is reserved"
invalid: "is invalid"
confirmation: "doesnt match %{attribute}"
confirmation: "doesn't match %{attribute}"
accepted: "must be accepted"
empty: "cant be empty"
blank: "cant be blank"
empty: "can't be empty"
blank: "can't be blank"
present: "must be blank"
too_long:
one: "is too long (maximum is 1 character)"
Expand Down
2 changes: 1 addition & 1 deletion activemodel/lib/active_model/secure_password.rb
Expand Up @@ -57,7 +57,7 @@ module ClassMethods
#
# user.save # => false, password required
# user.password = "vr00m"
# user.save # => false, confirmation doesnt match
# user.save # => false, confirmation doesn't match
# user.password_confirmation = "vr00m"
# user.save # => true
#
Expand Down
2 changes: 1 addition & 1 deletion activemodel/lib/active_model/validations.rb
Expand Up @@ -326,7 +326,7 @@ def initialize_dup(other) # :nodoc:
#
# person = Person.new
# person.valid? # => false
# person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["cant be blank"]}>
# person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
def errors
@errors ||= Errors.new(self)
end
Expand Down
2 changes: 1 addition & 1 deletion activemodel/lib/active_model/validations/confirmation.rb
Expand Up @@ -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: "doesnt match
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
# <tt>%{translated_attribute_name}</tt>").
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
# non-text columns (+true+ by default).
Expand Down
2 changes: 1 addition & 1 deletion activemodel/lib/active_model/validations/presence.rb
Expand Up @@ -26,7 +26,7 @@ module HelperMethods
# <tt>false.blank? # => true</tt>.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "cant be blank").
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
Expand Down
2 changes: 1 addition & 1 deletion activemodel/lib/active_model/validations/validates.rb
Expand Up @@ -144,7 +144,7 @@ def validates(*attributes)
# person = Person.new
# person.name = ''
# person.valid?
# # => ActiveModel::StrictValidationFailed: Name cant be blank
# # => ActiveModel::StrictValidationFailed: Name can't be blank
def validates!(*attributes)
options = attributes.extract_options!
options[:strict] = true
Expand Down
8 changes: 4 additions & 4 deletions activemodel/test/cases/error_test.rb
Expand Up @@ -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 "cant be blank", error.message
assert_equal "can't be blank", error.message
end

test "message with custom interpolation" do
Expand Down Expand Up @@ -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 cant be blank", error.full_message
assert_equal "name can't be blank", error.full_message
end

test "full_message uses default format" do
error = ActiveModel::Error.new(Person.new, :name, message: "cant be blank")
error = ActiveModel::Error.new(Person.new, :name, message: "can't be blank")

# Use a locale without errors.format
I18n.with_locale(:unknown) {
assert_equal "name cant be blank", error.full_message
assert_equal "name can't be blank", error.full_message
}
end

Expand Down
4 changes: 2 additions & 2 deletions activemodel/test/cases/errors_test.rb
Expand Up @@ -179,7 +179,7 @@ def test_no_key
person.errors.add(:name, :blank)

assert_equal :blank, person.errors.objects.first.type
assert_equal ["cant be blank"], person.errors[:name]
assert_equal ["can't be blank"], person.errors[:name]
end

test "add, with type as String" do
Expand Down Expand Up @@ -216,7 +216,7 @@ def test_no_key
person.errors.add(:name, type)

assert_equal :blank, person.errors.objects.first.type
assert_equal ["cant be blank"], person.errors[:name]
assert_equal ["can't be blank"], person.errors[:name]
end

test "add an error message on a specific attribute with a defined type" do
Expand Down
18 changes: 9 additions & 9 deletions activemodel/test/cases/secure_password_test.rb
Expand Up @@ -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 ["cant be blank"], @user.errors[:password]
assert_equal ["can't 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 ["cant be blank"], @user.errors[:password]
assert_equal ["can't be blank"], @user.errors[:password]
end

test "create a new user with validation and password length greater than 72 characters" do
Expand All @@ -85,7 +85,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 ["doesnt match Password"], @user.errors[:password_confirmation]
assert_equal ["doesn't match Password"], @user.errors[:password_confirmation]
end

test "create a new user with validation and a nil password confirmation" do
Expand All @@ -99,7 +99,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 ["doesnt match Password"], @user.errors[:password_confirmation]
assert_equal ["doesn't match Password"], @user.errors[:password_confirmation]
end

test "resetting password to nil clears the password cache" do
Expand Down Expand Up @@ -144,7 +144,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 ["cant be blank"], @existing_user.errors[:password]
assert_equal ["can't be blank"], @existing_user.errors[:password]
end

test "updating an existing user with validation and password length greater than 72" do
Expand All @@ -160,7 +160,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 ["doesnt match Password"], @existing_user.errors[:password_confirmation]
assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation]
end

test "updating an existing user with validation and a nil password confirmation" do
Expand All @@ -174,7 +174,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 ["doesnt match Password"], @existing_user.errors[:password_confirmation]
assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation]
end

test "updating an existing user with validation and a correct password challenge" do
Expand Down Expand Up @@ -222,14 +222,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 ["cant be blank"], @existing_user.errors[:password]
assert_equal ["can't 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 ["cant be blank"], @existing_user.errors[:password]
assert_equal ["can't be blank"], @existing_user.errors[:password]
end

test "setting a blank password should not change an existing password" do
Expand Down
4 changes: 2 additions & 2 deletions activemodel/test/cases/serializers/json_serialization_test.rb
Expand Up @@ -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, "cant be blank"
contact.errors.add :name, "can't 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] = ["cant be blank", "is too short (minimum is 2 characters)"]
hash[:name] = ["can't 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
Expand Down
Expand Up @@ -57,7 +57,7 @@ def test_validates_confirmation_of_for_ruby_class
p.karma_confirmation = "None"
assert_predicate p, :invalid?

assert_equal ["doesnt match Karma"], p.errors[:karma_confirmation]
assert_equal ["doesn't match Karma"], p.errors[:karma_confirmation]

p.karma = "None"
assert_predicate p, :valid?
Expand All @@ -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: "doesnt match %{attribute}" } },
errors: { messages: { confirmation: "doesn't 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 ["doesnt match Test Title"], t.errors[:title_confirmation]
assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation]
ensure
I18n.load_path.replace @old_load_path
I18n.backend = @old_backend
Expand Down
Expand Up @@ -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 "doesnt match Title", @person.errors.generate_message(:title, :confirmation)
assert_equal "doesn't match Title", @person.errors.generate_message(:title, :confirmation)
end

def test_generate_message_confirmation_with_custom_message
Expand All @@ -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 "cant be empty", @person.errors.generate_message(:title, :empty)
assert_equal "can't be empty", @person.errors.generate_message(:title, :empty)
end

def test_generate_message_empty_with_custom_message
Expand All @@ -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 "cant be blank", @person.errors.generate_message(:title, :blank)
assert_equal "can't be blank", @person.errors.generate_message(:title, :blank)
end

def test_generate_message_blank_with_custom_message
Expand Down
4 changes: 2 additions & 2 deletions activemodel/test/cases/validations/i18n_validation_test.rb
Expand Up @@ -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, "cant be blank")
error = ActiveModel::Error.new(person, :gender, "can't be blank")
person.errors.import(error, attribute: "person[0].contacts.gender")
assert_equal ["Gender cant be blank"], person.errors.full_messages
assert_equal ["Gender can't be blank"], person.errors.full_messages
end

def test_errors_full_messages_uses_attribute_format
Expand Down

0 comments on commit acfa045

Please sign in to comment.