Rails勉強会@東京 第10回 に行ってきた。
今回もドリコムのオフィスを貸していただいての開催。
前半
なんか、いっぱいセッション案が挙がってて、一部、すぐに終わっちゃいそうなネタもあったので統合してこんな感じのセッションを行なった。
私はRIA/Ajax/Cometのやつに出た。この辺の最近の動向についてのりおさんに色々お話を伺った。このセッションは Rails Chat にて 生中継 を行った。
Ruby on Rails RIA SDK by Adobe
なんか期待されて話題になってるけど、たいしたものではない。SDKと銘打ってはいるもの、なんか自前のライブラリを提供している訳ではなく、ただの接続チュートリアル。
Railsをバックエンドにして、UIはFlex2で作成という、誰もが考えそうで実際にいくつかの案が出ているのをAdobeブランドを関してまとめただけ。とくに接続が便利になるとかそういう訳ではない。
ただ、Rails 1.2のActionController#respond_to{|format|}を使うと、HTML版とFlex版を柔軟に切替えられて良い。これからはそういうのが便利かもしれないとのこと。
ちなみに、Flex2の開発環境は日本語版も出たところ。EclipseベースのIDEがあって、体験版を無料で利用できる。買うと6万円くらい。
Flexの競合製品
そもそもRIA自体がAjaxに食われた感があると、のりおさん。そういう感覚は私も持ってた。他の出席者にも大体共有されてた模様。 リッチクライアント ソリューションカンファレンス とか、リッチクライアント製品への期待が最高に高まったところにAjaxが来ちゃったからね。
あと、競合製品としてはOpenLazloとか? 私はCurlが好きですが、何か? LLDNでCurl団扇を貰って喜ぶぐらいCurlに期待してますが、何か? でも、「Curlは解説書見た時点で、これはダメだと思った」とも言われてしまった。課金モデルが変わったり、サーバー環境が高いバージョンしかなかったり、敷居が高い面はあったよなー。最近ようやく 個人向け無償版 が出たけど、遅すぎたかも知れない。個人の利用を通じて開発者層を形成するのが大切なのにね。これをもっと早くやってたらね。AdobeにせよMacromediaにせよ競合がたくさんいるところに進出するには少々戦略がまずかった気もする。
あとはXUL? XULのいいところはpiroたんが萌えキャラなところだと思ってる。
Cometって何さ
のりおさんによるCometの解説。
クライアント側主導のRequest - Response型通信を、無理矢理サーバーpush型にする。
クライアントの開いた通信路を必要になるまで開いたままにして、サーバーは必要なときにそれを使ってクライアントにメッセージをpush
クライアントはメッセージを受けたらすぐさま繋ぎ直す。これで常時サーバーとの間に通信路が開かれているも同然
パフォーマンスを考えなければ実装は簡単
パフォーマンス面で、セッション数が爆発しないように実装を工夫する必要がある。
Rails ChatではWebアクセス用とは別に専用のpushサーバーを持ってる。
複数人で連携して何かをやるのに便利。
Chat
- 同時編集Wiki
- 同時編集スプレッドシート
- 同時編集ホワイトボード
RailsChatの場合、pushサーバーへの接続にはFlashのXMLSocketを使ってソケット通信してる。XMLSocketは実はFlash 5の頃からある枯れてる機能。
資料をいくつか。
ちなみに、AjaxもCometも洗剤の名前から来てるそうだ。SOAPのパロディで。
Ajaxとかのライブラリ
Ajaxライブラリの利用調査 がある。一番使われてるのはやっぱりprototype.js
各種ライブラリを混用すると、大抵は問題なく動くもののとてつもなくメモリーを食う。……といっても、所詮は1ページあたり2MB程度なので、開きなおってMoo.fxもRicoも入れてなんとかならなくはない。ただし、開発環境ではさくさく動くのにお客さんの環境では重かったりして泣くことになるかもしれない。
色々なひとから出たコメント。
- MochiKitは速いし、書くのも楽
jQueryは面白い
デモサイト が良い
- Railsで使うのための jQuery on Rails もある。
- なんでQueryなのかは謎につつまれている。最初クエリ言語かと思った人が多数
- 拡張されていく入力欄 が素敵。ブログのコメント入力欄のように、初期状態であまり場所をとってほしくないけれども書くときは広いほうが嬉しい類のケースに良いかもしれない。
ThikBox も良い感じ。
でも、あまりにもダイナミックなUIは好みが分かれる。"It's cooool"と思ってても「使いにくい」と切り捨てられたりする。
他のセッション
-
ひととおりいろいろやったらしい。
Prelude, Selenium
すぐ終わったらしい。 Prelude はRubyでHaskellライクな表記を実現するライブラリで、まだまだ発展途上という評価らしい。Seleniumは、 前回 よりも少しだけRailsよりの所を扱ったけれども、基本的には前回と同じ内容みたい。
Rails 1.2を先取り
1.1.6以降のChangelogを淡々と読んだけれども、大きな変更は無いらしい。
後半
3つに分かれた。
- ソーシャルブックマークを作る(第4回)
- Capistrano 1.2
- Action Web Serviceを淡々と読む
私はAction Web Serviceのオーナーを務めさせていただいた。
勉強会の第5回 でAction Web Serviceを触ってみるのは一応やってあるけれども、今回はじゃあ中身を1つ見てみようという催し。
私のマシンの画面をプロジェクタに写せなくて、参加者のみなさんには少し不便を強いてしまった。申し訳ない。お詫びに、もし希望する方がいたら次回もう少し丁寧にやり直します。 shachi さんからは前回、「MacユーザーはVGA変換アダプタを買って持ってきなさい」という忠告をいただいていたのに土壇場まで忘れていて入手が間に合わなかったのが敗因。
エントリーポイント
ActionWebServiceのソースを見てみると、action_web_service/dispatcher/action_controller_dispatcher.rbというそのまんまの名前のファイルがある。 これを見てみると、ActionWebService::Dispatcher::ActionControllerというモジュールがある。このモジュールにはself.includedも定義されている。拡張機能をモジュールで定義してあとでincludeというRailsの慣習からすると、やっぱりこれがエントリーポイントらしい。これをあとでどこかでActionController::Baseからincludeするんだなというのはなんとなく想像がつく。
ActionWebService::Dispatcher::ActionController::included(base)を見ると、その中でやっぱり色々機能を足してるのが分かる。
class << base include ClassMethods alias_method_chain :inherited, :action_controller end
モジュールを元にクラスメソッドを足すのはRailsのコードではおなじみ。
base.class_eval do alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke end
これは何やってるんだろうね。取り合えず、web_service_direct_invokeに何かフィーチャーを足してる可能性はあり。
base.add_web_service_api_callback do |klass, api| if klass.web_service_dispatching_mode == :direct klass.class_eval 'def api; dispatch_web_service_request; end' end end
エエェェ(´д`)ェェエエ。必要もないのに文字列evalは止めようよ。しかも、ですよ。ActionWebServiceを使ったときにAPIコールのためのエンドポイントになるのは
/<var>controller_name</var>/api
というpathなのだけれど、そのpathに対してHTTP requestを掛けるとWebService呼び出しになるっていうのは、別にマジックでも何でもなくて、ただ単に、「そういう名前のアクションメソッドを勝手に定義してくれてる」だけってことね。
「それって何かでセキュリティ的にまずかったりするケースがないのか?」と危惧する人多数。
base.add_web_service_definition_callback do |klass, name, info| if klass.web_service_dispatching_mode == :delegated klass.class_eval "def #{name}; dispatch_web_service_request; end" elsif klass.web_service_dispatching_mode == :layered klass.class_eval 'def api; dispatch_web_service_request; end' end end
AWDwR や第5回のレポートでも触れてるdispatching modeによって切り分けしてる。でも、この程度の定義に文字列evalはやっぱり止めてほしい。
これを括ってるadd_web_service_api_callbackやadd_web_service_definition_callbackの中身はセッションでは追わなかったけれども、なんか面白そうだからあとで書く。
base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods)
で、シメはやっぱりモジュールのincludeによる拡張。
API呼び出し
さて、上によって定義された <var>YourController</var>#api
というアクションによって、dispatch_web_service_requestが呼ばれるということは分かった。じゃあ、そこから先は?
同じaction_web_service.dispatcher/action_controller_dispatcher.rbの下のほうに定義がある。
ActionWebService::Dispatcher::ActionController::InstanceMethods#dispatch_web_service_request
ね。
exception = nil begin ws_request = discover_web_service_request(request) rescue Exception => e exception = e end
まずは、普通のControllerで使ってるリクエストオブジェクトを、ActionWebServiceの世界に固有のリクエストオブジェクトに変換してる。プロトコル(XML-RPC/SOAP)依存の処理が入るのはここのところがメイン。で、ここで例外を捕捉して保存してるのはなぜかというと、下のほうを見ると分かる。
if ws_request # 正常系の処理 else exception ||= DispatcherError.new("Malformed SOAP or XML-RPC protocol message") log_error(exception) unless logger.nil? send_web_service_error_response(ws_request, exception) end
あー、そうね。エラーメッセージを返してるんね。で、省略してしまった正常系のところは、ログを取ってるのと、例外発生時にはやはり同様に捕捉してるのをのぞけば、
ws_response = invoke_web_service_request(ws_request)
send_web_service_response(ws_response, bm.real)
これだけ。 bm
は invoke_web_service_request
の処理時間を計ってるBenchmarkオブジェクトだけど。なんで計ってるんだろ。
プロトコルの特定と解析
さて、これ以上先に進む前に、さっき流してしまった discover_web_service_request(request)
を見てみる。このメソッドはaction_web_service/protocol/discovery.rbにある。
(self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol| protocol = protocol.create(self) request = protocol.decode_action_pack_request(action_pack_request) return request unless request.nil? end
ここで、read_inheritable_attributeってなんぞ、と一瞬焦ったけれども、舞波さんが解説してくれた。 RoR Wikiにも説明がある 。本当に名前そのまんま、継承される属性なのね。
クラス変数はその変数を定義しているクラスだけじゃなく、サブクラスからもアクセス可能。だけれども、クラス階層を通じてオブジェクトを共有しているので、サブクラスでその値をいじってしまうと親クラスにまで影響してしまう。
じゃあサブクラスごとに値を持てばいいんでしょ、と思って「Classオブジェクトのインスタンス変数」を使う手も有る。でも、これの問題はそのままではサブクラスから親クラスの属性にアクセスできないこと。サブクラスのClassオブジェクトと親クラスのClassオブジェクトは当然オブジェクトとしては別だから。
まぁ、この問題に対処する方法はいくつかあるけど、その辺をよしなに処理しておいてくれるのが(read|write)_inheritable_attributeだそうな。あー、完全に忘れてたよ。ありがとう舞波たん。
さて、そういうわけで、"web_service_protocols"属性に対してeachを回す。中に入ってるのはActionWebService::Protocol::AbstractProtocolのサブクラスのClassオブジェクトね。デフォルトではSOAPとXMLRPC。
protocol.create(self) で、上手いことインスタンス化する。それで、protocol.decode_action_pack_request(action_pack_request)で、そのプロトコルのメッセージとしてparseしてみる。それで、うまいことparseできたらその結果を返す。
じゃあ、decode_action_pack_requestはどうなってるの? SOAPは関連ファイルがいっぱいあって、ややこしそう。なのでXMLRPCのほうを見てみる。
XMLRPCの解析
action_web_service/protocol/xmlrpc_protocol.rbを見る。ActionWebService::Protocol::XmlRpc::XmlRpcProtocolクラスが定義されてる。
まず、さっき protocol.create(self)
として呼んでたやつの実装がある。
def self.create(controller) XmlRpcProtocol.new end
本当にインスタンス化してるだけ。でも、本当は self.new
のほうが望ましいけどナー。
で、decode_action_pack_requestのほうは、というと
def decode_action_pack_request(action_pack_request) service_name = action_pack_request.parameters['action'] decode_request(action_pack_request.raw_post, service_name) end
raw_postで、生のHTTP Request bodyにアクセスしてる。ふーん。次に行こう。
def decode_request(raw_request, service_name) method_name, params = XMLRPC::Marshal.load_call(raw_request) Request.new(self, method_name, params, service_name) end
あー、ここでファイルの最初を見ると
require 'xmlrpc/marshal'
XMLRPCメッセージのMarshalには標準添付ライブラリのやつを使ってるのね。
API呼び出しの実行
というわけで、requestをparseするところまでは読んだので、上に戻って、parseに成功した正常系の場合にどうやって実行されるのかを見てみる。今、parseして作成したリクエストオブジェクトを変数ws_requestに代入しておいて、
ws_response = invoke_web_service_request(ws_request)
しているのであった。
invoke_web_requrestはaction_web_service/dispatcher/abstract.rbにある。
def invoke_web_service_request(protocol_request) invocation = web_service_invocation(protocol_request) if invocation.is_a?(Array) && protocol_request.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol) xmlrpc_multicall_invoke(invocation) else web_service_invoke(invocation) end end
protocol_requestをここで更にinvocationに変換しなければならない理由がよくわからないんだけど、とにかくinvocationというのはこのファイルの下の方で定義されているActionWebService::Dispatcher::InstanceMethods::Invocationのインスタンスだ。
class Invocation # :nodoc: attr_accessor :protocol attr_accessor :protocol_options attr_accessor :service_name attr_accessor :api attr_accessor :api_method attr_accessor :method_ordered_params attr_accessor :method_named_params attr_accessor :service end
ようするに、呼び出しに必要な情報を束ねた構造体のようだ。
- protocol: protocol_requestを構築した段階で既にSOAPかXMLRPCかは分かっている。その情話法を格納しておく。ActionWebService::Protocol::AbstractProtocolのインスタンス
- protocol_options: HTTPヘッダなどの、付加情報
- service_name: SOAPならば"SoapAction" HTTPヘッダフィールドから、XMLRPCならばXML中の対応する属性から取得する。
- api: ActionWebService::APIのインスタンス。DSLで定義されたAPI定義を表すオブジェクト。これはdispatching modeによって取得方法が違って、Direct dispatching modeではcontrollerの定義で宣言してあるから、controllerクラスのinheritable attributeから取り出せば良い。dispatching modeがdelegated, layeredの場合は、実際にAPIを実装しているオブジェクトをcontrollerが知っている訳なので、そのオブジェクトに問い合わせればよい。
- api_methodには、呼び出されるAPI実装を表すActionWebService::API::Methodオブジェクトが入る。これ、セッションのときは組み込みのMethodオブジェクトかと思ったら、よく見たら違うのね。API宣言クラスでapi_method :name, :excepcts => [...], :returns => [...] を呼んだときに内部に構築されるオブジェクトで、メソッドの名前と型情報が入ってる。
- method_ordered_paramsは、api_methodが持ってる型情報に応じて変換された引数の列
- method_named_paramsは仮引数名をキーに、実引数を値に持つHash
- service: このAPIの実装を提供しているオブジェクト。direct dispatching modeではコントローラー自身。delegated, layeredの場合は転送先のオブジェクト
さて、そんな感じで、invoke_web_service_request(protocol_request)メソッドでは、invocationを構築した訳だ。で、次の行はktkr!
if invocation.is_a?(Array) && protocol_request.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol)
こういうコードがあと何ヶ所かあったんだよね。is_a?で分岐してる類のが。そこはAbstractProtocolのpolymorphismでなんとかしてほしい。そうでないとSOAP, XMLRPC以外のプロトコルを足すのが難しくなってくる。もうね、 GoF を100回読めと。せめて、respond_to?で分岐して欲しい。
XMLRPCのmulticallだけ特別扱いしてる模様で、よくわからないけど、とりあえず「普通」のほうを見ることにする。「普通」の場合に呼ばれるのは
ActionWebService::Dispatcher::InstanceMethods#web_service_invoke(invocation)
で、ここでもまたdispatching modeによる場合分けをしていて、directならはweb_service_direct_invokeを、delegatedやlayeredならばweb_service_layered_invokeを呼んでる。
def web_service_invoke(invocation) case web_service_dispatching_mode when :direct return_value = web_service_direct_invoke(invocation) when :delegated, :layered return_value = web_service_delegated_invoke(invocation) end web_service_create_response(invocation.protocol, invocation.protocol_options, invocation.api, invocation.api_method, return_value) end
んー。なんかこれも、dispatching modeはinvocationを構築したときの場合分けで既知なわけだし、これこそ「呼び出しの内容」の実装の詳細だから、invocationのpolymorphismで対処してほしいなぁ。
まぁ、それで、web_service_direct_invokeの場合はちょっとした処理が入るけれども、どちらも最終的にはActionWebService::Dispatcher::InstanceMethods#web_service_filtered_invokeに帰着する。これも、例外処理やコールバックの呼び出しを除けば、invocation.serviceにsendしてるだけ。かくて、呼び出しができたのであった。
そして、最後はweb_service_create_responseを呼ぶ訳だ。これも、こまごまと色々してるけれども、容易に想像がつくように、やりたいのはprotocol実装に返り値をmarshalさせることだけ。このあたりは時間がなかったので少々駆け足でとばしてしまった。
Include
そういえば、最初に見たようにActionWebServiceの機能はModule#includedを通じてコントローラーに提供されてる。じゃあ、そもそも関連モジュールがIncludeされるのはいつなんだろう。method_missingでも捕まえて、webサービス機能関連の宣言をするとincludeされる仕掛けでもあるんだろうか。
トップレベルのディレクトリにあるaction_web_service.rbを見ると書いてあった。
ActionController::Base.class_eval do include ActionWebService::Protocol::Discovery include ActionWebService::Protocol::Soap include ActionWebService::Protocol::XmlRpc include ActionWebService::Container::Direct include ActionWebService::Container::Delegated include ActionWebService::Container::ActionController include ActionWebService::Invocation include ActionWebService::Dispatcher include ActionWebService::Dispatcher::ActionController include ActionWebService::Scaffolding end
ちょww 無差別wwww
他のセッション
ソーシャルブックマークを作る
acts_as_authenticatedを使って認証機能を付けたそうだ。
Capistrano 1.2
なんだっけ? それはそうと、昨日あたり AWDwR 2nd ed. が更新されて、capistranoについて大幅加筆されてたね。あとで読む。
懇親会
ここしばらく同じ店で懇親会やってたので、ちょっと飽きが来はじめるころ。そのへんを考えてか、今回はほかの店にしてくれた。
- もろはしさん、ご結婚おめでとうございます。
- のりおさんのデザイナ向けRails本は遅れ気味
Rails和レシピ本も遅れ気味
レビューは喜んでお引き受けしますよ
ABD: Activity系とEvent系の違いがちょっとだけ分かってきたかも? マクタガートの時間論に言う2つの時制の違いに近いかも。でも、概念としての違いは分かったのかもしれないけど、それを結合しちゃいけない理由は未だに分からない。
- Rubyの多値代入/多値returnの件って、前にMultiValue < Arrayとかいう案が上がってたけど、もう少し汎用的にTupple < Arrayじゃだめかね。Symbol < Stringが許されるならこれもいいんじゃないか?
なんていうかさ、外部の技術者の自発的な協力を得て発展させて、デファクトスタンダードを目指そうっていうなら、少なくとももっとプロジェクト体制をオープンにしないと受け入れられないよね。
- そもそもが、"みかか"という負のブランドイメージを背負ってるんだから、せめてもう少しオープンさのアピールがないと。
- でも「負のブランドイメージって、内部からはなかなか見えないんだよね」と、もろはしさん評。
- まぁ、ひょっとしたら、現場は分かってるに上の頭が固くてあれがギリギリ頑張った結果という可能性もあるけど。NTTデータにもスーパーハカーがいるのは知ってるしね。
- とにかく、頑張れマスカット。