Grape Rails.

Grape Rails.

Grape - это API-фреймворк для Ruby. Grape создан для работы с Rack приложениями. Для создания API используется простой DSL (domain specific language). Grape поддерживает разнообразные общепринятые практики при создании API.

Для установки, добавить в Gemfile:

gem 'grape'

или сразу "всё" необходимое для Rails

# grape
gem 'grape'
gem 'grape-entity'
# swagger
gem 'grape-swagger'
gem 'grape-swagger-entity'
gem 'grape-swagger-rails'
gem 'grape-swagger-representable'

Простой пример:

module Twitter
  class API < Grape::API
    version 'v1', using: :path
    format :json
    perfix :api
    
    helpers do
      def current_user
        @current_user ||= User.authorize!(env)
      end
      
      def authenticate!
        error!('401 Unautorized', 401) unless current_user
      end
    end
    
    resource :statuces do
      get :public_timeline do
        Status.limit(20)
      end
      
      # GET --
      desc 'Get status'
      params do
        requires :id, type: Integer, desc: 'Status ID'
      end
      route_param :id do
        get do
          Status.find(params[:id])
        end
      end
      
      # POST --
      desc 'Create'
      params do 
        requires :status, type: String, desc: 'Status'
      end
      post do
        authenticate!
        Status.create({
          user: current_user,
          text: params[:status]
        })
      end
      
      # PUT --
      desc 'Update'
      params do 
        requires :id, type: Integer, desc: 'ID'
        requires :sttaus, type: String, desc: 'Status'        
      end
      put :id do
        authenticate!
        current_user.statuses.find(params[:id]).update({
        	user: current_user,
            text: params[:status]
        })
      end
      
      # DELETE --
      desc 'Delete'
      params do
        requires :id, type: Integer, desc: 'ID'
      end
      delete :id do
        authenticate!
        current_user.statuses.find(params[:id]).destroy
      end
    end
  end
end

Grape копмилирует маршруты при первом запросе, но можно принудительно создать все маршруты API методом compile!

Twitter::API.compile!

Вызов можно добавить в config/application.rb или в любой файл которые участвует в загрузке сервера.

# compile and pre-load
Twitter::API.compile!
run Twitter::API

Будут созданы маршруты

GET /api/v1/statuses/public_timeline
GET /api/v1/statuses/:id
POST /api/v1/statuses
PUT /api/v1/statuses/:id
DELETE /api/v1/statuses/:id

Grape автоматически отвечает HEAD и OPTIONS для всех GET запросов, и только OPTIONS для всех остальных.

Для использования с Rails (6.1) монтируем API в файле config/routes.rb.

mount Twitter::API => '/'

Для поддержки имени класса API, добавить акроним в config/initializers/inflections.rb

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym 'API'
end

API можно разбить на модули

class API < Grape::API

  # сначала декларации и объявления
  # затем уже монтирование модулей

  mount Users::API
  mount Products::API
  mount Lagacy::API => '/legacy';
end

Параметры запроса доступны через Hash params. Включая GET, POST, PUT параметры и именованные параметры пути. Так же поддерживается multipart.

Rails Api

Для создания приложения

rails new my_api --api

Три основные вещи

  • Приложение будет создано с минимальным набором промежуточных модулей чем обычное. В первую очередь не будут  включены модули, которые отвечают за работу приложения  в браузере (например поддержка cookie)
  • ApplicationController будет унаследован от ActionController::API вместо ActionController::Base.
  • Генераторы не будут создавать шаблоны, помощники.

Add Grape

  • config/application.rb
  • config/initializers/swagger.rb
  • config/routes.rb
  • app/assets/manifest/config.js
  • config/initializers/cors.rb
Чтобы Swagger работал потребуется добавить в config/application.rb require "sprockets/railtie". При создании приложения в api mode, эта функция отключена.

На начальном этапе config/application.rb может выглядеть примерно так:

# config/application.rb

require_relative "boot"

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_mailbox/engine"
require "action_text/engine"
require "action_view/railtie"
require "action_cable/engine"
require "sprockets/railtie" # required by swagger
require "rails/test_unit/railtie"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module FilimonApi
  class Application < Rails::Application    
    config.load_defaults 6.1
    config.api_only = true
  end
end

Добавить акроним API

# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym 'API'
end

Настройки Swagger

# config/initializers/swagger.rb
GrapeSwaggerRails.options.url = '/api/v1/swagger_doc.json'
GrapeSwaggerRails.options.app_url  = 'http://localhost:3000'
GrapeSwaggerRails.options.app_name = 'My API'

Swagger assets

// app/assets/config/manifest.js
//= link grape_swagger_rails/application.css
//= link grape_swagger_rails/application.js

CORS, можно настроить в соответствие с потребностями проекта

# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
    resource '*', headers: :any, methods: [:get, :post, :patch, :put]
  end
end

Маршруты, например Swagger подключается в режиме разработки

Rails.application.routes.draw do  
  mount API => '/'
  mount GrapeSwaggerRails::Engine => '/swagger' if Rails.env.development?
end

API

Создаем директорию app/api и файл app/api/api.rb, например

# app/api/api.rb
require 'grape-swagger'

class API < Grape::API
  format :json
  prefix :api
  version 'v1', using: :path
  
  # GET /api/v1
  get do
    {version: '1'}
  end
  
  # EXAMPLES
  
  # /api/v1/users
  resources :user do
    # GET /api/v1/users
    get do
      User.all
    end
    
    # POST /api/v1/users
    # <input name="user[email]" type="email">
    # <input name="user[password]" type="password">
    # <input name="user[file]" type="file">
    params do
      requires :user, type: Hash do
        requires :email, type: String
        requires :password, type: String
        optional :avatar, type: File
      end
    end
    post do
      User.create!(params[:user])
    end    
  end
  
  # Swagger docs
  add_swagger_documentation
end
Swagger будет доступен по адресу http://localhost:3000/swagger