2016-09-26

GrapeでTodoアプリなAPIを作ってみた【OAuth2.0対応まで】

TodoアプリなAPIサーバをRubyのWAFであるGrapeで作ってみました。

利用技術はこんな感じ↓

Rubyは2.2.4、Railsは5.0.0.1です。

アプリケーションの作成

毎度お決まりのアプリ作成
$ rails new {APP_NAME} -B

Gemfileに以下を追記。

gem 'grape'
gem 'devise'
gem 'doorkeeper'
gem 'rack-cors', :require => 'rack/cors'

いつものbundle install

$ bundle install --path=vendor/bundle

.gitignoreに追加

$ echo "vendor" >> .gitignore

Devise関連のインストール(設定は以前の記事を参照)

$ rails generate devise:install
$ rails generate devise:views
$ rails generate devise user

doorkeeper用の各種ファイルを作成

$ rails generate doorkeeper:install
$ rails generate doorkeeper:migration

マイグレーションファイルには以下を追記

add_foreign_key :oauth_access_tokens, :users, column: :resource_owner_id
add_foreign_key :oauth_access_grants, :users, column: :resource_owner_id

Taskモデルを作成する

$ rails g model task

マイグレーションファイルはこんな感じで

class CreateTaskTable < ActiveRecord::Migration
  def change
    create_table :tasks do |t|
      t.string :title, null: false
      t.text :description, null: true
      t.datetime :target_at, null: true
      t.datetime :completed_at, null: true
      t.boolean :completed, null: false, default: false
      t.integer :user_id, null: true
      t.timestamps
    end
  end
end

Userモデル、Taskモデルにアソシエーションを貼る

class User < ApplicationRecord
  has_many :tasks, dependent: :destroy
  # Include default devise modules. Others available are:
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :confirmable, :lockable, :timeoutable # and :omniauthable
end
class Task < ApplicationRecord
  belongs_to :user
end

API用のディレクトリを作成。

$ mkdir -p app/api/v1

app/api/v1/task.rbを作成。モジュール名とファイルパスは合わせる必要があるようです。

require 'doorkeeper/grape/helpers'

module V1
  class Task < Grape::API
    version 'v1', using: :header, vendor: 'hoge'
    format :json
    prefix :api

    helpers Doorkeeper::Grape::Helpers

    helpers do
      # Find the user that owns the access token
      def current_user
        User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
      end
    end

    before do
      doorkeeper_authorize!
    end

    resource :tasks do
      desc 'return all todo'
      get do
        current_user.tasks
      end

      desc 'create a todo'
      params do
        requires :title, type: String, desc: 'Todo title.'
        requires :description, type: String, desc: 'Todo title.'
      end
      post do
        current_user.tasks.create!({
                         title: params[:title],
                         description: params[:description],
                         completed: false
                     })
      end

      route_param :id do
        desc 'search a todo'
        get do
          current_user.tasks.find(params[:id])
        end

        desc 'update a todo'
        put do
          todo = current_user.tasks.find(params[:id])
          todo.update(
              title: params[:title],
              description: params[:description],
              completed: params[:completed]
          )
          todo
        end

        desc 'delete a todo'
        delete do
          todo = current_user.tasks.find(params[:id])
          todo.destroy
        end
      end
    end
  end
end

routesでGrapeをマウント追加

mount V1::Task => '/'

doorkeeperのOAuth2設定(config/initializers/doorkeeper.rb)。認証しているかどうかの判定ロジックを記述する。OAuthダンスの認証部分。

resource_owner_authenticator do
  current_user || warden.authenticate!(scope: :user)
end

config/application.rbに追記(apiディレクトリも自動的にロードする)。CORS対応も追記。

class Application < Rails::Application
  ...
  config.middleware.insert_before ActionDispatch::Static, Rack::Cors do
    allow do
      origins '*'
      resource '*', :headers => :any, :methods => [:get, :post, :options, :patch, :put, :delete]
    end
  end
  ...
  # Do not swallow errors in after_commit/after_rollback callbacks.
  config.active_record.raise_in_transactional_callbacks = true
  config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
  config.autoload_paths += %W(#{Rails.root}/app/api)
end

最後にマイグレーション

$ bundle exec rake db:migrate

起動して確認

$ bundle exec rails s

あとは{APP_HOST}/oauth/applicationsにアクセスして、アプリケーションの登録をするとCLIENT_ID、CLIENT_SECRETが発行されるので、OAuthダンスしてaccess_tokenを取得すればOK。

また、OAuth2.0の各画面のビューのカスタマイズは以下のコマンドでビューファイルをジェネレートして弄れば良いです。

$ rails generate doorkeeper:views

ちょっとハマったところ

最初application.rbのパス追加を以下のように記述していた。
config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]

この状態でTaskモデルを呼び出そうとすると、以下のようなエラーが発生した。

LoadError (Unable to autoload constant Task, 
expected /Users/xxx/work/ruby/todo-api/app/api/v1/task.rb to define it):

モジュール名を含めた名前解決でエラーになっているっぽかった。{APPLICATION_PATH}/app/apiまでのパスを通すようにしたら、正常に動いてめでたしめでたし。

参考URL

このエントリーをはてなブックマークに追加