render :partialのcollectionとしてHashのArrayを渡せるか?

タイトルを分かりやすく書くと、例えば

<%= render :partial => "foo/foo", :collection => @foox %>

のとき、@fooxとしてHashのArrayを渡せるか?つまり

@foox = [
  {:a => "moo", :b => "baw"},
  {:a => "coo", :b => "bah"}
]

を渡したら、{:a => "moo", :b => "baw"}{:a => "coo", :b => "bah"}を使ってfoo/_foo.rhtmlがrenderされるか?という問題です。

答えは”されません”。どうなるかと言うと、Hashオブジェクトの代わりにnilが渡され、nil.[]がNoMethodErrorになります。

なぜか。render :partial => "foo/foo", :collection => @fooxActionView::Partials#render_partial_collectionでrenderされますが、このメソッドはcollectionのメンバ毎にActionView::Partials#render_partialを呼び出します。このときメンバがHashだと(問題のケース)、render_partialはそのHashをlocalsだと思ってしまい、代わりにpartial名のついたインスタンス変数(上の例だと@foo)を探しに行ってしまうからです。@fooは用意していないのでnilになります。

どうしてもHashのArrayを使いたい場合は、次のようにpartial名のキーを持ったHashで包んでやります。

@foox = [
  {:foo => {:a => "moo", :b => "baw"}},
  {:foo => {:a => "coo", :b => "bah"}}
]

なぜかというと、render_partialは最終的に@fooをlocalsにpushしてrenderを実行するので、その通りにpushされたようなHashを作っておいてやればよい、ということです。

しかし普通はそんな変なハックはせずに、Hashではないオブジェクトを渡すようにするべきです。当然ですが。