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:
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 POST
ing 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 anindex
action/view set up already in the project. - Add a
new.html.erb
view and correspondingnew
action in the controller; create a form innew.html.erb
that will allow the end-user to input a post'sauthor
,body
, andimage
(seedb/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?) andimage
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.
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:
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:
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:
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:
- The name of the field (i.e. column in the database)
- 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
- 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 theid
field - 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: