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:
- take inputs from a request
- process them and interact with the database if needed
- 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 methodrender
.
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 bookBook.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 theBook
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.