Abstract Validation Message rendering
Declare ValidationMessageFormBuilder#validation_message
to combine all
of the newly introduced FormBuilder
concepts:
For fields without any errors , do nothing and render nothing.
When a field has any errors for the attribute, create an instance of
ActionView::Helpers::TagHelper#tag
that is pre-built with the correct
DOM [id]
attribute, generated by validation_message_id
.
When passed block, yield the collection of ActiveModel::Error
instances along with the pre-built tag
. The calling block is
responsible for building an element to contain the validation messages.
When a block is not given , render a <span>
element with the
pre-built tag
instance.
In either case, pass along any attributes passed as part of the options.
Collapse app/views/authentications/new.html.erb
Expand app/views/authentications/new.html.erb
app/views/authentications/new.html.erb
diff --git a/app/views/authentications/new.html.erb b/app/views/authentications/new.html.erb
index 8762274..6cc430b 100644
--- a/app/views/authentications/new.html.erb
+++ b/app/views/authentications/new.html.erb
@@ -1,28 +1,22 @@
<%= form_with(model: authentication) do |form| %>
- <% form.errors(:base) do |errors| %>
- <% if errors.any? %>
- <%= tag.span errors.to_sentence, aria: { live: "assertive" } %>
- <% end %>
+ <%= form.validation_message :base, aria: { live: "assertive" } do |errors, tag| %>
+ <%= tag.p errors.to_sentence %>
<% end %>
- <% form.errors(:username) do |errors| %>
- <%= form.label :username %>
- <%= form.text_field :username, aria: {
- describedby: form.validation_message_id(:username),
- } %>
- <% if errors.any? %>
- <%= tag.span errors.to_sentence, id: form.validation_message_id(:username) %>
- <% end %>
+ <%= form.label :username %>
+ <%= form.text_field :username, aria: {
+ describedby: form.validation_message_id(:username),
+ } %>
+ <%= form.validation_message :username do |errors, tag| %>
+ <%= tag.span errors.to_sentence %>
<% end %>
- <% form.errors(:password) do |errors| %>
- <%= form.label :password %>
- <%= form.password_field :password, aria: {
- describedby: form.validation_message_id(:password),
- } %>
- <% if errors.any? %>
- <%= tag.span errors.to_sentence, id: form.validation_message_id(:password) %>
- <% end %>
+ <%= form.label :password %>
+ <%= form.password_field :password, aria: {
+ describedby: form.validation_message_id(:password),
+ } %>
+ <%= form.validation_message :password do |errors, tag| %>
+ <%= tag.span errors.to_sentence %>
<% end %>
<%= form.button %>
Collapse app/views/messages/index.html.erb
Expand app/views/messages/index.html.erb
app/views/messages/index.html.erb
diff --git a/app/views/messages/index.html.erb b/app/views/messages/index.html.erb
index ff163b9..16ac38b 100644
--- a/app/views/messages/index.html.erb
+++ b/app/views/messages/index.html.erb
@@ -6,14 +6,12 @@
<% if Current.user %>
<%= form_with model: message do |form| %>
- <% form.errors(:content) do |errors| %>
- <%= form.label :content %>
- <%= form.rich_text_area :content, aria: {
- describedby: form.validation_message_id(:content),
- } %>
- <% if errors.any? %>
- <%= tag.span errors.to_sentence, id: form.validation_message_id(:content) %>
- <% end %>
+ <%= form.label :content %>
+ <%= form.rich_text_area :content, aria: {
+ describedby: form.validation_message_id(:content),
+ } %>
+ <%= form.validation_message :content do |errors, tag| %>
+ <%= tag.span errors.to_sentence %>
<% end %>
<%= form.button %>
Collapse config/application.rb
Expand config/application.rb
config/application.rb
diff --git a/config/application.rb b/config/application.rb
index eed0811..e7e125c 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -15,5 +15,6 @@ module ConstraintValidationExample
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
# the framework and any gems in your application.
+ config.action_view.field_error_proc = proc { |html_tag| html_tag }
end
end
Collapse lib/validation_message_form_builder.rb
Expand lib/validation_message_form_builder.rb
lib/validation_message_form_builder.rb
diff --git a/lib/validation_message_form_builder.rb b/lib/validation_message_form_builder.rb
index 9d3def8..d89a837 100644
--- a/lib/validation_message_form_builder.rb
+++ b/lib/validation_message_form_builder.rb
@@ -1,4 +1,20 @@
class ValidationMessageFormBuilder < ActionView::Helpers::FormBuilder
+ def validation_message(field, **attributes, &block)
+ errors field do |messages|
+ id = validation_message_id(field)
+
+ if messages.any?
+ @template.tag.with_options id: id, **attributes do |tag|
+ if block_given?
+ yield messages, tag
+ else
+ tag.span(messages.to_sentence)
+ end
+ end
+ end
+ end
+ end
+
def validation_message_id(field)
if errors(field).any?
field_id(field, :validation_message)
Collapse test/controllers/authentications_controller_test.rb
Expand test/controllers/authentications_controller_test.rb
test/controllers/authentications_controller_test.rb
diff --git a/test/controllers/authentications_controller_test.rb b/test/controllers/authentications_controller_test.rb
index 659e9bf..04e9c2d 100644
--- a/test/controllers/authentications_controller_test.rb
+++ b/test/controllers/authentications_controller_test.rb
@@ -8,6 +8,14 @@ class AuthenticationsControllerTest < ActionDispatch::IntegrationTest
assert_redirected_to messages_url
end
+ test "#new renders a fresh Authentication form" do
+ get new_authentication_path
+
+ assert_response :success
+ assert_select "input:not([aria-invalid]):not([aria-describedby])#authentication_username ~ span", count: 0
+ assert_select "input:not([aria-invalid]):not([aria-describedby])#authentication_password ~ span", count: 0
+ end
+
test "#create with a valid username/password pairing writes to the session" do
alice = users(:alice)
@@ -30,9 +38,9 @@ class AuthenticationsControllerTest < ActionDispatch::IntegrationTest
assert_redirected_to(new_authentication_url).then { follow_redirect! }
assert_flash_params({ authentication: { username: "" } })
assert_nil session[:user_id]
- assert_select "#authentication_username_validation_message", text: "can't be blank"
+ assert_select "input#authentication_username ~ #authentication_username_validation_message", text: "can't be blank"
assert_select %(input[type="text"][aria-invalid="true"][aria-describedby~="authentication_username_validation_message"])
- assert_select "#authentication_password_validation_message", text: "can't be blank"
+ assert_select "input#authentication_password ~ #authentication_password_validation_message", text: "can't be blank"
assert_select %(input[type="password"][aria-invalid="true"][aria-describedby~="authentication_password_validation_message"])
end
@@ -49,7 +57,7 @@ class AuthenticationsControllerTest < ActionDispatch::IntegrationTest
assert_redirected_to(new_authentication_url).then { follow_redirect! }
assert_flash_params({ authentication: { username: alice.username } })
assert_nil session[:user_id]
- assert_select %(span[aria-live="assertive"]), text: "is invalid"
+ assert_select %(p[aria-live="assertive"][id="authentication_base_validation_message"]), text: "is invalid", count: 1
assert_select %(input[type="text"][value="#{alice.username}"])
assert_select %(input[type="password"]:not([value]))
end
Collapse test/controllers/messages_controller_test.rb
Expand test/controllers/messages_controller_test.rb
test/controllers/messages_controller_test.rb
diff --git a/test/controllers/messages_controller_test.rb b/test/controllers/messages_controller_test.rb
index 7b91666..f63e917 100644
--- a/test/controllers/messages_controller_test.rb
+++ b/test/controllers/messages_controller_test.rb
@@ -41,7 +41,8 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest
get messages_path
assert_response :success
- assert_select "#message_content_validation_message", count: 0
+ assert_select "trix-editor#message_content ~ #message_content_validation_message", count: 0
+ assert_select "trix-editor#message_content ~ span", count: 0
assert_select %{trix-editor:not([aria-invalid="true"]):not([aria-describedby])}
end
@@ -52,7 +53,7 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest
}
assert_redirected_to(messages_url).then { follow_redirect! }
- assert_select "#message_content_validation_message", "can't be blank"
+ assert_select "trix-editor#message_content ~ #message_content_validation_message", "can't be blank"
assert_select %{trix-editor[aria-invalid="true"][aria-describedby~="message_content_validation_message"]}
end
end