RESTful Web API 開発をささえる Garage (client 編)

料理動画事業室の @yoshiori です。前に「RESTful Web API 開発をささえる Garage」で紹介した RESTful Web API を開発する Garage のクライアント側のライブラリを公開しました。この記事ではその使い方を紹介したいと思います。Garage の設計思想やサーバ側の実装については上記記事を御覧ください。

今回は簡単にクライアント側の挙動を知っていただくために pry を使って説明したいと思います。 アクセスするサーバは先程の記事で作成したアプリケーションを使用してみます。

サーバの準備

https://github.com/taiki45/garage-example の README にも書いてありますので簡単に進めたいと思います。

まずは下準備としてコードを github から clone してきて、ライブラリのインストールと DB のマイグレーションを行います

> git clone git@github.com:taiki45/garage-example.git
> cd  garage-example
> bundle install
> bundle exec rake db:create db:migrate

ここまででサーバの準備が整いました。次にユーザーを一人作り、アプリケーションを起動してみます

> bundle exec rails runner 'User.create(name: "alice", email: "alice@example.com")'
> bundle exec rails s

実際にブラウザで http://localhost:3000/oauth/applications にアクセスするとアプリケーションの登録画面が開きますのでサンプルアプリケーションひとつ作ってみます。

f:id:Yoshiori:20141226185629p:plain

ここで表示された Application IdSecret を使い下記コマンドを実行します。 (下記の $APPLICTION_ID$SECRET をそれぞれ Application IdSecret に書き換えて実行して下さい)

> curl -u "$APPLICTION_ID:$SECRET" -XPOST http://localhost:3000/oauth/token -d 'grant_type=password&username=alice@example.com' 

そうすると下記のように access_token が手に入ると思います。 今回はこれを利用します。

{"access_token":"975430489343f3d468d69f68763b3cb11d7cf98ee1f504aa319ab031a47462eb","token_type":"bearer","expires_in":7200,"scope":"public"}

GarageClient の基本的使い方

gem として公開していますので、gem コマンドでインストール出来ます

> gem install garage_client

インストールが終わったら pry を起動しライブラリをロードします。

[1] pry(main)> require "garage_client"
=> true

次にクライアントの初期設定を行います。 今回は endpoint のみを指定します。その他のオプションに関しては README に纏めてありますのでそちらを御覧ください。

[2] pry(main)> GarageClient.configure do |c|
[2] pry(main)*   c.endpoint = "http://localhost:3000"  
[2] pry(main)* end  
=> "http://localhost:3000"

次に先ほど取得した access_token を利用して client を作成します

[3] pry(main)> client = GarageClient::Client.new(access_token: "975430489343f3d468d69f68763b3cb11d7cf98ee1f504aa319ab031a47462eb")
=> #<GarageClient::Client:0x007f8e7ba20cb8 @options={:access_token=>"975430489343f3d468d69f68763b3cb11d7cf98ee1f504aa319ab031a47462eb"}>

では、Garage の記事で行っていたユーザーリソースを取得するリクエストを GarageClient で行ってみます。

[4] pry(main)> users = client.get("/users")
// 省略
[5] pry(main)> users.first.name
=> "alice"
[6] pry(main)> users.first.email
=> "alice@example.com"

取得出来ましたね。 上記のように GarageClient で取得した値はプロパティアクセスの様に(実際にはメソッド呼び出しですが)呼び出せます。

get 以外のメソッド

次に get 以外の動作を見ていきます。garage-example は記事を投稿できるようになっているので、それを使ってみます。

[7] pry(main)> client.post("/posts", title: "sushi", body: "naoya")

投稿できました。実際に見てみましょう。

[8] pry(main)> post = client.get("/posts").first
// 省略
[9] pry(main)> post.title
=> "sushi"

ちゃんと作られていますね。

では次に更新をしてみましょう。

[10] pry(main)> client.put("/posts/#{post.id}", title: "niku")
// 省略

実際に更新されたかの確認を行いますが、今度は garage の link 機能を使って見てみます。 GarageClient はリソースに link が指定してあると link 先のリソースを取得するときにリクエストを投げて取得してくれます。

[11] pry(main)> user = client.get("/users").first
// 省略
[12] pry(main)> posts = user.posts
// ここで posts 取得のためにリクエストを投げています
[13] pry(main)> posts.first.title
=> "niku"

ちゃんと更新されているのが確認できました。 では、削除をしてみましょう。

[14] pry(main)> client.delete("/posts/#{post.id}")
// 省略
[15] pry(main)> client.get("/posts").size
=> 0

ちゃんと消えましたね。 では次にもうちょっと応用的な使い方を見てみようと思います。

取得する値を絞る

Garage にはリソース取得時に必要な値だけ指定して所得する機能があります。 これは例えばレシピのタイトルだけ取得したい時などに材料や手順まで取得してしまうと効率が悪い時に値を絞ったり、逆にレシピに付いている動画情報を取得するなど標準では付加されない情報を取得したりする時に使います。

[16] pry(main)> users = client.get("/users", fields: "id,name")
// 省略
[17] pry(main)> users.first.name
=> "alice"
[18] pry(main)> users.first.email // 取得していないのでエラーになる
NoMethodError: undefined method `email' for #<GarageClient::Resource:0x007f8e7e03bbc8>
from /usr/local/opt/rbenv/versions/2.1.5/lib/ruby/gems/2.2.0/gems/garage_client-2.1.1/lib/garage_client/resource.rb:51:in `method_missing'

キャッシュを利用する

取得する値を絞るだけでなく、取得した値をキャッシュさせることも出来ます。 動作確認様に下記に簡単な Cacher クラスを書いてみました。(実際には README にあるように Rails.cache を使うなどの形になると思います)

class MyCacher < GarageClient::Cachers::Base
  private

  def read_from_cache?
    true
  end

  def written_to_cache?
    read_from_cache?
  end

  def key
    "test"
  end

  def store
    @@store ||= MyStore.new
  end

  class MyStore
    def read(key, options)
      store[key]
    end

    def write(key, response, options)
      store[key] = response
    end

    def store
      @store ||= {}
    end
  end
end

上記をコピーし、pry コンソール上にペーストして下さい。 次に、クライアントを作るときに Cacher を指定して作ります。

[20] pry(main)> client = GarageClient::Client.new(access_token: "e32234b3d113b97d1fd38f3233c663cad67ceb4cb57e8e0a66bec00d6742b1ec", cacher: MyCacher)

実際にアクセスするときにサーバ側のログを一緒に見て下さい。

[21] pry(main)> client.get("/users")
// 省略
[22] pry(main)> client.get("/users") //こっちはキャッシュから取得するのでサーバにリクエストが飛ばない
// 省略

このように簡単にキャッシュ機構を組み込むことが出来ます。


簡単ですが GarageClient をつかって Garage と連携する方法を説明してみました。

また、今回は認証周りは本筋と外れるため省略しています。実際に Garage の Doorkeeper と連携する時は OAuth2 のライブラリを使用したりすると思います。 クックパッドでは Garage の記事にも出てきましたが、認証認可サーバーを別途立てていたり、内部 API として連携するときは認証を省いて使ったりしています。

社内でも実際に運用しているライブラリなので先ほど紹介した取得する値を絞る機能やキャッシュ機構など実用的なライブラリになっていると思います。 また、 今後 iOS, Android の client ライブラリも準備が整い次第公開していこうと思っています。 ご期待ください。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/