code

Rails 5.1's form_with vs. form_tag vs. form_for

Yashu Mittal

form_tag and form_for are soft deprecated and they will be replaced by form_with in the future. If you want to know more about form_with then you can peruse the original DHH’s proposal, check the pull request in which it was implemented, check the API documentation and play with it in a test project.

Or, continue reading this post in which I explain differences between form_tag, form_for and form_with with code samples.

One syntax to rule them all

Previously when you wanted to create a form, but you did not have an underlying model for it, then you used form_tag.

<%= form_tag users_path do %>
  <%= text_field_tag :email %>
  <%= submit_tag %>
<% end %>

When you had a model, then you used form_for.

<%= form_for @user do |form| %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

As you can see, we use form builder field helpers with form_for but we don’t do that with form_tag. So the syntax for both forms is different.

This isn’t the case with form_with, because we use form builder all the time. form_with without a model:

<%= form_with url: users_path do |form| %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

form_with with a model:

<%= form_with model: @user do |form| %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

When you set the model argument then scope and url are automatically derived from it. This behaviour is similar to form_for.

Automatic ids and classes are gone

form_tag and form_for generate automatic ids for form fields. form_for does it also for forms.

<%= form_for User.new do |form| %>
  <%= form.text_field :email %>
<% end %>

Generates:

<form class="new_user" id="new_user" action="/users" ...>
  ...
  <input type="text" name="user[email]" id="user_email" />
</form>

With form_with you have to specify all of your ids and classes manually:

<%= form_with model: @user do |form| %>
  <%= form.text_field :name %>
  <%= form.text_field :email, id: :email, class: :email %>
<% end %>

Generates:

<form action="/users" ...>
  ...
  <input type="text" name="user[name]" />
  <input id="email" class="email" type="text" name="user[email]" />  </form>

Do not forget to specify ids for form fields if you want to make labels work.

<%= form_with model: @user do |form| %>
  <%= form.label :name %>
  <%= form.text_field :name, id: :user_name %>
<% end %>
Form id and class attributes aren’t wrapped anymore

Before:

<%= form_for @user, html: { id: :custom_id, class: :custom_class } do |form| %>
<% end %>

After:

<%= form_with model: @user, id: :custom_id, class: :custom_class do |form| %>
<% end %>
Form fields don’t have to correspond to model attributes

Before:

<%= form_for @user do |form| %>
  <%= form.text_field :email %>
  <%= check_box_tag :send_welcome_email %>
<% end %>

After:

<%= form_with model: @user, local: true do |form| %>
  <%= form.text_field :email %>
  <%= form.check_box :send_welcome_email %>
  <%= form.submit %>
<% end %>

Please note, that send_welcome_email will be scoped to user in controller parameters in the latter example:

params[:user][:send_welcome_email]

Therefore I would probably still use check_box_tag instead of form.check_box if I needed to.

All forms are remote by default

This change is the most exciting to me. All forms generated by form_with will be submitted by an XHR (Ajax) request by default. There is no need to specify remote: true as you have to with form_tag and form_for.

I like this feature, because it sends a message. Thanks to Turbolinks, (almost) all GET requests are XHR requests by default. And thanks to form_with this can become a reality for forms, too. You just need to do a little bit of work (create a JavaScript response template) and you can eliminate all full page refreshes.

However, if you want to disable remote forms then you can do it with local: true.

<%= form_with model: @user, local: true %>
<% end %>
Let’s use form_with since now and never look back!

I did not cover everything in this post, but it should be enough to get you started with form_with so you don’t have to use form_tag and form_for anymore (they will be removed anyway). Also, read the documentation for form_with.

Response to “Rails 5.1's form_with vs. form_tag vs. form_for”

Stay current

Sign up for our newsletter, and we'll send you news and tutorials on business, growth, web design, coding and more!