unloadableという不思議なメソッド
会社のRailsアプリを、今Rails1からRails2へアップデート中。
で、最近まで作っていた機能のブラウザテスト中に、developmentモードで動作させていて以下のような警告文が出た
A copy of [Module Name] has been removed from the module tree but is still active!
StackOverflowや個人ブログをみると、has been removedなmoduleに依存して、まだそのmoduleが存在しているような振る舞いをしている子を、おかしいよね、ということらしい
- http://stackoverflow.com/questions/6853471/ruby-on-rails-unloadable
- http://d.hatena.ne.jp/akm/20091019/1255936738
RedMineでも使ってるらしい
プラグイン開発するとき、必ず書かなきゃいけないって、ちょっと不思議な感じ。
ちなみに、この警告、productionモードでは出なかった。
問題を引き起こすのは、
- コード側では依存関係
- 設定側ではconfig/environment.rbのconfig.cache_classes = false
対応としては、
中略的に途中の思考をふっとばして結論は、moduleをincludeしている側のclassに、unloadableを追加することで回避した。
先輩のid:LukeSilviaさんのアドバイスで、影響を最小限にするために、RAILS_ENVがdevelopmentの時だけに、という条件も追加。
unloadable if RAILS_ENV == "development"
Railsのコードを追ってみた。
active_support周りの話なので、ココを中心に追いかけていく。
- activesupport-2.3.12/lib/active_support/dependenceis.rb
- mattr_accessor :explicitly_unloadable_constantsが入れ物
# An array of constant names that need to be unloaded on every request. Used # to allow arbitrary constants to be marked for unloading. mattr_accessor :explicitly_unloadable_constants self.explicitly_unloadable_constants = []
- 実際に実装側に追加したコードのメソッド定義
def unloadable(const_desc) Dependencies.mark_for_unload const_desc end
- unloadableの実態
def mark_for_unload(const_desc) name = to_constant_name const_desc if explicitly_unloadable_constants.include? name return false else explicitly_unloadable_constants << name return true end end
- remove_constantをまとめてしているロジック。
def remove_unloadable_constants! autoloaded_constants.each { |const| remove_constant const } autoloaded_constants.clear explicitly_unloadable_constants.each { |const| remove_constant const } end
- remove_constantの実態
def remove_constant(const) #:nodoc: return false unless qualified_const_defined? const const = $1 if /\A::(.*)\Z/ =~ const.to_s names = const.to_s.split('::') if names.size == 1 # It's under Object parent = Object else parent = (names[0..-2] * '::').constantize end log "removing constant #{const}" parent.instance_eval { remove_const names.last } return true end
ということで、unloadableしたconstantは意図的にremoveされるらしい。
まだ分かっていないこと。
unloadableで意図的にremoveされることに対して、cache_classesの設定が関わっているのかどうかが分かっていない。