Today we will create a Rails API. There are several ways to build an API in Rails.
If you're new to Ruby and Rails, check out the website GoRails - it will help you get started with a development environment for Rails. For this episode, we assume you already have this up and running.
Project
Formulae is a Rails based form management system. It comes in a few parts, the first being a Rails app that lets you create and manage complex forms. The second piece is a gem that you can plug into your Rails projects that acts as a client, allowing you to interact with the API’s resources. The third and final part is the front end stack, made up of a React app that renders the forms and provides the end user experience.
Today we will look at the creation of the first part, the Rails application that hosts and manages the forms. Let's get started by creating our Rails app:
rails new formulae
This will generate an entire Rails app for you. If you want, you can take a look in the folders and files it has generated.
We will use a linter in our project. It's called rubocop . Linters help us enforce coding standards. With Rubocop, we can decide which rules we will use. For example, here is our rules file that specifies our coding conventions . It will run before our tests in our Continuous Integration server, so if someone does not follow our coding standards, the build will fail. This is an interesting way to enforce coding style across your project, especially for open source projects when many people are used to writing in their own disparate styles.
Another interesting library that will run in our CI will be Brakeman , Brakeman helps us to find security vulnerabilities, especially if a library is outdated and has security issues. It’s a great thing to add to our build when we’re first getting started.
Setting up our tests
We will be using
rspec
to test our models. Additionally, we will use some
libraries to help us when writing our tests. Rails by default uses
minitest
,which is a great testing
library, but we prefer
rspec
so we’ll roll with it. The first thing we can do is
remove the
/test
folder, since it is used by
minitest
and not
rspec
. Next,
Let's add
rspec-rails
to our
Gemfile
and
bundle
Now we can set up our Rails app for testing with
rspec
, using the following
command:
rails generate rspec:install
It will generate some files and it creates a
/spec
folder where our files will
be located. To run our tests, we just need to run
rspec
.
We will be using some other libraries to help us: Factory Girl to build models for our tests. Faker to produce realistic looking fake data in our factories. Shoulda Matchers to make our tests read a bit more nicely.
We’ll talk about these in more detail as we go. For now, let’s go ahead and add
them to the Gemfile and
bundle
again to install them. Creating our Models
Our application’s data model contains Forms, Questions, Choices, and more. Let's create a Question model using Rails’ model generator.
rails g model Question key:string label:string content:text order:integer hidden:boolean
This generates our Question model, as well as the test files using rspec, and a migration.
The relations and validations are inside the model file, but if we want to check the fields, we need to check the migration.
Creating our Controllers
Since it is an API that needs to be versioned, we will not use a generator for controller. Instead, we will namespace the controllers under the app/controllers/api/v1 folder. We will use this standard to keep track of the files from our API, and provide an API version;in this case V1. Versioning our API means we can make changes in the future to it without worrying about breaking existing integrations.
We need to create our routes on
routes.rb
, in our routes we are using our
namespace api and v1, corresponding to our version. When we
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :questions
end
end
end
Now we have our routes for this controller, let's create a
QuestionsController
at this structure in our controllers folder:
/api/v1/questions_controller.rb
It will have:
index
,
show
,
update
, and
destroy
methods. All these
methods will return JSON for us.
Before we move on, we’ll create an
ApiController
that all of our API
controllers inherit from. This way we can share common code between them.
Let's see how our index method would be:
def index
@questions = Question.all
render json: @questions
end
Or more simply:
def index
render json: Question.all
end
In this case, we are using the method render to render the json for us.
The
show
method will get the question id and it will return the question
object for us.
def show
@question = Question.find(params[:id])
render json: @question
end
As the
Question.find
will be re-used in other methods, we can create a
before_action
in our controller and say a method will be executed before
specific actions. In our case:
before_action :find_question, only: %i[show destroy update]
Rails filters parameters by default to help protect against malicious requests. For the parameters to get passed through, we need to explicitly whitelist them. In our case we will have a method question_params that will do it for us.
def question_params
params
.require(:question)
.permit(
:key,
:label,
:content,
:order,
:hidden
)
end
Basically this method checks if the key is question and it checks the fields of this hash. So, we are validating we are really using the necessary fields for the Question.
We’ll use this in the
update
method. Let’s go ahead and add that. We’ll try to
update, and we’ll tell the client whether or not it was successful.
def update
if @question.update_attributes(question_params)
render json: @question, status: :ok
else
render json: @question.errors, status: :unprocessable_entity
end
end
And finally we'll add the
destroy
method, for removing a resource.
def destroy
if @question.destroy
render json: :no_content, status: :no_content
else
render json: @question.errors, status: :unprocessable_entity
end
end
Here you can see what our finished controller looks like.
# frozen_string_literal: true
class Api::V1::QuestionsController < Api::V1::ApiController
before_action :find_question, only: %i[show destroy update]
def index
@questions = Question.all
render json: @questions
end
def show
render json: @question
end
def update
if @question.update_attributes(question_params)
render json: @question, status: :ok
else
render json: @question.errors, status: :unprocessable_entity
end
end
def destroy
if @question.destroy
render json: :no_content, status: :no_content
else
render json: @question.errors, status: :unprocessable_entity
end
end
private
def find_question
@question = Question.find(params[:id])
end
def question_params
params.require(:question).permit(:key,
:label,
:content,
:order,
:hidden,
:question_type,
:validate_as,
:section_id)
end
end
We have created other models to finish our application and you can
see them in
our repo at the tag
add-models
.
Summary
Today we saw how to create a Rails app, how to set up an API, and created some models and controllers. We also saw how to create our tests and some interesting libraries we can use in our app to make it better.
See you soon!
Resources
- Ruby on Rails Website
- Ruby on Rails Guides
- Help you set up your Ruby on Rails environment
- Code for this episode + other models
- Render method
- Factory Girl to build models for our tests.
- rspec-rails
- Faker to produce realistic looking fake data in our factories.
- Shoulda Matchers to make our tests read a bit more nicely.
Comments for Setting up your Rails API