This article was originally published on Rails Designer.
Have a look at the following screenshot from one of my SaaS'.
It's a form to edit a “filter”, and inside is a button to “Delete…” said filter. Have a think on it: how would you do this in your typical Hotwired, Rails app?
Likely your first idea is to write this:
link_to "Delete…", filter_path(filter), data: {turbo_method: :delete}
And that will work, but it's not the right-way. link_to
is for navigation and retrieving data without side effects, whereas button_to
is used for actions like deleting a resource, as it generates a form with built-in security features, for both safety and semantic reasons. Also what happens if the user opens the “page” in a new tab? And how about accessibility?
Alright, easy! Rewrite it to this:
button_to "Delete…", filter_path(filter), method: :delete, data: {turbo_method: :delete}
While semantically correct, it now creates a form inside a form. This is the output of above line:
<form class="button_to" method="post" action="/filters/1234">
<input type="hidden" name="_method" value="delete" autocomplete="off">
<button data-turbo-method="delete" type="submit">Delete…</button>
<input type="hidden" name="authenticity_token" value="LONG_STRING" autocomplete="off">
</form>
And now we have a form inside a form. Besides being invalid HTML, you will notice it won't even work! Sigh!
The solution to nested forms
The solution is simply to use the HTML5 attribute: form
. First create another form (above, below, or wherever on the page).
form_with model: filter, method: :delete, data: {turbo_method: "delete"}, id: "destroy_filter_form"
Take note of the id destroy_filter_form
.
Then inside the other form, add a button like so:
form_with model: filter do |form|
# …
button_tag "Delete…", type: :submit, form: "destroy_filter_form"
# …
end
The important bit is the form
attribute with the value of the form it should trigger destroy_filter_form
.
The PRO solution to nested forms
And now for the final act! It's even possible to write it all in one line:
button_tag "Delete…", type: :submit, formmethod: :post, formaction: filters_path(filter), data: {turbo_method: "delete"} %>
Key bits are the formaction
which “overrides” the parent form's action
attribute. formmethod
is used to make sure it's a :post
request (the parent could be :put
or :get
).
The last solution is the one I typically use for straightforward actions like “delete”. If there are more (hidden) attributes needed, I go for the other form solution.
(Note: while these code snippets are using ERB, these techniques can be used with every other language!)
Top comments (0)