
RESTful Hypermedia API サーバサイド編 – garage
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
モバイルアプリサービス部の五十嵐です。
前回の記事ではハイパーメディアAPIの概要について説明しました。これから何回かにわたって、Ruby on RailsでハイパーメディアAPIを実装するためのgemを紹介したいと思います。今回はcookpadさんが提供されているgarageというgemを試してみます。
Garageとは
Garageは、Ruby on RailsにRESTful hypermedia APIを追加するgemです。
Rails framework to add RESTful hypermedia API to your application.
GarageはRailsネイティブのRESTfulなルーティングを使い、シンプルなHypermediaフレンドリーなRESTful APIを提供します。中略。またDoorkeeperなどを使用したOauth2認証や、リソースベースのアクセスコントロールを提供します。
Garage provides a simple, Hypermedia friendly RESTful API to your Rails application using its native RESTful routes. Garage provides a descriptive way to serve your ActiveRecord models, as well as plain old Ruby objects as JSON-based resources.
Garage supports OAuth 2 authorizations via Doorkeeper (more extensions to come), and provides resource-based access controls.
(公式リポジトリのREADME.mdより)
サンプルプログラム
クックパッド開発者ブログで紹介されている手順を参考にしつつ、Hypermediaフレンドリーな機能も試してみたいと思います。実装する内容はブログの通りですが、以下に抜粋を記載します。
・アプリケーションが提供するリソースはログインユーザーである user と投稿された投稿である post の2つ。 ・user について以下の操作を提供します ・ユーザーの一覧の表示 GET /v1/users ・それぞれのユーザーの情報の表示 GET /v1/users/:user_id ・自身の情報の更新 PUT /v1/users/:user_id ・post については以下の操作を提供します。 ・新規記事の作成 POST /v1/posts ・アプリケーション全体の記事の一覧の表示 GET /v1/posts ・あるユーザーの投稿した記事一覧の表示 GET /v1/users/:user_id/posts ・それぞれの記事の情報の表示 GET /v1/posts/:post_id ・自身の投稿した記事の更新 PUT /v1/posts/:post_id ・投稿した記事の削除 DELETE /v1/posts/:post_id ・user の作成や削除については実装しません。
また、本記事の目的はハイパーメディアAPIの実装なので、認証やテストなど本題と外れるところは省略していきます。今回のサンプルコードはGitHubのリポジトリにありますので参考にしてください。サンプルコードには簡単なテストも書いています。
動作環境
- Ruby (2.2.2)
- rails (4.2.1)
- garage (1.5.2)
Rails new
まずアプリケーションを作成します。
rails new blog --skip-bundle --skip-test-unit -q && cd blog
次にGemfileに必要なgemを設定していきます。何点か注意がありますが、まずgarageはGitHubのcookpad/garageを参照してください。rubygemsに登録されているgarageは全く別物です。また、今回は省略しますが認証にdoorkeeperを使用される場合、最新のgarageではdoorkeeperはgarageに含まれませんので、別途garage-doorkeeperを追加する必要があります。respondersもgarageが使うので設定しておきます。
# Gemfile gem 'garage', github: 'cookpad/garage' gem 'responders', '~> 2.0'
設定したらbundle installを行います。
bundle install
Configuration and authentication/authorization
次にgarageの設定を行います。Garage.configureにはgarage本体の設定をします。Garage::TokenScope.configureにはアクセスコントロールの設定を行います。Garage.configuration.strategyには認証の方法を設定します。今回は認証は行わないので、Garage::Strategy::NoAuthenticationを設定します。
# config/initializers/garage.rb
Garage.configure {}
Garage::TokenScope.configure {}
Garage.configuration.strategy = Garage::Strategy::NoAuthentication
コントローラの作成
applicationコントローラにはGarage::ControllerHelperを追加します。これはコントローラ共通で使用するフィルタとメソッドが提供されます。今回は使用しませんがお決まりの書き方のようなので書いておきます。
# app/controllers/application_controller.rb include Garage::ControllerHelper def current_resource_owner @current_resource_owner ||= User.find(resource_owner_id) if resource_owner_id end
usersコントローラとpostsコントローラを作成します。
bundle exec rails g controller users bundle exec rails g controller posts
コントローラにGarage::RestfulActionsをincludeすることで、index/create/show/update/deleteメソッドそれぞれをラップしたrequire_resources/create_resource/require_resource/update_resource/destroy_resourceメソッドが使えるようになります。
# app/controllers/users_controller.rb include Garage::RestfulActions # index def require_resources @resources = User.all end # show def require_resource @resource = User.find(params[:id]) end # update def update_resource @resource.update_attributes!(user_params) end private def user_params params.permit(:name) end
# app/controllers/posts_controller.rb include Garage::RestfulActions # index def require_resources if params[:user_id] @resources = User.find(params[:user_id]).posts else @resources = Post.all end end # create def create_resource @resources.create(post_params.merge(user_id: resource_owner_id)) end # show def require_resource @resource = Post.find(params[:id]) end # update def update_resource @resource.update_attributes!(post_params) end # destroy def destroy_resource @resource.destroy! end private def post_params params.permit(:title, :body, :published_at) end
ルーティングを設定します。postリソースはuserリソースにネストされるようにし、必要なアクションだけを設定しておきます。
# config/routes.rb scope :v1 do resources :users, only: %i(index show update) do resources :posts, shallow: true, except: %i(new edit) end end
モデルとリソースの定義
userモデルとpostモデルを作成し、マイグレーションします。
bundle exec rails g model user name:string email:string bundle exec rails g model post title:string body:string published_at:datetime user:references bundle exec rake db:migrate
モデルでレスポンスの内容を定義します。propertyは属性、linkはリンク、collectionはアソシエーションしたモデルの情報を返します。selectableオプションをtrueにすると、デフォルトでは値が返らず、リクエストで何らかのパラメータを与えることで返るようになるのだと思いますがパラメータの与え方が分かりませんでした。
# app/model/user.rb
include Garage::Representer
has_many :posts
property :id
property :name
property :email
link(:posts) { user_posts_path(self) }
collection :posts
# app/model/post.rb include Garage::Representer belongs_to :user property :id property :title property :body property :published_at property :user, selectable: true
ローカルサーバーでリクエストを試す
テストデータを準備します。
bundle exec rails c user = User.create(name: "name1", email: "mail1@example.com") user.posts.create(title: 'title1', body: 'body1', published_at: DateTime.now) user.posts.create(title: 'title2', body: 'body2', published_at: DateTime.now) user.posts.create(title: 'title3', body: 'body3', published_at: DateTime.now)
サーバを起動します。
bundle exec rails s
ターミナルからcurlで/v1/users/:idにGETリクエストするとjsonデータが返されます。モデルに設定したproperty、link、collection、それぞれが表示されているのが分かりますでしょうか。
curl -s http://localhost:3000/v1/users/1 | jq .
{
"id": 1,
"name": "name1",
"email": "mail1@example.com",
"_links": {
"posts": {
"href": "/v1/users/1/posts"
}
},
"posts": [
{
"id": 1,
"title": "title1",
"body": "body1",
"published_at": "2015-09-23T15:23:53.828Z"
},
{
"id": 2,
"title": "title2",
"body": "body2",
"published_at": "2015-09-23T15:24:18.783Z"
},
{
"id": 3,
"title": "title3",
"body": "body3",
"published_at": "2015-09-23T15:24:25.218Z"
}
]
}
所感
今回はgarageのAPI機能とハイパーメディア要素の作り方の一部を紹介しました。ご覧頂いた通り、簡単にハイパーメディアなAPIを作成することができました。クックパッドさんのサンプルコードを見たところ、他にもページネーションの要素を出力する方法などがありそうでしたが、それ以上の情報がなく試すところまでは至りませんでした。サンプルコードの時点からも結構バージョンアップしていて書き方とかも変わっていて、今まさに開発中という感じでしたのでこれからに期待しましょう!また、今回は省略した認証やアクセスコントロールは別の機会に紹介したいと思います。









