How to Work with Nested Forms in Rails

This article was originally published on Rails Designer.


Have a look at the following screenshot from one of my SaaS’.

User interface of an email scheduling application with dropdown filters for Name, Email, and Status set to 'Scheduled' and a Save button.

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}
Enter fullscreen mode
Exit fullscreen mode

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}
Enter fullscreen mode
Exit fullscreen mode

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>
Enter fullscreen mode
Exit fullscreen mode

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 a form next (above, below, or wherever on the page).

form_with model: filter, method: :delete, data: {turbo_method: "delete"}, id: "destroy_filter_form"
Enter fullscreen mode
Exit fullscreen mode

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
Enter fullscreen mode
Exit fullscreen mode

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"} %>
Enter fullscreen mode
Exit fullscreen mode

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.


Discover more from Coursity

Subscribe to get the latest posts sent to your email.

Leave a Comment

Your email address will not be published. Required fields are marked *

Discover more from Coursity

Subscribe now to keep reading and get access to the full archive.

Continue reading

Scroll to Top
Hover offers domain privacy protection and excellent customer support. Financier and philanthropist jacob rothschild dies at 87 – cna.