API on Rails
Who am I?
What is API and why do we need it?
Goals
- Flexible
- Consistent
- Fast
API types
REST API
GET /v1/users.json users#index
GET /v1/users/:id.json users#show
POST /v1/users.json users#create
PATCH|PUT /v1/users/:id.json users#update
DELETE /v1/users/:id.json users#destroyAuthentication
Signing requests
Gem 'api-auth'
canonical_string = [
http_method, content_type, content_MD5, request_URI, timestamp
].join('.')Canonical string is then used to create the signature which is a Base64 encoded SHA1 HMAC, using the client's private secret key.
This signature is then added as the Authorization HTTP header in the form:
Authorization = APIAuth #{client_access_id}:#{canonical_string}API action(grape)
# app/api/v1/users_api.rb
module V1
class UsersApi < Grape::API
namespace :users do
desc 'Returns a list of users for company.', {
entity: Entities::User,
is_array: true
}
params do
requires :company_id, type: Integer
end
get do
company = Company.find(params[:company_id])
scope = company.users
scope = scope.includes(:projects) if params[:include_projects]
last_updated_at = User.last_updated_at(scope)
cache("users", last_updated_at, { parent: "company-#{params[:company_id]}" }) do
present(
scope, with: Entities::User, include_projects: params[:include_projects]
).as_json
end
end
end
end
end
API action(rails-api)
# app/controllers/api/v1/users_controller.rb
module Api::V1
class UsersController < ApiController
# GET /v1/users
def index
scope = company.users
scope = scope.includes(:projects) if params[:include_projects]
render json: if params[:inclide_projects]
scope.as_json(include: :projects)
else
scope
end
end
private def company
@company ||= Company.find(params[:company_id])
end
end
endSerializers (grape)
# app/api/v1/entities/user.rb
module V1
module Entities
class User < Grape::Entity
root 'result', 'result'
expose :id, documentation: { type: "Integer"}
expose :first_name, documentation: { type: "String", desc: "First name" }
expose :last_name, documentation: { type: "String", desc: "Last name" }
expose :projects, if: { include_projects: :true }, using: V1::Entities::Project
expose :avatar
private def avatar
self.object.avatar.file.scale.url
end
end
end
end
Serializers (jbuilder)
# app/views/api/v1/users/index.jbuilder
json.users do
json.id user.id
json.first_name user.first_name
json.last_name user.last_name
json.array! user.projects, partial: 'projects/project', as: :project
endVersions
Postman

RSpec
require 'rails_helper'
describe "users api" do
let(:basic_headers){{"Accept-Version" => 'v1'}}
let(:response_body) { JSON.parse response.body }
context "GET #index" do
let(:company) { create(:company) }
let!(:correct_user) { create(:user, company: company) }
let!(:incorrect_user) { create(:user) }
it "returns users for given company" do
get '/api/users', params: { company_id: company.id }, headers: basic_headers
expect(response.status).to eq(200)
expect(response_body["result"].length).to eq 1
expect(response_body["result"][0]["id"]).to eq correct_user.id
end
end
end
API on Rails
By Andrii Sapozhnykov
API on Rails
- 243