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:

  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
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. 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.