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 to the new
view. The main differences are:
- the
action
attribute is directing the form to submit to the update route - the form fields are pre-populated with the existing values using the
value
attribute in the HTML.
<h1>Edit Company</h1>
<form action="/companies/<%= @company["id"] %>" method="post">
<input type="hidden" name="_method" value="patch">
<p>
<label for="name">Name</label>
<input type="text" name="name" value="<%= @company["name"] %>">
</p>
<p>
<label for="city">City</label>
<input type="text" name="city" value="<%= @company["city"] %>">
<label for="state">State</label>
<input type="text" name="state" value="<%= @company["state"] %>">
</p>
<button>Save</button>
</form>
There is one other important difference. By default, the form
element in HTML doesn't know about the PATCH
method. So to trick it, we use a hidden input field that will override the form's request method and send our request to the update action as it should in the REST pattern.
Now, let's examine the controller:
def new
end
def create
@company = Company.new
@company["name"] = params["name"]
@company["city"] = params["city"]
@company["state"] = params["state"]
@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["name"] = params["name"]
@company["city"] = params["city"]
@company["state"] = params["state"]
@company.save
redirect_to "/companies"
end
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:
<form action="/companies/<%= @company["id"] %>" method="post">
<input type="hidden" name="_method" value="delete">
<button>Delete Company</button>
</form>
And similar to the edit form, we override the form method with a hidden field so that the request follows the REST design pattern.
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
end
def create
@company = Company.new
@company["name"] = params["name"]
@company["city"] = params["city"]
@company["state"] = params["state"]
@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["name"] = params["name"]
@company["city"] = params["city"]
@company["state"] = params["state"]
@company.save
redirect_to "/companies/#{@company["id"]}"
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.