LoginSignup
27

More than 5 years have passed since last update.

Garageことはじめ

Posted at

Garageというgemを使ってWeb APIを作るときに、知っておくと便利なことを紹介したいと思います。
今回はモデル、クエリについて見ていきます。

Garage gemとは?

Garage gemはクックパッド社が開発・公開しているRESTful Web API用のgemで、Railsに組み込んで使用します。
Garageのインストール方法やモデル、コントローラ、テストの書き方などはクックパッド社のこちらのブログを参考にしてください。

下準備

今回はGarageのサンプルを使用します。git cloneして、README.mdに従って準備します。なお執筆時点の最新コミットはbc9fa4cです。

モデルの書き方

属性の宣言

モデルの情報をAPIで公開する場合には、下記の3つの宣言(クラスメソッド)を利用できます。

  • property

    User#idUser#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メソッドが実装されたオブジェクトをとります。オブジェクトはrepresenterresponderの二つ引数とともに呼び出されます。呼び出しの結果が偽のとき、その属性は結果に含まれなくなります。representerは自分自身のオブジェクトです。responderGarage::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
   }
]

例えばユーザーの特定の情報(名前やメールアドレスなど)を公開するかどうか、各ユーザーが設定できるようにするときなど、このオプションを利用することができます。

このオプションはpropertycollectionだけでなくlinkでも使用可能です。

selectableオプション

その属性を選択可能な属性にします。選択可能な属性は、デフォルトでは非表示側に倒れます。そのためクエリに明示する必要があります(クエリについては後述します)。

引数として、true/falseもしくは#callメソッドが実装されたオブジェクトをとります。#callメソッドが実装されたオブジェクトの呼び出しなどは、ifと同様です。

postではuserselectableになっています。

# 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
}

このオプションはpropertycollectionで使用可能です。

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
   }
]

このオプションはpropertycollectionで使用可能です。

アクセス権の制御

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のemailnameのみが必要なときは、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はモデルの属性レベルで制御ができるところが魅力的だと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27