Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add form_with to unify form_tag/form_for. #26976

Merged
merged 16 commits into from
Nov 21, 2016
Merged
Next Next commit
Add form_with to unify form_tag/form_for.
`form_tag` and `form_for` serve very similar use cases. This
PR unifies that usage such that `form_with` can output just
the opening form tag akin to `form_tag` and can just work with
a url, for instance.

`form_with` by default doesn't attach class or id to the form —
removing them on fields is moved out to a default revisiting PR later.

Ported over old tests where applicable to ensure maximum coverage,
but left some commented out because they don't yet apply (e.g.
`fields_for` later being replaced by `fields`).

[ Kasper Timm Hansen & Marek Kirejczyk ]
  • Loading branch information
kaspth committed Nov 6, 2016
commit 18a44be8b9c13911da21fe2cc95a84e95dee7456
67 changes: 66 additions & 1 deletion actionview/lib/action_view/helpers/form_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,71 @@ def apply_form_for_options!(record, object, options) #:nodoc:
end
private :apply_form_for_options!

# Passing model: @post will 1) set scope: :post, 2) set url: url_for(@post)
#
# form_with(model: @post) do |form|
# form.text_field :title # Will reference @post.title as normal
# form.text_area :description, "Overwrite @post.description if present, if not, it will still work"
#
# form.submit
# end
#
# form_with(scope: :post, url: posts_path) do |form|
# form.text_field :title # post[title]
# form.text_area :description, "Overwrite @post.description or ignore if it's not present"
#
# form.submit
# end
# # =>
# <form action="/posts" accept-charset="utf-8" method="post">
# <input name="utf8" type="hidden" value="✓">
# <input type="hidden" name="authenticity_token" value="...">
#
# <input type="text" name="post[title]">
# <textarea name="post[description]"></textarea>
#
# <input type="submit" name="commit" value="Create Post" data-disable-with="Create Post">
# </form>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like wrong output?

#
# form_with(url: different_path, class: 'something', id: 'specific') do |form|
# form.text_field :title, 'This is the value of the title'
#
# form.text_area :description, class: 'No value has been supplied here'
#
# form.fields(:permission) do |fields|
# # on/off instead of positional parameters for setting values
# fields.check_box :admin, on: 'yes', off: 'no'
# end
#
# form.select :category, Post::CATEGORIES, blank: 'None'
# form.select :author_id, Person.all.collect { |p| [ p.name, p.id ] }, blank: 'Pick someone'
#
# form.submit
# end
def form_with(model: nil, scope: nil, url: nil, format: nil, html: {}, **options)
if model
url ||= polymorphic_path(model, format: format)

model = model.last if model.is_a?(Array)
scope ||= model_name_from_record_or_class(model).param_key
end

html_options = html.merge(options.except(:index, :include_id, :builder))
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?

if block_given?
builder = instantiate_builder(scope, model, options)
output = capture(builder, &Proc.new)
html_options[:multipart] ||= builder.multipart?

html_options = html_options_for_form(url || {}, html_options)
form_tag_with_body(html_options, output)
else
html_options = html_options_for_form(url || {}, html_options)
form_tag_html(html_options)
end
end

# Creates a scope around a specific model object like form_for, but
# doesn't create the form tags themselves. This makes fields_for suitable
# for specifying additional model objects in the same form.
Expand Down Expand Up @@ -1183,7 +1248,7 @@ def instantiate_builder(record_name, record_object, options)
object_name = record_name
else
object = record_name
object_name = model_name_from_record_or_class(object).param_key
object_name = model_name_from_record_or_class(object).param_key if object
end

builder = options[:builder] || default_form_builder_class
Expand Down
Loading