Rails 5 Complex Forms: Configuring Nested Attributes in the Form
In this guide we're going to finish up the complex Rails 5 form feature. Specifically, we will walk through how to configure the form, controller, and view files to work with nested attributes.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

This is going to be a really exciting guide because we're going to be able to implement all the functionality that we've been building for our nested attributes. We're also going to see all of it in the browser.

For now, this is what the create a new portfolio item page looks like.

large

We have a title, subtitle and body. However, we want to have the ability to save multiple technologies, all at the same time, right here on the form, and then have those saved into the database. This means we are going to be communicating with multiple models at the same time. There are three places we need to make changes in order to get this working in the view.

Updating the Controller

The first thing we need to do is open the controller. In our portfolios_controller.rbfile, we need to update the new action. Because we need to make the form elements available and we need to generate them in this method.

def new
  @portfolio_item = Portfolio.new
end

For now, I'm going to show you a hard coded version, but we'll come back later in the course and refactor it using JavaScript to get some dynamic behavior. You’ll notice this code will be similar to what we did in our seeds file as we are using .times.

def new
  @portfolio_item = Portfolio.new
  3.times { @portfolio_item.technologies.build }
end

By using .build this code will essentially instantiate three version of the portfolio_item with technologies. In other words, it will indicate, here is a portfolio item, I want you to create three types of the technologies and make them available to the form. This part will make a bit more sense when we actually put it in the form, and you can see what the line
3.times { @portfolio_item.technologies.build } is directly communicating to in the form.

The next thing we need to do is make these form attributes available to strong parameters. Remember in our create method, we already have strong parameters such as title, subtitle and body. We also need to whitelist our new attributes for technologies. Right after body we are going to add technologies_attributes: [:name]

- @portfolio_item = Portfolio.new(params.require(:portfolio).permit(:title, :subtitle, :body))
+ @portfolio_item = Portfolio.new(params.require(:portfolio).permit(:title, :subtitle, :body, technologies_attributes: [:name]))

Notice how this looks very similar to what we did in the console, as we are passing in an array of values to technologies_attributes. This array simply contains a single attribute of name. If your item had additional attributes we would add those in as well. With this code, you're telling Rails to see if there's anything coming in under the technologies_attributes flag, and it has name in it, then it's perfectly fine to let it go to the database. Remember strong params are a security precaution in Rails forms that will submit the form, but will verify against our whitelist items that we've specified. This is to ensure that whatever is being passed through belongs to an attribute in the database. So we’ve indicated that there is a name in our techonology table.

That's all we need to do in our controller.

Updating the View

Next, we have to update the form in app/views/portfolios/new.html.erb.

<h1>Create a new Portfolio Item</h1>

<%= form_for(@portfolio_item) do |f| %>
  <div class="field">
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>

  <div class="field">
    <%= f.label :subtitle %>
    <%= f.text_field :subtitle %>
  </div>

  <div class="field">
    <%= f.label :body %>
    <%= f.text_area :body %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Directly under the form field for body, I'm going to add a <ul>. In HTML a <ul> is a tag for an unordered list. Inside we will have some <li> tags, which are list items.

<div class="field">
    <%= f.label :body %>
    <%= f.text_area :body %>
</div>
    <ul>
        <li>
        </li>
    </ul>

Because of the way Rails dictates nested attributes, we need to have a form within form.

At the top of the view file we have <%= form_for(@portfolio_item) do |f| %> , which is form for aportfolio_item, but we need to implement another form within this:

Inside of the <ul> we will add some embedded ruby <%= %> We will take in the f variable (there is nothing special about it, it is just the f from block variable |f| in the form_for method)

We can call a method on f that is titled fields_for. This is a special connection in Rails forms that inherits from the form_for method. fields_for allows you to create nested forms and have nested attributes for our forms. We will just pass in the attribute the nested form is for, which in this case is :technologies

<ul>
  <%= f.fields_for :technologies %>
    <li>
    </li>
</ul>

If you're wondering how this is wired up and how the form knows what to access, you simply need to look at the portfolio model. Open portfolio.rb.

Here you have code that indicates
accept_nested_attributes_for :technologies. So, we will use the same technologies in our form and that is how it is mapped.

Again, this is a block, much like the form_for at the top of the file. Our block takes a block variable called technology_form. You can call it whatever you want. Each of the attributes in our form can make use of this block variable for form inputs.

-  <%= f.fields_for :technologies %>
+ <%= f.fields_for :technologies do |technology_form| %>

Let’s go ahead and end the block using <% end %>

  <ul>
    <%= f.fields_for :technologies do |technology_form| %>
      <li></li>
    <% end %>
  </ul>

Remember, in our controller, where we created three instances of technologies for each portfolio? Essentially, this code will iterate through those three instances. Next, we'll build our items using some embedded ruby. Remember we use <%= to indicate that it is embedded ruby. Each instance will have a label <%= technology_form.label :name %>and a text field <%= technology_form.text_field :name %>

 <ul>
    <%= f.fields_for :technologies do |technology_form| %>
      <li>
        <%= technology_form.label :name %>
        <%= technology_form.text_field :name %>
      </li>
    <% end %>
  </ul>

That is everything we need to do to get our new form working.

Testing the Code

Let's test it out. Save the file, and refresh the browser, and you should see this:

large

Remember, this will not work for portfolio edit, as we’ve only set this for portfolio new. Here on the create new page you can see we now have three fields for our technologies. Admittedly, it’s not very pretty, but we will fix that later on. Let’s test it out using:

large

When we click the Create Portfolio button, it will take us to the portfolios page, which indicates everything is working. However, when we go to the show page for the newest Portfolio we can't see the new technologies because we haven't integrated them for this page yet. So, there is no way to verify if this worked or not.

On the other hand, if you go to the terminal, you can examine the SQL code.

large

This is very similar to what we saw when we created nested attributes in our console. You’ll see that it created a portfolio with three technologies named “Rails”, “Angular” and, “Ionic”.

However, this data isn’t very useful if you can not view it in the browser. In order for this to work the way we want we need to update the `/portfolio/show.html.erb’ file.

<%= image_tag @portfolio_item.main_image %>

<h1><%= @portfolio_item.title %></h1>

<em><%= @portfolio_item.subtitle %></em>

<p><%= @portfolio_item.body %></p>

At the bottom of the file, add a heading <h2>Technologies Used:</h2>

Under that, we want to display the technologies.

<% @portfolio_item.technologies

Remember, this is exactly like what we did in the Rails console. There we grabbed the last portfolio item and called .technologies on it to list all of the technologies associated with it. Here, we're running a query on a particular portfolio item and then listing out all the technologies associated with it.

We will be using an iterator so we will use .each do and |t| as the block variable.

- <% @portfolio_item.technologies
<% @portfolio_item.technologies.each do |t| %>

Be sure to use <% end %> to close the loop. Inside we need to add a <p> tag to list out the names of the technologies implementing the block variable. <%= t.name %>

<% @portfolio_item.technologies.each do |t| %>
<p><%= t.name %></p>
<% end %>

Save the file. Now, if you refresh the browser, you can see that the last portfolio we created has three technologies associated with it.

medium

Go ahead and test it out again. Try creating a new item with different technologies such as ".net", "asp" and “java”. It should work perfectly.

Now, you not only know how to create complex forms in Rails, but also know how to display them on the show page. Fantastic job building that out!

I do want to make a few comments before I end this guide, as I think you may be curious about a few things.

Items to Consider

First, when we hit create a new portfolio item, by default we have three technologies hardcoded. This is because inside the controller we indicated we wanted the technologies to .build three times.

  def new
    @portfolio_item = Portfolio.new
    3.times { @portfolio_item.technologies.build }
  end

This gives the new form access to use this, and it runs this code three times:

  <ul>
    <%= f.fields_for :technologies do |technology_form| %>
      <li>
        <%= technology_form.label :name %>
        <%= technology_form.text_field :name %>
      </li>
    <% end %>
  </ul>

This is not a modern approach to development, nor would you usually use it in your production application. Normally, your form will display a single form field for a nested item. Then, there would be a button that indicated "click to add another one". Once you click it, the next form field will be created. You can, of course, have an unlimited number of technologies that can be created for each portfolio. This is something that we'll implement in our JavaScript section.

This implementation is not possible in Rails by itself simply because Rails is a server side type of language. What this means is, if you clicked on that add button, it would refresh the entire form. By doing that, you would lose all the data you just typed in. If you clicked it again, it would refresh the page again. You would have to keep track of what you had previously typed in and, overall, this would be a really bad user experience. This is where JavaScript comes in. JavaScript allows you to click a button, and then dynamically slide something inside the page, without the need to refresh the page and without having to interact with the server.

We will implement this functionality when we we get to the JavaScript section of the course.

The other thing you may be curious about is why we don't have this technologies concept in our edit page. The main reason is that we haven’t talked about _partials yet. If you open our
portfolios/new.html.erb and portfolios/edit.html.erb, You will see that they are similar except for the technologies display code. Technically, I can just copy and paste it, but that would be really a bad practice because then we have to change our edit and update methods too.

We still have a lot of this course left. In the rest of the course we’ll learn things like how to create partials that will be forms that are shared by both new and edit actions. This way, we won't have to repeat the same code across different files.

Essentially, we're going to have a lot more cool things to do with these pages. I wanted to point that out so you so you don't think I am leaving this in it’s current state long term.

Now, go to PivotalTracker, and check off Nested attributes for portfolio technologies as we have finished that. We have now completed this entire section. Fantastic job! Go ahead and click the Finish button and continue thru to accept it.

Our last step will be to go to the terminal and run our git commands.

git status
git add .
git commit -m ‘Implementd form configuration for nested attributes’
git push origin data-feature

With those steps done, let's do our pull request. Go to your GitHub repository and click on the compare and pull request button. It will take you to this page

large

We've done quite a bit of work in this section.
Let’s add our latest items to the list.

- Implemented Validations
- Created db relationships
- Implemented nested attribute forms
- Created custom scopes

Then, click the Create Pull request button

Next, click the merge button and confirm merge button.

The last thing to do is to go to the terminal and use the command git checkout master branch then run git pull. This means our local master branch is synced with our remote branch, and will now contain all the latest code.

Great job going through all of that! That was a lot of work, but I think you will be happy because now you have a lot of knowledge that many introductory Rails students do not. There are even those who have been coding in Rails for a few years that struggle working with nested attributes.

The next guide is the deep dive for this section. We’ll talk all about analyzing Rails models, how to run complex database queries and things like that.

Resources