[Rails4] Grape で API を簡単に実装 ＆ API を複数バージョンで分ける方法


[Grape](https://github.com/intridea/grape) を利用すると **Restful な WEB-API** が簡単に作成できるらしいので試してみました。その際、実際の運用を視野にいれて、次のこともやってみました。

- **複数の API バージョン** に対応する。
	- 例：`http://ドメイン/api/v1/..` `http://ドメイン/api/v2/..`
- １つのソースがファットにならないよう **モデル毎にソースを分ける** 。

まずは Grape の導入手順から説明します。

## Grape の導入手順

### 前提

- 手元の OS：`Mac OS X 10.9.5 (Mavericks)`
- Ruby：`2.1.2`
- Rails：`4.1.1`

Grape は結果、`0.9.0` が入りました。

### 手順

- 適当な Rails プロジェクトを作成。

	```shell:
	$ bundle exec rails new grape_app --skip-bundle -T
	```

- Gemfile への追記。

	```ruby:Gemfile
	#..

	gem 'grape'

	#..
	```

- gem をインストール。

	```shell:
	$ cd grape_app
	$ bundle install --path vendor/bundle
	```

## API の作成

### 前提

APIのソースコードは `app/apis/` 下に置くこととします。ファイル構成はこんな感じ。

![スクリーンショット 2014-10-09 17.47.13.png](https://qiita-image-store.s3.amazonaws.com/0/35733/c23818a5-499f-8d40-e776-4dc88bfe57b1.png "スクリーンショット 2014-10-09 17.47.13.png")

**ディレクトリ構成およびファイル名は、クラスの名前空間およびクラス名と一致させる必要があります。** 大文字/小文字は、気にしなくてもいいみたい。

### 手順

- 適当に Scaffold します。

	```shell:
	$ ./bin/rails g scaffold person name:string age:integer memo:text
	$ ./bin/rails g scaffold product name:string price:integer memo:text
	$ ./bin/rake db:migrate
	```

	※ `Person` モデルと `Product` モデルが作られます。

- `app/apis/` 下の Ruby ファイルが読み込まれるようにします。

	```ruby:config/application.rb
	#..
	
	module GrapeApp
	  class Application < Rails::Application
	    #..  
	
	    config.paths.add File.join('app', 'apis'), glob: File.join('**', '*.rb')
	    config.autoload_paths += Dir[Rails.root.join('app', 'apis', '*')]
	  end
	end
	```

- 基底となるAPIクラスを作成します。
	- このファイルで各バージョンのAPIをマウントします（今回は、バージョン１のみ）。

	```ruby:app/apis/api/root.rb
	module API
	  class Root < Grape::API
	    # http://localhost:3000/api/
	    prefix 'api'
	
	    mount API::Ver1::Root
	    #mount API::Ver2::Root
	  end
	end
	```

- バージョン１の中で、基底となるAPIクラスを作成します。
	- ここで 各APIの実体をマウントします。

	```ruby:app/apis/api/ver1/root.rb
	module API
	  module Ver1
	    class Root < Grape::API
	      # http://localhost:3000/api/v1/
	      version 'v1'
	      format :json
	
	      mount API::Ver1::People
	      mount API::Ver1::Products
	    end
	  end
	end
	```

- 各APIの実体を作成します。
	- 今回は「全件取得」と「1件取得」のAPIを作成します。

	```ruby:app/apis/api/ver1/people.rb
	module API
	  module Ver1
	    class People < Grape::API
	      resource :people do
	
	        # GET /api/v1/people
	        desc 'Return all people.'
	        get do
	          Person.all
	        end
	
	        # GET /api/v1/people/{:id}
	        desc 'Return a person.'
	        params do
	          requires :id, type: Integer, desc: 'Person id.'
	        end
	        get ':id' do
	          Person.find(params[:id])
	        end
	      end
	    end
	  end
	end
	```

	```ruby:app/apis/api/ver1/products.rb
	module API
	  module Ver1
	    class Products < Grape::API
	      resource :products do
	
	        # GET /api/v1/products
	        desc 'Return all products.'
	        get do
	          Product.all
	        end
	
	        # GET /api/v1/products/{:id}
	        desc 'Return a product.'
	        params do
	          requires :id, type: Integer, desc: 'Product id.'
	        end
	        get ':id' do
	          Product.find(params[:id])
	        end
	      end
	    end
	  end
	end
	```

- 最後に、基底となるAPIクラスをルーティングに追加します。

	```ruby:config/routes.rb
	Rails.application.routes.draw do
	#..
	
	  mount API::Root => '/'
	
	#..
	end
	```

## 動作確認

適当にデータを登録し、ブラウザでアクセスしてみます。

![screen1 copy.png](https://qiita-image-store.s3.amazonaws.com/0/35733/08b5ca51-008a-1541-f25f-566f973c4e24.png "screen1 copy.png") ![screen2 copy.png](https://qiita-image-store.s3.amazonaws.com/0/35733/f6c7f70e-bdb9-8d52-a886-6ea828c28a86.png "screen2 copy.png")

![screen3 copy.png](https://qiita-image-store.s3.amazonaws.com/0/35733/8523b060-ab92-b899-fb8c-3b197173210e.png "screen3 copy.png") ![screen4 copy.png](https://qiita-image-store.s3.amazonaws.com/0/35733/6f48b9b0-a1f7-c602-0e42-0096400705c4.png "screen4 copy.png")

## おわりに

簡単に API が作成できました。自身の課題として、次のものがあります。

- [Grape の GitHubページ](https://github.com/intridea/grape) に記載されている機能を検証できていない
- エラーのハンドリングまわりの実装
- JSONデータの入れ子に対応できるよう、テンプレートエンジンを導入（[rabl](https://github.com/nesquena/rabl) が良い？）

今回はわりとよい感触を得られたので、引き続き触って評価してみようと思います。