DEV Community

Cover image for How to Work with Forms inside Forms in Rails
Rails Designer
Rails Designer

Posted on • Edited on • Originally published at railsdesigner.com

How to Work with Forms inside 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 another form (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.

(Note: while these code snippets are using ERB, these techniques can be used with every other language!)

Top comments (0)