- Why we used rails
-
Controllers
- API
- Models
- Nested Attributes
- How we tested our API
- How we generate docs
We could use another language to build our back-end, for example Elixir. There are undoubtedly other great languages. But we choose Ruby and the framework Rails. The environment to build an API with Rails is great. We have a bunch of tools to help the entire process.
Controllers
We are using simple rails controllers to handle our requests. We could use something like grape , but we chose that for simplicity.
Let's check our routes. We have a namespace for the API version. We are also using shallow nesting in our routes to avoid deep nesting.
Rails.application.routes.draw do
devise_for :admin_users, ActiveAdmin::Devise.config
ActiveAdmin.routes(self)
namespace :api do
namespace :v1 do
resources :form_submissions
resources :forms, shallow: true, except: %i[new edit] do
resources :sections, except: %i[new edit] do
resources :questions, except: %i[new create edit] do
resources :choices, except: %i[new create edit]
end
end
end
end
end
end
Let's check our form controller.
We are following the rails standard with methods: index, create, show, update, etc.
Everything starts when we check the authorization, each form has a key and we check that.
vim app/controllers/api/v1/authorized_controller.rb
class AuthorizedController < ApplicationController
protect_from_forgery with: :exception
before_action :authenticate_request
private def authenticate_request
command = AuthorizeApiRequest.call(request.headers)
if command.success?
@api_key = command.result
render_no_application
@application = @api_key.application
else
render json: { error: 'Not Authorized' }, status: 401
end
end
private def render_no_application
render json: { error: 'There is no application for this Api Key' }, status: 500 if @api_key.application.nil?
end
end
The ApiController inherits from
AuthorizedController
.
vim app/controllers/api/v1/api_controller.rb
class Api::V1::ApiController < AuthorizedController
skip_before_action :verify_authenticity_token
end
And all our controllers inherits from
ApiController
.
In our FormsController, in our index, we render all forms, we get the form from the application that is already set in our
AuthorizedController
.
def index
@forms = @application.forms.all
render json: @forms
end
def show
render json: @form
end
In our controller, we can just render the variable. The responsibility of what our JSON will look like will be with our serializer. Here we are using Active Model Serializer.
class FormSerializer < ActiveModel::Serializer
attributes :id, :application, :completion_content
has_many :sections
has_many :questions
end
We pass the attributes we have, in this case
id
,
application
, and
completion_content
. The interesting thing using this is the relationships. We have sections and questions. We just need to say, has many questions and has many sections. It will call the sections and questions serializer and render this.
Nested Attributes
One interesting thing to say about our controllers is the use of Nested Attributes. We have nested models in our form. A form has many sections, a section has many questions and question can have many choices. Nested things. We just send receive this and our rails app handles all the details if we are updating a model or deleting it. We have an episode only talking about it here at DailyDrip.com . Without it, we would need to add a lot of logic to handle the details.
We handle the nested attributes when we receive the parameters. As you can see here in the
make_form_params
method.
Testing
We used rspec-api-documentation to test our API. With this, when we test our API, we are also generating the docs. The tests are generated into docs. To have a better visualization of these docs, we used apitome .
If we check apitome documentation, we can set layout, title, themes and other information. And we can always change the CSS.
vim config/initializers/apitome.rb
This is great. Let's take a look at how we did our tests. Our tests are located in /acceptance. For each endpoint we have tests.
For example, for our
form
endpoint:
resource 'Forms' do
let(:authorization) { nil }
let!(:form_object) { FactoryGirl.create(:form, :with_questions_and_choices) }
header 'Content-Type', 'application/json'
header 'Authorization', :authorization
context 'authorized' do
let(:api_key) { form_object.application.api_keys.first }
...
We have the resource name, we can set headers and context. All this information will be used in our tests and it will be in our docs.
...
example_request 'Updating a Form' do
response = JSON.parse(response_body)
expect(response.keys).to eq %w[id application completion_content sections questions]
expect(response['id']).to eq form_object.id
expect(response['completion_content']).to eq new_completion_content
expect(response['application']['id']).to eq form_object.application_id
expect(status).to eq(200)
end
...
We can use
example_request
to make requests to the endpoint and inside of this block, make our tests.
We tested our models using rspec, shoulda matchers and factory girl, there is an episode talking about it here at DailyDrip .
Here is one example of how we tested our form:
require 'rails_helper'
RSpec.describe Form, type: :model do
describe 'fields' do
it { is_expected.to respond_to(:application_id) }
it { is_expected.to respond_to(:completion_content) }
end
describe 'relations' do
it { is_expected.to belong_to(:application) }
it { is_expected.to have_many(:questions).through(:sections) }
it { is_expected.to have_many(:questions) }
end
end
Documentation
We do our tests. ow, the best part is we can generate its docs. There is a rake docs that runs the tests and generates the output.
rake docs:generate
Doing that, we can open
doc/api/index.html
and our html output will be here.
open doc/api/index.html
Here is our generated documentation. There are our endpoints, that in our case they were our resources. We can see Choices, Form Submissions, Forms.
Let's see creating a Form documentation. There is the body, response, headers and also a cURL command to be tested. This is really good.
Summary
Today we saw an overview of how we have implemented the Formulae back-end.
Comments for Formulae: The back-end