Controller

Controller is the C in MVC. In this section, we are going to write a primitive API endpoint.

When a request is sent to your API, routes (that you have defined) will pass the request to corresponding controllers for handling. Inside the controller, you can define how yu want to handle and process the request.

For example, we want to allow any person to create a book record through a URL.

There are 4 main types of requests: GET / POST / PUT / DELETE to get / create / update / delete information, respectively.

We are going to create an API endpoint that accepts requests with POST method. The endpoint would be /books.

So, if you make a POST request to localhost:3000/books with the correct parameters, it should create new records in the book table in our database.

First of all, we have to define a route first.

In config/routes.rb,

Rails.application.routes.draw do
  get '/', to: 'static_pages#index'

  # <http-request-type> <url>, to: '<controller-name>#<method-in-controller>'
  post '/books', to: 'books#create'
end

Now that we have the route defined, can we just try sending a request already? Sure! But, how do you send a POST request? Can you do it on a Chrome browser? Yes, but you need some other tools to help you. Normally when you put a URL in the browser's URL bar, it will only make a GET request.

To help us make a lot of different HTTP requests, we are going to use a tool called Postman. Please install Postman in your Chrome. After installation, there's no need to sign up. Just click on "Skip this, go straight to the app".

Before we move on, we also need to go to app/controllers/application_controller.rb and comment out the line protect_from_forgery with: :exception. We will explain this later on.

Let's test to see if it works. Even though you probably know that it won't work, it's still good to see what errors it will produce.

First, open Postman.

Let's input the URL localhost:3000/books with the HTTP request type set to POST. Hit Send.

Are you getting "Could not get any response"? If so, that's because you haven't started the server yet! Do you remember how to start the server? Hint: $ rails s

So, after making sure that your server is indeed running, Hit Send again to try sending the request. Now, you should be getting "Routing Error", which is what we expected.

Let's look at the error message. It says "uninitialized constant BooksController". It implies that it cannot find the BooksController. And, indeed, we haven't created the controller BooksController.

So, let's fix this problem by creating our controller. Similar to creating a model, there's a generator Rails provided.

To generate the books controller, simply say $ rails generate controller books. The generic format is $ rails generate controller <name of controller in plural>.

Note that books here is plural. Remember this. For models, it's singular. For controllers, it's plural. This is just a Rails convention.

$ rails g controller books

     create  app/controllers/books_controller.rb
     invoke  erb
     create    app/views/books
     invoke  test_unit
     create    test/controllers/books_controller_test.rb
     invoke  helper
     create    app/helpers/books_helper.rb
     invoke    test_unit
     invoke  assets
     invoke    coffee
     create      app/assets/javascripts/books.coffee
     invoke    scss
     create      app/assets/stylesheets/books.scss

The controller generator has generated several files for us. The one file that we are currently interested in is the main file for the books controller app/controllers/books_controller.rb.

In app/controllers/books_controller.rb,

class BooksController < ApplicationController
end

So, let's try to test the endpoint again. Go back to Postman and send a new request.

This time, it gives you a different error "The action 'create' could not be found for BooksController". Was this expected?

Well, our route was post '/books', to: 'books#create'. We made the books controller, but we haven't defined the create method. So, yes, this is expected, and let's define the create method.

In app/controllers/books_controller.rb,

class BooksController < ApplicationController
  def create

  end
end

Let's try to test it on Postman again.

This time, we received a blank page. And, this is also expected because everything seems to be working.

The purpose of the controller is to:

  1. take inputs from a request
  2. process them and interact with the database if needed
  3. output a response

The controller, by default, will respond with the view file app/views/books/create.html.erb, which is currently empty.

So, let's try to specify what we want to respond.

class BooksController < ApplicationController
  def create
    render text: "This works!"
  end
end

Here we are skipping the defaule view file app/views/books/create.html.erb and directly rendering text using the method render.

The render method can define the response. It takes several parameters, such as the format and content of the response. Here, we rendered "This works!" in text format.

However, the response format for a RESTful API is JSON, rather than text.

JSON stands for JavaScript Object Notation, which has every characteristics that a JavaScript Object has, except you cannot put functions in it. You can read JSON: What It Is, How It Works, & How to Use It

class BooksController < ApplicationController
  def create
    render json: { name: 'Nash' }
  end
end

As expected, we get the output:

{
  "name": "Nash"
}

Alright. So, now we can output responses the way we'd like to. Let's try to output the input parameters. No processing, just output. How do we retrieve the input values?

class BooksController < ApplicationController
  def create
    render json: params
  end
end

params is the keyword variable to retrieve all parameters (inputs) from the endpoint user. If we don't change the request and just output the params, we will get

{
  "controller": "books",
  "action": "create"
}

When we add some parameters in the body, it will be reflected in the response as well.

{
  "name": "Harry Potter",
  "controller": "books",
  "action": "create"
}

Likewise, to access individual parameter, we can do params[:name] to access the value paired with the key name.

We said that the purpose of the controller is also to process the input parameters from a request and interact with the database. In this case, we want to store the input data by creating a new record in the database. Still remember how to create a new record in the database using Rails model?

In the last section, we said Book.create(name: 'Harry Potter') will generate SQL necessary to create a book record in the database.

Let's try the following.

class BooksController < ApplicationController
  def create
    new_book = Book.create(name: params[:name])

    render json: { book: new_book }
  end
end
  • params[:name] retrieves the name of the book
  • Book.create(name: params[:name]) creates a new record in the Book table
  • We store the new record in the variable named new_book.
  • After interacting with the database, the method responds back with the JSON object { book: new_book }.

Now, try sending a new request. You should get:

{
  "book": {
    "id": 7,
    "name": "Harry Potter",
    "publishing_year": null,
    "num_of_pages": null,
    "created_at": "2016-12-13T07:26:13.074Z",
    "updated_at": "2016-12-13T07:26:13.074Z"
  }
}

THERE! We have successfully gone through the entire MVC framework once!

  • Step 1: You (the client) send the server a request demanding that a Book of yours to be created in the database
  • Step 2: Router interprets the URL, your request and its method type
  • Step 3: Router pass the request along to the books controller responsible for processing
  • Step 4: The books controller goes through the Book model to interact with the database to create a new book record.
  • Step 5: The books controller, in this case, doesn't pick a view (response templates) but directly defines the response.
  • Step 6: A response is sent back to client.

results matching ""

    No results matching ""