Rails form_with - alternative to form_for and form_tag

Rails 5.1 added form_with form helper method that provides capabilities of form_for and form_tag. Rails unified form_for and form_tag that provide similar interfaces to generate forms with form_with helper.

  • form_for was being used to generate form for a new/existing model object.
  • form_tag was used to create form without a model object by passing a URL to submit the form.
Before Rails 5.1
form_for with model object
  • form_for was used when we had to create a form for a model object.
<%= form_for User.new do |form| %>
  <%= form.text_field :name %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

This generates DOM shown below.

<form class="new_user" id="new_user" action="/users" accept-charset="UTF-8" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input type="hidden" name="authenticity_token" value="token_value" />
  <input type="text" name="user[password]" id="user_password" />
  <input type="text" value="" name="user[email]" id="user_email" />
  <input type="submit" name="commit" value="Create User" data-disable-with="Create User" />
</form>

Behavior:

  • We can see Rails automaitcally assigns id attribute value to the new_user if record is new.
  • id attribute is set to edit_user_<id>, where <id> is the primary key of users table.
  • It fills out the input values if the model object is not new.
After Rails 5.1
form_with with a model object
<%= form_with model: @user do |form| %>
  <%= form.text_field :email %>
  <%= form.submit %>
<% end %>

This generates DOM given below.

<form action="/users" accept-charset="UTF-8" data-remote="true" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input type="hidden" name="authenticity_token" value="token_value" />
  <input type="text" name="email" />
  <input type="submit" name="commit" value="Save " data-disable-with="Save " />
</form>

Behavior:

  • Automatic IDs for the form are gone.
  • The above way of creating form works for both new and existing records.
  • It fills out the input values if the model object is not new.

Before Rails 5.1
form_tag without model object
  • form_tag was used when we had to create a form without any model object providing URL endpoint to submit the form.
<%= form_tag users_path do %>
  <%= text_field_tag :name %>
  <%= text_field_tag :email %>
  <%= submit_tag %>
<% end %>

Both helper methods used to create DOM of the form tag with necessary DOM content.

<form action="/users" accept-charset="UTF-8" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input type="hidden" name="authenticity_token" value="token_value" />
  <input type="text" name="name" id="name" />
  <input type="text" name="email" id="email" />
  <input type="submit" name="commit" value="Save changes" data-disable-with="Save changes" />
</form>
After Rails 5.1
form_with without a model object

form_with provides an option to pass a URL to be used for the submit action.

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

This generates DOM given below.

<form action="/users" accept-charset="UTF-8" data-remote="true" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input type="hidden" name="authenticity_token" value="token_value" />
  <input type="text" name="email" />
  <input type="submit" name="commit" value="Save " data-disable-with="Save " />
</form>

form_with with a scope (prefix)

Now, we can see that above code, does not prefix input fields as it does when generated with model object.

To generate input fields with some prefix, Rails provides scope option.

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

This generates DOM given below.

<form action="/users" accept-charset="UTF-8" data-remote="true" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input type="hidden" name="authenticity_token" value="token_value" />
  <input type="text" name="user[name]" />
  <input type="text" name="user[email]" />
  <input type="submit" name="commit" value="Save User" data-disable-with="Save User" />
</form>

form_with ajax submit events

As we can see form_with adds data-remote attribute with value set to true. This makes sure that form is submitted with AJAX if unobtrusive javascript driver like rails-ujs is used.

If your application uses rails-ujs, the form will be submitted via ajax, and it listens on following events.

  • ajax:success : This event is called when Ajax response is success.
  • ajax:error : This event is called when Ajax response is failure.

Event can be binded on form as given below.

  $(document).on('ajax:success', '#new_user', function(e) {
    console.log('form_with: successfully submitted form via ajax');
  });

  $(document).on('ajax:error', '#new_user', function(e) {
    console.log('form_with: error submitting form via ajax');
  });