pluginのinit.rbでメソッドを再定義できない理由

日付コントロールを変える」の最後で、プラグインの読み込みをconfig/environment.rbに記述しました。


require 'yads'

その後調べてみたら、vendor/plugin/(プラグイン名)/init.rbで上記コードを記述すれば良いことが分かりました。各プラグインのinit.rbはrailsの初期化プロセスから自動的に呼び出される為、script/plugin install ~ でインストールすれば、フレームワークのメソッド(前回の場合はActionView::Helper::DateHelper)を再定義することができます。

でもinit.rbが自動的に呼び出されるのであれば、最初からinit.rbでメソッドの再定義を行えば良いはず。

ということで、vendor/plugin/yads/lib/yads.rbの中身をvendor/plugin/yads/init.rbに移動して実行したところ、メソッドが再定義されておらず、元々のドロップダウンリストを使った日付コントロールが表示されていました。コードは以下のとおりです。

[vendor/plugins/yads/init.rb]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Include hook code here                 
module ActionView
  module Helpers
    module DateHelper
      def date_select(object_name, method, options = {}, html_options = { })
        options[:size] ||= 12
        html = InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
        js = <<EOS

<script> 
$('##{object_name}_#{method}').datepicker({  dateFormat: 'yy-mm-dd'});
</script>
EOS
        html.concat(js)
      end
    end  
  end  
end

init.rbの中でrequireして再定義するのと、init.rbの中で直接再定義することの違いが分からず、webで調べてみたところ、以下のようなページがありました。

Platte daddy: Rails plugins: keep init.rb thin

Rails plugins' initializer script, init.rb, is currently invoked via eval, not require—so it inherits whatever module-space Rails calls it from. If you reopen any classes in init.rb itself (like the will_paginate guys quite reasonably attempted to define Hash#slice), your changes will be made—but to the wrong module. So, to avoid strange gotchas, consider init.rb just a generic hook point to kick things off, and always require in any code that's to do actual work at plugin load time.

「If you reopen any classes in init.rb itself, your changes will be made—but to the wrong module.」と記述されているので、init.rbで再定義してもダメなのはどうも正しいようですが、その理由がわからない…。そもそもプラグインの初期化時にはフレームワークのメソッドが定義されていないのか、と考えましたが、init.rbでrequireされるファイル内で再定義してもタイミングは同じはずだし…。

requireに何か特殊な仕掛け(たとえば遅延評価とか)があるのかと思い、Rubyのrequireメソッドを再定義しているActiveSupport::Dependenciesのログを出力させてみたりしたものの、プラグインの初期化時にはログが出力されていないことから、あまり関係あるようにも思えない、という結論に至り。

プラグインのinit.rbはどのように呼び出されているか調べてみると、以下のようになっていました。

[rails-2.3.2/lib/rails/plugin.rb]
1
2
3
4
5
6
7
8
9
10
def evaluate_init_rb(initializer)
  if has_init_file?
    silence_warnings do
      # Allow plugins to reference the current configuration object
      config = initializer.configuration
      
      eval(IO.read(init_path), binding, init_path)
    end
  end
end 

evalでinit.rbを評価しているので、普通に再定義できそう…と暫く気がつかなかったのですが、evalで評価ということは呼び出し側の名前空間(今回の場合はRails::Plugin)を引き継いでしまうので、それを考慮に入れる必要がありました。つまり、init.rbで以下のように記述すると、

1
2
3
module ActionView
  module Helpers
    module DateHelper
「Rails::Plugin::ActionView::Helpers::DateHelper」を定義していることになるという…。なので、「::ActionView」と書けば再定義することができます。
1
2
3
module ::ActionView
  module Helpers
    module DateHelper

結論としては、プラグインのinit.rbでメソッドの再定義はできるものの、lib下のファイル内で再定義してinit.rbでrequireすべき。

Sorry, comments are closed for this article.