• 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.

Resources