Here's the "cheatsheet" for all of the available actions for a resource, from the last lesson:

Action Use Case HTTP Method Path
index Read (display) a list of all tacos GET /tacos
show Read (display) information on a single taco GET /tacos/123
new A form to create a new taco GET /tacos/new
create Receives the information from the "new" form and creates the new taco POST /tacos
edit A form to update an existing taco GET /tacos/123/edit
update Received the information from the "edit" form and updates the existing taco PATCH /tacos/123
destroy Processes a request to destroy an existing taco DELETE /tacos/123

In this lesson, we're going to concentrate on creating new records. As briefly explained in the last lesson, creating new instances of a resource involves two actions – the new and create actions. This is simply due to the nature of HTML – we need to display a form to fill out – the new action – and also need to have an action to accept the form data as parameters, do the work of creating the new record in the database, and send the user on their way – the create action.

The new Action and Rails' Form Helpers

Let's continue building out our CRM application and give the user the ability to create a new company. Here's the code for both the controller and view:

def new
  @company = Company.new
end
In app/controllers/companies_controller.rb
<%= form_with :model => @company do |form| %>
  <p>
    <%= form.label "name", "Company Name" %>
    <%= form.text_field "name" %>
  </p>
  <p>
    <%= form.label "city" %>
    <%= form.text_field "city" %>
    <%= form.label "state" %>
    <%= form.text_field "state" %>
  </p>
  <%= form.submit %>
<% end %>
In app/views/companies/new.html.erb

To break it down, we have a new action defined in our controller, and the only thing happening is that we're creating a brand-new instance of our Company class. Note that we're not performing a save on this instance just yet; we're simply creating an empty Company, with the fields to be completed by the user in the view.

Moving over to the view, we see some weird-looking code that we haven't seen before. What we're seeing is some of Rails' form helpers. Helpers are bits of code built into Rails that are designed to help us write HTML. We could, of course, write the HTML ourselves, but using the helpers tends to make the code more succinct and readable. The above code basically, with some extra code removed for clarity, gets translated into:

<form action="/companies" method="post">
  <p>
    <label for="company_name">Company Name</label>
    <input type="text" name="company[name]" id="company_name">
  </p>
  <p>
    <label for="company_city">City</label>
    <input type="text" name="company[city]" id="company_city">
    <label for="company_state">State</label>
    <input type="text" name="company[state]" id="company_state">
  </p>
  <input type="submit" name="commit" value="Create Company">
</form>

We can see that it's a fairly vanilla HTML form, with the form IDs and names automatically configured to allow proper transfer of information to the create action.

We can also see that this form is designed to submit to the create action. How do we know it's the create action? Recall that the create action is the combination of the pluralized resource name as the URL (i.e. /companies) and the HTTP POST method.

The create Action

Try filling out and submitting the form!

That's ok – we don't fear errors! Of course, Rails is letting us know that we don't have a create action in our controller, so let's create one:

def create
end

And let's go back, fill out, and submit the form again... and this time, nothing happens.

This is because we've done something a little different this time than we've done before. Remember that, in the past, when we've gotten this error, we solved it by creating a view file – that is, if we were to add a file called create.html.erb in our app/views/companies/ folder, this error would go away. But that's not what we want in this case. Not all actions have views! In this case, we want our action to be a method in our controller that only does three things:

  • Grab the information the user filled out in the form
  • Use the information to create a new Company and save it to the database
  • Send the user back to the full list of companies

Let's work backwards and add a line of code to our action that will do the last thing – send the user back to the list of companies.

def create
  redirect_to "/companies"
end

Submit the form again, and we'll notice that we're back at the list of companies. But the new company we tried to create isn't there – of course, this is because we didn't do anything with the data submitted.

Remember how we've previously talked about the transfer of state between pages/actions on the web? And how each request-response cycle doesn't really know about the previous ones, or really anything about the order things have happened in the user's journey? The transition from new to create that we're doing here is really no different. We have to get the data that the new action has sent to our create action, and that's done in the same way we've done before – through the params hash.

Let's comb through our server log (that's the output from the rails server command) and look for the POSTing of the form. We'll find this:

Started POST "/companies" for ...
Processing by CompaniesController#create as HTML
  Parameters: {"authenticity_token"=>"[FILTERED]", "company"=>{"name"=>"Netflix", "city"=>"Los Gatos", "state"=>"CA"}, "commit"=>"Create Company"}

Ah! The data the user has typed into the form is, indeed, in the params hash, under the key for "company". So params["company"] contains all the data we need to create a new company. Let's use it in our controller!

def create
  @company = Company.new(params["company"])
  @company.save
  redirect_to "/companies"
end

If we go back, fill out the form, and submit... success!

Try it!

What better way to get some practice with creating records than by creating our own social network!

  • You'll find that there's a PostsController and an index action/view set up already in the project.
  • Add a new.html.erb view and corresponding new action in the controller; create a form in new.html.erb that will allow the end-user to input a post's author, body, and image (see db/schema.rb). author is the author's name (e.g. Brian), body is the text body of the post (e.g. Don't these tacos look awesome?) and image is simply the address of an image on the Internet.
  • For example, we can head over to https://unsplash.com/ and search for square images of tacos – right-click and grab the URL of the image.
  • Add a create action to accept values entered into the form and redirect back to the page with all posts.

Handling Relationships

Creating a company is a good example of a single-model form. But what about contacts? Contacts belong to a company, and the contacts table has a contact_id to fill in – how do we handle a situation like this?

There are a couple of good ways to treat a model form that requires a relationship. The first way would be, as we've done already, to use query string parameters to transfer state from one action to the next. For example, we could create a link on, say, the companies/show page that allows our user to "Create a new contact for this company". Let's give that a try.

<p>
  <a href="/contacts/new?company_id=<%= @company.id %>">New contact for <%= @company.name %></a>
</p>
In app/views/companies/show.html.erb

This is fairly straightforward to implement and the user experience is not bad, either. Clicking on the link will take us to the contacts/new action, transferring the company_id of the company for which we want to create a contact. Then, in the code for that action:

def new
  @contact = Contact.new
  @contact.company_id = params["company_id"]
end
In app/controllers/contacts_controller.rb

We're creating a variable to for the new contact form to use, just like we did for the new company form. We're also pre-filling the company_id field on that new contact, using the company_id we've passed into the action via the query string.

Almost there! Only the view and the code for the create action is left; here is a good start:

<h1>New Contact</h1>

<%= form_with model: @contact do |form| %>
  <p>
    <%= form.label "first_name" %>
    <%= form.text_field "first_name" %>
    <%= form.label "last_name" %>
    <%= form.text_field "last_name" %>
  </p>
  <p>
    <%= form.label "email" %>
    <%= form.text_field "email" %>
    <%= form.label "phone_number" %>
    <%= form.text_field "phone_number" %>
  </p>
  <%= form.submit %>
<% end %>
app/views/contacts/new.html.erb
def create
  @contact = Contact.new(params["contact"])
  @contact.save
  redirect_to "/companies/#{@contact.company.id}"
end
In app/controllers/contacts_controller.rb

Isn't there a problem, though? Where did our company_id go? We did fill it in, in the code for our new action, but don't end up passing it to the create action, do we? In fact, if we end up running this code as-is, we'll find that a new contact does get created in the database, but that it's not associated with any company, because the company_id is blank.

The issue is that the @contact.company_id that we provided in the new action isn't anywhere on the form. So, when the form gets submitted, that data is not part of it and isn't included in the params hash.

The solution is to put the company_id in the form! However, we don't want to make it a user-editable field. That is, we could make it another text_field, but the user would then see 123 or whatever the ID of the company is, and have the ability to edit that field. Luckily, there is a different type of HTML form field we can use, one that's designed precisely for this purpose – a hidden field.

<h1>New Contact</h1>

<%= form_with :model => @contact do |form| %>
  <p>
    <%= form.label "first_name" %>
    <%= form.text_field "first_name" %>
    <%= form.label "last_name" %>
    <%= form.text_field "last_name" %>
  </p>
  <p>
    <%= form.label "email" %>
    <%= form.text_field "email" %>
    <%= form.label "phone_number" %>
    <%= form.text_field "phone_number" %>
  </p>
  <%= form.hidden_field "company_id" %>
  <%= form.submit %>
<% end %>

We can put it anywhere we want to, within the form_with tag... because it's hidden!

Now, the company_id will be successfully included each step of the way – we've transferred state all the way from the <a> tag to create a new contact for a company, through the form, and finally, to the create action.

Relationships in Form Fields

We mentioned that passing the association via the query string is only one way to handle creating records that include relationships. We can also include the associated ID in the form itself, much like we did with the hidden field – only not hidden. Instead, we can provide a choice for the user. A common UX for this is a dropdown box, or a select element in HTML parlance.

Let's look at this in action. Suppose we didn't want a "New contact for this company" link, and instead wanted just a general "new contact" form, with the company as a choice from a select dropdown? First, we should remove the code that queries and fills the company in the new action of the contacts controller:

def new
  @contact = Contact.new
  @contact["company_id"] = params["company_id"]
end
In app/controllers/contacts_controller.rb, with code that gets the company from the query string commented out

Then, we can re-write our view with a new form helper that will create the HTML dropdown we need:

<h1>New Contact</h1>

<%= form_with :model => @contact do |form| %>
  <p>
    <%= form.label "first_name" %>
    <%= form.text_field "first_name" %>
    <%= form.label "last_name" %>
    <%= form.text_field "last_name" %>
  </p>
  <p>
    <%= form.label "email" %>
    <%= form.text_field "email" %>
    <%= form.label "phone_number" %>
    <%= form.text_field "phone_number" %>
  </p>
  <p>
    <%= form.label "company_id" %>
    <%= form.collection_select "company_id", Company.all, "id", "name" %>
  </p>
  <%= form.submit %>
<% end %>

Here, we're using another form helper called collection_select. This form helper, as we can see, requires 4 parameters:

  1. The name of the field (i.e. column in the database)
  2. The collection to select from – in this case, we want all companies to be in the dropdown, but we can easily have this be any subset of that data that we would like
  3. The name of the column that will be used as the value in the params hash – in this case, we want the numeric ID of the company, so we're using the id field
  4. The name of the column we'd like to use in the dropdown itself – we want the name of the company to be in the dropdown box

And the net result is this – a dropdown where the end-user can select the company for the new contact:

Rails has a variety of form helpers to choose from. You can read about them all on the very helpful Rails Guides website:

Action View Form Helpers — Ruby on Rails Guides
Action View Form HelpersForms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of the need to handle form control naming and its numerous attributes. Rails does away with this complexity by providing view hel…