Rails Controllers & Routing




Lesson Objectives

After this lesson, students will be able to:

  • Review setting up a Rails project
  • Review generating and running migrations
  • Use the Faker gem to seed data
  • Configure RESTFul routes with the Rails router
  • Explain what rails routes returns
  • Write controller methods for index and show



Setup

We are going to make an app called songs_app_api.

This will be a single-model app that serves json data for songs.

  • Initialize an app called songs_app_api
rails new songs_app_api --api --skip-git -d postgresql 
  • Change into the new directory:
cd songs_app_api

Start the server:

  • Run rails s to start the server on port 3000
  • rails s is short for rails server
  • Go to localhost:3000 in the browser.

We should all get an error in the browser.

FATAL: database \"____________\" does not exist

JSON response:

screenshot

To format JSON nicely, use JSON Formatter Chrome extension

  • Quit the server: ctrl + c
  • Create the database: rails db:create
  • Run the server again: rails s. We should see this:

screenshot






Make a Song model

Our app will serve a JSON catalog of songs

  • Generate the migration for the songs table: rails g migration CreateSongs
  • In the migration file, the song table should have columns for title, artist_name, and artwork (all strings):
class CreateSongs < ActiveRecord::Migration[5.0]
  def change
    create_table :songs do |t|
      t.string :title
      t.string :artist_name
      t.string :artwork
    end
  end
end

screenshot

  • Run the migration: rails db:migrate
  • Check schema.rb
  • Create the model file: app/models/song.rb
  • Add in the class Song
class Song < ApplicationRecord

end

screenshot




SEED

Install Faker

We will seed a little differently this time using a gem called Faker to give us a bunch of random results. With Faker we won't have to invent any seed data.

  • Add gem faker to the Gemfile. (It can go anywhere but let's put it around line 15/16)

screenshot

  • Run bundle to install all your gems

screenshot




Use Faker

In seeds.rb let's write some Ruby to use the Faker gem in a loop:

10.times do
  Song.create(
    title: Faker::Hipster.sentence(3),
    artist_name: Faker::Team.name,
    artwork: Faker::Placeholdit.image("50x50")
  )
end

screenshot

Faker documentation

What is :: the double colon

  • Run the seed file: rails db:seed
  • Check in rails console rails c:
Song.all
Song.find(1)

screenshot




Routes



Establish the routes

Routing works differently in Rails than in Node/Express. There is a separate file that will provide a convention for our routes. We can't just make them willy-nilly.

  • Open config/routes.rb
  • If we add the line resources :songs, Rails will route to the five JSON RESTFul routes for the resource songs.
Rails.application.routes.draw do
  resources :songs
end

screenshot

  • Run rails routes

This will show us the routes that have been baked-in for us:

$ rails routes
Prefix Verb   URI Pattern          Controller#Action
 songs GET    /songs(.:format)     songs#index
       POST   /songs(.:format)     songs#create
  song GET    /songs/:id(.:format) songs#show
       PATCH  /songs/:id(.:format) songs#update
       PUT    /songs/:id(.:format) songs#update
       DELETE /songs/:id(.:format) songs#destroy

screenshot




Rails Routes What is this stuff?

This is a list of all the routes that Rails knows about given what we put in config/routes.rb. We could change that file and rails routes would give us different results. For now, this is what we have:

Let's look at the first line

Ignore the prefix for now

Verb is GET and URI Pattern is /songs, so we can assume we have a GET route for songs.

The Controller#Action is songs#index. This means that for this route, we will have to make a method called index within the controller (coming soon). That method will handle requests to GET /songs.

So, for our index route, there will will be uri /songs (that has no params or anything) according to the usual RESTFul conventions.

Explanation of .:format can be ignored.

Let's look at the other lines

As for the others, they are the same pattern we have seen for RESTFul routing of create, show, update, and destroy routes.

For show, update, and destroy, Rails tells us that the first param will be called :id. We don't have a choice what it will be called. But at least rails routes tells us what is expected.

Rails routes docs




Controllers



Make a controller file

convention over configuration

Rails is expecting your controller file to conform to its convention. The convention is model-name plural underscore controller.

Put it in the app/controllers folder:

app/controllers/songs_controller.rb

screenshot




Make the controller class

See that application_controller.rb file? We want our controller to inherit from that thing.

class SongsController < ApplicationController
end



screenshot

Index

Our controller is going to have a method for each route. If we rails routes, what method do we use for an index route?

The answer is: index.

songs_controller.rb

def index
end

This is the same business like when we did this in Express for our index route:

router.get('/songs', function(req, res) {

});

It's much shorter in Rails.

render() method

To render JSON we use the render method and pass it a hash: render json: some_value

  def index
    render json: "hi"
  end

The render method takes a hash. To make more visual sense of this, you could write it this way:

  def index
    render( json: "hi" )
  end

Or, this way:

  def index
    render({ :json => "hi" })
  end

Check it out in the browser.

To get our songs data, we could just write raw SQL into the controller:

  def index
    render json: ActiveRecord::Base.connection.execute('SELECT * FROM songs;')
  end

re-factored

def index
  query_string = "SELECT * FROM songs;"
  results = ActiveRecord::Base.connection.execute(query_string)
  render json: results
end

But what we want is to use the convenient Ruby shorthand provided by ActiveRecord: Song.all. The same queries we have been using in rails console.

Putting it together:

class SongsController < ApplicationController

  def index
	render json: Song.all
  end

end

screenshot

Checking our rails routes, what is the URI for our index route? Let's go to that route in the browser.

localhost:3000/songs

If all goes according to plan, we have successfully established our API:

screenshot




Status Codes

If we are going to deliver our API to some end-user, we should provide a status code.

Change the code to deliver a 200 OK status code:

  def index
    songs = Song.all
    render json: {status: 200, songs: songs}
  end

screenshot

screenshot

Visit the index route in the browser.




Show

Looking at rails routes, what method do we use for a show route?

The answer is: show

Show routes always come with an id attached -- The purpose of a show route it to display one of a given resource, which we get by id.

Looking at rails routes, the param will be called :id. This is just the first param in the uri. Rails has gone ahead and just named the first param :id.

In Express, we got the id through req.params.id

In Rails, The URL params are stored in a globally existing hash called params. We can access params inside the params hash like so:

params[:id]

What ActiveRecord query could we use to get a particular song using the id?

Answer: .find() will do.

  def show
    render json: Song.find(params[:id])
  end

screenshot

Looking at rails routes, what URI do we visit to see the show page?

localhost:3000/songs/1

screenshot


Status Code

Include the status code, 200 OK

  def show
    song = Song.find(params[:id])
    render json: {status: 200, song: song}
  end

screenshot

  • Visit it in the browser

screenshot




Params hash

In Express, the params came through in an object, req.params.

In Rails, the request object also has params, and those params are also stored as key-value pairs (in a hash).

There not much difference.

To see the params, try putsing them in the controller, and seeing the output in Terminal:

screenshot

Terminal:

screenshot

screenshot