ActionWebServiceでxmlrpcサービスを作る

靴下が変な人に「くれりんこ」と云われたような気もするので作り方を解説します。
RailsではActionWebServiceを使うとxmlrpcやsoapのサービスを簡単に作ることができます。今回はTraipyという以前実装したTropyクローンに簡単にxmlrpcで投稿(newTropy)、エントリーの取得(getTropy)をできるAPIを実装してみましょう。
まずAPIのライブラリを置く場所ですが、Rails的にはRAILS_ROOT/app/apis/*_api.rbとして置くのが一般的です。今回はxmlrpc_api.rbという名前を付けて配置します。

module XmlrpcStruct
  class Tropy < ActionWebService::Struct
    member :key, :string
    member :source, :string
  end
end

class XmlrpcApi < ActionWebService::API::Base
  inflect_names false
  api_method :getTropy,
             :returns => [XmlrpcStruct::Tropy],
             :expects => [{:key => :string}]

  api_method :newTropy,
             :returns => [:string],
             :expects => [{:source => :string}]
end

class XmlrpcService < ActionWebService::Base
  web_service_api XmlrpcApi
  def getTropy(key = nil)
    if key
      tropy = Tropy.find_by_key key
    else
      tropy = Tropy.find_by_random
    end
    XmlrpcStruct::Tropy.new :key => tropy.key, :source => tropy.source
  end

  def newTropy(source)
    tropy = Tropy.new(:source => source)
    tropy.save
    tropy.key
  end
end

moduleのXmlrpcStructではTropyというActionWebservice::Structを継承したクラスを作ってます。べつにこれは必須というわけではなく、getTropyメソッドが叩かれた時の返す構造を簡単に定義するために利用します。
class XmlrpcApiではActionWebService::API::Baseクラスを継承し、APIインターフェイスを定義します。ここではメソッド名と返り値、入力値の型を定義します。たとえばgetTropyメソッドでは、返り値は先ほど定義したXmlrpcStruct::Tropyの構造体、一番目の引数の型はstring(定義しなくても動くが、それにあわせキャストされるため定義すべき)を定義してます。inflect_namesをfalseにしてるのは、railsお節介機能でメソッド名の自動キャメライズを防ぐためです。
そして最後のXmlrpcServiceでは、ActionWebService::Baseクラスを継承し、各メソッドの実装部を定義します。web_service_apiではどのクラスのAPIを利用するか定義してます。getTropyではkeyがあればそのkeyのデータをDBから検索し、なければランダムで1件取得してます。そして返り値でXmlrpcService::Tropyの構造体を作って返しています。netTropyではsource(本文)をDBに新規データ登録して、keyの値を返しています。
次にコントローラの実装です。今回はxmlrpc_controllerとずばりそのまんまなコントローラを作ります。

class XmlrpcController < ApplicationController
  web_service_dispatching_mode :delegated
  web_service :api, XmlrpcService.new
end

で終わりです。web_service_dispatching_mode :delegatedと指定することで、コントローラのアクション実行をActionWebServiceに委譲します。そして、web_serviceにapiという名前でXmlrpcServiceを登録します。これでhttp://host/xmlrpc/api を叩くことによってxmlrpcが利用できるようになります。
以上で、実装における説明は終わりです。長ったらしいですけど、慣れれば簡単に実装可能でしょう。今回のxmlrpcを実装したTraipyのソースが欲しい方は http://svn.rails2u.com/public/traipy/trunk/ からどうぞ。また実際にhttp://traipy.rails2u.com/xmlrpc/api を叩くことができます。
それでは実際にxmlrpcとして使えるかどうかテストしてみましょう。

#!/usr/bin/env ruby
require 'xmlrpc/client'

server = XMLRPC::Client.new('example.com', '/xmlrpc/api')
# ランダムに取得
p server.call('getTropy')

# 新規に作成してkeyを取得
key = server.call('newTropy', "title hoge\nsource foobar")
p key
# 先ほどのkeyを引数にしてgetTropyを実行
p server.call('getTropy', key)
# 結果
{"source"=>"rails!rails!", "key"=>"a9e9daba40e1c3f8"}
"85baf8804159ac01"
{"source"=>"title hoge\nsource foobar", "key"=>"85baf8804159ac01"}

うまく使えているようですね。このようにActionWebServiceを使うと、xmlrpcやsoap(試してないけど)をすんなり使えるようになります。また、BloggerAPIやMetaWeblogApiはTypoの実装を見るのが一番理解しやすいと思います。たとえば、metaWeblogApiのgetPostの実装は

 def getPost(postid, username, password)
    article = Article.find(postid)

    article_dto_from(article)
  end

のように行っており、username引数で渡してるけど認証どうやってるの?という疑問はbefore_invocation :authenticateで行ってたり、非常に参考になります。
ActionWebServicerailsライブラリの中でもバツグンにドキュメントが少ないので、他の実装例を見るのが手っ取り早いですね、、、。