2009年8月28日金曜日

[Rails][CodeReading] Rails::Initializer (26) load_plugins

Initializer.process で26番目に呼ばれる load_plugins メソッド。

def load_plugins
plugin_loader.load_plugins
end

処理はplugin_loader(実体はRails::Plugin::Loader)に委譲されます。
以下プラグインのロード処理を、少し詳しく追ってみます。


# rails-2.3.3/lib/rails/plugin/loader.rb
def load_plugins
plugins.each do |plugin|
plugin.load(initializer)
register_plugin_as_loaded(plugin)
end

configure_engines

ensure_all_registered_plugins_are_loaded!
end

なにはともあれ、2行目にいきなり出てくる plugins の出所を探らねば。

# rails-2.3.3/lib/rails/plugin/loader.rb
# 便宜的に、メソッドの定義順を呼ばれる順に変更
def plugins
@plugins ||= all_plugins.select { |plugin| should_load?(plugin) }.sort { |p1, p2| order_plugins(p1, p2) }
end

def all_plugins
@all_plugins ||= locate_plugins
@all_plugins
end

protected
def locate_plugins
configuration.plugin_locators.map do |locator|
locator.new(initializer).plugins
end.flatten
# TODO: sorting based on config.plugins
end

locate_plugins 2行目の configuration は、Initializerに渡されるConfigurationオブジェクトを参照しています。3行目の initializer はInitializerオブジェクトそのもの。
Configuration.plugin_locators のデフォルト値を確認すると、

# rails-2.3.3/lib/initializer.rb
def default_plugin_locators
locators = []
locators << Plugin::GemLocator if defined? Gem
locators << Plugin::FileSystemLocator
end

Plugin::GemLocator, Plugin::FileSystemLocator が含まれます。どうも、この2つのLocatorオブジェクトのpluginsメソッドが肝らしいので、そっちにとんでみる。

# rails-2.3.3/lib/rails/plugin/locator.rb
class FileSystemLocator < Locator
def plugins
initializer.configuration.plugin_paths.flatten.inject([]) do |plugins, path|
plugins.concat locate_plugins_under(path)
plugins
end.flatten
end
~~ 略 ~~
end

class GemLocator < Locator
def plugins
gem_index = initializer.configuration.gems.inject({}) { |memo, gem| memo.update gem.specification => gem }
specs = gem_index.keys
specs += Gem.loaded_specs.values.select do |spec|
spec.loaded_from && # prune stubs
File.exist?(File.join(spec.full_gem_path, "rails", "init.rb"))
end
specs.compact!

require "rubygems/dependency_list"

deps = Gem::DependencyList.new
deps.add(*specs) unless specs.empty?

deps.dependency_order.collect do |spec|
Rails::GemPlugin.new(spec, gem_index[spec])
end
end
end

GemLocatorのほうは、Gemの詳細を知らないと追えそうにないので、FileSystemLocatorのほうを追いかけてみる。
initializer.configuration.plugin_paths はデフォルトで vendor/plugins だけ含みます。

# rails-2.3.3/lib/initializer.rb
def default_plugin_paths
["#{root_path}/vendor/plugins"]
end


(初期設定で) FileSystemLocator.plugins は、locate_plugins_under に $RAILS_ROOT/vendor/plugins を噛ませた結果を返すことになります。

# rails-2.3.3/lib/rails/plugin/locator.rb
def locate_plugins_under(base_path)
Dir.glob(File.join(base_path, '*')).sort.inject([]) do |plugins, path|
if plugin = create_plugin(path)
plugins << plugin
elsif File.directory?(path)
plugins.concat locate_plugins_under(path)
end
plugins
end
end

おお Dir.glob !! ここで、vendor/plugin の中を(再帰的に)見に行ってることになります。
3行目 create_plugin に vendor/plugin のサブディレクトリが次々渡されて、その評価結果が plugins に入る。create_plugin メソッドは以下。

# rails-2.3.3/lib/rails/plugin/locator.rb
def create_plugin(path)
plugin = Rails::Plugin.new(path)
plugin.valid? ? plugin : nil
end

ここで、Rails::Pluginオブジェクトが生成されています。

# rails-2.3.3/lib/rails/plugin.rb (抜粋)
class Plugin
include Comparable

attr_reader :directory, :name

def initialize(directory)
@directory = directory
@name = File.basename(@directory) rescue nil
@loaded = false
end

def valid?
File.directory?(directory) && (has_app_directory? || has_lib_directory? || has_init_file?)
end

private
def lib_path
File.join(directory, 'lib')
end
def classic_init_path
File.join(directory, 'init.rb')
end
def gem_init_path
File.join(directory, 'rails', 'init.rb')
end
def init_path
File.file?(gem_init_path) ? gem_init_path : classic_init_path
end
def has_app_directory?
File.directory?(File.join(directory, 'app'))
end
def has_lib_directory?
File.directory?(lib_path)
end
def has_init_file?
File.file?(init_path)
end
end

valid? メソッドが true を返すのは
  • (初期化時に渡された)パスがディレクトリである

かつ、以下のどれかをみたす場合。
  • サブディレクトリ app/ 存在する
  • サブディレクトリ lib/ が存在する
  • ファイル rails/init.rb または init.rb が存在する

valid? で篩にかけられて残ったPluginオブジェクトが、メソッド呼び出し元へ次々返されていき、最初の Plugin::Loader.load_plugins に入ります。

###

ここから、(やっと)ロード処理。Plugin::Loader.load_pluginsメソッド再掲。

# rails-2.3.3/lib/rails/plugin/loader.rb
def load_plugins
plugins.each do |plugin|
plugin.load(initializer)
register_plugin_as_loaded(plugin)
end

configure_engines

ensure_all_registered_plugins_are_loaded!
end

Plugin.load が、実際に個々のプラグインのコードを読み込む処理をしています。

# rails-2.3.3/lib/rails/plugin.rb
def load(initializer)
return if loaded?
report_nonexistant_or_empty_plugin! unless valid?
evaluate_init_rb(initializer)
@loaded = true
end

private
def classic_init_path
File.join(directory, 'init.rb')
end
def gem_init_path
File.join(directory, 'rails', 'init.rb')
end
def init_path
File.file?(gem_init_path) ? gem_init_path : classic_init_path
end
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(IO.read('init.rb'), binding, 'init.rb') が見つかりました。ノノ

[Rails][CodeReading] Railsの初期化コードを読む (イントロ&目次)

0 件のコメント:

コメントを投稿