TodoアプリなAPIサーバをRubyのWAFであるGrapeで作ってみました。
利用技術はこんな感じ↓
- APIフレームワーク => Grape(Railsにマウント)
- 認証用の画面 => Rails + Devise
- OAuth2.0 => doorkeeper
アプリケーションの作成
毎度お決まりのアプリ作成$ 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までのパスを通すようにしたら、正常に動いてめでたしめでたし。