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>
app/views/companies/edit.html.erb

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
In app/controllers/companies_controller.rb

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, empty Company, the data to be filled out by the end-user
  • In edit, we're grabbing the existing Company from the database; the ID is provided in the URL and is available in the params hash as params["id"]
  • In create, we're getting the user-inputted data from the params hash and using it to create and save a new Company
  • In update, we're getting the user-inputted data from the params hash as well; but first, we're grabbing the existing Company from the database based on the provided id in the params hash, then we're updating the existing record using the data from the params 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.