Releasing courses as early access is our way of trying to get the latest updates into your hands as quickly as possible. So while early-access courses aren’t initially 100% complete, our hope is that the early modules give you something to chew on while we incrementally finish up the remaining videos.

Every once in a while during early access we have to go back and change something. That happened this week with the Rails 6 course.

For as long as we can remember, there have been two ways to generate forms in Rails: form_for and form_tag. We’ve been teaching these two methods since rolling out the initial Rails video course in 2013, and even before that in live training sessions. Indeed, the interface for creating forms has been remarkably stable all these years.

At the same time, it has always been problematic to have two ways to do effectively the same thing. And so Rails has been decidedly moving towards a new form_with method that aims to unify the way all forms are generated. As this seems to be the future of Rails, we’ve gone back through the Rails 6 videos and made updates accordingly. It was a big effort, but we hope you’ll appreciate it.

Updated Videos

Now don’t worry: you don’t have to rewatch all the videos! At the end of this email you’ll find a list of videos with specific timecode ranges that have changed. You may just want to rewatch those sections. You also don’t have to use form_with in order to get on the Rails 6 train. Both form_for and form_tag still work as they always have. However, they’ve been soft deprecated starting in Rails 6 which means in future versions you’ll start to see deprecation warnings.

How Is form_with Different?

So you might be wondering at a high level how using form_with is different from using form_for and form_tag. Let’s look at a few examples…

Previously, you had two ways to create a form. If the form was for an ActiveRecord model object such as a user stored in the database, then you used form_for. For example:

<%= form_for(@user) do |f| %>
  <%= f.label :email %>
  <%= f.email_field :email %>

  <%= f.label :password %>
  <%= f.password_field :password %>

  <%= f.submit %>
<% end %>

This generates a form tag and yields a form builder object (f) to the associated block. You then call methods on that form builder object to generate form elements such as labels and input fields.

Sometimes, however, you need to generate a form that’s not bound to an ActiveRecord model object. In those cases, you had to use form_tag to generate the form. A common example is a “Sign In” form:

<%= form_tag(session_path) do %>
  <%= label_tag :email %>
  <%= email_field_tag :email %>

  <%= label_tag :password %>
  <%= password_field_tag :password %>

  <%= submit_tag "Sign In" %>
<% end %>

Calling form_tag differs from calling form_for in two distinct ways: 1) you pass it a URL rather than a model object and 2) it does not yield a form builder to the associated block. As such, you have to use a different set of methods (ending in _tag) to generate form elements.

So form_for and form_tag both generate forms, but using two different coding styles. Wouldn’t it be nice if there was one uniform way in Rails to create all forms? Well, that’s the rationale behind the introduction of form_with.

Using form_with, if you have a model you specify it as the value of the model option, like so:

<%= form_with(model: @user) do |f| %>
  <%= f.label :email %>
  <%= f.email_field :email %>

  <%= f.label :password %>
  <%= f.password_field :password %>

  <%= f.submit %>
<% end %>

And if you don’t have a model, you call form_with and pass it the url option with the URL the form submits to:

<%= form_with(url: session_path) do |f| %>
  <%= f.label :email %>
  <%= f.email_field :email %>

  <%= f.label :password %>
  <%= f.password_field :password %>

  <%= f.submit "Sign In" %>
<% end %>

In both cases, form_with yields a form builder object and you generate form elements by calling methods on that object. So the “guts” of the form code are the same regardless of whether you have a model or not. The only difference is whether you specify the model or the url option. The overall coding style is consistent.

XHR Requests

Now there’s one more important thing you need to know about form_with. By default, a form generated using form_with will submit its data using an XHR (Ajax) request. Both form_for and form_tag can do this, too, but you have to set the remote option to true. With form_with, remote: true is the default.

Using an XHR request by default makes sense given the close relationship Rails has with Turbolinks to speed up navigating between pages. We’ll explore those benefits later on in the course when we get to the topic of Turbolinks. Until then, we think forms are easier to learn if they use the browser’s normal submit mechanism. To do that, we set the local option to true:

<%= form_with(model: @user, local: true) do |f| %>
  ...
<% end %>

<%= form_with(url: session_path, local: true) do |f| %>
  ...
<% end %>

So that’s a quick glance at the code differences. Despite having to refilm a bunch of videos, we like how form_with unifies the interface for generating forms. We would have preferred that form_with not generate a remote form by default, but that’s just our personal preference coming from a teaching perspective.

Videos That Changed

Anyway, as promised here’s a list of the changes we’ve made so you can quickly drop in to each video to see the updates:

  • The first form we design is for editing events in Module 12 Forms Edit Part 1 at 4:36 - 10:58

  • The second form is for creating new events in Module 13 Forms Create at 5:52 - 6:49

  • Next we move the forms into a partial in Module 14 Partials at 2:36 - 3:57

  • The registration form is next in Module 24 One-To-Many: Forms at 3:56 - 6:07

  • Then it’s on to the form for creating a user account in Module 27 User Sign Up at 4:30 - 6:04

  • And of course, we need a form for editing a user account in Module 28 Edit User Account at 1:22 - 3:37

Also, all the exercises and code have been updated to reflect form_with.

Carry on!