fields_for: What it Does and How to Use It

The .erb code for a form I created in a recent project. See an edited version in the examples below!

Writing forms in HTML can be a tedious task, but fortunately Rails provides a host of form helpers that reduce the amount of typing you have to do and the amount of syntax you need to memorize. For instance, the following HTML code that provides a form for the user to add a song to a database —

<form class="new_song" id="new_song" action="/songs" accept-charset="UTF-8" method="post">
<input name="utf8" type="hidden" value="✓">
<input type="hidden" name="authenticity_token" value="0boS3/K4pFOUvggOBuWD1E16V7XjN2vcJ4v0yOmvMmsSvTeKIdONQ561mKWZqpp0QHkdy8gnJBirHg4eBZX49w==">
<label for="song_title">Title</label><br>
<input type="text" name="song[title]" id="song_title"><br><br>
<label for="song_release_date">Release date</label><br>
<input type="date" name="song[release_date]" id="song_release_date"><br><br>
<label for="song_Youtube Embed Link">Youtube embed link</label><br>
<a href="">(how to find it)</a><br>
<input type="text" name="song[link]" id="song_link"><br><br>
<input type="submit" name="commit" value="Create Song" data-disable-with="Create Song"></form>

— can be created in an .erb file using this much shorter block of code:

<%= form_for(@song) do |f| %>  <%= f.label :title %><br>  <%= f.text_field :title %><br><br>  <%= f.label :release_date %><br>  <%= f.date_field :release_date %><br><br>  <%= f.label "Youtube Embed Link" %><br>  <%= link_to "(how to find it)", "" %><br>  <%= f.text_field :link %><br><br>
<%= f.submit "Create Song" %><% end %>

It’s pretty cool! And because it is so handy, most people who have worked with Ruby on Rails are familiar with the process of using form_for, form_tag, or the newest form helper, form_with. There is also a lesser known helper, fields_for, that you may not be experienced with. However, despite its obscurity, fields_for has a very distinct purpose and is a necessity in some more complex forms.

In the above example, we’re giving the user the opportunity to create an instance of the Song class — one with a title, release date, and embed link — and store it inside of our website’s database. Assuming that there is a controller set up in another file that handles post requests to ‘/songs’, it should work without issue. One can easily imagine using this same format to allow the user to create an account, write a blog post, make a comment, etc.

Now, let’s say we want to make a change so that a user can enter in the name of a song’s artist. But instead of “artist_name” being stored as an attribute in the Song model, we want to create an instance of an Artist model and associate our new Song instance with the new Artist instance. (For the sake of example, let’s say that an Artist has many Songs and a Song belongs to an Artist.) The form as a whole may be for a Song instance, but we want the new artist name field to be for an Artist instance. That’s where fields_for comes in!

<%= f.label "Artist Name" %><br><%= f.fields_for :artist do |artist| %>  <%= artist.text_field :name %><% end %>

This code on its own will not work if inserted into the above form. The reason for this is that fields_for requires a nested attribute writer in the code of the model the form is created for. A simpler way of putting it is that we need to give the Song model the ability to keep track of data on its Artist before the two instances are associated. Remember in the days before ActiveRecord, when we had to manually define a model’s setter methods? It’s kind of like that. In fact, it’s exactly like that!

So, over in the file where I define the Song class…

class Song < ApplicationRecord  belongs_to :artist  def artist_attributes=(attributes)    self.artist = Artist.find_or_create_by(name: attributes[:name])    self.artist.update(attributes)  end...end

Now, every Song can set its own artist_attributes value, and will do so using the attributes hash passed into it via fields_for. Thanks to Rails, there’s no need to specify where the attributes are coming from; Rails recognizes the “artist” in artist_attributes and uses that data accordingly. This code has the extra bonus of using the find_or_create_by method to ensure that our form can’t create duplicates of an Artist (at least, not with the same name).

Let’s take a look at our complete form.

<%= form_for(@song) do |f| %><%= f.label :title %><br><%= f.text_field :title %><br><br>
<%= f.label "Artist Name" %><br><%= f.fields_for :artist do |artist| %> <%= artist.text_field :name %><% end %><br><br><%= f.label :release_date %><br><%= f.date_field :release_date %><br><br><%= f.label "Youtube Embed Link" %><br><%= link_to "(how to find it)", "" %><br><%= f.text_field :link %><br><br><%= f.submit "Create Song" %><% end %>

Upon pressing submit, this form will create a new Song instance with the inputted attributes, and use the text entered in artist.text_field to either

a. identify an Artist in the database with that name, and associate the new Song with that Artist, or

b. identify that there is no Artist in the database with that name, create one, and then associate the new Song with the new Artist.

And in the latter case, that means we’ve made two instances — and associated them — with only one form! It’s a much more elegant alternative to forcing users to fill out one form per model.

fields_for on APIdock

Nested Forms on the official Rails guide

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store