Once again, here's the "cheatsheet" for all of the available actions for a resource, from the last couple of lessons:
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 |
Let's complete the story of REST. We've done index
, show
, new
, and create
– only edit
, update
, and destroy
are left.
Edit/Update
The editing and subsequent updating of existing data is very similar to new
and create
. The only difference, of course, is that we're dealing with data that's already in the database instead of brand-new records. In fact, the edit
view is usually going to be very similar, if not identical, to the new
view:
<%= 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 this case, the new
and edit
views are indeed identical. One of the really nice things about using the form_with
helper, instead of writing our own form
element is that we can give it an instance of a model – @company
in this case – and it automatically determines whether it's a new record that's not already saved to the database, or if it's an existing, saved record. If it's a new record, submitting the form will perform a POST
, and if it's an existing record, submitting the form will perform a PATCH
, conforming to our formula of REST as specified in our cheatsheet.
Now, let's examine the controller:
There are only a couple of differences between new/create and edit/update, due to the nature of new vs. existing data.
- In
new
, we're creating a brand-new, emptyCompany
, the data to be filled out by the end-user - In
edit
, we're grabbing the existingCompany
from the database; the ID is provided in the URL and is available in theparams
hash asparams["id"]
- In
create
, we're getting the user-inputted data from theparams
hash and using it to create and save a newCompany
- In
update
, we're getting the user-inputted data from theparams
hash as well; but first, we're grabbing the existingCompany
from the database based on the providedid
in theparams
hash, then we're updating the existing record using the data from theparams
hash
Deleting (Destroying) Data
Finally, we've reached the final action of REST – deleting an existing record. Let's add a destroy
action to our CompaniesController
:
def destroy
@company = Company.find_by({ "id" => params["id"] })
@company.destroy
redirect_to "/companies"
end
When this action is invoked, we'll start by finding the existing record in the data, based on the id
provided in the params
hash – much like the edit
or update
actions. We'll then perform the destroy
method on the Company
. This permanently and irreversibly deletes this record from the database. Once that's done, we then send the user back to the list of companies.
But, how do we invoke this action? We've talked about how an end-user can only perform GET
requests via the URL, so we can't create a link to do this or otherwise provide a web address for a user to delete a record. And for good reason! It's a destructive action, and we want to be able to control the user's access to it.
Just like HTTP POST
and PATCH
requests, DELETE
requests can only be performed via a form submission. Does that mean that the user needs to fill out a form to delete a record? No – that would suck – we want the user to be able to delete data by clicking a button. But the underlying implementation is indeed a form submission. Luckily, we don't have to worry about building the actual form – for this, we can use another of Rails' form helpers, and it's all taken care of for us.
For this example, let's put the button to delete a company on the company show
page:
<p>
<%= button_to "Delete this company", @company, :method => "delete" %>
</p>
As mentioned, the Rails button_to
helper does all of the work here. We simply give it the text of the button, the Company
in question, and the HTTP method that we want – DELETE
. Under the hood in the generated HTML, a form is built that submits to the destroy
action, and there's nothing else for us to worry about.
The Completed Controller
Here is the complete code for our CompaniesController
, in all its glory:
class CompaniesController < ApplicationController
def index
@companies = Company.all
end
def show
@company = Company.find_by({ "id" => params["id"] })
end
def new
@company = Company.new
end
def create
@company = Company.new(params["company"])
@company.save
redirect_to "/companies"
end
def edit
@company = Company.find_by({ "id" => params["id"] })
end
def update
@company = Company.find_by({ "id" => params["id"] })
@company.update(params["company"])
redirect_to "/companies"
end
def destroy
@company = Company.find_by({ "id" => params["id"] })
@company.destroy
redirect_to "/companies"
end
end
Of course, this is a fairly basic implementation. It doesn't include things like data validation, error handling, security, or otherwise any other logic that protects against users doing weird things – which they often do. But it works! We've successfully built an app that completely CRUDs company data and allows for the creation of associated contact data as well.
The best news of all is that, with the exception of possible small changes in business logic, all of our controllers can follow the exact same formula that we've followed here. As we add controllers and views for activities, salespeople, industries, or whatever else might be a part of our CRM world, we can use the same conventions and techniques as we have for companies. That's the beauty of REST, MVC, and software design patterns in general – we don't have to "reinvent the wheel" for every new thing we want to build. We only need to think about our domain model and our resources, and we're well on our way to building our finished product.