diff --git a/railties/CHANGELOG b/railties/CHANGELOG index db60f67d3b..ba4f7bdf81 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Split out the basic plugin locator functionality into an abstract super class. Add a FileSystemLocator to do the job of checking the plugin_paths for plugins. Add plugin_locators configuration option which will iterate over the set of plugin locators and load each of the plugin loaders they return. Rename locater everywhere to locator. [Marcel Molina Jr.] + * 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] diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index b2b7b3b545..36797026c4 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -2,7 +2,7 @@ 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/locator') require File.join(File.dirname(__FILE__), 'plugin/loader') @@ -183,12 +183,14 @@ module Rails # * evaluate init.rb if present # # After all plugins are loaded, duplicates are removed from the load path. - # If an array of plugin names is specified in config.plugins, the plugins - # will be loaded in that order. Otherwise, plugins are loaded in alphabetical + # If an array of plugin names is specified in config.plugins, only those plugins will be loaded + # and they plugins will be loaded in that order. Otherwise, plugins are loaded in alphabetical # order. def load_plugins - Plugin::Locater.new(self).each do |plugin| - plugin.load + configuration.plugin_locators.each do |locator| + locator.new(self).each do |plugin| + plugin.load + end end $LOAD_PATH.uniq! end @@ -429,6 +431,12 @@ module Rails # vendor/plugins. attr_accessor :plugin_paths + # The classes that handle finding the desired plugins that you'd like to load for + # your application. By default it is the Rails::Plugin::FileSystemLocator which finds + # plugins to load in vendor/plugins. You can hook into gem location by subclassing + # Rails::Plugin::Locator and adding it onto the list of plugin_locators. + attr_accessor :plugin_locators + # 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. @@ -449,6 +457,7 @@ module Rails self.whiny_nils = default_whiny_nils self.plugins = default_plugins self.plugin_paths = default_plugin_paths + self.plugin_locators = default_plugin_locators self.plugin_loader = default_plugin_loader self.database_configuration_file = default_database_configuration_file @@ -605,6 +614,10 @@ module Rails ["#{root_path}/vendor/plugins"] end + def default_plugin_locators + [Plugin::FileSystemLocator] + end + def default_plugin_loader Plugin::Loader end diff --git a/railties/lib/plugin/loader.rb b/railties/lib/plugin/loader.rb index c3116d84c5..d5ad3aa8e3 100644 --- a/railties/lib/plugin/loader.rb +++ b/railties/lib/plugin/loader.rb @@ -32,16 +32,36 @@ module Rails def plugin_path? File.directory?(directory) && (has_lib_directory? || has_init_file?) end - + def enabled? - config.plugins.nil? || config.plugins.include?(name) + !explicit_plugin_loading_order? || registered? + end + + def registered? + explicit_plugin_loading_order? && registered_plugins.include?(name) + end + + def plugin_does_not_exist!(plugin_name = name) + raise LoadError, "Can not find the plugin named: #{plugin_name}" end private - def report_nonexistant_or_empty_plugin! - raise LoadError, "No such plugin: #{directory}" unless plugin_path? + # 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 + config.plugins end - + + def explicit_plugin_loading_order? + !registered_plugins.nil? + end + + def report_nonexistant_or_empty_plugin! + plugin_does_not_exist! unless plugin_path? + end + def lib_path File.join(directory, 'lib') end @@ -88,7 +108,15 @@ module Rails end def <=>(other_plugin_loader) - name <=> other_plugin_loader.name + if explicit_plugin_loading_order? + if non_existent_plugin = [self, other_plugin_loader].detect {|plugin| !registered_plugins.include?(plugin.name)} + plugin_does_not_exist!(non_existent_plugin.name) + end + + registered_plugins.index(name) <=> registered_plugins.index(other_plugin_loader.name) + else + name <=> other_plugin_loader.name + end end end end diff --git a/railties/lib/plugin/locater.rb b/railties/lib/plugin/locater.rb deleted file mode 100644 index 4a7aa774ee..0000000000 --- a/railties/lib/plugin/locater.rb +++ /dev/null @@ -1,88 +0,0 @@ -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/lib/plugin/locator.rb b/railties/lib/plugin/locator.rb new file mode 100644 index 0000000000..326a2a74f5 --- /dev/null +++ b/railties/lib/plugin/locator.rb @@ -0,0 +1,75 @@ +module Rails + module Plugin + class Locator + include Enumerable + attr_reader :initializer + + def initialize(initializer) + @initializer = initializer + end + + def plugins + located_plugins.select(&:enabled?).sort + end + + def each(&block) + plugins.each(&block) + end + + def plugin_names + plugins.map(&:name) + end + + private + def located_plugins + raise "The `located_plugins' method must be defined by concrete subclasses of #{self.class}" + end + end + + class FileSystemLocator < Locator + private + def located_plugins + returning locate_plugins do |loaders| + ensure_all_registered_plugins_are_loaded!(loaders) + end + end + + def locate_plugins + initializer.configuration.plugin_paths.flatten.inject([]) do |plugins, path| + plugins.concat locate_plugins_under(path) + plugins + end.flatten + end + + def ensure_all_registered_plugins_are_loaded!(loaders) + registered_plugins = initializer.configuration.plugins + unless registered_plugins.nil? || registered_plugins.empty? + missing_plugins = registered_plugins - loaders.map(&:name) + unless missing_plugins.empty? + raise LoadError, "Could not locate the following plugins: #{missing_plugins.inspect}" + end + 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? && plugin_loader.enabled? + plugins << plugin_loader + 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/plugin_loader_test.rb b/railties/test/plugin_loader_test.rb index 7010a60298..911f854f75 100644 --- a/railties/test/plugin_loader_test.rb +++ b/railties/test/plugin_loader_test.rb @@ -7,6 +7,13 @@ class TestPluginLoader < Test::Unit::TestCase @empty_plugin_path = plugin_fixture_path('default/empty') end + def test_determining_if_the_plugin_order_has_been_explicitly_set + loader = loader_for(@valid_plugin_path) + assert !loader.send(:explicit_plugin_loading_order?) + only_load_the_following_plugins! %w(stubby acts_as_chunky_bacon) + assert loader.send(:explicit_plugin_loading_order?) + end + def test_determining_whether_a_given_plugin_is_loaded plugin_loader = loader_for(@valid_plugin_path) assert !plugin_loader.loaded? @@ -44,7 +51,7 @@ class TestPluginLoader < Test::Unit::TestCase 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." + failure_tip = "Perhaps someone has written another test that loads this same plugin and therefore makes the StubbyMixin 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 diff --git a/railties/test/plugin_locater_test.rb b/railties/test/plugin_locator_test.rb similarity index 50% rename from railties/test/plugin_locater_test.rb rename to railties/test/plugin_locator_test.rb index ba843a8e45..5ea89844d0 100644 --- a/railties/test/plugin_locater_test.rb +++ b/railties/test/plugin_locator_test.rb @@ -1,51 +1,41 @@ require File.dirname(__FILE__) + '/plugin_test_helper' -class TestPluginLocater < Test::Unit::TestCase +class TestPluginFileSystemLocator < 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 + # the locator 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?) + @locator = new_locator end def test_no_plugins_are_loaded_if_the_configuration_has_an_empty_plugin_list only_load_the_following_plugins! [] - assert_equal [], @locater.plugins + assert_equal [], @locator.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 + assert_equal plugin_names, @locator.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) + def test_registering_a_plugin_name_that_does_not_exist_raises_a_load_error + only_load_the_following_plugins! %w(stubby acts_as_a_non_existant_plugin) assert_raises(LoadError) do - @locater.plugin_names + @locator.plugins 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 + assert_equal %w(a acts_as_chunky_bacon plugin_with_no_lib_dir stubby), @locator.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 + def new_locator(initializer = @initializer) + Rails::Plugin::FileSystemLocator.new(initializer) end end \ No newline at end of file diff --git a/railties/test/plugin_test_helper.rb b/railties/test/plugin_test_helper.rb index 58649ea2fa..0b065a5444 100644 --- a/railties/test/plugin_test_helper.rb +++ b/railties/test/plugin_test_helper.rb @@ -11,4 +11,8 @@ class Test::Unit::TestCase def plugin_fixture_root_path File.join(File.dirname(__FILE__), 'fixtures', 'plugins') end + + def only_load_the_following_plugins!(plugins) + @initializer.configuration.plugins = plugins + end end \ No newline at end of file