読者です 読者をやめる 読者になる 読者になる

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が存在しているような振る舞いをしている子を、おかしいよね、ということらしい

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の設定が関わっているのかどうかが分かっていない。