ども、@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 連載シリーズを終えたいと思う。