2020年9月1日火曜日

ruby grape 超入門

概要

grape は RESTful な API を構築するためのフレームワークです
DSL を使うことで簡単に RESTful API を構築することができます
今回は基本的に GET/POST/DELETE/PUT の API を grape で構築してみたので紹介します

環境

  • macOS 10.15.6
  • Ruby 2.7.1p83
    • grape 1.4.0

インストール

  • bundle init
  • vim Gemfile
gem "grape"
  • bundle config path vendor
  • bundle install

Getting Started

とりあえず超簡単なサンプルを作成してみます

  • vim app.rb
require 'grape'

module Test
  class API < Grape::API
    format :json
    prefix :api

    resource :blog do
      desc 'Return an article.'
      get :article do
        {
          "title" => "title",
          "body" => "body"
        }
      end
    end
  end
end
  • vim config.ru
require './app'

Test::API.compile!
run Test::API
  • bundle exec rackup config.ru

記事を取得するリクエストは以下のようになります

  • localhost:9292/api/blog/article

とりあえずは単純な JSON が返ってくるだけです
ここから POST や DELETE, PUT の API を追加していきます

POST の追加

まずは記事を追加できる機能を作ってみます
本来ならデータベースで管理したほうが良いですが今回はテストなのでメモリ上で管理します

require 'grape'

module Test
  class API < Grape::API
    format :json
    prefix :api
    articles = []

    resource :blog do
      desc 'Return an article.'
      get :article do
        articles
      end

      desc 'Create new article.'
      params do
        requires :title, type: String, desc: 'An article title'
        requires :body, type: String, desc: 'An article body'
      end
      post do
        articles.append({
          title: params[:title],
          body: params[:body]
        })
      end
    end
  end
end

articles という配列を変数として管理し POST 時にその配列に追加していきます
必須パラメータは requires を使います
必要なパラメータ分 requires しタイプとパラメータの説明を付与します
実際に処理をするブロック内でパラメータを参照する場合は params[:title] という感じで取得します

記事を登録するリクエストは以下のようになります

  • curl -XPOST -H 'Content-Type: application/json' -d '{"title":"First article","body":"Today is sunny"}' localhost:9292/api/blog

また記事を GET する API も articles をそのまま返却するようにします

DELETE の追加

次に記事を削除する API を追加します
今回は配列のインデックスで削除できるようにしてみます

require 'grape'

module Test
  class API < Grape::API
    format :json
    prefix :api
    articles = []

    resource :blog do
      desc 'Return an article.'
      get :article do
        articles
      end

      desc 'Create new article.'
      params do
        requires :title, type: String, desc: 'An article title'
        requires :body, type: String, desc: 'An article body'
      end
      post do
        articles.append({
          title: params[:title],
          body: params[:body]
        })
      end

      desc 'Delete an article.'
      params do
        requires :index, type: Integer, desc: 'An article index'
      end
      delete ':index' do
        error!("Does not found index", 404) if articles[params[:index]].nil?
        articles.delete_at(params[:index])
      end
    end
  end
end

記事を削除するリクエストは以下のようになります

  • curl -XDELETE localhost:9292/api/blog/0

もし指定のインデックスが存在しない場合には {"error":"Does not found index"} が返ってきます
:index のように URI からパラメータを受け取る場合には delete ':index' という感じで引数に指定します
こうすることでブロック内で params[:index] として値を受け取ることができるようになります

PUT の追加

最後に更新の API を作ってみましょう
これまで実装してきた POST や DELETE の機能を組み合わせる感じになります

require 'grape'

module Test
  class API < Grape::API
    format :json
    prefix :api
    articles = []

    resource :blog do
      desc 'Return an article.'
      get :article do
        articles
      end

      desc 'Create new article.'
      params do
        requires :title, type: String, desc: 'An article title'
        requires :body, type: String, desc: 'An article body'
      end
      post do
        articles.append({
          title: params[:title],
          body: params[:body]
        })
      end

      desc 'Delete an article.'
      params do
        requires :index, type: Integer, desc: 'An article index'
      end
      delete ':index' do
        error!("Does not found index", 404) if articles[params[:index]].nil?
        articles.delete_at(params[:index])
      end

      desc 'Update an article.'
      params do
        requires :index, type: Integer, desc: 'An article index'
        requires :title, type: String, desc: 'An article title'
        requires :body, type: String, desc: 'An article body'
      end
      put ':index' do
        error!("Does not found index", 404) if articles[params[:index]].nil?
        articles[params[:index]] = {
          title: params[:title],
          body: params[:body]
        }
      end
    end
  end
end

PUT のリクエストは以下のようになります

  • curl -XPUT -H 'Content-Type: application/json' -d '{"title":"Third article","body":"It is cloudy"}' localhost:9292/api/blog/0

DELETE と同様で index がない場合にはエラーにしています

helpers メソッドを使う

helpers メソッドはブロック内に定義したメソッドを各 API で使うことができる機能です
各 API での共通処理などを記述しておきます
例えば認証機能などを各 API に付与したい場合には以下のように使えます

require 'grape'

module Test
  class API < Grape::API
    format :json
    prefix :api
    articles = []
    @username = ""

    http_basic do |username, password|
      @username = username
    end

    helpers do
      def authenticate
        error!('Unauthorized', 401) unless @username == "admin"
      end
    end

    resource :blog do
      desc 'Return an article.'
      get :article do
        authenticate
        articles
      end
    end
  end
end

ベーシック認証で特定のユーザ名やパスワードでないとアクセスできないようになります
helpers 内で変数を参照する場合はインスタンス変数にしておきましょう

最後に

grape を使って簡単な RESTful API を構築してみました
DSL なので初めは少し戸惑うかもしれませんが慣れれば簡単に使えると思います
RESTful API に必要な基本的なヘッダ処理 (Content-Type や Method) などのハンドリングがすでに実装されているのは嬉しい点かなと思います

まだ使い込んでないので何とも言えませんが REST 専用のフレームワークっぽいので HTML などを返却する場合には Rails や Sinatra などを使ったほうが良いかもしれません
ただ grape には swagger と連携したりRails と連携して REST を構築したりできる拡張機能も充実しているのでサードパーティ系のエコシステムが充実していそうな感じもします

参考サイト

0 件のコメント:

コメントを投稿