RailsでJSON-RPC

http://blog.masuidrive.jp/articles/2006/03/01/newrails で、Rails1.1からObjectにto_jsonメソッドが追加されるっていうんで、もしかしてJSON-RPCが特別な仕組み無しで実現できちゃうんじゃないの?と思ってたら、一部できちゃった、というお話。
ここで想定しているのは、パラメータを幾つか渡してサーバから一覧を取得するとか、そういう場合。複雑な構造を持つオブジェクトをJSONでサーバに送って更新、とかそういう場合はもっと別な仕組みがいるはず。

まずサーバサイド。例えば書籍一覧を書名で絞り込みたい場合

class BookController < ActionController::Base
  def json_find_books
    render_text Book.find_by_book_name( params[:book_name] ).to_json
  end
end

検索結果をto_jsonメソッドでjson形式の文字列に変換する。これでサーバサイドは準備OK!
あ、余計なattributeを送らないようにHashのArrayに変換したほうが良いかも。

次はクライアントサイド。全部書くと大変なので重要なとこだけ書きます。まずは呼び出すところ。prototype.jsAjax.Requestを使います。

var queryParamString = $H(queryParams).toQueryString();
var options = {
        method: "get",
        parameters: queryParamString,
        asynchronous: true,
        requestHeaders: ["Content-type", "text/plain"],
        onComplete: this.responseComplete.bind(this)
    };
    new Ajax.Request( "/books/json_find_books", options );

ポイントはURLが "//"でOKというところです。素敵。queryParamsはパラメータが入っているただのオブジェクトです。例えば{"book_name":"Rails"}とか。$HでHashに変換するとtoQueryStringメソッドが使えます。それからパラメータがそれ程長くなく、呼び出すアクションも破壊的ではないので、GETメソッドを使っています。

responseCompleteは次のような感じのメソッドです。

    responseComplete: function( transport, json ) {
        var jsonString = transport.responseText;
        var object = eval(jsonString);
        //・・・
    }

transportオブジェクトはいわゆるXMLHttpRequestオブジェクトです。jsonはレスポンスのHeadersに'X-JSON'として渡された文字列をevalしたものです。ここではresponseTextをevalしてオブジェクトをゲットしているので、関係ありません。本当はstatusとかを判断したりする処理が必要な気もするけど、省略してます。

また、Functionのbindメソッドに引数を追加すると、呼び出されるときの引数のに、追加された引数のオブジェクトが渡されます。下の例ではcallbackが追加された引数です。

    this.responseComplete.bind(this, callback)
    responseComplete: function( callback, transport, json ) {


考えてみたらRPCと呼ぶには面倒くさいやり方ですね。メソッドを呼び出していることは間違いなけど、透過的じゃないというか。でも魔法みたいに見える部分が少なくて良いかも。だんだん面倒くさくなるんだろうけど。