Garageというgemを使ってWeb APIを作るときに、知っておくと便利なことを紹介したいと思います。
今回はモデル、クエリについて見ていきます。
Garage gemとは?
Garage gemはクックパッド社が開発・公開しているRESTful Web API用のgemで、Railsに組み込んで使用します。
Garageのインストール方法やモデル、コントローラ、テストの書き方などはクックパッド社のこちらのブログを参考にしてください。
下準備
今回はGarageのサンプルを使用します。git clone
して、README.mdに従って準備します。なお執筆時点の最新コミットはbc9fa4c
です。
モデルの書き方
属性の宣言
モデルの情報をAPIで公開する場合には、下記の3つの宣言(クラスメソッド)を利用できます。
-
property
User#id
やUser#name
といったモデルの属性や、Post#user
といった単体のアソシエーション向けのメソッド -
collection
User#posts
といった複数のアソシエーション向けのメソッド -
link
userが所有しているpostへのリンク(
/v1/users/1/posts
)といった、リンク情報のためのメソッド
collection宣言
ここでは他であまり触れられていないcollection
を使ってみます。userにcollection :posts
を追記し、postを2つ作成します。
# app/models/user.rb
class User < ActiveRecord::Base
property :id
property :name
property :email
+ collection :posts
link(:posts) { user_posts_path(self) }
...
end
> bundle exec rails runner 'Post.create(title: "My string", body: "My text.", user_id: 1)'
> bundle exec rails runner 'Post.create(title: "Your string", body: "Your text.", user_id: 1)'
/v1/users
にアクセスすると、"posts"
というキーが追加され、postの配列が取得できるようになっています。
> curl -XGET -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost:3000/v1/users | json_pp
[
{
"email" : "alice@example.com",
"_links" : {
"posts" : {
"href" : "/v1/users/1/posts"
}
},
"name" : "alice",
"posts" : [
{
"body" : "My text.",
"published_at" : null,
"title" : "My string",
"id" : 1
},
{
"body" : "Your text.",
"published_at" : null,
"title" : "Your string",
"id" : 2
}
],
"id" : 1
}
]
属性宣言のオプション
-
if
属性をレスポンスに含めるかどうかの条件を設定するためのオプション
-
selectable
属性を選択可能にするためのオプション
-
as
属性の名前(キー名)を変更するためのオプション
ifオプション
引数として#call
メソッドが実装されたオブジェクトをとります。オブジェクトはrepresenter
とresponder
の二つ引数とともに呼び出されます。呼び出しの結果が偽のとき、その属性は結果に含まれなくなります。representer
は自分自身のオブジェクトです。responder
はGarage::AppResponder
オブジェクトです。
email
に次のような条件を設定してみます。
# app/models/user.rb
class User < ActiveRecord::Base
property :id
property :name
- property :email
+ property :email, if: ->(representer, responder) { representer.name != 'alice' }
link(:posts) { user_posts_path(self) }
...
end
idが1のuserは名前がaliceなので、"email"
属性が表示されなくなります。
> curl -XGET -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost:3000/v1/users | json_pp
[
{
"_links" : {
"posts" : {
"href" : "/v1/users/1/posts"
}
},
"name" : "alice",
"id" : 1
}
]
例えばユーザーの特定の情報(名前やメールアドレスなど)を公開するかどうか、各ユーザーが設定できるようにするときなど、このオプションを利用することができます。
このオプションはproperty
、collection
だけでなくlink
でも使用可能です。
selectableオプション
その属性を選択可能な属性にします。選択可能な属性は、デフォルトでは非表示側に倒れます。そのためクエリに明示する必要があります(クエリについては後述します)。
引数として、true
/false
もしくは#call
メソッドが実装されたオブジェクトをとります。#call
メソッドが実装されたオブジェクトの呼び出しなどは、if
と同様です。
postではuser
がselectable
になっています。
# app/models/post.rb
class Post < ActiveRecord::Base
property :id
property :title
property :body
property :published_at
property :user, selectable: true
...
end
通常のリクエストではuser
に関する情報は表示されません。
> curl -XGET -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost:3000/v1/posts/1 | json_pp
{
"body" : "My text.",
"published_at" : null,
"title" : "My string",
"id" : 1
}
{"fields":"*"}
を渡して全部取得するようにすると、user
の情報も取得できます。
> curl -XGET -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"fields":"*"}' http://localhost:3000/v1/posts/1 | json_pp
{
"body" : "My text.",
"published_at" : null,
"user" : {
"email" : "alice@example.com",
"_links" : {
"posts" : {
"href" : "/v1/users/1/posts"
}
},
"name" : "alice",
"id" : 1
},
"title" : "My string",
"id" : 1
}
このオプションはproperty
、collection
で使用可能です。
asオプション
レスポンスに表示したい属性の名前(キー名)をシンボルか文字列で渡します。property
宣言とcollection
宣言は、第一引数と同名のメソッドをモデルのオブジェクトに対して呼び出し、第一引数をキー名に使用します。
upcased_name
というメソッドを定義し、as
オプションを指定してみます。
# app/models/user.rb
class User < ActiveRecord::Base
property :id
- property :name
+ property :upcased_name, as: :name
property :email
link(:posts) { user_posts_path(self) }
+ def upcased_name
+ name.upcase
+ end
+
...
end
"upcased_name"
ではなく"name"
属性として表示されます。
> curl -XGET -H "Authorization: Bearer $ACCESS_TOKEN" http://localhost:3000/v1/users | json_pp
[
{
"email" : "alice@example.com",
"_links" : {
"posts" : {
"href" : "/v1/users/1/posts"
}
},
"name" : "ALICE",
"id" : 1
}
]
このオプションはproperty
、collection
で使用可能です。
アクセス権の制御
Garageのdocにも書いてありますが、モデルのアクセス権についても説明します。モデルには"クラスレベル/オブジェクトレベル"で、"read/wirte"のアクセス権が設定できます。"クラスレベル/オブジェクトレベル"というのは"クラスメソッド/インスタンスメソッド"としてそれぞれbuild_permissions
を実装することです。"read/wirte"のアクセス権はbuild_permissions
内でperms.permits! :read/:write
することで設定できます。
このように設定が分割されているのは、"index/create"、"show/update/destroy"といったコントローラのアクションごとに必要なアクセス権が異なるからです。
下記のコードのコメントに書いてあるように、クラスメソッドでは"index/create"の設定を、インスタンスメソッドでは"show/update/destroy"の設定を行います。
# app/models/post.rb
class Post < ActiveRecord::Base
...
def self.build_permissions(perms, other, target)
perms.permits! :read # index
perms.permits! :write # create
end
def build_permissions(perms, other)
# 公開されていれば誰でも読めるが、非公開のときはpostの作成者しか読めない
if published_at?
perms.permits! :read # show
elsif user == other
perms.permits! :read # show
end
perms.permits! :write # update destroy
end
...
end
クエリの書き方
クエリを渡すときはContent-Type: application/json
にして、fields
に必要なパラメータを記述します。
全ての属性を取得する
全ての属性を取得するには*
を使用します。
> curl -XGET -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"fields":"*"}' http://localhost:3000/v1/posts/1 | json_pp
{
"body" : "My text.",
"published_at" : null,
"user" : {
"email" : "alice@example.com",
"_links" : {
"posts" : {
"href" : "/v1/users/1/posts"
}
},
"name" : "alice",
"id" : 1
},
"title" : "My string",
"id" : 1
}
デフォルトの属性のみを取得する
デフォルトの属性を取得するには__default__
を使用します。fieldsを指定しないときはデフォルトと同じ挙動になります。
なお__default__
は*
と異なり、selectable
な属性は取得しません。
> curl -XGET -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"fields":"__default__"}' http://localhost:3000/v1/posts/1 | json_pp
{
"body" : "My text.",
"published_at" : null,
"title" : "My string",
"id" : 1
}
特定の属性のみを取得する
取得したい属性を,
区切りで指定します。
> curl -XGET -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"fields":"id,body"}' http://localhost:3000/v1/posts/1 | json_pp
{
"body" : "My text.",
"id" : 1
}
属性をネストして指定する
[]
を使用することで、ネストした属性指定が可能です。たとえばpostのuserのemail
とname
のみが必要なときは、user[email,name]
と書きます。
> curl -XGET -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"fields":"body,user[email,name]"}' http://localhost:3000/v1/posts/1 | json_pp
{
"body" : "My text.",
"user" : {
"email" : "alice@example.com",
"name" : "alice"
}
}
まとめ
今回はモデルの設定方法と読み出しかた(クエリ)を中心にみてみました。Garageはモデルの属性レベルで制御ができるところが魅力的だと思います。