Skip to content

Commit 9c86593

Browse files
committed
Execute field_error_proc within view
Instead of treating it as an anonymous block, execute the `ActionView::Base.field_error_proc` within the context of the `ActionView::Base` instance. This enables consumer applications to continue to override the proc as they see fit, but frees them from declaring templating logic within a `config/initializers/*.rb`, `config/environments/*.rb` or `config/application.rb` file. This makes it possible to replace something like: ```ruby config.action_view.field_error_proc = proc do |html_tag, instance| <<~HTML.html_safe #{html_tag} <span class="errors">#{instance.error_message.to_sentence}</span> HTML end ``` With inline calls to Action View helpers like: ```ruby config.action_view.field_error_proc = proc do |html_tag, instance| safe_join [ html_tag, tag.span(instance.error_message.to_sentence, class: "errors") ] end ``` Or with a view partial rendering, like: ```ruby config.action_view.field_error_proc = proc do |html_tag, instance| render partial: "application/field_with_errors", locals: { html_tag: html_tag, instance: instance } end ``` Then, elsewhere in `app/views/application/field_with_errors.html.erb`: ```erb <%= html_tag %> <span class="errors"><%= instance.error_message.to_sentence %></span> ```
1 parent 9f98066 commit 9c86593

File tree

5 files changed

+72
-2
lines changed

5 files changed

+72
-2
lines changed

actionview/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
* Execute the `ActionView::Base.field_error_proc` within the context of the
2+
`ActionView::Base` instance:
3+
4+
```ruby
5+
config.action_view.field_error_proc = proc { |html| content_tag(:div, html, class: "field_with_errors") }
6+
```
7+
8+
*Sean Doyle*
9+
110
* Add `:day_format` option to `date_select`
211

312
date_select("article", "written_on", day_format: ->(day) { day.ordinalize })

actionview/lib/action_view/base.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ class Base
142142
include Helpers, ::ERB::Util, Context
143143

144144
# Specify the proc used to decorate input tags that refer to attributes with errors.
145-
cattr_accessor :field_error_proc, default: Proc.new { |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
145+
cattr_accessor :field_error_proc, default: Proc.new { |html_tag, instance| content_tag :div, html_tag, class: "field_with_errors" }
146146

147147
# How to complete the streaming when an exception occurs.
148148
# This is our best guess: first try to close the attribute, then the tag.

actionview/lib/action_view/helpers/active_model_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def tag(type, options, *)
2727

2828
def error_wrapping(html_tag)
2929
if object_has_errors?
30-
Base.field_error_proc.call(html_tag, self)
30+
@template_object.instance_exec(html_tag, self, &Base.field_error_proc)
3131
else
3232
html_tag
3333
end

actionview/test/actionpack/controller/render_test.rb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ def nested_partial_with_form_builder
3333
end
3434
end
3535

36+
class ValidatingPost < Post
37+
include ActiveModel::Validations
38+
39+
validates :title, presence: true
40+
end
41+
3642
class TestController < ActionController::Base
3743
protect_from_forgery
3844

@@ -487,6 +493,14 @@ def partial_with_form_builder
487493
render partial: ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {})
488494
end
489495

496+
def partial_with_form_builder_and_invalid_model
497+
post = ValidatingPost.new
498+
499+
post.validate
500+
501+
render partial: ActionView::Helpers::FormBuilder.new(:post, post, view_context, {})
502+
end
503+
490504
def partial_with_form_builder_subclass
491505
render partial: LabellingFormBuilder.new(:post, nil, view_context, {})
492506
end
@@ -680,6 +694,7 @@ class RenderTest < ActionController::TestCase
680694
get :partial_only, to: "test#partial_only"
681695
get :partial_with_counter, to: "test#partial_with_counter"
682696
get :partial_with_form_builder, to: "test#partial_with_form_builder"
697+
get :partial_with_form_builder_and_invalid_model, to: "test#partial_with_form_builder_and_invalid_model"
683698
get :partial_with_form_builder_subclass, to: "test#partial_with_form_builder_subclass"
684699
get :partial_with_hash_object, to: "test#partial_with_hash_object"
685700
get :partial_with_locals, to: "test#partial_with_locals"
@@ -1300,6 +1315,44 @@ def test_partial_with_form_builder
13001315
assert_equal "<label for=\"post_title\">Title</label>\n", @response.body
13011316
end
13021317

1318+
def test_partial_with_form_builder_and_invalid_model
1319+
get :partial_with_form_builder_and_invalid_model
1320+
1321+
assert_equal <<~HTML.strip, @response.body.strip
1322+
<div class="field_with_errors"><label for="post_title">Title</label></div>
1323+
HTML
1324+
end
1325+
1326+
def test_partial_with_form_builder_and_invalid_model_custom_field_error_proc
1327+
old_proc = ActionView::Base.field_error_proc
1328+
ActionView::Base.field_error_proc = proc { |html| tag.div html, class: "errors" }
1329+
1330+
get :partial_with_form_builder_and_invalid_model
1331+
1332+
assert_equal <<~HTML.strip, @response.body.strip
1333+
<div class="errors"><label for="post_title">Title</label></div>
1334+
HTML
1335+
ensure
1336+
ActionView::Base.field_error_proc = old_proc if old_proc
1337+
end
1338+
1339+
def test_partial_with_form_builder_and_invalid_model_custom_rendering_field_error_proc
1340+
old_proc = ActionView::Base.field_error_proc
1341+
ActionView::Base.field_error_proc = proc do |html_tag, instance|
1342+
render inline: <<~ERB, locals: { html_tag: html_tag, instance: instance }
1343+
<div class="field_with_errors"><%= html_tag %> <span class="error"><%= [instance.error_message].join(', ') %></span></div>
1344+
ERB
1345+
end
1346+
1347+
get :partial_with_form_builder_and_invalid_model
1348+
1349+
assert_equal <<~HTML.strip, @response.body.strip
1350+
<div class="field_with_errors"><label for="post_title">Title</label> <span class="error">can&#39;t be blank</span></div>
1351+
HTML
1352+
ensure
1353+
ActionView::Base.field_error_proc = old_proc if old_proc
1354+
end
1355+
13031356
def test_partial_with_form_builder_subclass
13041357
get :partial_with_form_builder_subclass
13051358
assert_equal "<label for=\"post_title\">Title</label>\n", @response.body

guides/source/configuring.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,14 @@ Controls whether or not templates should be reloaded on each request. Defaults t
10831083
10841084
#### `config.action_view.field_error_proc`
10851085
1086+
* `config.action_view.field_error_proc` provides an HTML generator for
1087+
displaying errors that come from Active Model. The block is evaluated within
1088+
the context of an Action View template. The default is
1089+
1090+
```ruby
1091+
Proc.new { |html_tag, instance| content_tag :div, html_tag, class: "field_with_errors" }
1092+
```
1093+
10861094
Provides an HTML generator for displaying errors that come from Active Model. The default is
10871095
10881096
```ruby

0 commit comments

Comments
 (0)