Welcome to the Rails 6 API tutorial. In this series we’ll walk through building a backend API using Ruby on Rails. The topics in this series include:

  1. Creating a Rails API Project and Routing
  2. Basic Controller and Models
  3. Building a POST Endpoint
  4. HTTP Status Codes
  5. Active Record Validations
  6. Destroy Controller Action
  7. Exception Handling in Controllers

Generating a controller

In part 1 we added a route for GET /books. The next step is to add a controller for it. To do that we can use the Rails generator, via the command line we can do:

$ bin/rails g controller BooksController index

This will generate a controller called BooksController with an index action method.

This will also create an additional route which looks like get books/index. We don’t need that because we’ve already written the route so the generated route can be deleted.

The generated controller can be found under app/controller/books_controller.rb and looks like this:

class BooksController < ApplicationController
  def index
  end
end

As you can see, BooksController is a regular Ruby class that inherits from ApplicationController. The inheritance provides the class with methods for handling incoming requests, params, etc.

You’ll notice that the class was also generated with an empty index method (Rails developers call this an ‘index action’). Even though the method is empty it does have a purpose. Any requests which hit BooksController#index will be served a successful response, with a 204 status code.

Calling the controller action via cURL

We can test that via cURL:

$ curl http://localhost:3000/books -v

You should see that the cURL request returns a 204 status code. We can also look at the server logs:

Started GET "/books" for ::1 at 2020-10-27 19:31:35 +0100
Processed by BooksController#index as */*
Completed 204 No Content in 0ms (ActiveRecord: 0.0ms | Allocations: 32)

The server log shows that we made a GET request to /books, the request was processed by the BooksController#index method and it returned a 204.

Updating the controller action to return a list of objects

Currently BooksController#index returns a successful response code but it doesn’t return any data. Let’s update the method to return a list of book objects:

class BooksController < ApplicationController
  def index
    render json: Book.all
  end
end

We’ll use render, with the json option and pass in an ActiveRecord collection. In this case we use Book.all which will return all books in the database. We haven’t defined the Book model yet but we’ll do that next.

Generating a model

We can generate a model using the Rails generate command. We just need to provide a name for our model and a list of attributes:

rails g model Book title:string author:string

In this example we’re generating a model called Book which has two fields, title and author, both of which are string fields. When we run the command Rails will generate a couple of files for us:

  • a migration file - db/migrate/20200620150063_create_books.rb
  • a model file - app/models/book.rb
  • some test files

Here’s a look inside the files that were created:

book.rb

class Book < ApplicationRecord
end

As you can see there’s not much going on in this file, at the moment. We’re defining a Book class and inheriting from ApplicationRecord. Although we haven’t added any of our own logic, to this file, it still gives us a lot of capabilities. By inheriting from ApplicationRecord we’re giving Book all the power of Rails, by making it an ActiveRecord class.

Rails will map this Ruby class to the book table in our database. We can then use this model to access the database in our Rails application by calling methods. For example: Book.all will return all records.

migration file

class CreateBooks < ActiveRecord::Migration[6.0]
  def change
    create_table :books do |t|
      t.string :title
      t.string :author

      t.timestamps
    end
  end
end

As well as the books model, we need to actually create a database table. To do that, Rails has generated a migration file for us which is like a blueprint for creating a database table. Here we’re creating a table called books with two fields: title and author, both of which are string fields. The table also includes created_at and updated_at timestamps. This table will be used to store books in our system.

If we try to run the server now it will fail because we have migrations in our codebase that haven’t been applied yet. So before we continue we should run the migrations. To do that, we can execute:

$ bin/rails db:migrate

This will apply the migration, creating the books table. You may be wondering which database Rails has updated. Rails ships with sqlite3 by default. When you run the migrations you may notice a new file appear called development.sqlite3. This is your development database.

Testing the controller with cURL

Now that we’ve run the migrations we can test the controller/action again using the cURL request we ran before:

$ curl http://localhost:3000/books
[]

In the response you’ll notice we’re getting back an empty array. This is what we would expect because the controller is returning all books but we don’t have any books in the database to return. This is good because it means the response is consistent whether we have zero, one or multiple books being returned

Creating some database records in the rails console

To test the controller/action when we have records in the database, we can use the rails console. To start the console run:

bin/rails c

Then in the prompt you can create a Book by doing:

> Book.create!(author: 'J.K Rowling', title: 'Harry Potter and the Philosophers Stone')
=> #<Book id: 1, title: "Harry Potter and the Philosophers Stone", author: "J.K Rowling", created_at: "2020-11-20 07:48:12", updated_at: "2020-11-20 07:48:12">

create is an ActiveRecord query method that allows us to create database records. It’s available to all of our ActiveRecord models and takes a hash of database fields as it’s arguments.

Now we can run the cURL request again to see some data in the response body:

$ curl http://localhost:3000/books
[{"id": 1, "title": "Harry Potter and the Philosophers Stone", "author": "J.K Rowling", "created_at": "2020-11-20 07:48:12", "updated_at": "2020-11-20 07:48:12"}]