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

3

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!)

AWS Q Developer image

Your AI Code Assistant

Ask anything about your entire project, code and get answers and even architecture diagrams. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Start free in your IDE

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay