Rails3のビューでブロックを伴なうHelperを使う時の注意
globalize3というgemを評価していて、Rails3のおかしな挙動に気がつきました。
ビュー内で、Globalize.with_locale というメソッド使うと、一部の内容がダブって出力されるというものです。
最初、globalize3の問題かと思いましたが、調べてみると、Rails3の問題のようです。ビュー内でブロックを使うHelperメソッドを使うと、そのブロックまでの内容が二重に出力されます。
以下の手順で再現できます。(Rails 3.0.3で確認)
新規アプリケーション作成
$ rails new just_yield $ cd just_yield
コントローラを追加
$ rails g controller welcome index $ rm public/index.html
config/routes.rbに以下を追加
root :to => "welcome#index"
app/helpers/application_helper.rb に、yieldを行なうだけのHelperメソッドを追加
module ApplicationHelper def just_yield yield end end
ビューを以下のように変更
<h1>Welcome#index</h1> A <% just_yield do %> B <% end %> C
サーバを起動して、http://localhost:3000/にアクセスすると、以下の出力になります。
<!DOCTYPE html> <html> <head> <title>JustYield</title> <script src="/javascripts/prototype.js?1290752743" type="text/javascript"></script> <script src="/javascripts/effects.js?1290752743" type="text/javascript"></script> <script src="/javascripts/dragdrop.js?1290752743" type="text/javascript"></script> <script src="/javascripts/controls.js?1290752743" type="text/javascript"></script> <script src="/javascripts/rails.js?1290752743" type="text/javascript"></script> <script src="/javascripts/application.js?1290752743" type="text/javascript"></script> <meta name="csrf-param" content="authenticity_token"/> <meta name="csrf-token" content="Dow8TXbSPVQh9azlacoLXSTrlOTnfsdJIuJvgfJP8EQ="/> </head> <body> <h1>Welcome#index</h1> A B <h1>Welcome#index</h1> A B C </body> </html>
つまり、ビューの先頭から、ブロックの終わりまでが二重に出力されます。
ビューを以下ように修正して、just_yieldの評価結果を変更すると正常に出力されます。
<h1>Welcome#index</h1> A <% just_yield do %> B <% 1; end %> C
出力結果
<!DOCTYPE html> <html> <head> <title>JustYield</title> <script src="/javascripts/prototype.js?1290752743" type="text/javascript"></script> <script src="/javascripts/effects.js?1290752743" type="text/javascript"></script> <script src="/javascripts/dragdrop.js?1290752743" type="text/javascript"></script> <script src="/javascripts/controls.js?1290752743" type="text/javascript"></script> <script src="/javascripts/rails.js?1290752743" type="text/javascript"></script> <script src="/javascripts/application.js?1290752743" type="text/javascript"></script> <meta name="csrf-param" content="authenticity_token"/> <meta name="csrf-token" content="Dow8TXbSPVQh9azlacoLXSTrlOTnfsdJIuJvgfJP8EQ="/> </head> <body> <h1>Welcome#index</h1> A B C </body> </html>
つまり、ビュー内で使用するHelperメソッドが、yieldの結果をそのままリターンすると、二重の出力になるようです。
さらにこれの原因を調べてみると、do ... end を含むERBソースは、次のようにコンパイルされます。
@output_buffer.append_if_string= just_yield do @output_buffer.safe_concat('B\n'); end
つまり、<%= ... %> ではなくて、<% ... %>を使っているのに、その式の評価結果を出力結果として使用するようです。このメソッドの実装は以下の所です。
gems/actionpack-3.0.3/lib/action_view/template/handlers/erb.rb
def append_if_string=(value) if value.is_a?(String) && !value.is_a?(NonConcattingString) ActiveSupport::Deprecation.warn("<% %> style block helpers are deprecated. Please use <%= %>", caller) self << value end end
この Deprecation.warn は、確かに上記の問題が発生する時に、出力されています。
後方互換性のために、ブロックを伴う式の評価結果が文字列だと、それが出力されるようにしてある、ということのようです。
結論として、ブロックを伴う Helperメソッドの実装を変更できる場合には、
module ApplicationHelper def just_yield yield 1 # 文字列を返却するとテンプレートに出力されてしまうので、文字列以外を返す end end
とすればよくて、実装が変更しにくい場合はビューの方を下記のように変更すれば、良いと思います。
<h1>Welcome#index</h1> A <% just_yield do %> B <% 1 # ブロックが文字列を返却するとテンプレートに出力されてしまうので、文字列以外を返す end %> C