Finishing the Story – Edit, Update, and Delete
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
actionattribute is directing the form to submit to the update route - the form fields are pre-populated with the existing values using the
valueattribute 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"
endThere 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 existingCompanyfrom the database; the ID is provided in the URL and is available in theparamshash asparams["id"] - In
create, we're getting the user-inputted data from theparamshash and using it to create and save a newCompany - In
update, we're getting the user-inputted data from theparamshash as well; but first, we're grabbing the existingCompanyfrom the database based on the providedidin theparamshash, then we're updating the existing record using the data from theparamshash
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"
endWhen 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
endOf 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.