InfoQに、Weeという継続ベースのWebアプリケーションフレームワークが紹介されていたので、試してみました。
gemでインストール後、インストールディレクトリ下のexamples/demo.rbを実行すると、自動的にWEBrickが起動します。
Counterというカウントの増減ができるページが表示され、++で1増加、--で1減算となるのですが、途中で戻るボタンを押下し、その後++/--を押下しても戻った地点から計算されます。
同じことをHTTP Sessionを使ってやると、戻るボタンを押下しても最後にsubmitした値からの加減値となり、挙動が異なります。数値をhiddenに埋め込むとできるので、この例だとメリットを感じづらいところはありますが。
Weeのアプリケーションではaction実行時にbacktrackというメソッドが呼ばれ、そこでステートを保存することができます。
1 2 3 4 |
def backtrack(state) super state.add_ivar(self, :@count, @count) end |
それがURLのpage-idと関連付けられることで、戻るボタン押下時にステートが戻るようになっているとのこと。
The solution to this problem is to take snapshots of the components state after an action is performed and restoring the state before peforming actions. Each action generates a new state, which is indicated by a so-called page-id within the URL.
次にexamples/continuation.rbを動かしてみました。こちらは、callccメソッドにMessageBoxコンポーネントを引数で渡して呼ぶと、htmlが表示されます。表示されたページにはOK/Cancelボタンがあり、どちらかを押下するかによって次の処理が決まる、というものです。
clickメソッドが1回呼び出されると、その中のcallccメソッドの呼び出し回数分(条件によって2~3回)ページが表示されている、ということになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
$LOAD_PATH.unshift "../lib" require 'rubygems' require 'wee' require 'demo/messagebox' class MainPage < Wee::Component def initialize super add_decoration(Wee::PageDecoration.new("Test")) end def click if callcc Wee::MessageBox.new('Really quit?') callcc Wee::MessageBox.new('You clicked YES') else callcc Wee::MessageBox.new('You clicked Cancel') callcc Wee::MessageBox.new('super') end end def render(r) r.anchor.callback_method(:click).with('show') end end Wee.runcc(MainPage) |
あまり継続の良さを理解できていませが、複数のリクエストに跨ってステートフルなコンポーネントを簡単に扱うことができるのがWeeの特徴の1つなのだと思います。
上記ソースのcallccはWeeのメソッドで、Kernel.callccとは異なりますが、その実装は以下のようにKernel.callccを呼び出していました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# # Similar to method #call, but using continuations. # def callcc(component) delegate = Wee::Delegate.new(component) answer = Wee::AnswerDecoration.new add_decoration(delegate) component.add_decoration(answer) answ = Kernel.callcc {|cc| answer.answer_callback = cc session.send_response(nil) } remove_decoration(delegate) component.remove_decoration(answer) return *answ.args end |
Sorry, comments are closed for this article.