aboutsummaryrefslogtreecommitdiffstats
path: root/actionview
diff options
context:
space:
mode:
authorKasper Timm Hansen <kaspth@gmail.com>2016-12-18 20:01:54 +0100
committerKasper Timm Hansen <kaspth@gmail.com>2016-12-18 20:18:47 +0100
commit9f0f7ec2223ae60ee6e2eab5bc9f036a480e9f81 (patch)
treeadd9c1af0690fd0970d77978c21250c448bea7d6 /actionview
parent2f197022e41f07f17301e11927b602a001c71192 (diff)
downloadrails-9f0f7ec2223ae60ee6e2eab5bc9f036a480e9f81.tar.gz
rails-9f0f7ec2223ae60ee6e2eab5bc9f036a480e9f81.tar.bz2
rails-9f0f7ec2223ae60ee6e2eab5bc9f036a480e9f81.zip
form_with: allow methods outside the model.
Has the handy effect of making the initial examples in the form_with docs work too. Had to do some finagling such that form_with's without a scope didn't wrap their names in braces ala `[title]`.
Diffstat (limited to 'actionview')
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb14
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb17
-rw-r--r--actionview/test/template/form_helper/form_with_test.rb81
3 files changed, 96 insertions, 16 deletions
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 4a52a69b7b..c446e5bc55 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -513,6 +513,17 @@ module ActionView
# <input type="text" name="post[title]" value="<the title of the post>">
# </form>
#
+ # # Though the fields don't have to correspond to model attributes:
+ # <%= form_with model: Cat.new do |form| %>
+ # <%= form.text_field :cats_dont_have_gills %>
+ # <%= form.text_field :but_in_forms_they_can %>
+ # <% end %>
+ # # =>
+ # <form action="/cats" method="post" data-remote="true">
+ # <input type="text" name="cat[cats_dont_have_gills]">
+ # <input type="text" name="cat[but_in_forms_they_can]">
+ # </form>
+ #
# The parameters in the forms are accessible in controllers according to
# their name nesting. So inputs named +title+ and <tt>post[title]</tt> are
# accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt>
@@ -700,6 +711,7 @@ module ActionView
# form_with(**options.merge(builder: LabellingFormBuilder), &block)
# end
def form_with(model: nil, scope: nil, url: nil, format: nil, **options)
+ options[:allow_method_names_outside_object] = true
options[:skip_default_ids] = true
if model
@@ -1626,7 +1638,7 @@ module ActionView
def initialize(object_name, object, template, options)
@nested_child_index = {}
@object_name, @object, @template, @options = object_name, object, template, options
- @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids) : {}
+ @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
convert_to_legacy_options(@options)
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index b8c446cbed..74d6324771 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -14,6 +14,7 @@ module ActionView
@object_name.sub!(/\[\]$/, "") || @object_name.sub!(/\[\]\]$/, "]")
@object = retrieve_object(options.delete(:object))
@skip_default_ids = options.delete(:skip_default_ids)
+ @allow_method_names_outside_object = options.delete(:allow_method_names_outside_object)
@options = options
@auto_index = Regexp.last_match ? retrieve_autoindex(Regexp.last_match.pre_match) : nil
end
@@ -26,7 +27,11 @@ module ActionView
private
def value(object)
- object.public_send @method_name if object
+ if @allow_method_names_outside_object
+ object.public_send @method_name if object && object.respond_to?(@method_name)
+ else
+ object.public_send @method_name if object
+ end
end
def value_before_type_cast(object)
@@ -93,7 +98,10 @@ module ActionView
def tag_name(multiple = false, index = nil)
# a little duplication to construct less strings
- if index
+ case
+ when @object_name.empty?
+ "#{sanitized_method_name}#{"[]" if multiple}"
+ when index
"#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
else
"#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
@@ -102,7 +110,10 @@ module ActionView
def tag_id(index = nil)
# a little duplication to construct less strings
- if index
+ case
+ when @object_name.empty?
+ sanitized_method_name.dup
+ when index
"#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
else
"#{sanitized_object_name}_#{sanitized_method_name}"
diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb
index c078b47e14..96b797992f 100644
--- a/actionview/test/template/form_helper/form_with_test.rb
+++ b/actionview/test/template/form_helper/form_with_test.rb
@@ -323,6 +323,75 @@ class FormWithActsLikeFormForTest < FormWithTest
assert_dom_equal expected, output_buffer
end
+ def test_form_with_only_url_on_create
+ form_with(url: "/posts") do |f|
+ concat f.label :title, "Label me"
+ concat f.text_field :title
+ end
+
+ expected = whole_form("/posts") do
+ '<label for="title">Label me</label>' +
+ '<input type="text" name="title">'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_only_url_on_update
+ form_with(url: "/posts/123") do |f|
+ concat f.label :title, 'Label me'
+ concat f.text_field :title
+ end
+
+ expected = whole_form("/posts/123") do
+ '<label for="title">Label me</label>' +
+ '<input type="text" name="title">'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_general_attributes
+ form_with(url: "/posts/123") do |f|
+ concat f.text_field :no_model_to_back_this_badboy
+ end
+
+ expected = whole_form("/posts/123") do
+ '<input type="text" name="no_model_to_back_this_badboy">'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_attribute_not_on_model
+ form_with(model: @post) do |f|
+ concat f.text_field :this_dont_exist_on_post
+ end
+
+ expected = whole_form("/posts/123", method: :patch) do
+ '<input type="text" name="post[this_dont_exist_on_post]">'
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_doesnt_call_private_or_protected_properties_on_form_object_skipping_value
+ obj = Class.new do
+ private def private_property
+ "That would be great."
+ end
+
+ protected def protected_property
+ "I believe you have my stapler."
+ end
+ end.new
+
+ form_with(model: obj, scope: "other_name", url: "/", id: "edit-other-name") do |f|
+ assert_dom_equal '<input type="hidden" name="other_name[private_property]">', f.hidden_field(:private_property)
+ assert_dom_equal '<input type="hidden" name="other_name[protected_property]">', f.hidden_field(:protected_property)
+ end
+ end
+
def test_form_with_with_collection_radio_buttons
post = Post.new
def post.active; false; end
@@ -599,18 +668,6 @@ class FormWithActsLikeFormForTest < FormWithTest
assert_dom_equal expected, output_buffer
end
- def test_form_tags_do_not_call_private_properties_on_form_object
- obj = Class.new do
- private def private_property
- raise "This method should not be called."
- end
- end.new
-
- form_with(model: obj, scope: "other_name", url: "/", id: "edit-other-name") do |f|
- assert_raise(NoMethodError) { f.hidden_field(:private_property) }
- end
- end
-
def test_form_with_with_method_as_part_of_html_options
form_with(model: @post, url: "/", id: "create-post", html: { method: :delete }) do |f|
concat f.text_field(:title)