RESTful APIが簡潔に構築できるgrape Gemについて調べてみた

## はじめに

grape, grape-entityを利用したRESTful API開発をする機会があるため、それらのGemの基本的な使い方について調べてみました。

## grape, grape-entityとは？

https://github.com/ruby-grape/grape
https://github.com/ruby-grape/grape-entity

grapeはRESTful APIを構築できるRubyフレームワークで、Rails/Sinatoraなどを組み合わせたり、Rackアプリケーションとして単独で利用することができるとのことです。

grape-entityはgrapeと一緒に利用し、プレゼンテーション層を定義するためのGemとのことです。レスポンスの構造を管理し、API内で統一することができます。

## grape

### 環境構築とAPIの基本
シンプルなRackアプリケーションとして簡単なAPIのサンプルを実装してみます。

まずは grape と rack をbundle installします。
```ruby
source "https://rubygems.org"

gem 'grape'
gem 'rack'
```

以下の構成とします。
```
$ tree .
.
├── Gemfile
├── Gemfile.lock
├── app
│   └── api.rb # APIを定義
└── config.ru
```

Rackで起動できるようにします。

```ruby:config.ru
require_relative 'app/api'

run API
```

まずは `Grape::API` を継承したAPIクラスを定義し、`GET /hello` を実装します。

```ruby:app/api.rb
require 'grape'

class API < Grape::API
  format :json

  get '/hello' do
    {
      message: 'Hello, World!'
    }
  end
end
```

`rackup` を実行し、rackを起動します。curlでRackサーバーの `/hello` APIエンドポイントにリクエストしたところ、JSONレスポンスが返ってきました。:tada:

```bash
$ curl http://localhost:9292/hello
{"message":"Hello, World!"}
```

### ルーティング定義

`namespace`, `resource` と複数の方法がある。

#### resource 

resource を利用することで `/books`, `/users` など特定のリソースに対する操作を定義することができます。

```ruby:app/api.rb
require 'grape'

class API < Grape::API
  format :json

  resource :posts do
    # GET /posts
    get do
      [
        {
          id: 1,
          title: 'Post Title 1',
          content: 'Post Context 1'
        },
      ]
    end

    # GET /posts/:id
    get ':id' do
      {
        id: params[:id],
        title: 'Post Title 1',
        content: 'Post Content 1'
      }
    end
  end
end
```

#### namespace

namespaceは特定のリソースに依存しない名前空間の定義をすることができます。

```ruby:app/api.rb
require 'grape'

class API < Grape::API
  format :json

  # GET /admin/dashboard
  namespace :admin do
    namespace :dashboard do
      get do
        {
          message: 'Admin Dashboard'
        }
      end
    end
  end
end
```

### パラメーターとバリデーション

`params` ブロックを利用することでパラメーターの定義をすることができます。

* `requires` : 必須パラメーターを定義
* `optional` : 任意パラメーターを定義

必須または任意によって `requires`, `optional` を使い分けます。

またパラメーターに対してバリデーションに設定ができます。ここでは`category` パラメーターの型をString、値には `fiction`, `non-fiction` のみに制限しています。

```ruby:app/api.rb
class API < Grape::API
  format :json

  resource :books do
    params do
      requires :category, type: String, values: ['fiction', 'non-fiction']
    end
    # GET /books/:id
    get ':id' do
      {
        id: params[:id],
        title: 'Book Title 1',
        author: 'Author Name 1'
      }
    end
  end
end
```

そのため、指定の値以外の値を渡すとエラーになります。

```bash
$ curl "http://localhost:9292/books/1?category=hoge"
{"error":"category does not have a valid value"}
```

## grape-entity

`Gemfile` に `grape-entity` を追加して `bundle install` します。

```ruby:Gemfile
source "https://rubygems.org"

gem 'grape'
gem 'grape-entity'
gem 'rack'
```

Entity を定義することで、表示する属性を明示的に定義することができます。以下の構成で簡単なサンプルを作っていきます。

```
$ tree .
.
├── Gemfile
├── Gemfile.lock
├── app
│   ├── api.rb
│   └── entity
│       └── user_entity.rb
└── config.ru
```

まずは Grape::Entity クラスを継承した UserEntity クラスを用意します。 表示したい属性を expose で定義します。

```ruby:app/entity/user_entity.rb
require 'grape-entity'

class UserEntity < Grape::Entity
  expose :id
  expose :name
end
```

grape 側のAPIエンドポイントでEntityを利用していきます。例えばUSERS定数のような配列データがあったとします。

`GET /users` で、`present` メソッドに対象データを渡します。withオプションでどのエンティティクラスを使用するか定義します。これによりオブジェクトをエンティティを通じて整形できます。

```ruby:app/api.rb
require 'grape'
require 'grape-entity'
require_relative 'entity/user_entity'

class API < Grape::API
  format :json

  USERS = [
    {
      id: 1,
      name: 'Foo Bar1',
      email: 'foo1@example.com'
    },
    {
      id: 2,
      name: 'Foo Bar2',
      email: 'foo2@example.com'
    }
  ]

  # GET /users
  resource :users do
    get do
      present USERS, with: UserEntity
    end
  end
end
```

実際にリクエストをすると以下になりました。ユーザー配列にはメールアドレスも保持していましたが、エンティティでは表示する設定をしていなかったのでレスポンスとしては返ってきません。余計な情報を省くことができるので便利ですね。
```bash
$ curl "http://localhost:9292/users" | jq .
[
  {
    "id": 1,
    "name": "Foo Bar1"
  },
  {
    "id": 2,
    "name": "Foo Bar2"
  }
]
```

### 条件付き公開

`:if`, `:unless` を使用してフィールドを条件付きで公開することができます。
例えば email フィールドは `with_email` オプションが指定されているときのみ公開したいとします。

```ruby:app/entity/user_entity.rb
require 'grape-entity'

class UserEntity < Grape::Entity
  expose :id
  expose :name

  expose :email, if: ->(instance, options) { options[:with_email] }
end
```

presentメソッドに引数として `with_email: true` を渡します。

```ruby
present USERS, with: UserEntity, with_email: true
```

こうすることで email が条件によりレスポンスとして公開されるようになりました。上記では with_email は固定値を渡していますが、条件判定の結果を渡すこともできます。

特定の条件のときだけフィールドを公開・非公開にしたいときに使えますね。

```bash
$ curl http://127.0.0.1:9292/users | jq .
[
  {
    "id": 1,
    "name": "Foo Bar1",
    "email": "foo1@example.com"
  },
  {
    "id": 2,
    "name": "Foo Bar2",
    "email": "foo2@example.com"
  }
]
```

## まとめ

grape, grape-entity を利用することで簡単にRESTful APIを実装できることが分かった。またバリデーション、エンティティなど便利な機能も揃っており、機能を活用することで迅速にAPI開発ができそうです。

## 参考リンク
* [ruby-grape/grape: An opinionated framework for creating REST-like APIs in Ruby.](https://github.com/ruby-grape/grape)
* [ruby-grape/grape-entity: An API focused facade that sits on top of an object model.](https://github.com/ruby-grape/grape-entity)