#348 The Rails API Gem
- Download:
- source codeProject Files in Zip (32 KB)
- mp4Full Size H.264 Video (27.2 MB)
- m4vSmaller H.264 Video (13.9 MB)
- webmFull Size VP8 Video (13.7 MB)
- ogvFull Size Theora Video (33.5 MB)
リッチクライアントの人気が高まる中で、よくあるのは「JSON APIがあればいいだけの場合にもRailsを使うべきか?」という疑問です。そのようなAPIを開発する場合には、RailsコアメンバのSantiago PastorinoによるRails::API gemをお勧めします。このgemのREADMEには、APIを作成したいだけの場合にRailsを利用することの利点について詳しい説明があります。このgemは、Railsのモジュラー性の高さを活かして、APIを作成する場合に不要なものを全部外してスリム化されたRailsアプリケーションを作成するためのジェネレータを提供します。今回のエピソードでは、このgemを使ってとてもシンプルなアプリケーションを作りながらその仕組みを見ていきます。
Rails::APIを使う
最初にgemをインストールするために以下のコマンドを実行します。
$ gem install rails-api
rbenvを使用している場合は、rbenv rehash
を実行してrails-api
コマンドを利用できるようにします。rails
コマンドと同じ要領で、このコマンドを用いて新規のアプリケーションを作成できます。今回はtodoという名前のアプリケーションを作成します。
$ rails-api new todo
このコマンドで、APIの提供に特化したスリム化されたアプリケーションが作成されます。通常のアプリケーションとの大きな違いは、/app
ディレクトリの中がずっと単純であるという点です。その中にはassets
やviews
ディレクトリはなく、controllers
、mailers
、models
だけがあります。生成されたApplicationController
を見ると、ActionController::Base
よりもずっと軽量なActionController::API
を継承していることがわかります。
class ApplicationController < ActionController::API end
これらの違いについては、後ほど詳しく見ていきます。
もう一つ大きな違いはgemfileの中にあります。rails-api
gemが含まれていますが、assets
グループはありません。jquery-rails
gemはまだ含まれていますが削除することができます。Rails::APIの今後のバージョンにも含まれません。
source 'https://rubygems.org' gem 'rails', '3.2.3' # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' gem 'rails-api' gem 'sqlite3'
ジェネレータの動きにも違いがあります。Task
モデルに対してscaffoldを生成しようとすると、テンプレートやassetファイルがないので、生成されるファイル数はずっと少なくなります。
$ rails g scaffold task name invoke active_record create db/migrate/20120517174851_create_tasks.rb create app/models/task.rb invoke test_unit create test/unit/task_test.rb create test/fixtures/tasks.yml route resources :tasks, except: :edit invoke scaffold_controller create app/controllers/tasks_controller.rb invoke test_unit create test/functional/tasks_controller_test.rb
このscaffoldを作成したら、それを使えるようにするためにデータベースのマイグレーションを行ないます。
$ rake db:migrate
scaffoldで生成されたTasksController
を見ると、中身はJSONレスポンスを出力するだけなのがわかります。
class TasksController < ApplicationController # GET /tasks # GET /tasks.json def index @tasks = Task.all render json: @tasks end # GET /tasks/1 # GET /tasks/1.json def show @task = Task.find(params[:id]) render json: @task end # Other actions omitted. end
この種のアプリケーションはHTMLビューやユーザインターフェースを出力するように設定されていないので、コントローラアクションは単にJSONを返します。ブラウザで/tasks
のパスにアクセスすると、JSONの出力が表示されますが、今はタスクが一つもないので空の配列になります。
APIを利用するユーザインターフェースを作成する
タスクを作成したり管理するためのユーザインターフェースを提供したいのであれば、/public
ディレクトリの中で行なうか、または完全にこのアプリケーションの外で行なうことになります。このアプリケーションの目的のために、デフォルトのindex.html
ファイルの内容を置き換えて、タスクのリスト表示と新規タスク作成の機能をJavaScriptとCSSも少し使いながら作成します。
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <title>To Do List</title> <style type="text/css" media="screen"> html, body { background-color: #4B7399; font-family: Verdana, Helvetica, Arial; font-size: 14px; } a { color: #0000FF; } #container { width: 75%; margin: 0 auto; background-color: #FFF; padding: 20px 40px; border: solid 1px black; margin-top: 20px; } </style> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script> <script type="text/javascript" charset="utf-8"> $(function() { function addTask(task) { $('#tasks').append('<li>' + task.name + '</ul>'); } $('#new_task').submit(function(e) { $.post('/tasks', $(this).serialize(), addTask); this.reset(); e.preventDefault(); }); $.getJSON('/tasks', function(tasks) { $.each(tasks, function() { addTask(this); }); }); }); </script> </head><body> <div id="container"> <h1>To-Do List</h1> <form id="new_task"> <input type="text" name="task[name]" id="task_name"> <input type="submit" value="Add"> </form> <ul id="tasks"></ul> </div> </body> </html>
今回はすべてを一つにまとめましたが、もちろん好きなように構造化させることができます。このアプリケーションのルートには、リストに項目を追加するページがあり、項目はAPIを介して保存されます。ページをリロードすると、項目リストがこれもAPIを介して表示されます。
これはjQueryコードで実装されています。$.getJSON
でタスクリストを取得し、$.post
で新規タスクを作成します。
$('#new_task').submit(function(e) { $.post('/tasks', $(this).serialize(), addTask); this.reset(); e.preventDefault(); }); $.getJSON('/tasks', function(tasks) { $.each(tasks, function() { addTask(this); }); });
このサンプルはあえてシンプルな構造にしましたが、Backbone.jsのようなフル機能のクライアントサイド用フレームワークを利用することもできます。
追加のフォーマットを処理する
次にRails::APIコントローラに足りない部分について紹介します。例えば、作成したAPIを別のフォーマットにも対応させて、JSONだけでなくXMLでもタスクリストを返せるようにしたいとしましょう。通常の場合は、以下のようにrespond_to
ブロックを追加します。
def index @tasks = Task.all respond_to do |format| format.json { render json: @tasks } format.xml { render xml: @tasks } end end
http://localhost:3000/tasks.xml
にアクセスするとXML形式のデータが表示されるはずですが、代わりにundefined method `respond_to’
というエラーが表示されます。これはRails::APIコントローラをより軽量にするために省かれている機能があるからですが、必要な機能を簡単に追加しなおすこともできます。TasksController
を修正してActionController::MimeResponds
というモジュールをincludeします。
class TasksController < ApplicationController include ActionController::MimeResponds # Actions omitted end
他のコントローラもXMLを返せるようにするには、このモジュールを代わりにApplicationController
にincludeします。ページをリロードすると、タスクリストがXMLで出力されました。
Rails::APIのREADMEには、includeできるモジュールのリストがあります。ActionController::MimeResponds
の他にもtranslation、Basic HttpAuthenticationなどがあります。しかし、このリストはすべてのモジュールを網羅していません。ソースコードを見ると、Rails::API controllerとActionController::Base5に含まれているモジュールを比較できます。ActionController::Baseのすべてのモジュールをincludeすると機能が重複してしまいます。そこで必要なものだけを選択するようにします。
しかし、コントローラにモジュールをincludeするだけでは足りない場合もあります。例えば、クッキーをサポートするにはモジュールをincludeするだけではなくRackミドルウェアを追加する必要があります。これによって重要なことがわかります。Rails APIはコントローラだけではなく、Rackミドルウェア層もスリム化しています。Railsアプリケーション内でrake middleware
コマンドを実行すると、使用しているミドルウェアのリストが表示されます。表示されるリストは通常のRailsアプリケーションよりも少なくなっています。
$ rake middleware use ActionDispatch::Static use Rack::Lock use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007f9704a76118> use Rack::Runtime use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::ParamsParser use ActionDispatch::Head use Rack::ConditionalGet use Rack::ETag run Todo::Application.routes
ここでは5つのミドルウェアがなくなっています。通常のrailsアプリケーションと今回のアプリケーションでrake middleware
の実行結果の差分は次のとおりです。
> use Rack::MethodOverride > use ActionDispatch::Cookies > use ActionDispatch::Session::CookieStore > use ActionDispatch::Flash > use ActionDispatch::BestStandardsSupport
これらのミドルウェアのうち機能がわからないものがある場合は、エピソード319を参照してください。
この機能のうちいずれか(例えばクッキーのサポート)を戻したい場合は、そのミドルウェアを追加してコントローラモジュールをincludeします。アプリケーションの設定ファイルに、次のようにミドルウェアを追加します。
module Todo class Application < Rails::Application config.middleware.insert_after ActiveRecord::QueryCache, ActionDispatch::Cookies # Rest of class omitted end end
ミドルウェアの順番はできる限り通常のアプリケーションと同じようにするのがいいので、insert_after
を使ってフルスタックの場合と同じようにCookies middlewareをQueryCache
の後に追加しました。これでTasksController
でCookies
モジュールをincludeしてクッキー機能を追加できます。
class TasksController < ApplicationController include ActionController::MimeResponds include ActionController::Cookies # Actions omitted end
アプリケーションを再起動してミドルウェアへの変更を有効化させます。再起動後にページをリロードすると、helper_method
が未定義だというエラーが表示されます。
これによって重要なことがわかります。モジュールの中には、別のモジュールに依存しているものがあります。今回の場合、Cookies
モジュールはHelpers
モジュールに依存するのでこれも追加する必要があります。
class TasksController < ApplicationController include ActionController::MimeResponds include ActionController::Helpers include ActionController::Cookies # Actions omitted end
ページをリロードすると正しく動作するようになりました。これで通常のRailsアプリケーションと同じようにクッキーを利用できます。
JbuilderとRABLを用いてJSONを生成する
次にJSON APIを作るために利用できるその他のgemについて説明します。過去のエピソードでJbuilderとRABLを紹介しました。これらのgemは、ビューテンプレートを用いてJSONを出力します。これはRails::APIのgrainに反するかも知れませんが、これらを使うことも可能です。RABLを使った例を示します。いつものようにgemをgemfileに追加してbundleコマンドを実行してインストールします。
gem 'rabl'
/app
ディレクトリには現在viewsディレクトリがありませんが、RABLはビューテンプレートで動作するので新規にviewsディレクトリを作成します。viewsディレクトリの下にtasks
ディレクトリを作成し、ここにindex.json.rabl
ファイルを作成します。このファイルでRABLに対して、全てのタスクをid
とname
属性付きの集合として出力させます。
collection @tasks attributes :id, :name
コントローラでJSONを出力する振る舞いを修正してこのテンプレートを出力するように変更します。通常のRailsアプリケーションの場合、render
の呼び出しを完全に削除すればフォールバックしてテンプレートを出力させることができます。Rails::APIアプリケーションの場合は出力の振る舞いは明示的に書く必要がありますが、ここでrender
を呼び出すだけで現在のアクションに一致するテンプレートが出力されます。
def index @tasks = Task.all respond_to do |format| format.json { render } format.xml { render xml: @tasks } end end
アプリケーションを再起動して/tasks.json
にアクセスすると、RABLが生成したJSONレスポンスが表示されます。
出力を指定しなくてもいいようにするには、ImplicitRender
モジュールをincludeすればrender
の呼び出しも必要なくなります。XMLテンプレートを追加すれば、フォーマットが検知されて必要なテンプレートが自動的に出力されるようになるので、respond_to
ブロックを完全に削除できます。
ビューテンプレートの暗示的な出力を行なっているので、通常のRailsアプリケーションに近くなってきました。これらの機能をすべて追加しなおすとなると、そもそもRails::APIをあえて使う価値があるのかという疑問がでてきます。ここで行なったことを見ると、APIを出力しているだけだとしてもビューを出力しているのであれば、最初から通常のRailsアプリケーションを使った方がよかったかも知れません。結局はそれぞれのアプリケーションに要求されることと、Railsの機能のどの部分を使用するかによって選択は変わってきます。
JSON APIを作成するのであれば、READMEの“Why use Rails for JSON APIs?”(JSON APIにRailsを使う理由)のセクションを読むことをお勧めします。そこにはRailsがHTML出力以外に提供する機能についての詳細な解説があります。