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

あかんわ

ブログで日誌でも書くようにすれば多少はやる気が出るかと思ったんです

GrapeとDoorkeeperでRailsアプリに認証付きのREST-like Web APIを実装する 前編

Railsチュートリアルの第2版を参考にして作ったマイクロブログに、GrapeDoorkeeperを使用して、OAuth認可を利用するWeb APIを実装しようと試みました。

前編では、Grapeを使ったAPIの実装までを記載しています。
Doorkeeperを使ったOAuth認可機能の実装は、後編に記載します。

目次

補足:実装したいAPI v1の仕様

  • マイクロブログにチャットボットからマイクロポストをPOSTしたい
  • APIを利用するアプリの作成や認可はサインインしているユーザのみ可能にしたい
  • つまるところTwitter APIの機能を縮小したようなAPIを作りたい

APIの実装を試みるマイクロブログは、Railsチュートリアルの第2版を参考にして作ってますので、UserモデルとMicropostモデルが関連して成り立っています。

f:id:b0npu:20160709222053p:plain

認証システムも、認証システムを提供するgemを使用せずにRailssessionメソッドを使用して構築していますので、sessionメソッドを使用する認証メソッドをSessionsHelperに定義して、色々な場所で認証メソッドを使えるようになっています。

GrapeとDooreeperのGemを追加

REST-like APIフレームワークGrapeと、OAuth 2プロパイダ機能を導入できるDoorkeeperのgemをGemfileに追加*1し、bundle installします。

gem 'grape', '0.16.2'
gem 'doorkeeper', '4.0.0'

GrapeでAPIを実装

RailsアプリにGrapeでAPIを実装する場合の作法に習ってapp/apiディレクトリを作成し、APIを構成するファイルを配置します。
一般的に、APIはバージョンごとにパスを分ける方が良いらしいので、API v1の機能を記述したv1_api.rbを配置するために、v1ディレクトリも作成します。

 /railsapp  
    |- /app  
    |    |- /api  
    |    |    |- api.rb  
    |    |    |- /v1  
    |    |        |- v1_api.rb  
config/application.rb

Railsapp/api配下のファイルやディレクトリを読み込むように、configにパスの自動読込を記述します。

require File.expand_path('../boot', __FILE__)
・
・
・
  class Application < Rails::Application
  ・
  ・
  ・
    # Auto-load API and its subdirectories
    config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
    config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
  end
end
app/api/api.rb

app/apiの直下にAPIの基底クラスを記述したapi.rbを作成し、API v1の機能が記述されているモジュールV1::V1APImountします。

module API
  class Base < Grape::API
    prefix 'api'
    version 'v1', using: :path
    format :json

    mount V1::V1API
  end
end
app/api/v1/v1_api.rb

API v1では、v1_api.rbresourceに指定したシンボルstatuses*2APIのroot URLになるため、API v1のroot URLはhttp://railsapp.com/api/v1/statusesになるようです。

APIを実装するマイクロブログでは、user_idcontentPOSTを受け取ってマイクロポストを作成するため、postブロックで必要なパラメータを受け取り、Micropostcreateできるようにします。
また、paramsブロックで必要なパラメータを記述しておくと、パラメータの誤りや不足があった場合に{"error":"user_id is invalid, content is missing"}%とか教えてくれるようです。

実際に利用する予定のpostの他に、動作確認のためのgetブロックも作成しています。

module V1
  class V1API < Grape::API

    resource :statuses do

      desc 'Get the root url'
      get '/' do
        status 200
      end

      desc 'Get 3 Microposts'
      get '/index' do
        Micropost.limit(3)
      end

      desc 'Post new micropost'
      params do
        requires :user_id, type: Integer
        requires :content, type: String
      end
      post do
        Micropost.create!({
          user_id: params[:user_id],
          content: params[:content]
        })
        status 201
      end

    end
  end
end

ルーティングにAPIをMount

config/routes.rbに、APIの基底クラスBaseを含むモジュールAPI::BasemountAPIのルートを設定します。

Rails.application.routes.draw do
  mount API::Base => '/'

・
・
・

API v1の動作確認

rails serverを起動して、curlコマンドでAPI v1の動作を確認します。

$rails server
=> Booting WEBrick
=> Rails 4.2.6 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2016-06-14 21:57:52] INFO  WEBrick 1.3.1
[2016-06-14 21:57:52] INFO  ruby 2.3.0 (2015-12-25) [x86_64-darwin15]
[2016-06-14 21:57:52] INFO  WEBrick::HTTPServer#start: pid=62210 port=3000
Get the root url

まずは、root URLにアクセスしてみます。

$curl http://localhost:3000/api/v1/statuses/                                        
200%

すんなりと、ステータスコード200が表示されました。
rails serverのログも、問題無さそうです。

Started GET "/api/v1/statuses/" for ::1 at 2016-06-14 21:59:24 +0900
Get 3 microposts

次に、/indexにアクセスして、limit(3)のマイクロポストを取得できるか確認してみます。

$curl http://localhost:3000/api/v1/statuses/index
[{"id":300,"content":"Nesciunt quia numquam vel pariatur earum corrupti.","user_id":6,"created_at":"2016-06-09T12:47:11.903Z","updated_at":"2016-06-09T12:47:11.903Z"},{"id":299,"content":"Nesciunt quia numquam vel pariatur earum corrupti.","user_id":5,"created_at":"2016-06-09T12:47:11.900Z","updated_at":"2016-06-09T12:47:11.900Z"},{"id":298,"content":"Nesciunt quia numquam vel pariatur earum corrupti.","user_id":4,"created_at":"2016-06-09T12:47:11.898Z","updated_at":"2016-06-09T12:47:11.898Z"}]%  

Faker gemで生成した、愉快なマイクロポストが表示されました。
rails serverのログでも、SQLが実行されている様子が確認できます。

Started GET "/api/v1/statuses/index" for ::1 at 2016-06-14 22:04:17 +0900
  Micropost Load (0.6ms)  SELECT  "microposts".* FROM "microposts"  ORDER BY created_at DESC LIMIT 3
Post new micropost

それでは、API v1を実装した目的であるマイクロポストのPOST機能を試してみます。

$curl -d "user_id=2&content=test" http://localhost:3000/api/v1/statuses
201%           
  • curl … 指定したURLへデータの送受信を行うコマンド
  • -d "name=value" … データをPOSTリクエストとして送信できるオプションで'&'を使って複数項目をまとめて送れる

user_id2のユーザのマイクロポストに、testの文字をPOSTしてみました。
rails serverのログでは、SQLのINSERSTが確認できます。

Started POST "/api/v1/statuses" for ::1 at 2016-06-14 22:22:12 +0900
   (0.8ms)  BEGIN
  SQL (28.7ms)  INSERT INTO "microposts" ("user_id", "content", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["user_id", 2], ["content", "test"], ["created_at", "2016-06-14 13:22:12.080868"], ["updated_at", "2016-06-14 13:22:12.080868"]]
   (27.5ms)  COMMIT

念のため、データベースも確認してみます。

$psql railsapp_development
Expanded display is used automatically.
Null display is "NULL".
psql (9.5.1)
Type "help" for help.

railsapp_development=# SELECT * FROM microposts ORDER BY id DESC LIMIT 1;
 id  | content  | user_id |         created_at         |         updated_at         
-----+----------+---------+----------------------------+----------------------------
 301 | api test |       2 | 2016-06-14 13:22:12.080868 | 2016-06-14 13:22:12.080868
(1 row)

railsapp_development=#

ちゃんと保存されていましたヽ(´д`)ノ

参考記事

Grapeに関しては、こちらの記事を参考にさせていただきました。

Doorkeeperに関しては、こちらの記事を参考にさせていただきました。

cURLに関しては、こちらの記事を参考にさせていただきました。

開発環境

*1:gemのバージョンは最新版を選びました

*2:シンボルの値はモジュール名やクラス名と関係無くても使えるようです