Railsがバージョン1.2で確実に参加するようになったRESTfulリソースの世界では、当然のことながらXMLを共通言語として使用します。しかし、多言語となり得ない理由はひとつもなく、さらにRailsの万能性のおかげで、RESTfulなアプリケーションではXMLと並んで他の標準を簡単にサポートでき、よりたくさんのオーディエンスへアプリケーションを開放し、また、バンド幅要件の緩和につながる可能性があります。
コントローラの設定
ここでは、1リソース、つまりイベントのみを扱うアプリケーションに重点的に取り組みます。標準セットのCRUDアクションに多言語の仕掛けを加えたものを提供することになります。まず始めに新規Rails appを作成します。
% Rails lingua
% cd lingua
そして、scaffold_resourceジェネレータを使ってイベントリソースを1つ作成します。
% script/generate scaffold_resource Event
app/controllers/events_controller.rbを開いてみれば、「show」アクションなどの各アクションにXML出力を追加するためにrespond_toが多用されていることがわかるはずです。
def show
@event = Event.find(params[:id])
respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @event.to_xml }
end
end
(この論文では認証/許可を取り扱いません。認証/許可については、まずrestful_authenticationプラグインをお使いになることを強くお勧めします。)
JSONの紹介
JSONは最近人気の標準で、その人気の立役者としてとりわけ、UI開発言語としてのjavascriptの成熟と、AJAXの利用増加が挙げられます。直列化したjavascriptを基にしたJSONは、単純なデータ構造の直列化と送信においてはXMLと比較して格段に優れた方法であると多くの人たちが考えるようになり、冗長の程度も確実に低くなっています。
RailsにJSONを出力させるのは驚くほど簡単ですが、その理由はActiveRecordが前もってレコードをそのフォーマットで直列化するからです。JSON出力のみを希望している場合は、アクションを以下のようにアップデートすればよいだけです。
def show
@event = Event.find(params[:id])
respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @event.to_xml }
format.json { render :text => @event.to_json }
end
end
次にここで/events/1.jsonにGETを使えばJSONのイベントが返されます。
しかし、真に多言語化するためには、JSONを話すだけでなく、理解(パース)できるようになりたいでしょう。それには様々な方法があります。まず、RubyからJSONをパースできるようにする必要があります。
ほとんどの場合、有効なJSONは有効なYAMLでもあります。ですからRailsが早いうちからYAMLを容認していたことを考えると、最も単純なパーサはYAMLパーサとなり、すでにシンボルとして利用可能です。
ActionController::Base.param_parsers[Mime::YAML] = :yaml
しかし、JSONでは臨時のコンストラクト(特にコメント)を数種認めているため、注意不足のJSON作成者が入力し損なうことを意味します。オフザシェルフのJSON gemは、RailsのJSON生成を中断させますが、その理由は、JSONのパースと生成の両方を行うメソッドを提供する上で、to_jsonメソッドを再定義するからです。これまでの私の解決策は、1つ取り出し、パース機能のみを抽出し、json.rbとしてlibフォルダに保存しておくことでした。そのコードはここにペーストするには長すぎるので、以下のURIでご覧ください。
http://jystewart.net/code/json/json.txt
JSONをパースする方法を手に入れたら、次はその方法をRailsに入れる必要があります。最も理解しやすいアプローチは、JSON入力をインターセプトし、パースし、標準のパラメータハッシュとしてアクションへ渡すコントローラにbefore_filterを追加することです。以下のような方法で処理可能です。
before_filter :intercept_json, :if => Proc.new { |p| p.content_type == Mime::JSON }
def intercept_json
params[:event] = JSON::parse(params[:event])
end
しかし、別のリソースを加えようとすると、それほどDRY(同じことを繰り返さない)にはならず、より多くのフォーマットをサポートしようと追加しようものなら、各コントローラに何行ものコードが必要になるでしょう。Railsはコントローラに対して透過的にXML入力を処理しますが、JSONでも同様のことが可能であれば素敵です。ありがたいことに、できるのです!
Rails 1.2ではプラガブルなparam_parsersも導入されていますが、これを使って各MIMEタイプのパース方法を定義できます。実際、そうやってXMLのパースを設定しますが、コードは次のようになります。
ActionController::Base.param_parsers[Mime::XML] = :xml_simple
特定パーサをいかなるMIMEタイプにも追加可能で、Railsの標準パラメータは(CGIから)パースして、その隙間に抜け落ちたあらゆるものを捕捉します。自分のenvironment.rbに独自のパーサを追加できます。JSONを追加するには、次のようにすればよいだけです。
ActionController::Base.param_parsers[Mime::JSON] = Proc.new do |data|
JSON::parse(post)
end
これで、パラメータハッシュが非直列のJSONリクエストになりました。
これだけで終わりです。実際にデータベース作成の段階になったら、今ではイベントコントローラがRESTfulなリソースを提示するようになり、このリソースはXMLのみならずJSONを使用しても読み/書き可能で、さらにJSONはjavascriptで非常に簡単に使用できるため、動作レイヤとアプリケーションロジック間をJSONを使って通信することにより、とても容易に動作レイヤをアプリケーションロジックから切り離すことができますし、あるいは、ほとんどの主要言語に軽量なJSONライブラリが用意されているため、低バンド幅の方法として提供し、RESTfulなAPIへの入力を受け取ります。
マイクロフォーマットの紹介
JSON同様、マイクロフォーマットも最近ますます注目を集めていますが、その立場にはかなりの違いがあります。標準をベースにしたHTMLの使用がようやく十分サポートされるようになったWeb開発の世界では、(構造的、そしてIDとクラス名の使用において)セマンティック的にリッチなHTMLにする方がより現実的です。右側のナビゲーションバーを単に表の中の一番右のブロックにしたり、あるいは「右側サイドバー」というIDでラベルづけしたりする代わりに、「第2ナビゲーション」というマークをつけて、その体裁よりも内容について意見し始めることができます。
マイクロフォーマットは一般に使われる要素のセマンティクスを標準化することを狙ったもので、イベント(iCalendar標準由来のhCalendar)や個人ならびに企業の詳細(vcards由来のhCard)、新規ストーリー(Atom配信フォーマット由来のhAtom)などにクラス名一式を提供します。
.rhtmlビューファイルでクラス名の規則を確実に適用すればよいだけなので、マイクロフォーマット出力の追加はJSON生成よりさらに簡単です。しかしパースとなると話は別で、(HTMLになっている)マイクロフォーマットには一般に認められた独自のMIMEタイプがないため、標準リクエストとの区別ができません。この件については後ほどお話しするとして、まずは識別の済んだ入力のパース方法を見てみましょう。
Rubyistの方々には運良く、Chris Wanstrathのmofo gemという、ライブラリをパースして回る非常に多用途のマイクロフォーマットがあります。mofoはwhyのhpricotライブラリに基づいており、マイクロフォーマットを記述するDSLを提供します。mofoは最も一般的なマイクロフォーマットの定義を提供するクラスと一緒に配布されており、そのため、たとえば1つのHTML文字列内の全hCalendarイベントをパースするには、以下を使って文字列を呼び出せばよいだけです。
require 'mofo' data =events = HCalendar.find(:all, :text => data)
以下を使えば、mofoで分かっている全てのマイクロフォーマットを読み出すことさえ可能です。
require 'mofo'
data =
parsed_data = Microformat.find(:all, :text => data)
そうするとmofoは、簡単にアクセスできるマイクロフォーマットの全データとともに、オブジェクトを返します。
hpricotとmofoは非常に効率のよいライブラリですが、それでも、全ての入力のパースにhpricotやmofoを呼び出すのはやり過ぎで、考慮に入れるべきWeb標準インタフェースがある場合は、特にそうでしょう。プロファイルURIを使ってマイクロフォーマットを識別しようとする動きが多少見られましたが、それに対するサポートは一般化や安定化にはほど遠いのが現状です。マイクロフォーマットが成熟するにつれて、こうしたケースの処理方法の標準化が進むことを期待しましょう。
現在のところ、最も簡単なアプローチは標準CGIパーサを使って入力のパースを試み、POST bodyが単一文字列として理解されるのか、あるいは別個のパラメータとして理解されるのか、その返り値をテストすることです。私はmicroformat_interceptorと呼ばれる一般的なProcを使います。
microformat_interceptor = Proc.new do |data|
parsed = CGI.parse(data)
if parsed.collect { |key, value| key + '=' + value[0] }.first == data
Microformat.find(:all, :text => data)
else
CGIMethods.parse_request_parameters(parsed)
end
end
次に、これをtext/htmlとapplication/x-www-form-urlencodedの両方のMIMEタイプにアサインします。
Mime::FORM = Mime::Type.lookup("application/x-www-form-urlencoded")
ActionController::Base.param_parsers[Mime::FORM] = microformat_interceptor
ActionController::Base.param_parsers[Mime::HTML] = microformat_interceptor
ここのアクションは、単一イベントをその入力として予期しているため、生成される配列から最初のイベントを抽出するという作業がまだ残っており、そうすると、Eventモデルにパッチをあててコンストラクタ・メソッドにHCalendarを受け入れる必要が生じますが、モデルのコラムがマイクロフォーマットの属性名と名称を共有すると仮定すると、それは重要な問題にはなりません。
ところで、標準のPOSTS、XML、JSONをすでにサポートしていることを考えれば、Railsアプリケーションでマイクロフォーマットをサポートしたい理由は何でしょうか。フロントエンドなら、非常に簡単です。人間やWebブラウザ、その他のパーサが理解できる標準化フォーマットでデータを発行できるなら、検索エンジンのランキングが良くなり、インデックスにデータが入れられるようになり、その他、自らのメッセージをWeb中に広められるようになります。
しかしデータ受け入れに関しては、アピールを広めることになるので、ここでも非常に意味があります。HTMLのグルにしてみれば、マイクロフォーマットの習得にほとんど時間はかかりませんが、XMLの新スキーマやJSONの理解は大仕事でしょう。これで、1つのシステムで生成されるページは、入力として直接Webサービスに簡単に送られます。データを一度作成すればWebページとシステム間の通信に使用でき、マイクロフォーマットをサポートする発行ツールがますます増加するにつれ、あなたのサービスのオーディエンスも実際に増加していきます。
著者について
James Stewart氏はWeb開発者であり、主にRailsを扱っています。現在は米国に住んでいますが、英国への引っ越しの最中です。http://jystewart.net/process/ でWeb開発に関するブログを定期的に更新しています。
原文はこちらです:http://www.infoq.com/articles/rails-rest-and-microformats
(このArticleは2008年3月13日に原文が掲載されました)