読者です 読者をやめる 読者になる 読者になる

ボクココ

サービス開発を成功させるまでの歩み

Grape on Rails で API 開発

Rails

ども、@kimihom です。2016年になりました。今年もよろしくお願いします。

前回は、API の周辺技術の選定と設計について書いた。 さて、昨年から書き続けている API 関連の話題だが、実際にコード書くところを細かく見ていきたいと思う。スタートさえうまくいけば後は追加していくだけなので、環境作りとコードの構成のところで参考になる部分があれば幸いだ。

環境構築

さて、 Grape の世界に飛び込もう。

Gemfile

# api
gem 'grape'
gem 'grape-swagger'
gem 'grape-swagger-rails'
gem 'grape-entity'
gem 'api-pagination'
gem 'ruby-swagger'

前にもちょっと解説したが、それぞれのGemを解説しよう。

  • Grape : API 開発用のRuby DSL
  • grape-entity: リクエストやレスポンスをモデルと紐付ける
  • grape-swagger: Swagger 形式でドキュメントを出力してくれる
  • grape-swagger-rails : grape-swaggerで出力した Swagger 形式を 自分のRails アプリのViewとして見ることができる
  • api-paginattion: ページング Gem であるkaminari を Grapeで扱えるようにする
  • ruby-swagger: Swagger 2.0 形式で JSON 出力する rake タスク。API Gateway 連携で利用

config/application.rb

前回の話で、 app/api/v1 のような感じでパスを切ることにしていたので、そのルーティング設定回り。

    # grape api settings
    config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
    config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]

config/routes

以下をroutes.rbの一番下に追加しないと、通常の Rails アプリにおける MethodOverride(POSTリクエスト _method="put" を付与するとPUTとして受け取る) が正しく動作しない問題があったのでご注意を。

  # API
  mount API::Root => '/'
  mount GrapeSwaggerRails::Engine => '/swagger'

app/api/api.rb

バージョン振り分けのみ。

module API
  class Root < Grape::API
    mount V1::Root
  end
end

app/api/v1/root.rb

Grape のドキュメントを見ればわかるが、 rescue_from は各種 API 内で発生した例外を補足することができる。なので、:allの所に例外クラスを指定すれば、その時のレスポンスコードも定義可能だ。また。routes.rbの設定と、add_swagger_documentationにより、http://localhost:3000/swagger で Swagger ドキュメントにアクセスできる。

module V1
  class Root < Grape::API
    version 'v1'
    format :json

    rescue_from :all do |e|
      Root.logger.error(e.message.to_s << "\n" << e.backtrace.join("\n"))
      error!({ message: "Server Error"}, 500)
    end

    # Each APIs
    mount V1::Users

    # swagger doc
    add_swagger_documentation(
      api_version: "v1",
      base_path: "#{ENV['API_BASE_PATH']}",
      hide_documentation_path: true,
      info: {
        title: "APIドキュメント",
        description: "説明です。"
      }
    )
  end
end

app/api/v1/users.rb

実際のAPI Resource。 Swaggerドキュメントのために色々とドキュメントも書いてある。今回は1つのファイルにGrape::Entity と Grape::API それぞれが入っている構成にした。こっちの方が個人的にはわかりやすい。headers, errors は場合によって追加。 entity, response は Swaggger形式のjsonファイル出力とそこからの API Gateway 連携のために必要。

module V1
  class UsersEntity < Grape::Entity
    expose :id, documentation: {type: Integer, desc: "ユーザーid"}
    expose :email, documentation: {type: String, desc: "メールアドレス"}
    expose :name, documentation: {type: String, desc: "名前"}
    expose :created_at, documentation: {type: String, desc: "作成日時"}
    expose :updated_at, documentation: {type: String, desc: "更新日時"}
  end

  class Users < Grape::API
    helpers RequestHelper

    resource 'users', desc: 'ユーザー', swagger: { nested: false } do
      # GET /v1/users
      desc 'ユーザーリストの取得', {
        entity: UsersEntity,
        response: {isArray: true, entity: UsersEntity}
        #headers: [...],
        #errors: [...]
      }
      get do
        authenticate!
        present @users, with: UsersEntity
      end
  end
end

app/api/v1/request_helper.rb

ここでトークンを認証する。あらかじめ、 User と 1対多 な関係の ApiKey モデルを作成しておく。 HTTP Header に今回の例だと X-AccessToken を指定すれば認証が実行される。

module V1
  module RequestHelper
    extend Grape::API::Helpers
    def authenticate!
      error!({message: 'Invalid Token', code: 401}, 401) unless current_user
    end

    def current_user
      header_token = request.headers["X-Access-Token"]
      token = ApiKey.where(access_token: header_token).first
      return false if token.blank? || !token.active
      @user = token.user
    end
  end
end

こうすると Grape 内で authenticate! が呼ばれ、@user が入った状態で各種APIを書くことができる。

終わりに

サンプルコードしか載せなかった今回だが、適宜ドキュメントを見ながらやれば躓くことはあまりないはずだ。それくらい Grape とその周辺 Gem は洗練されていて使いやすかった。

あとはもう Rest API をゴリゴリ書いていくだけだ。

次はいよいよ Amazon API Gateway との連携。次回を持って API 連載シリーズを終えたいと思う。