ゲーム攻略からプログラミングまで、その時に思いついたことをだらだらと書き連ねていきます。

【Rails】helperさんのおかしな挙動

2014.05.20 10:57 | コメント(0) | 技術

program.jpg

この記事は最終更新日から1年以上が経過しています。

ハマりました。

少し真面目な記事かも。

遊びで昔作った古いシステムを触ってる時に見つけたRuby on Railsの微妙な仕様。

とりあえず症状としては、

helperファイルに書いたメソッドが機能しない。

いや、もう少し詳しく書くと、overwriteしたはずのメソッドがもとの形のまま出てくる。

何言ってるかわかんないね。

詳細は以下から。

環境


Ruby → 1.8.7 p327
Rails → 2.2.2


実験


試してみましょう。
まずcontrollerをいくつか作ります。

ruby ./script/generate controller apple_juice

ruby ./script/generate controller banana_ole
ruby ./script/generate controller cherry_pie


美味しそうな名前のコントローラが3つできました。

script/generateでコントローラを作成した場合、こんな感じに作られます。

      exists  app/controllers/

exists app/helpers/
create app/views/apple_juice
exists test/functional/
exists test/unit/helpers/
create app/controllers/apple_juice_controller.rb
create test/functional/apple_juice_controller_test.rb
create app/helpers/apple_juice_helper.rb
create test/unit/helpers/apple_juice_helper_test.rb


ふむ。
今回重要なのはapp/helpers/apple_juice_helper.rbこの子。

railsの場合1コントローラに対し1つヘルパーが作成されます。
ここに書いたヘルパーメソッドの挙動が変。

さて、どのように変かというと・・・。

それぞれのヘルパーにメソッドを書き加えてみますよ。

module AppleJuiceHelper

def klass_name
'りんごジュース'
end
end

module BananaOleHelper
def klass_name
'バナナオーレ'
end
end

module CherryPieHelper
def klass_name
'さくらんぼパイ'
end
end


klass_nameを呼び出すとそれぞれの名前が返ってくる簡単なヘルパーメソッドです。

そして各コントローラのviewも作成。
めんどくさいので全部index.html.erbとしますね。
書く内容は全て同じ。

<%= klass_name %>


こちらが期待しているのは、以下の通り。

host_name/apple_juice/にアクセス → 「りんごジュース」
host_name/banana_ole/にアクセス → 「バナナオーレ」
host_name/apple_juice/にアクセス → 「さくらんぼパイ」

と、それぞれ画面に表示されるはず・・・。

host_name/apple_juice/にアクセス → 「さくらんぼパイ」
host_name/banana_ole/にアクセス → 「さくらんぼパイ」
host_name/apple_juice/にアクセス → 「さくらんぼパイ」

いや、君たちいつから固形物になったの。


勘違いと思い込み


helperには暗黙のルールとして、
  • 全体で使うヘルパーはapplication_helper.rbに書く
  • コントローラ単体で必要なヘルパーは<controller_name>_helper.rbに書く

とされてますね。

apple_juice_helper.rbやbanana_ole_helper.rbなんかはapplication_helper.rbに記述されているヘルパーメソッドと同一のものを書くとoverwriteするものだと思ってました。
また、<controller_name>_helper.rbはそのコントローラに対応したものだけを読み込むものだと思ってました。

が、

勘違いのようです。

上のテストからもわかるように、各コントローラですべてのhelperを読み込むようです。
さらにさらに、複数のファイルに同一のヘルパーメソッドが書かれていた場合、最後に読み込まれたファイルに記述されているヘルパーメソッドの内容になるようです。
ちなみに読み込み順は辞書順(昇順)みたい。

つまり、上の例だと
  1. apple_juice_helper.rbを読み込む
  2. banana_ole_helper.rbを読み込む
  3. cherry_pie_helper.rbを読み込む

で、cherry_pie_helper.rbの中のklass_nameメソッドが有効となり、液体2名が固体のふりをしたみたいです。


じゃあどうするの?


①ヘルパーメソッドの名前が重複しないように調整する。
名前が被らないようにすればとりあえずは解決。
でもviewでの制御もしないとだめだし、こんな感じになって不細工。
今後コントローラが増えるたびにずんずん記述が増えていく。
<% case controller_name -%>

<% when 'AppleJuiceController' -%>
<%= apple_klass_name %>
<% when 'BananaOleController' -%>
<%= banana_klass_name %>
<% when 'CherryPieController' -%>
<%= cherry_klass_name %>
<% end -%>


こんな感じでも書けるけど見難い。
<%= eval("#{controller_name.sub(/Controller/, '').underscore}_name") %>


②コントローラ内でヘルパーメソッドを定義する。
class AppleJuiceController < ApplicationController

def klass_name
'りんごジュース'
end
helper_method :klass_name
end


これだと確実にoverwriteしてくれます。
それぞれのコントローラ内で上記のように定義してあげると・・・
host_name/apple_juice/にアクセス → 「りんごジュース」
host_name/banana_ole/にアクセス → 「バナナオーレ」
host_name/apple_juice/にアクセス → 「さくらんぼパイ」

はい、アイデンティティが保たれました。

これはRails 2.x系での現象ですが、何もしなければ3.x系でも起こるみたいですね。
3.x系の場合、一応回避策はある模様。

Rails 3.x系の回避策


environment.rbに以下を記述。
config.action_controller.include_all_helpers = false


これで何が変わったかというと、デフォルトとなっているhelperディレクトリの下にあるファイルの全読込をOFFにしてます。
つまり、各コントローラは対応するコントローラのhelperファイルのみ読み込む。

うーん・・・。
結局同名のメソッドをいろんなファイルに書かなきゃいけないわけだし、変更する場合に触るファイル数が増えちゃう。

なんか根本的な解決方法考えてください・・・。
技術の記事

コメント

まだコメントがありません...(´・ω・`)