Handling Requests

Learning Goals

  • Create a new rails app
  • Explain the purpose of the routes.rb file
  • Interpret the output of rails routes
  • Explain the connection between routes.rb and controller files
  • Create routes by hand
  • Create and migrate a database
  • Create a basic view to render some data

Vocabulary

  • CRUD
  • MVC
  • Migration
  • HTTP Request
  • HTTP Response
  • Route
  • Action

Handling HTTP Requests

Our Rails app’s responsibility is to receive HTTP Requests and send back HTTP Responses. When we start our server with rails server or rails s, we are telling our app to wait patiently for a client to send it a request. When our app receives a request, our app then needs to handle the request. This includes both what should happen in the database (CRUD), and what response (HTML) should be sent back to the client.

Verb and Path

Every request needs to be able to tell a server what information is requested and how that information is being requested. The what is the path (also known as a URI), indicating what resource this request is referencing.

Examples of a path:

  • /tasks
  • /tasks/4
  • /items/6/reviews

The how is the verb, indicating what actions the server should take regarding the requested resource. While the path can vary greatly based on the application, the verbs follow common patterns. There are 5 common HTTP verbs:

  • GET - retrieve some information to be READ by the client/user
  • POST - CREATE a new resource with information contained in the request
  • PUT - UPDATE an entire resource with information contained in the request
  • PATCH - UPDATE a part of a resource with information contained in the request
  • DELETE - DESTROY a resource, typically indicating that it is removed from the database

With these 5 verbs, a client can send requests that allow our app to perform all CRUD functions (create, read, update, destroy) for resources in the database.

What is a “URL”?

Users tell a client to ask for information by giving it a URL: a Universal Resource Locator.

A URL allows us to send data to, or retrieve, a “resource” on the Internet. A resource could be a page of HTML content, it could be an image or music file, or it could be part of a web application that will save data you send to it.

URL vs URI/Path

You may also hear the term URI used interchangeably with “path” when talking about things on the Internet. A “URI”, or “Universal Resource Identifier” is not the same as a URL, but it’s easy to confuse them.

A URI is part of a URL (see below).

Parts of a URL

Following the example of this URL:

http://task-manager.herokuapp.com/task/new?title=New&task=Task#new_form_anchor

We can take this example and split it into distinct parts:

  • Protocol: http:// - Tells us the application protocol we will be using to interact on the web.
  • Domain: task-manager.herokuapp.com - Tells us where the resources we are trying to access are located (tied to an IP address using DNS).
  • URI or “Path”: /task/new - The specific path for the resources that we are trying to access at that location.
  • Query String: ?title=New&task=Task - Params that give our server additional information about what we would like to access.
  • Fragment Identifier: #new_form_anchor - An indicator of a specific section of a website we would like to view (e.g. if there is an anchor tag tied to a heading half way down the page). This can be seen by visiting this link to the rails docs which references the array-conditions section of the Query Interface page.

SetList

Now that we have some background in URLs, Paths, and HTTP Request/Responses, let’s put it all together to design a feature in a new app we will be developing together, SetList.

In SetList, we want Users to have the ability to manage Songs, so our first task will be to allow users to see (READ) all Songs. Here is the behavior we are looking for:

  • A user opens a web browser and types this URL into their address bar: http://localhost:3000/songs
    • Note that we are using the domain localhost:3000 because we are developing locally. When this app is live on the internet, it will have a different domain. If we are hosting on Heroku, it will have a domain name like set-list.herokuapp.com, so the request to that site would be http://set-list.herokuapp.com/songs
  • When the user hits enter, an HTTP request will be sent. That request will have two key pieces of information
    • An HTTP Verb of GET (URLs entered into an address bar will default to GET)
    • A Path (or URI) of /songs
  • Upon receiving that request, our App should send back an HTTP response of an HTML page that shows all of the songs

Create your app

“Convention over Configuration”

Let’s create a whole new Rails app. We’re going to use this codebase for the rest of the inning in mod 2.

$ rails new set_list -T --database=postgresql
$ cd set_list
  • T - Rails has minitest by default. When this flag is used, gem 'minitest' will not be in the Gemfile.
  • -database=postgresql - by default, Rails uses sqlite3. We want to tell it to use postgresql instead because platforms we use for deploying our projects will expect to use a PostgreSQL database.

Take a few minutes to explore what rails new generates.

Gems

Add pry to your group :development, :test block in your Gemfile.

Gemfile

group :development, :test do
  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem "debug", platforms: %i[ mri mingw x64_mingw ]
  gem "pry"
end

Always run bundle install whenever you update your Gemfile.**

Create the Database

If you try to run your server with rails s and visit localhost:3000 in your browser you will see the error:

FATAL: database "set_list_development" does not exist

Let’s create our app’s database with

$ rails db:create

Refresh the page and you should see a big red Rails logo.

Routing the Request

Our first error when we visit localhost:3000/songs is a RoutingError No route matches [GET] "/songs".

Rails doesn’t know how to route a GET request to /songs, so let’s tell it how to do that.

config/routes.rb

Rails.application.routes.draw do
  get "/songs", to: "songs#index"
end

Remember, an HTTP request includes both the verb (GET in this case) and the path (/songs).

The second argument, to: "songs#index", tells Rails what controller action should handle the request. The format is <controller>#<action>. In this format, the controller name is snaked case. Rails will interpret this snake case into a camel-cased controller name. For example, to: snake_cased_names#index would look for a SnakeCasedNames controller.

From the command line, we can see which routes we have available by running 

$ rails routes

We should see this output:

Prefix Verb URI Pattern      Controller#Action
songs  GET  /songs(.:format) songs#index

This means whenever a get VERB is used in a request to the path /songs, the application will look at a “Songs” controller, and look for an “action method” in there called index.

Don’t worry about the (.:format) piece on the end of the URI pattern. You’ll use that more in mod3 and allows us to retrieve resource data in different formats than HTML, such as CSV or XML.

Based on our Rails routes, what controller do we need? Do we have it?

Let’s refresh our browser request to localhost:3000/songs.

uninitialized constant SongsController

Make a songs controller.

$ touch app/controllers/songs_controller.rb

Naming is important. The name of the file should be the plural of what it is handling (in this case, songs).

**app/controllers/songs_controller.rb**

class SongsController < ApplicationController

end

Notice that the name of the class matches the name of the file (songs_controller.rb => class SongsController), the file is “snake-cased” and the class name is “camel-cased”.

What is ApplicationController? Look at the controllers folder and you should see an application_controller.rb file. This file defines the ApplicationController class, which (generally) all of your other controllers will inherit from. This is very similar to how we have an ApplicationRecord which all of our models inherit from.

When we refresh our browser, we get this error:

The action 'index' could not be found for SongsController

Let’s add the index action.

app/controllers/songs_controller.rb

class SongsController < ApplicationController
  def index
  end
end

An action in the context of a rails app is a method defined inside a Controller.

We have now successfully routed our request to a controller action.

Rendering the View

Refresh the page again and you should see this error.

ActionController::UnknownFormat:
    SongsController#index is missing a template for request formats text/html

    NOTE!
    Unless told otherwise, Rails expects an action to render a template with the same name,
    contained in a folder named after its controller. If this controller is an API responding with 204 (No Content),
    which does not require a template, then this error will occur when trying to access it via browser,
    since we expect an HTML template to be rendered for such requests. If that's the case, carry on.

Remember, HTTP consists of both requests and responses.

At this point, we have set up our App to receive a request, but haven’t told it how to respond. By default, rails will try to respond by rendering a view. It automatically looks for a view folder with the same name as the controller (app/views/songs folder), then look for a template file with the same name as the action method (index.html.erb). We can override this behavior by using the render or redirect commands, but for now we will follow the Rails convention.

$ mkdir app/views/songs
$ touch app/views/songs/index.html.erb

Rails convention says that we should include the output format type in the filename and then the .erb extension. That’s why this file is index.html.erb rather than index.erb. The latter would work, but it would not be following convention.

Refresh your page and you should see a blank page. We have successfully rendered a response.

Before we move on. Lets pry.

**app/controllers/songs_controller.rb**

class SongsController < ApplicationController
  def index
    binding.pry
  end
end

Refresh the page and check the terminal where the server is running. You should see something like:

Started GET "/songs" for ::1 at 2020-10-06 15:58:55 -0600
Processing by SongsController#index as HTML

From: /Users/tim_tyrrell/staff_turing/2mod/explorations/set_list_2008/app/controllers/songs_controller.rb:3 SongsController#index:

   2: def index
=> 3:   require "pry"; binding.pry
   4: end

[1] pry(<SongsController>)>

Lets start here. Your controllers and views have access to both a request AND response object that Rails builds for you with each request.

Type in request in your pry session.

That’s not incredibly helpful, because it’s an overload of information that we need to parse through. But, what if we scope the inspection of this request object to be more specific?

Type in ls request in your pry session.

This will list all of the methods available for the request object

Try these 3 methods in your pry session: request.get? request.path request.base_url?

Now we’re getting much more helpful information.

Lets look at the response object:

Type in ls response in your pry session.

This will list all of the methods available for the response object

Try these 3 methods in your pry session: response.status response.body response.sent?

Remember that each of these objects will have a plethora of relevant information about the request/response cycle at any given time.

Database Setup

We want this page to show all the Songs from our database. Let’s set up our Songs table.

We are going to need a migration in order to create a table. Any time we need to alter the structure of our database, we are going to do so with a migration. Some other examples of what you might need a migration for:

  • Creating a Table
  • Adding/removing a column from a table
  • Renaming a table/column
  • Much More

Luckily, Rails gives us some built in syntax for generating migrations:

$ rails g migration CreateSongs title:string length:integer play_count:integer

Look inside the db/migrate folder and you should see a file named something like 20190422213736_create_songs.rb. That number at the beginning is a timestamp, so yours will be different. Inside it, you should see:

class CreateSongs < ActiveRecord::Migration[7.0]
  def change
    create_table :songs do |t|
      t.string :title
      t.integer :length
      t.integer :play_count

      t.timestamps
    end
  end
end

We have written the instructions for our database but haven’t executed those instructions. Run rails db:migrate.

Our Database should be good to go.

Song Model

Now that we have our table set up, we will need to create a corresponding Model in order to interact with that table from our Ruby code.

$ touch app/models/song.rb

Note that our model files will always be named in singular form (song.rb, not songs.rb)

**app/models/song.rb**

class Song < ApplicationRecord
end

We want this model to inherit from ActiveRecord so why are we using ApplicationRecord  instead? If we take a look around, we see a file in the app/models directory called  application_record.rb. Open that and peek around:

**app/models/application_record.rb**

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

That file inherits from ActiveRecord already, so if we inherit from ApplicationRecord then we’ll also inherit ActiveRecord. This allows us to have a parent model that could contain some shared methods/behaviors.

Creating Songs

In order to see Songs on our index page, we are going to need to create some Songs. We might later add in a feature where can create a new song with a form, but we don’t have the feature yet, so we are going to have to insert Songs into our database manually.

Let’s open up the Rails console with rails console or rails c from the command line.

The Rails console will give you access to your development database through your models. If you enter Song.all into the console, you should get back an empty ActiveRecord Relation. This is because we don’t have any Songs in our database yet, so let’s create some:

irb(main):015:0> Song.create(title: "I Really Like You", length: 208, play_count: 23546543)
irb(main):016:0> Song.create(title: "Call Me Maybe", length: 431, play_count: 8759430)

You should see this output:

(0.3ms)  BEGIN
  Song Create (0.5ms)  INSERT INTO "songs" ("title", "length", "play_count", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["title", "I Really Like You"], ["length", 208], ["play_count", 23546543], ["created_at", "2020-08-10 20:45:58.643242"], ["updated_at", "2020-08-10 20:45:58.643242"]]
   (0.5ms)  COMMIT

...
   (0.4ms)  BEGIN
  Song Create (0.4ms)  INSERT INTO "songs" ("title", "length", "play_count", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["title", "Call Me Maybe"], ["length", 431], ["play_count", 8759430], ["created_at", "2020-08-10 20:46:21.379206"], ["updated_at", "2020-08-10 20:46:21.379206"]]
   (1.7ms)  COMMIT

You can see in this output that our ActiveRecord commands were translated into SQL to insert new rows into the database.

Now if we do Song.all, we should see some Songs coming back from our Database.

Displaying Songs in the View

Now that we have Songs in our Database, let’s retrieve them in our controller action:

**app/controllers/songs_controller.rb**

class SongsController < ApplicationController
  def index
    @songs = Song.all
  end
end

Remember, any instance variables you create in the controller action will be available in the view.

Let’s display these songs in the view:

**app/views/songs/index.html.erb**

<h1>All Songs</h1>

<% @songs.each do |song| %>
  <h2><%= song.title %></h2>
  <p>Play Count: <%= song.play_count %></p>
<% end %>

Start up your rails server: rails server or rails s from the command line.

Navigate to localhost:3000/songs to see your songs.

Put a pry in your views at any point to inspect your variables!

Checks for Understanding

  • What setup do you need to do to start a new Rails application?
  • What is a migration?
  • What command do you use to create a database?
  • What command do you use to apply migrations?
  • Where do our routes live?
  • What is the syntax to define a route?
  • How is MVC implemented in Rails?
  • Explain the four pieces of information that rails routes give us.
  • In a Rails app, what is an “action”?
  • Where does Rails look for a view by default?
  • Which helpful objects can we view in pry in both our views and controllers?

Lesson Search Results

Showing top 10 results