aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--railties/CHANGELOG2
-rw-r--r--railties/lib/initializer.rb98
-rw-r--r--railties/lib/plugin/loader.rb95
-rw-r--r--railties/lib/plugin/locater.rb88
-rw-r--r--railties/test/fixtures/plugins/default/plugin_with_no_lib_dir/init.rb0
-rw-r--r--railties/test/plugin_loader_test.rb71
-rw-r--r--railties/test/plugin_locater_test.rb51
-rw-r--r--railties/test/plugin_test.rb111
-rw-r--r--railties/test/plugin_test_helper.rb14
9 files changed, 338 insertions, 192 deletions
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 3aa84433cb..db60f67d3b 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Split plugin location and loading out of the initializer and into a new Plugin namespace, which includes Plugin::Locater and Plugin::Loader. The loader class that is used can be customized using the config.plugin_loader option. Those monkey patching the plugin loading subsystem take note, the internals changing here will likely break your modifications. The good news is that it should be substantially easier to hook into the plugin locating and loading process now. [Marcel Molina Jr.]
+
* Added assumption that all plugin creators desire to be sharing individuals and release their work under the MIT license [DHH]
* Added source-annotations extractor tasks to rake [Jamis Buck]. This allows you to add FIXME, OPTIMIZE, and TODO comments to your source code that can then be extracted in concert with rake notes (shows all), rake notes:fixme, rake notes:optimize and rake notes:todo.
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index b535c8624b..b2b7b3b545 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -2,6 +2,9 @@ require 'logger'
require 'set'
require File.join(File.dirname(__FILE__), 'railties_path')
require File.join(File.dirname(__FILE__), 'rails/version')
+require File.join(File.dirname(__FILE__), 'plugin/locater')
+require File.join(File.dirname(__FILE__), 'plugin/loader')
+
RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV)
@@ -184,19 +187,10 @@ module Rails
# will be loaded in that order. Otherwise, plugins are loaded in alphabetical
# order.
def load_plugins
- if configuration.plugins.nil?
- # a nil value implies we don't care about plugins; load 'em all in a reliable order
- find_plugins(configuration.plugin_paths).sort.each { |path| load_plugin path }
- elsif !configuration.plugins.empty?
- # we've specified a config.plugins array, so respect that order
- plugin_paths = find_plugins(configuration.plugin_paths)
- configuration.plugins.each do |name|
- path = plugin_paths.find { |p| File.basename(p) == name }
- raise(LoadError, "Cannot find the plugin '#{name}'!") if path.nil?
- load_plugin path
- end
- end
- $LOAD_PATH.uniq!
+ Plugin::Locater.new(self).each do |plugin|
+ plugin.load
+ end
+ $LOAD_PATH.uniq!
end
# Loads the environment specified by Configuration#environment_path, which
@@ -343,74 +337,6 @@ module Rails
load(initializer)
end
end
-
- protected
- # Return a list of plugin paths within base_path. A plugin path is
- # a directory that contains either a lib directory or an init.rb file.
- # This recurses into directories which are not plugin paths, so you
- # may organize your plugins within the plugin path.
- def find_plugins(*base_paths)
- base_paths.flatten.inject([]) do |plugins, base_path|
- Dir.glob(File.join(base_path, '*')).each do |path|
- if plugin_path?(path)
- plugins << path if plugin_enabled?(path)
- elsif File.directory?(path)
- plugins += find_plugins(path)
- end
- end
- plugins
- end
- end
-
- def plugin_path?(path)
- File.directory?(path) and (File.directory?(File.join(path, 'lib')) or File.file?(File.join(path, 'init.rb')))
- end
-
- def plugin_enabled?(path)
- configuration.plugins.nil? || configuration.plugins.include?(File.basename(path))
- end
-
- # Load the plugin at <tt>path</tt> unless already loaded.
- #
- # Each plugin is initialized:
- # * add its +lib+ directory, if present, to the beginning of the load path
- # * evaluate <tt>init.rb</tt> if present
- #
- # Returns <tt>true</tt> if the plugin is successfully loaded or
- # <tt>false</tt> if it is already loaded (similar to Kernel#require).
- # Raises <tt>LoadError</tt> if the plugin is not found.
- def load_plugin(directory)
- name = File.basename(directory)
- return false if loaded_plugins.include?(name)
-
- # Catch nonexistent and empty plugins.
- raise LoadError, "No such plugin: #{directory}" unless plugin_path?(directory)
-
- lib_path = File.join(directory, 'lib')
- init_path = File.join(directory, 'init.rb')
- has_lib = File.directory?(lib_path)
- has_init = File.file?(init_path)
-
- # Add lib to load path *after* the application lib, to allow
- # application libraries to override plugin libraries.
- if has_lib
- application_lib_index = $LOAD_PATH.index(File.join(RAILS_ROOT, "lib")) || 0
- $LOAD_PATH.insert(application_lib_index + 1, lib_path)
- Dependencies.load_paths << lib_path
- Dependencies.load_once_paths << lib_path
- end
-
- # Allow plugins to reference the current configuration object
- config = configuration
-
- # Add to set of loaded plugins before 'name' collapsed in eval.
- loaded_plugins << name
-
- # Evaluate init.rb.
- silence_warnings { eval(IO.read(init_path), binding, init_path) } if has_init
-
- true
- end
end
# The Configuration class holds all the parameters for the Initializer and
@@ -502,6 +428,11 @@ module Rails
# The path to the root of the plugins directory. By default, it is in
# <tt>vendor/plugins</tt>.
attr_accessor :plugin_paths
+
+ # The class that handles loading each plugin. Defaults to Rails::Plugin::Loader, but
+ # a sub class would have access to fine grained modification of the loading behavior. See
+ # the implementation of Rails::Plugin::Loader for more details.
+ attr_accessor :plugin_loader
# Create a new Configuration instance, initialized with the default
# values.
@@ -518,6 +449,7 @@ module Rails
self.whiny_nils = default_whiny_nils
self.plugins = default_plugins
self.plugin_paths = default_plugin_paths
+ self.plugin_loader = default_plugin_loader
self.database_configuration_file = default_database_configuration_file
for framework in default_frameworks
@@ -672,6 +604,10 @@ module Rails
def default_plugin_paths
["#{root_path}/vendor/plugins"]
end
+
+ def default_plugin_loader
+ Plugin::Loader
+ end
end
end
diff --git a/railties/lib/plugin/loader.rb b/railties/lib/plugin/loader.rb
new file mode 100644
index 0000000000..c3116d84c5
--- /dev/null
+++ b/railties/lib/plugin/loader.rb
@@ -0,0 +1,95 @@
+module Rails
+ module Plugin
+ class Loader
+ include Comparable
+ attr_reader :initializer, :directory, :name
+
+ class << self
+ def load(*args)
+ new(*args).load
+ end
+ end
+
+ def initialize(initializer, directory)
+ @initializer = initializer
+ @directory = directory
+ @name = File.basename(directory)
+ end
+
+ def load
+ return false if loaded?
+ report_nonexistant_or_empty_plugin!
+ add_to_load_path!
+ register_plugin_as_loaded
+ evaluate
+ true
+ end
+
+ def loaded?
+ initializer.loaded_plugins.include?(name)
+ end
+
+ def plugin_path?
+ File.directory?(directory) && (has_lib_directory? || has_init_file?)
+ end
+
+ def enabled?
+ config.plugins.nil? || config.plugins.include?(name)
+ end
+
+ private
+ def report_nonexistant_or_empty_plugin!
+ raise LoadError, "No such plugin: #{directory}" unless plugin_path?
+ end
+
+ def lib_path
+ File.join(directory, 'lib')
+ end
+
+ def init_path
+ File.join(directory, 'init.rb')
+ end
+
+ def has_lib_directory?
+ File.directory?(lib_path)
+ end
+
+ def has_init_file?
+ File.file?(init_path)
+ end
+
+ def add_to_load_path!
+ # Add lib to load path *after* the application lib, to allow
+ # application libraries to override plugin libraries.
+ if has_lib_directory?
+ application_lib_index = $LOAD_PATH.index(application_library_path) || 0
+ $LOAD_PATH.insert(application_lib_index + 1, lib_path)
+ Dependencies.load_paths << lib_path
+ Dependencies.load_once_paths << lib_path
+ end
+ end
+
+ def application_library_path
+ File.join(RAILS_ROOT, 'lib')
+ end
+
+ # Allow plugins to reference the current configuration object
+ def config
+ initializer.configuration
+ end
+
+ def register_plugin_as_loaded
+ initializer.loaded_plugins << name
+ end
+
+ # Evaluate in init.rb
+ def evaluate
+ silence_warnings { eval(IO.read(init_path), binding, init_path)} if has_init_file?
+ end
+
+ def <=>(other_plugin_loader)
+ name <=> other_plugin_loader.name
+ end
+ end
+ end
+end
diff --git a/railties/lib/plugin/locater.rb b/railties/lib/plugin/locater.rb
new file mode 100644
index 0000000000..4a7aa774ee
--- /dev/null
+++ b/railties/lib/plugin/locater.rb
@@ -0,0 +1,88 @@
+module Rails
+ module Plugin
+ class Locater
+ include Enumerable
+ attr_reader :initializer
+
+ def initialize(initializer)
+ @initializer = initializer
+ end
+
+ def plugins
+ if !explicit_plugin_loading_order?
+ # We don't care about which plugins get loaded or in what order they are loaded
+ # so we load 'em all in a reliable order
+ located_plugins.sort
+ elsif !registered_plugins.empty?
+ registered_plugins.inject([]) do |plugins, registered_plugin|
+ report_plugin_missing!(registered_plugin) unless plugin = locate_registered_plugin(registered_plugin)
+ plugins << plugin
+ end
+ else
+ []
+ end
+ end
+
+ def each(&block)
+ plugins.each(&block)
+ end
+
+ def plugin_names
+ plugins.map {|plugin| plugin.name}
+ end
+
+ private
+ def locate_registered_plugin(registered_plugin)
+ located_plugins.detect {|plugin| plugin.name == registered_plugin }
+ end
+
+ def report_plugin_missing!(name)
+ raise LoadError, "Cannot find the plugin you registered called '#{name}'!"
+ end
+
+ def explicit_plugin_loading_order?
+ !registered_plugins.nil?
+ end
+
+ # The plugins that have been explicitly listed with config.plugins. If this list is nil
+ # then it means the client does not care which plugins or in what order they are loaded,
+ # so we load all in alphabetical order. If it is an empty array, we load no plugins, if it is
+ # non empty, we load the named plugins in the order specified.
+ def registered_plugins
+ initializer.configuration.plugins
+ end
+
+ def located_plugins
+ # We cache this as locate_plugins_under on the entire set of plugin directories could
+ # be potentially expensive
+ @located_plugins ||=
+ begin
+ initializer.configuration.plugin_paths.flatten.inject([]) do |plugins, path|
+ plugins.concat locate_plugins_under(path)
+ plugins
+ end.flatten
+ end
+ end
+
+ # This starts at the base path looking for directories that pass the plugin_path? test of the Plugin::Loader.
+ # Since plugins can be nested arbitrarily deep within an unspecified number of intermediary directories,
+ # this method runs recursively until it finds a plugin directory.
+ #
+ # e.g.
+ #
+ # locate_plugins_under('vendor/plugins/acts/acts_as_chunky_bacon')
+ # => 'acts_as_chunky_bacon'
+ def locate_plugins_under(base_path)
+ Dir.glob(File.join(base_path, '*')).inject([]) do |plugins, path|
+ plugin_loader = initializer.configuration.plugin_loader.new(initializer, path)
+ if plugin_loader.plugin_path?
+ plugins << plugin_loader if plugin_loader.enabled?
+ elsif File.directory?(path)
+ plugins.concat locate_plugins_under(path)
+ end
+ plugins
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/test/fixtures/plugins/default/plugin_with_no_lib_dir/init.rb b/railties/test/fixtures/plugins/default/plugin_with_no_lib_dir/init.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/railties/test/fixtures/plugins/default/plugin_with_no_lib_dir/init.rb
diff --git a/railties/test/plugin_loader_test.rb b/railties/test/plugin_loader_test.rb
new file mode 100644
index 0000000000..7010a60298
--- /dev/null
+++ b/railties/test/plugin_loader_test.rb
@@ -0,0 +1,71 @@
+require File.dirname(__FILE__) + '/plugin_test_helper'
+
+class TestPluginLoader < Test::Unit::TestCase
+ def setup
+ @initializer = Rails::Initializer.new(Rails::Configuration.new)
+ @valid_plugin_path = plugin_fixture_path('default/stubby')
+ @empty_plugin_path = plugin_fixture_path('default/empty')
+ end
+
+ def test_determining_whether_a_given_plugin_is_loaded
+ plugin_loader = loader_for(@valid_plugin_path)
+ assert !plugin_loader.loaded?
+ assert_nothing_raised do
+ plugin_loader.send(:register_plugin_as_loaded)
+ end
+ assert plugin_loader.loaded?
+ end
+
+ def test_if_a_path_is_a_plugin_path
+ # This is a plugin path, with a lib dir
+ assert loader_for(@valid_plugin_path).plugin_path?
+ # This just has an init.rb and no lib dir
+ assert loader_for(plugin_fixture_path('default/plugin_with_no_lib_dir')).plugin_path?
+ # This would be a plugin path, but the directory is empty
+ assert !loader_for(plugin_fixture_path('default/empty')).plugin_path?
+ # This is a non sense path
+ assert !loader_for(plugin_fixture_path('default/this_directory_does_not_exist')).plugin_path?
+ end
+
+ def test_if_you_try_to_load_a_non_plugin_path_you_get_a_load_error
+ # This path is fine so nothing is raised
+ assert_nothing_raised do
+ loader_for(@valid_plugin_path).send(:report_nonexistant_or_empty_plugin!)
+ end
+
+ # This is an empty path so it raises
+ assert_raises(LoadError) do
+ loader_for(@empty_plugin_path).send(:report_nonexistant_or_empty_plugin!)
+ end
+
+ assert_raises(LoadError) do
+ loader_for('this_is_not_a_plugin_directory').send(:report_nonexistant_or_empty_plugin!)
+ end
+ end
+
+ def test_loading_a_plugin_gives_the_init_file_access_to_all_it_needs
+ failure_tip = "Perhaps someone has written another test that loads this same plugin and therefore makes the SubbyMixin constant defined already."
+ assert !defined?(StubbyMixin), failure_tip
+ assert !added_to_load_path?(@valid_plugin_path)
+ # The init.rb of this plugin raises if it doesn't have access to all the things it needs
+ assert_nothing_raised do
+ loader_for(@valid_plugin_path).load
+ end
+ assert added_to_load_path?(@valid_plugin_path)
+ assert defined?(StubbyMixin)
+ end
+
+ private
+ def loader_for(path, initializer = @initializer)
+ Rails::Plugin::Loader.new(initializer, path)
+ end
+
+ def plugin_fixture_path(path)
+ File.join(plugin_fixture_root_path, path)
+ end
+
+ def added_to_load_path?(path)
+ $LOAD_PATH.grep(/#{path}/).size == 1
+ end
+
+end \ No newline at end of file
diff --git a/railties/test/plugin_locater_test.rb b/railties/test/plugin_locater_test.rb
new file mode 100644
index 0000000000..ba843a8e45
--- /dev/null
+++ b/railties/test/plugin_locater_test.rb
@@ -0,0 +1,51 @@
+require File.dirname(__FILE__) + '/plugin_test_helper'
+
+class TestPluginLocater < Test::Unit::TestCase
+ def setup
+ configuration = Rails::Configuration.new
+ # We need to add our testing plugin directory to the plugin paths so
+ # the locater knows where to look for our plugins
+ configuration.plugin_paths << plugin_fixture_root_path
+ @initializer = Rails::Initializer.new(configuration)
+ @locater = new_locater
+ end
+
+ def test_determining_if_the_plugin_order_has_been_explicitly_set
+ assert !@locater.send(:explicit_plugin_loading_order?)
+ only_load_the_following_plugins! %w(stubby acts_as_chunky_bacon)
+ assert @locater.send(:explicit_plugin_loading_order?)
+ end
+
+ def test_no_plugins_are_loaded_if_the_configuration_has_an_empty_plugin_list
+ only_load_the_following_plugins! []
+ assert_equal [], @locater.plugins
+ end
+
+ def test_only_the_specified_plugins_are_located_in_the_order_listed
+ plugin_names = %w(stubby acts_as_chunky_bacon)
+ only_load_the_following_plugins! plugin_names
+ assert_equal plugin_names, @locater.plugin_names
+ end
+
+ def test_registering_a_plugin_name_that_does_not_exist_raisesa_load_error
+ only_load_the_following_plugins! %w(stubby acts_as_non_existant_plugin)
+ assert_raises(LoadError) do
+ @locater.plugin_names
+ end
+ end
+
+ def test_all_plugins_are_loaded_when_registered_plugin_list_is_untouched
+ failure_tip = "It's likely someone has added a new plugin fixture without updating this list"
+ assert_equal %w(a acts_as_chunky_bacon plugin_with_no_lib_dir stubby), @locater.plugin_names, failure_tip
+ end
+
+ private
+ def new_locater(initializer = @initializer)
+ Rails::Plugin::Locater.new(initializer)
+ end
+
+ def only_load_the_following_plugins!(plugins)
+ @initializer.configuration.plugins = plugins
+ end
+
+end \ No newline at end of file
diff --git a/railties/test/plugin_test.rb b/railties/test/plugin_test.rb
deleted file mode 100644
index f46f1db615..0000000000
--- a/railties/test/plugin_test.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-$:.unshift File.dirname(__FILE__) + "/../lib"
-$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib"
-
-require 'test/unit'
-require 'active_support'
-require 'initializer'
-
-unless defined?(RAILS_ROOT)
- module Rails
- class Initializer
- RAILS_ROOT = '.'
- end
- end
-end
-
-class PluginTest < Test::Unit::TestCase
- class TestConfig < Rails::Configuration
- protected
- def root_path
- File.dirname(__FILE__)
- end
- end
-
- def setup
- @init = Rails::Initializer.new(TestConfig.new)
- end
-
- def test_plugin_path?
- assert @init.send(:plugin_path?, "#{File.dirname(__FILE__)}/fixtures/plugins/default/stubby")
- assert !@init.send(:plugin_path?, "#{File.dirname(__FILE__)}/fixtures/plugins/default/empty")
- assert !@init.send(:plugin_path?, "#{File.dirname(__FILE__)}/fixtures/plugins/default/jalskdjflkas")
- end
-
- def test_find_plugins
- base = "#{File.dirname(__FILE__)}/fixtures/plugins"
- default = "#{base}/default"
- alt = "#{base}/alternate"
- acts = "#{default}/acts"
- assert_equal ["#{acts}/acts_as_chunky_bacon"], @init.send(:find_plugins, acts)
- assert_equal ["#{acts}/acts_as_chunky_bacon", "#{default}/stubby"], @init.send(:find_plugins, default).sort
- assert_equal ["#{alt}/a", "#{acts}/acts_as_chunky_bacon", "#{default}/stubby"], @init.send(:find_plugins, base).sort
- end
-
- def test_load_plugin
- stubby = "#{File.dirname(__FILE__)}/fixtures/plugins/default/stubby"
- expected = ['stubby']
-
- assert @init.send(:load_plugin, stubby)
- assert_equal expected, @init.loaded_plugins
-
- assert !@init.send(:load_plugin, stubby)
- assert_equal expected, @init.loaded_plugins
-
- assert_raise(LoadError) { @init.send(:load_plugin, 'lakjsdfkasljdf') }
- assert_equal expected, @init.loaded_plugins
- end
-
- def test_load_default_plugins
- assert_loaded_plugins %w(stubby acts_as_chunky_bacon), 'default'
- end
-
- def test_load_alternate_plugins
- assert_loaded_plugins %w(a), 'alternate'
- end
-
- def test_load_plugins_from_two_sources
- assert_loaded_plugins %w(a stubby acts_as_chunky_bacon), ['default', 'alternate']
- end
-
- def test_load_all_plugins_when_config_plugins_is_nil
- @init.configuration.plugins = nil
- assert_loaded_plugins %w(a stubby acts_as_chunky_bacon), ['default', 'alternate']
- end
-
- def test_load_no_plugins_when_config_plugins_is_empty_array
- @init.configuration.plugins = []
- assert_loaded_plugins [], ['default', 'alternate']
- end
-
- def test_load_only_selected_plugins
- plugins = %w(stubby a)
- @init.configuration.plugins = plugins
- assert_loaded_plugins plugins, ['default', 'alternate']
- end
-
- def test_load_plugins_in_order
- plugins = %w(stubby acts_as_chunky_bacon a)
- @init.configuration.plugins = plugins
- assert_plugin_load_order plugins, ['default', 'alternate']
- end
-
- def test_raise_error_when_plugin_not_found
- @init.configuration.plugins = %w(this_plugin_does_not_exist)
- assert_raise(LoadError) { load_plugins(['default', 'alternate']) }
- end
-
- protected
- def assert_loaded_plugins(plugins, paths)
- assert_equal plugins.sort, load_plugins(paths).sort
- end
-
- def assert_plugin_load_order(plugins, paths)
- assert_equal plugins, load_plugins(paths)
- end
-
- def load_plugins(*paths)
- @init.configuration.plugin_paths = paths.flatten.map { |p| "#{File.dirname(__FILE__)}/fixtures/plugins/#{p}" }
- @init.load_plugins
- @init.loaded_plugins
- end
-end
diff --git a/railties/test/plugin_test_helper.rb b/railties/test/plugin_test_helper.rb
new file mode 100644
index 0000000000..58649ea2fa
--- /dev/null
+++ b/railties/test/plugin_test_helper.rb
@@ -0,0 +1,14 @@
+$:.unshift File.dirname(__FILE__) + "/../lib"
+$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib"
+
+require 'test/unit'
+require 'active_support'
+require 'initializer'
+
+# We need to set RAILS_ROOT if it isn't already set
+RAILS_ROOT = '.' unless defined?(RAILS_ROOT)
+class Test::Unit::TestCase
+ def plugin_fixture_root_path
+ File.join(File.dirname(__FILE__), 'fixtures', 'plugins')
+ end
+end \ No newline at end of file