How to Work with Forms inside Forms in Rails

Abstract, 3D-like image in a duotone color scheme depicting the concept of nesting with interlocking geometric shapes or smooth curves, evoking a sense of order and harmony

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}

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!

Get the latest Rails Designer updates

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.

Published at . Last updated at . Have suggestions or improvements on this content? Do reach out. Interested in sharing Rails Designer with the Ruby on Rails community? Become an affiliate.

UI components for Ruby on Rails apps

$ 99 69 one-time
payment

Get Access
  • One-time payment

  • Access to the entire library

  • Built for Ruby on Rails

  • Designed with Tailwind CSS and enhanced with Hotwire

  • Includes free updates (to any 1.x version)

  • Last update recently