Ktouth Brand. on Web

け〜くんこと K.Ktouth のだらだらした日常と突発的に作るプログラムや読み物とかの雑多サイト



[2009年08月30日]

Ramaze のアクションメソッドに必須パラメータをつけた時の予想外の挙動

2009年08月30日 08:50更新 筆者:K.Ktouth

Ramaze の コントローラクラスの公開メソッドはアクションメソッドとして認識されます。
その際、パラメータを指定しておくとURLのパラメータを簡単に取得できます……が、ここにちょっとした落とし穴があることに気づきました。
簡単にまとめると「テンプレートの存在するアクションメソッドに(初期値の存在しない)必須パラメータがある場合、アクションメソッドが呼ばれずにテンプレートだけ適用されてしまう」という現象です。そのため、テンプレート内でアクションメソッド内で初期化されているべきインスタンス変数にアクセスするとエラーを発生させてしまいます。

(以下、詳細説明)

まずは例文を

以下のソースは、その現象を再現するためのコントローラです。

class TestController < Ramaze::Controller
 engine :Erubis
 map '/test'

 def foo
  @data = ['foo', 156]
 end

 def bar(key = nil)
  @data = [key || 'bar', 11849]
 end

 def baz(key)
  @data = [key || 'baz', 782]
 end

 def bad(key)
  @data = [key || 'bad', 123548]
 end
end

対応するテンプレートとして以下のものを使っています。

<%= @data.last %>

これを、view/test フォルダ内に、foo.rhtml / bar.rhtml / bad.html の3つの名前で作成しています。baz.rhtml はありません

結果は以下の通り。

No.アドレス出力
1http://****/test/foo156
2http://****/test/foo/no_argsNo action found at: "/foo/no_args"
3http://****/test/bar11849
4http://****/test/bar/no_args11849
5http://****/test/bazNo action found at: "/foo/no_args"
6http://****/test/baz/no_argsno_args782
7http://****/test/badNoMethodError at /test/bad
undefined method 'last' for nil:NilClass
8http://****/test/bad/no_args123548

例7、bad メソッドにパラメータ無しでアクセスした場合、例外が発生してしまっています。
# 例6 の出力はメソッドの返値 (['no_args', 782]) がそのまま出力されています。

Action クラスのレンダリング処理

上記トラブルは、Action クラスのレンダリングの際に、メソッド実行とテンプレート適用が独立して行われているために発生しているものです。
簡単に Action クラスが行っている処理を説明すると……

  • Action クラスの生成時

    1. 該当するコントローラ (Action#node) の検索
    2. 対応するメソッド (Action#method) の検索。この際、パラメータが一致するかも確認する。
    3. 対応するテンプレート (Action#view) の検索。
  • Action#render 実行時

    1. コントローラの生成
    2. Action#method が指定されているなら、コントローラ内の該当メソッドを呼び出す
    3. Action#view が指定されているなら、該当テンプレートを読み込む
    4. テンプレート、もしくはメソッドの返値を元にテンプレートエンジンでレンダリングを行う

となっています。
この際、メソッドとテンプレートは、各々がURLから切り出した「アクション名」と一致するものを検索し、加えてメソッド検索の際にはパラメータの数が一致しているかどうかも確認しています。
つまり、上記例の場合……

No.アドレスメソッドテンプレート
1http://****/test/fooTestController#fooview/test/foo.rhtml
2http://****/test/foo/no_argsTestController#foo('no_args') => nilview/test/foo.rhtml
3http://****/test/barTestController#barview/test/bar.rhtml
4http://****/test/bar/no_argsTestController#bar('no_args')view/test/bar.rhtml
5http://****/test/bazTestController#baz => nilnil
6http://****/test/baz/no_argsTestController#baz('no_args')nil
7http://****/test/badTestController#bad => nilview/test/bad.rhtml
8http://****/test/bad/no_argsTestController#bad('no_args')view/test/bad.rhtml

例2・5・7 のメソッド探索は赤字のような形で行われますが、最初のソースのように引数の数が一致しません。結果として Action#method には nil が設定されます。
例5・6 のテンプレート探索は、該当するものがないので Action#view には nil が設定されます。

例7 の挙動だけが想定外

例1・3・4・6・8 に関しては、正しいアクセスに正しくレンダリングした結果を返しています。
例2・5 に関しても、エラーを正しく返しているという点で問題ありません。
ところが例7 、「パラメータが足りず、かつ、テンプレートが存在する時」だけが想定外の挙動を行っているわけです。例2 でも同様のエラーが出るのならわかるのですが……やはりバグ?

対処方法としては、「パラメータが足りない」という状況を作らないようにする、つまり省略値をつけるのが一番手っ取り早いです。上記例だと TestController#baz のように実装し、内部でパラメータチェックを行い、自前でエラー処理を行うと言うことになります。

上記に加えて「該当メソッドが無くても、テンプレートがあればレンダリング可能」という仕様をきちんと把握しておくことは、想定外の挙動を生まないためには重要だと思います。

本日のリンク元
アンテナ
その他のリンク元
検索