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?
Maybe 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 semantically and for accessibility. 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?
Alright, easy! Rewrite it to this:
button_to "Delete…", filter_path(filter), method: :delete, data: {turbo_method: :delete}
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>
Now you 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 to use the HTML5 attribute: form
. First create another form (above, below, or wherever on the same page as the other form).
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.