From 37081bf507734b61a0662045f80d92c2384a3b6c Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Fri, 8 Oct 2021 12:52:53 -0400 Subject: [PATCH] Introduce `field_name` view helper The `field_name` helper and corresponding `FormBuilder#field_name` method provide an Action View-compliant way of overriding a form field element's `[name]` attribute (similar to `field_id` and `FormBuilder#field_id` introduced in rails/rails#40127[][]). ```ruby text_field_tag :post, :title, name: field_name(:post, :title, :subtitle) # => text_field_tag :post, :tag, name: field_name(:post, :tag, multiple: true) # => form_for @post do |f| f.field_tag :tag, name: f.field_name(:tag, multiple: true) # => end ``` [rails/rails#40127]: https://github.com/rails/rails/pull/40127 --- actionview/CHANGELOG.md | 12 ++ .../lib/action_view/helpers/form_helper.rb | 22 ++++ .../action_view/helpers/form_tag_helper.rb | 26 ++++ .../lib/action_view/helpers/tags/base.rb | 10 +- actionview/test/template/form_helper_test.rb | 120 ++++++++++++++++++ .../test/template/form_tag_helper_test.rb | 60 +++++++++ 6 files changed, 241 insertions(+), 9 deletions(-) diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 21af906bcc2be..b4f8919d789d3 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,15 @@ +* Introduce the `field_name` view helper, along with the + `FormBuilder#field_name` counterpart: + + ```ruby + form_for @post do |f| + f.field_tag :tag, name: f.field_name(:tag, multiple: true) + # => + end + ``` + + *Sean Doyle* + * Add `:day_format` option to `date_select` date_select("article", "written_on", day_format: ->(day) { day.ordinalize }) diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index ab7e88f92c562..5388677096de3 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -1746,6 +1746,28 @@ def field_id(method, *suffixes, index: @index) @template.field_id(@object_name, method, *suffixes, index: index) end + # Generate an HTML name attribute value for the given name and + # field combination + # + # Return the value generated by the FormBuilder for the given + # attribute name. + # + # <%= form_for @post do |f| %> + # <%= f.text_field :title, name: f.field_name(:title, :subtitle) %> + # <%# => + # <% end %> + # + # <%= form_for @post do |f| %> + # <%= f.field_tag :tag, name: f.field_name(:tag, multiple: true) %> + # <%# => + # <% end %> + # + def field_name(method, *methods, multiple: false, index: @index) + object_name = @options.fetch(:as) { @object_name } + + @template.field_name(object_name, method, *methods, index: index, multiple: multiple) + end + ## # :method: text_field # diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 3d5c2e174a3b0..cfc3fa9ecf319 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -114,6 +114,32 @@ def field_id(object_name, method_name, *suffixes, index: nil) end end + # Generate an HTML name attribute value for the given name and + # field combination + # + # Return the value generated by the FormBuilder for the given + # attribute name. + # + # <%= text_field_tag :post, :title, name: field_name(:post, :title, :subtitle) %> + # <%# => + # + # <%= text_field_tag :post, :tag, name: field_name(:post, :tag, multiple: true) %> + # <%# => + # + def field_name(object_name, method_name, *method_names, multiple: false, index: nil) + names = method_names.map! { |name| "[#{name}]" }.join + + # a little duplication to construct fewer strings + case + when object_name.empty? + "#{method_name}#{names}#{multiple ? "[]" : ""}" + when index + "#{object_name}[#{index}][#{method_name}]#{names}#{multiple ? "[]" : ""}" + else + "#{object_name}[#{method_name}]#{names}#{multiple ? "[]" : ""}" + end + end + # Creates a dropdown selection box, or if the :multiple option is set to true, a multiple # choice selection box. # diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index 5e8ed754cca69..86278d41ca6f2 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -105,15 +105,7 @@ def add_default_name_and_id(options) end def tag_name(multiple = false, index = nil) - # a little duplication to construct fewer strings - case - when @object_name.empty? - "#{sanitized_method_name}#{multiple ? "[]" : ""}" - when index - "#{@object_name}[#{index}][#{sanitized_method_name}]#{multiple ? "[]" : ""}" - else - "#{@object_name}[#{sanitized_method_name}]#{multiple ? "[]" : ""}" - end + @template_object.field_name(@object_name, sanitized_method_name, multiple: multiple, index: index) end def tag_id(index = nil) diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index f4716b1601e7c..8329968c86c35 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -1659,6 +1659,126 @@ def test_form_for_field_id_with_index assert_dom_equal expected, output_buffer end + def test_form_for_field_name_with_blank_as + form_for(Post.new, as: "") do |form| + concat form.text_field(:title, name: form.field_name(:title)) + end + + expected = whole_form("/posts", "new_", "new_") do + %() + end + + assert_dom_equal expected, output_buffer + end + + def test_form_for_field_name_with_blank_as_and_multiple + form_for(Post.new, as: "") do |form| + concat form.text_field(:title, name: form.field_name(:title, multiple: true)) + end + + expected = whole_form("/posts", "new_", "new_") do + %() + end + + assert_dom_equal expected, output_buffer + end + + def test_form_for_field_name_without_method_names_or_multiple_or_index + form_for(Post.new) do |form| + concat form.text_field(:title, name: form.field_name(:title)) + end + + expected = whole_form("/posts", "new_post", "new_post") do + %() + end + + assert_dom_equal expected, output_buffer + end + + def test_form_for_field_name_without_method_names_and_multiple + form_for(Post.new) do |form| + concat form.text_field(:title, name: form.field_name(:title, multiple: true)) + end + + expected = whole_form("/posts", "new_post", "new_post") do + %() + end + + assert_dom_equal expected, output_buffer + end + + def test_form_for_field_name_without_method_names_and_index + form_for(Post.new, index: 1) do |form| + concat form.text_field(:title, name: form.field_name(:title)) + end + + expected = whole_form("/posts", "new_post", "new_post") do + %() + end + + assert_dom_equal expected, output_buffer + end + + def test_form_for_field_name_without_method_names_and_index_and_multiple + form_for(Post.new, index: 1) do |form| + concat form.text_field(:title, name: form.field_name(:title, multiple: true)) + end + + expected = whole_form("/posts", "new_post", "new_post") do + %() + end + + assert_dom_equal expected, output_buffer + end + + def test_form_for_field_name_with_method_names + form_for(Post.new) do |form| + concat form.text_field(:title, name: form.field_name(:title, :subtitle)) + end + + expected = whole_form("/posts", "new_post", "new_post") do + %() + end + + assert_dom_equal expected, output_buffer + end + + def test_form_for_field_name_with_method_names_and_index + form_for(Post.new, index: 1) do |form| + concat form.text_field(:title, name: form.field_name(:title, :subtitle)) + end + + expected = whole_form("/posts", "new_post", "new_post") do + %() + end + + assert_dom_equal expected, output_buffer + end + + def test_form_for_field_name_with_method_names_and_multiple + form_for(Post.new) do |form| + concat form.text_field(:title, name: form.field_name(:title, :subtitle, multiple: true)) + end + + expected = whole_form("/posts", "new_post", "new_post") do + %() + end + + assert_dom_equal expected, output_buffer + end + + def test_form_for_field_name_with_method_names_and_multiple_and_index + form_for(Post.new, index: 1) do |form| + concat form.text_field(:title, name: form.field_name(:title, :subtitle, multiple: true)) + end + + expected = whole_form("/posts", "new_post", "new_post") do + %() + end + + assert_dom_equal expected, output_buffer + end + def test_form_for_with_collection_radio_buttons post = Post.new def post.active; false; end diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb index 50801076d8e4c..8ba6d6434460e 100644 --- a/actionview/test/template/form_tag_helper_test.rb +++ b/actionview/test/template/form_tag_helper_test.rb @@ -211,6 +211,66 @@ def test_field_id_with_nested_object_name assert_equal "post_author_name", value end + def test_field_name_without_object_name + value = field_name("", :title) + + assert_equal "title", value + end + + def test_field_name_without_object_name_and_multiple + value = field_name("", :title, multiple: true) + + assert_equal "title[]", value + end + + def test_field_name_without_method_names_or_multiple_or_index + value = field_name(:post, :title) + + assert_equal "post[title]", value + end + + def test_field_name_without_method_names_and_multiple + value = field_name(:post, :title, multiple: true) + + assert_equal "post[title][]", value + end + + def test_field_name_without_method_names_and_index + value = field_name(:post, :title, index: 1) + + assert_equal "post[1][title]", value + end + + def test_field_name_without_method_names_and_index_and_multiple + value = field_name(:post, :title, index: 1, multiple: true) + + assert_equal "post[1][title][]", value + end + + def test_field_name_with_method_names + value = field_name(:post, :title, :subtitle) + + assert_equal "post[title][subtitle]", value + end + + def test_field_name_with_method_names_and_index + value = field_name(:post, :title, :subtitle, index: 1) + + assert_equal "post[1][title][subtitle]", value + end + + def test_field_name_with_method_names_and_multiple + value = field_name(:post, :title, :subtitle, multiple: true) + + assert_equal "post[title][subtitle][]", value + end + + def test_field_name_with_method_names_and_multiple_and_index + value = field_name(:post, :title, :subtitle, index: 1, multiple: true) + + assert_equal "post[1][title][subtitle][]", value + end + def test_hidden_field_tag actual = hidden_field_tag "id", 3 expected = %()