diff options
22 files changed, 386 insertions, 215 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 06d73819fb..4d9dd2006f 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *2.3.0 [Edge]* +* Added the option to declare an asset_host as an object that responds to call (see http://github.com/dhh/asset-hosting-with-minimum-ssl for an example) [DHH] + * Added support for multiple routes.rb files (useful for plugin engines). This also means that draw will no longer clear the route set, you have to do that by hand (shouldn't make a difference to you unless you're doing some funky stuff) [DHH] * Dropped formatted_* routes in favor of just passing in :format as an option. This cuts resource routes generation in half #1359 [aaronbatalion] diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 7e38f95076..dca66ff0a5 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -867,8 +867,9 @@ module ActionController #:nodoc: end end - response.layout = layout = pick_layout(options) - logger.info("Rendering template within #{layout}") if logger && layout + layout = pick_layout(options) + response.layout = layout.path_without_format_and_extension if layout + logger.info("Rendering template within #{layout.path_without_format_and_extension}") if logger && layout if content_type = options[:content_type] response.content_type = content_type.to_s diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index 1c7a4b0545..6e4aba2280 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -137,7 +137,6 @@ module ActionController run_callbacks :prepare_dispatch Routing::Routes.reload - ActionController::Base.view_paths.reload! ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear end diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb index 3631ce86af..54108df06d 100644 --- a/actionpack/lib/action_controller/layout.rb +++ b/actionpack/lib/action_controller/layout.rb @@ -175,13 +175,12 @@ module ActionController #:nodoc: def default_layout(format) #:nodoc: layout = read_inheritable_attribute(:layout) return layout unless read_inheritable_attribute(:auto_layout) - @default_layout ||= {} - @default_layout[format] ||= default_layout_with_format(format, layout) - @default_layout[format] + find_layout(layout, format) end - def layout_list #:nodoc: - Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] } + def find_layout(layout, *formats) #:nodoc: + return layout if layout.respond_to?(:render) + view_paths.find_template(layout.to_s =~ /layouts\// ? layout : "layouts/#{layout}", *formats) end private @@ -189,7 +188,7 @@ module ActionController #:nodoc: inherited_without_layout(child) unless child.name.blank? layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '') - child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty? + child.layout(layout_match, {}, true) if child.find_layout(layout_match, :all) end end @@ -200,15 +199,6 @@ module ActionController #:nodoc: def normalize_conditions(conditions) conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})} end - - def default_layout_with_format(format, layout) - list = layout_list - if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty? - (!list.grep(%r{layouts/#{layout}\.([a-z][0-9a-z]*)+$}).empty? && format == :html) ? layout : nil - else - layout - end - end end # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method @@ -217,20 +207,18 @@ module ActionController #:nodoc: # weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard. def active_layout(passed_layout = nil) layout = passed_layout || self.class.default_layout(default_template_format) + active_layout = case layout - when String then layout when Symbol then __send__(layout) when Proc then layout.call(self) + else layout end - # Explicitly passed layout names with slashes are looked up relative to the template root, - # but auto-discovered layouts derived from a nested controller will contain a slash, though be relative - # to the 'layouts' directory so we have to check the file system to infer which case the layout name came from. if active_layout - if active_layout.include?('/') && ! layout_directory?(active_layout) - active_layout + if layout = self.class.find_layout(active_layout, @template.template_format) + layout else - "layouts/#{active_layout}" + raise ActionView::MissingTemplate.new(self.class.view_paths, active_layout) end end end @@ -271,12 +259,6 @@ module ActionController #:nodoc: end end - def layout_directory?(layout_name) - @template.__send__(:_pick_template, "#{File.join('layouts', layout_name)}.#{@template.template_format}") ? true : false - rescue ActionView::MissingTemplate - false - end - def default_template_format response.template.template_format end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 7697848713..da1f283deb 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -322,9 +322,7 @@ module ActionView #:nodoc: end # OPTIMIZE: Checks to lookup template in view path - if template = self.view_paths["#{template_file_name}.#{template_format}"] - template - elsif template = self.view_paths[template_file_name] + if template = self.view_paths.find_template(template_file_name, template_format) template elsif (first_render = @_render_stack.first) && first_render.respond_to?(:format_and_extension) && (template = self.view_paths["#{template_file_name}.#{first_render.format_and_extension}"]) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 4352d7819b..4ec7a383e5 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -80,6 +80,12 @@ module ActionView # end # } # + # You can also implement a custom asset host object that responds to the call method and tasks one or two parameters just like the proc. + # + # config.action_controller.asset_host = AssetHostingWithMinimumSsl.new( + # "http://asset%d.example.com", "https://asset1.example.com" + # ) + # # === Using asset timestamps # # By default, Rails will append all asset paths with that asset's timestamp. This allows you to set a cache-expiration date for the @@ -359,6 +365,7 @@ module ActionView # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching # is set to true (which is the case by default for the Rails production environment, but not for the development # environment). Examples: + # # ==== Examples # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false => @@ -585,7 +592,7 @@ module ActionView source else CacheGuard.synchronize do - Cache[@cache_key] ||= begin + Cache[@cache_key + [source]] ||= begin source += ".#{extension}" if missing_extension?(source) || file_exists_with_extension?(source) source = "/#{directory}/#{source}" unless source[0] == ?/ source = rewrite_asset_path(source) @@ -629,11 +636,12 @@ module ActionView # Pick an asset host for this source. Returns +nil+ if no host is set, # the host if no wildcard is set, the host interpolated with the # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4), - # or the value returned from invoking the proc if it's a proc. + # or the value returned from invoking the proc if it's a proc or the value from + # invoking call if it's an object responding to call. def compute_asset_host(source) if host = ActionController::Base.asset_host - if host.is_a?(Proc) - case host.arity + if host.is_a?(Proc) || host.respond_to?(:call) + case host.is_a?(Proc) ? host.arity : host.method(:call).arity when 2 host.call(source, request) else diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index d6bf2137af..ecb6103ade 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -40,18 +40,10 @@ module ActionView #:nodoc: end class Path #:nodoc: - def self.eager_load_templates! - @eager_load_templates = true - end - - def self.eager_load_templates? - @eager_load_templates || false - end - attr_reader :path, :paths delegate :to_s, :to_str, :hash, :inspect, :to => :path - def initialize(path, load = true) + def initialize(path, load = false) raise ArgumentError, "path already is a Path class" if path.is_a?(Path) @path = path.freeze reload! if load @@ -65,9 +57,35 @@ module ActionView #:nodoc: to_str == path.to_str end + # Returns a ActionView::Template object for the given path string. The + # input path should be relative to the view path directory, + # +hello/index.html.erb+. This method also has a special exception to + # match partial file names without a handler extension. So + # +hello/index.html+ will match the first template it finds with a + # known template extension, +hello/index.html.erb+. Template extensions + # should not be confused with format extensions +html+, +js+, +xml+, + # etc. A format must be supplied to match a formated file. +hello/index+ + # will never match +hello/index.html.erb+. + # + # This method also has two different implementations, one that is "lazy" + # and makes file system calls every time and the other is cached, + # "eager" which looks up the template in an in memory index. The "lazy" + # version is designed for development where you want to automatically + # find new templates between requests. The "eager" version is designed + # for production mode and it is much faster but requires more time + # upfront to build the file index. def [](path) - raise "Unloaded view path! #{@path}" unless @loaded - @paths[path] + if loaded? + @paths[path] + else + Dir.glob("#{@path}/#{path}*").each do |file| + template = create_template(file) + if path == template.path_without_extension || path == template.path + return template + end + end + nil + end end def loaded? @@ -84,9 +102,7 @@ module ActionView #:nodoc: @paths = {} templates_in_path do |template| - # Eager load memoized methods and freeze cached template - template.freeze if self.class.eager_load_templates? - + template.load! @paths[template.path] = template @paths[template.path_without_extension] ||= template end @@ -98,11 +114,13 @@ module ActionView #:nodoc: private def templates_in_path (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file| - unless File.directory?(file) - yield Template.new(file.split("#{self}/").last, self) - end + yield create_template(file) unless File.directory?(file) end end + + def create_template(file) + Template.new(file.split("#{self}/").last, self) + end end def load @@ -121,5 +139,20 @@ module ActionView #:nodoc: end nil end + + def find_template(path, *formats) + if formats && formats.first == :all + formats = Mime::EXTENSION_LOOKUP.values.map(&:to_sym) + end + formats.each do |format| + if template = self["#{path}.#{format}"] + return template + end + end + if template = self[path] + return template + end + nil + end end end diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index 5ff5569db6..7258ad04bf 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -96,7 +96,7 @@ module ActionView # The template will be compiled if the file has not been compiled yet, or # if local_assigns has a new key, which isn't supported by the compiled code yet. def recompile?(symbol) - !(ActionView::PathSet::Path.eager_load_templates? && Base::CompiledTemplates.method_defined?(symbol)) + !Base::CompiledTemplates.method_defined?(symbol) || !loaded? end end end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index ca82454c0b..b486392ac4 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -57,6 +57,11 @@ module ActionView #:nodoc: end memoize :relative_path + def mtime + File.mtime(filename) + end + memoize :mtime + def source File.read(filename) end @@ -79,6 +84,19 @@ module ActionView #:nodoc: end end + def stale? + File.mtime(filename) > mtime + end + + def loaded? + @loaded + end + + def load! + @loaded = true + freeze + end + private def valid_extension?(extension) Template.template_handler_extensions.include?(extension) diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index cbf7351172..51697fda2f 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -31,8 +31,8 @@ ActionController::Base.logger = nil ActionController::Routing::Routes.reload rescue nil FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') -ActionView::PathSet::Path.eager_load_templates! ActionController::Base.view_paths = FIXTURE_LOAD_PATH +ActionController::Base.view_paths.load def uses_mocha(test_name) yield diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index 61c20f8299..18c01f755c 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -3,6 +3,10 @@ require 'abstract_unit' # The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited # method has access to the view_paths array when looking for a layout to automatically assign. old_load_paths = ActionController::Base.view_paths + +ActionView::Template::register_template_handler :mab, + lambda { |template| template.source.inspect } + ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../fixtures/layout_tests/' ] class LayoutTest < ActionController::Base @@ -31,9 +35,6 @@ end class MultipleExtensions < LayoutTest end -ActionView::Template::register_template_handler :mab, - lambda { |template| template.source.inspect } - class LayoutAutoDiscoveryTest < ActionController::TestCase def setup @request.host = "www.nextangle.com" @@ -52,10 +53,9 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase end def test_third_party_template_library_auto_discovers_layout - ThirdPartyTemplateLibraryController.view_paths.reload! @controller = ThirdPartyTemplateLibraryController.new get :hello - assert_equal 'layouts/third_party_template_library', @controller.active_layout + assert_equal 'layouts/third_party_template_library.mab', @controller.active_layout.to_s assert_equal 'layouts/third_party_template_library', @response.layout assert_response :success assert_equal 'Mab', @response.body @@ -64,14 +64,14 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase def test_namespaced_controllers_auto_detect_layouts @controller = ControllerNameSpace::NestedController.new get :hello - assert_equal 'layouts/controller_name_space/nested', @controller.active_layout + assert_equal 'layouts/controller_name_space/nested', @controller.active_layout.to_s assert_equal 'controller_name_space/nested.rhtml hello.rhtml', @response.body end def test_namespaced_controllers_auto_detect_layouts @controller = MultipleExtensions.new get :hello - assert_equal 'layouts/multiple_extensions', @controller.active_layout + assert_equal 'layouts/multiple_extensions.html.erb', @controller.active_layout.to_s assert_equal 'multiple_extensions.html.erb hello.rhtml', @response.body.strip end end diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 2c0caef583..7597927f6d 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -359,6 +359,46 @@ class AssetTagHelperTest < ActionView::TestCase FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) end + def test_caching_javascript_include_tag_when_caching_on_with_2_argument_object_asset_host + ENV['RAILS_ASSET_ID'] = '' + ActionController::Base.asset_host = Class.new do + def call(source, request) + if request.ssl? + "#{request.protocol}#{request.host_with_port}" + else + "#{request.protocol}assets#{source.length}.example.com" + end + end + end.new + + ActionController::Base.perform_caching = true + + assert_equal '/javascripts/vanilla.js'.length, 23 + assert_dom_equal( + %(<script src="http://assets23.example.com/javascripts/vanilla.js" type="text/javascript"></script>), + javascript_include_tag(:all, :cache => 'vanilla') + ) + + assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'vanilla.js')) + + class << @controller.request + def protocol() 'https://' end + def ssl?() true end + end + + assert_equal '/javascripts/secure.js'.length, 22 + assert_dom_equal( + %(<script src="https://localhost/javascripts/secure.js" type="text/javascript"></script>), + javascript_include_tag(:all, :cache => 'secure') + ) + + assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) + + ensure + FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'vanilla.js')) + FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) + end + def test_caching_javascript_include_tag_when_caching_on_and_using_subdirectory ENV["RAILS_ASSET_ID"] = "" ActionController::Base.asset_host = 'http://a%d.example.com' diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb index f7688b2072..a68b09bb45 100644 --- a/actionpack/test/template/compiled_templates_test.rb +++ b/actionpack/test/template/compiled_templates_test.rb @@ -30,8 +30,8 @@ uses_mocha 'TestTemplateRecompilation' do assert_equal "Hello world!", render(:file => "test/hello_world.erb") end - def test_compiled_template_will_always_be_recompiled_when_eager_loaded_templates_is_off - ActionView::PathSet::Path.expects(:eager_load_templates?).times(4).returns(false) + def test_compiled_template_will_always_be_recompiled_when_template_is_not_cached + ActionView::Template.any_instance.expects(:loaded?).times(3).returns(false) assert_equal 0, @compiled_templates.instance_methods.size assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb") ActionView::Template.any_instance.expects(:compile!).times(3) @@ -39,9 +39,48 @@ uses_mocha 'TestTemplateRecompilation' do assert_equal 1, @compiled_templates.instance_methods.size end + def test_template_changes_are_not_reflected_with_cached_templates + assert_equal "Hello world!", render(:file => "test/hello_world.erb") + modify_template "test/hello_world.erb", "Goodbye world!" do + assert_equal "Hello world!", render(:file => "test/hello_world.erb") + end + assert_equal "Hello world!", render(:file => "test/hello_world.erb") + end + + def test_template_changes_are_reflected_with_uncached_templates + assert_equal "Hello world!", render_without_cache(:file => "test/hello_world.erb") + modify_template "test/hello_world.erb", "Goodbye world!" do + assert_equal "Goodbye world!", render_without_cache(:file => "test/hello_world.erb") + end + assert_equal "Hello world!", render_without_cache(:file => "test/hello_world.erb") + end + private def render(*args) - ActionView::Base.new(ActionController::Base.view_paths, {}).render(*args) + render_with_cache(*args) + end + + def render_with_cache(*args) + view_paths = ActionController::Base.view_paths + assert view_paths.first.loaded? + ActionView::Base.new(view_paths, {}).render(*args) + end + + def render_without_cache(*args) + view_paths = ActionView::Base.process_view_paths(FIXTURE_LOAD_PATH) + assert !view_paths.first.loaded? + ActionView::Base.new(view_paths, {}).render(*args) + end + + def modify_template(template, content) + filename = "#{FIXTURE_LOAD_PATH}/#{template}" + old_content = File.read(filename) + begin + File.open(filename, "wb+") { |f| f.write(content) } + yield + ensure + File.open(filename, "wb+") { |f| f.write(old_content) } + end end end end diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index b316d5c23e..9e827abbca 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -1,10 +1,10 @@ require 'abstract_unit' require 'controller/fake_models' -class ViewRenderTest < Test::Unit::TestCase - def setup +module RenderTestCases + def setup_view(paths) @assigns = { :secret => 'in the sauce' } - @view = ActionView::Base.new(ActionController::Base.view_paths, @assigns) + @view = ActionView::Base.new(paths, @assigns) end def test_render_file @@ -157,7 +157,7 @@ class ViewRenderTest < Test::Unit::TestCase end def test_render_fallbacks_to_erb_for_unknown_types - assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :foo) + assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :bar) end CustomHandler = lambda do |template| @@ -196,3 +196,26 @@ class ViewRenderTest < Test::Unit::TestCase @view.render(:file => "test/nested_layout.erb", :layout => "layouts/yield") end end + +class CachedViewRenderTest < Test::Unit::TestCase + include RenderTestCases + + # Ensure view path cache is primed + def setup + view_paths = ActionController::Base.view_paths + assert view_paths.first.loaded? + setup_view(view_paths) + end +end + +class LazyViewRenderTest < Test::Unit::TestCase + include RenderTestCases + + # Test the same thing as above, but make sure the view path + # is not eager loaded + def setup + view_paths = ActionView::Base.process_view_paths(FIXTURE_LOAD_PATH) + assert !view_paths.first.loaded? + setup_view(view_paths) + end +end diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 5324a7c73b..ad8ba43c17 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,9 @@ *2.3.0 [Edge]* +* Enhanced Rails.root to take parameters that'll be join with the root, like Rails.root('app', 'controllers') => File.join(Rails.root, 'app', 'controllers') #1482 [Damian Janowski] + +* Added view path support for engines [DHH] + * Added that config/routes.rb files in engine plugins are automatically loaded (and reloaded when they change in dev mode) [DHH] * Added app/[models|controllers|helpers] to the load path for plugins that has an app directory (go engines ;)) [DHH] diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb index b0abf3379c..8e8bcf21fe 100644 --- a/railties/lib/initializer.rb +++ b/railties/lib/initializer.rb @@ -48,12 +48,8 @@ module Rails end end - def root - if defined?(RAILS_ROOT) - RAILS_ROOT - else - nil - end + def root(*args) + File.join(RAILS_ROOT, *args.compact) if defined?(RAILS_ROOT) end def env @@ -372,9 +368,10 @@ Run `rake gems:install` to install the missing gems. def load_view_paths if configuration.frameworks.include?(:action_view) - ActionView::PathSet::Path.eager_load_templates! if configuration.cache_classes - ActionController::Base.view_paths.load if configuration.frameworks.include?(:action_controller) - ActionMailer::Base.template_root.load if configuration.frameworks.include?(:action_mailer) + if configuration.cache_classes + ActionController::Base.view_paths.load if configuration.frameworks.include?(:action_controller) + ActionMailer::Base.template_root.load if configuration.frameworks.include?(:action_mailer) + end end end @@ -487,12 +484,8 @@ Run `rake gems:install` to install the missing gems. def initialize_routing return unless configuration.frameworks.include?(:action_controller) - ActionController::Routing.controller_paths = configuration.controller_paths + plugin_loader.controller_paths - - ([ configuration.routes_configuration_file ] + plugin_loader.routing_files).each do |routing_file| - ActionController::Routing::Routes.add_configuration_file(routing_file) - end - + ActionController::Routing.controller_paths += configuration.controller_paths + ActionController::Routing::Routes.add_configuration_file(configuration.routes_configuration_file) ActionController::Routing::Routes.reload end diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb index 82537d962f..d8626aaf14 100644 --- a/railties/lib/rails/backtrace_cleaner.rb +++ b/railties/lib/rails/backtrace_cleaner.rb @@ -3,11 +3,11 @@ module Rails ERB_METHOD_SIG = /:in `_run_erb_.*/ VENDOR_DIRS = %w( vendor/plugins vendor/gems vendor/rails ) - MONGREL_DIRS = %w( lib/mongrel bin/mongrel ) + SERVER_DIRS = %w( lib/mongrel bin/mongrel lib/rack ) RAILS_NOISE = %w( script/server ) RUBY_NOISE = %w( rubygems/custom_require benchmark.rb ) - ALL_NOISE = VENDOR_DIRS + MONGREL_DIRS + RAILS_NOISE + RUBY_NOISE + ALL_NOISE = VENDOR_DIRS + SERVER_DIRS + RAILS_NOISE + RUBY_NOISE def initialize super diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index 2b1e877e2b..4901abe808 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -71,6 +71,11 @@ module Rails File.exist?(routing_file) end + + def view_path + File.join(directory, 'app', 'views') + end + def controller_path File.join(directory, 'app', 'controllers') end @@ -95,11 +100,7 @@ module Rails def app_paths - [ - File.join(directory, 'app', 'models'), - File.join(directory, 'app', 'controllers'), - File.join(directory, 'app', 'helpers') - ] + [ File.join(directory, 'app', 'models'), File.join(directory, 'app', 'helpers'), controller_path ] end def lib_path diff --git a/railties/lib/rails/plugin/loader.rb b/railties/lib/rails/plugin/loader.rb index f08d9b74e2..be81bdf4fa 100644 --- a/railties/lib/rails/plugin/loader.rb +++ b/railties/lib/rails/plugin/loader.rb @@ -39,6 +39,8 @@ module Rails register_plugin_as_loaded(plugin) end + configure_engines + ensure_all_registered_plugins_are_loaded! end @@ -63,19 +65,31 @@ module Rails $LOAD_PATH.uniq! end - # Returns an array of all the controller paths found inside engine-type plugins. - def controller_paths - engines.collect(&:controller_path) - end - - # Returns an array of routing.rb files from all the plugins that include config/routes.rb - def routing_files - plugins.select(&:routed?).collect(&:routing_file) - end - protected + def configure_engines + if engines.any? + add_engine_routing_configurations + add_engine_controller_paths + add_engine_view_paths + end + end + def add_engine_routing_configurations + engines.select(&:routed?).collect(&:routing_file).each do |routing_file| + ActionController::Routing::Routes.add_configuration_file(routing_file) + end + end + + def add_engine_controller_paths + ActionController::Routing.controller_paths += engines.collect(&:controller_path) + end + + def add_engine_view_paths + # reverse it such that the last engine can overwrite view paths from the first, like with routes + ActionController::Base.view_paths += ActionView::PathSet.new(engines.collect(&:view_path).reverse) + end + # The locate_plugins method uses each class in config.plugin_locators to # find the set of all plugins available to this Rails application. def locate_plugins diff --git a/railties/test/fixtures/plugins/engines/engine/app/controllers/engine_controller.rb b/railties/test/fixtures/plugins/engines/engine/app/controllers/engine_controller.rb index f08373de40..323ee1c4dc 100644 --- a/railties/test/fixtures/plugins/engines/engine/app/controllers/engine_controller.rb +++ b/railties/test/fixtures/plugins/engines/engine/app/controllers/engine_controller.rb @@ -1,2 +1,2 @@ -class EngineController < ActionController::Base +class EngineController end
\ No newline at end of file diff --git a/railties/test/initializer_test.rb b/railties/test/initializer_test.rb index 2104412c54..33c81bc5ad 100644 --- a/railties/test/initializer_test.rb +++ b/railties/test/initializer_test.rb @@ -311,3 +311,13 @@ uses_mocha 'i18n settings' do end end end + +class RailsRootTest < Test::Unit::TestCase + def test_rails_dot_root_equals_rails_root + assert_equal RAILS_ROOT, Rails.root + end + + def test_rails_dot_root_accepts_arguments_for_file_dot_join + assert_equal File.join(RAILS_ROOT, 'app', 'controllers'), Rails.root('app', 'controllers') + 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 0d3aec5fd6..23b81ddbf6 100644 --- a/railties/test/plugin_loader_test.rb +++ b/railties/test/plugin_loader_test.rb @@ -1,5 +1,8 @@ require 'plugin_test_helper' +$:.unshift File.dirname(__FILE__) + "/../../actionpack/lib" +require 'action_controller' + # Mocks out the configuration module Rails def self.configuration @@ -7,149 +10,152 @@ module Rails end end -uses_mocha "Plugin Loader Tests" do - - class TestPluginLoader < Test::Unit::TestCase - ORIGINAL_LOAD_PATH = $LOAD_PATH.dup - - def setup - reset_load_path! +class TestPluginLoader < Test::Unit::TestCase + ORIGINAL_LOAD_PATH = $LOAD_PATH.dup - @configuration = Rails::Configuration.new - @configuration.plugin_paths << plugin_fixture_root_path - @initializer = Rails::Initializer.new(@configuration) - @valid_plugin_path = plugin_fixture_path('default/stubby') - @empty_plugin_path = plugin_fixture_path('default/empty') + def setup + reset_load_path! - @failure_tip = "It's likely someone has added a new plugin fixture without updating this list" + @configuration = Rails::Configuration.new + @configuration.plugin_paths << plugin_fixture_root_path + @initializer = Rails::Initializer.new(@configuration) + @valid_plugin_path = plugin_fixture_path('default/stubby') + @empty_plugin_path = plugin_fixture_path('default/empty') - @loader = Rails::Plugin::Loader.new(@initializer) - end + @failure_tip = "It's likely someone has added a new plugin fixture without updating this list" - def test_should_locate_plugins_by_asking_each_locator_specifed_in_configuration_for_its_plugins_result - locator_1 = stub(:plugins => [:a, :b, :c]) - locator_2 = stub(:plugins => [:d, :e, :f]) - locator_class_1 = stub(:new => locator_1) - locator_class_2 = stub(:new => locator_2) - @configuration.plugin_locators = [locator_class_1, locator_class_2] - assert_equal [:a, :b, :c, :d, :e, :f], @loader.send(:locate_plugins) - end + @loader = Rails::Plugin::Loader.new(@initializer) + end - def test_should_memoize_the_result_of_locate_plugins_as_all_plugins - plugin_list = [:a, :b, :c] - @loader.expects(:locate_plugins).once.returns(plugin_list) - assert_equal plugin_list, @loader.all_plugins - assert_equal plugin_list, @loader.all_plugins # ensuring that locate_plugins isn't called again - end + def test_should_locate_plugins_by_asking_each_locator_specifed_in_configuration_for_its_plugins_result + locator_1 = stub(:plugins => [:a, :b, :c]) + locator_2 = stub(:plugins => [:d, :e, :f]) + locator_class_1 = stub(:new => locator_1) + locator_class_2 = stub(:new => locator_2) + @configuration.plugin_locators = [locator_class_1, locator_class_2] + assert_equal [:a, :b, :c, :d, :e, :f], @loader.send(:locate_plugins) + end - def test_should_return_empty_array_if_configuration_plugins_is_empty - @configuration.plugins = [] - assert_equal [], @loader.plugins - end + def test_should_memoize_the_result_of_locate_plugins_as_all_plugins + plugin_list = [:a, :b, :c] + @loader.expects(:locate_plugins).once.returns(plugin_list) + assert_equal plugin_list, @loader.all_plugins + assert_equal plugin_list, @loader.all_plugins # ensuring that locate_plugins isn't called again + end - def test_should_find_all_availble_plugins_and_return_as_all_plugins - assert_plugins [ :engine, :stubby, :plugin_with_no_lib_dir, :gemlike, :acts_as_chunky_bacon, :a], @loader.all_plugins.reverse, @failure_tip - end + def test_should_return_empty_array_if_configuration_plugins_is_empty + @configuration.plugins = [] + assert_equal [], @loader.plugins + end - def test_should_return_all_plugins_as_plugins_when_registered_plugin_list_is_untouched - assert_plugins [:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip - end + def test_should_find_all_availble_plugins_and_return_as_all_plugins + assert_plugins [ :engine, :stubby, :plugin_with_no_lib_dir, :gemlike, :acts_as_chunky_bacon, :a], @loader.all_plugins.reverse, @failure_tip + end - def test_should_return_all_plugins_as_plugins_when_registered_plugin_list_is_nil - @configuration.plugins = nil - assert_plugins [:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip - end + def test_should_return_all_plugins_as_plugins_when_registered_plugin_list_is_untouched + assert_plugins [:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip + end - def test_should_return_specific_plugins_named_in_config_plugins_array_if_set - plugin_names = [:acts_as_chunky_bacon, :stubby] - only_load_the_following_plugins! plugin_names - assert_plugins plugin_names, @loader.plugins - end + def test_should_return_all_plugins_as_plugins_when_registered_plugin_list_is_nil + @configuration.plugins = nil + assert_plugins [:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip + end - def test_should_respect_the_order_of_plugins_given_in_configuration - plugin_names = [:stubby, :acts_as_chunky_bacon] - only_load_the_following_plugins! plugin_names - assert_plugins plugin_names, @loader.plugins - end + def test_should_return_specific_plugins_named_in_config_plugins_array_if_set + plugin_names = [:acts_as_chunky_bacon, :stubby] + only_load_the_following_plugins! plugin_names + assert_plugins plugin_names, @loader.plugins + end - def test_should_load_all_plugins_in_natural_order_when_all_is_used - only_load_the_following_plugins! [:all] - assert_plugins [:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip - end + def test_should_respect_the_order_of_plugins_given_in_configuration + plugin_names = [:stubby, :acts_as_chunky_bacon] + only_load_the_following_plugins! plugin_names + assert_plugins plugin_names, @loader.plugins + end - def test_should_load_specified_plugins_in_order_and_then_all_remaining_plugins_when_all_is_used - only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon, :all] - assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :engine, :gemlike, :plugin_with_no_lib_dir], @loader.plugins, @failure_tip - end + def test_should_load_all_plugins_in_natural_order_when_all_is_used + only_load_the_following_plugins! [:all] + assert_plugins [:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip + end - def test_should_be_able_to_specify_loading_of_plugins_loaded_after_all - only_load_the_following_plugins! [:stubby, :all, :acts_as_chunky_bacon] - assert_plugins [:stubby, :a, :engine, :gemlike, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], @loader.plugins, @failure_tip - end + def test_should_load_specified_plugins_in_order_and_then_all_remaining_plugins_when_all_is_used + only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon, :all] + assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :engine, :gemlike, :plugin_with_no_lib_dir], @loader.plugins, @failure_tip + end - def test_should_accept_plugin_names_given_as_strings - only_load_the_following_plugins! ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir] - assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :plugin_with_no_lib_dir], @loader.plugins, @failure_tip - end + def test_should_be_able_to_specify_loading_of_plugins_loaded_after_all + only_load_the_following_plugins! [:stubby, :all, :acts_as_chunky_bacon] + assert_plugins [:stubby, :a, :engine, :gemlike, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], @loader.plugins, @failure_tip + end - def test_should_add_plugin_load_paths_to_global_LOAD_PATH_array - only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] - stubbed_application_lib_index_in_LOAD_PATHS = 4 - @loader.stubs(:application_lib_index).returns(stubbed_application_lib_index_in_LOAD_PATHS) + def test_should_accept_plugin_names_given_as_strings + only_load_the_following_plugins! ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir] + assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :plugin_with_no_lib_dir], @loader.plugins, @failure_tip + end - @loader.add_plugin_load_paths + def test_should_add_plugin_load_paths_to_global_LOAD_PATH_array + only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] + stubbed_application_lib_index_in_LOAD_PATHS = 4 + @loader.stubs(:application_lib_index).returns(stubbed_application_lib_index_in_LOAD_PATHS) - assert $LOAD_PATH.index(File.join(plugin_fixture_path('default/stubby'), 'lib')) >= stubbed_application_lib_index_in_LOAD_PATHS - assert $LOAD_PATH.index(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) >= stubbed_application_lib_index_in_LOAD_PATHS - end + @loader.add_plugin_load_paths - def test_should_add_plugin_load_paths_to_Dependencies_load_paths - only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] + assert $LOAD_PATH.index(File.join(plugin_fixture_path('default/stubby'), 'lib')) >= stubbed_application_lib_index_in_LOAD_PATHS + assert $LOAD_PATH.index(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) >= stubbed_application_lib_index_in_LOAD_PATHS + end - @loader.add_plugin_load_paths + def test_should_add_plugin_load_paths_to_Dependencies_load_paths + only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] - assert ActiveSupport::Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) - assert ActiveSupport::Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) - end + @loader.add_plugin_load_paths + assert ActiveSupport::Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) + assert ActiveSupport::Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) + end - def test_should_add_engine_load_paths_to_Dependencies_load_paths - only_load_the_following_plugins! [:engine] + def test_should_add_engine_load_paths_to_Dependencies_load_paths + only_load_the_following_plugins! [:engine] - @loader.add_plugin_load_paths + @loader.add_plugin_load_paths - %w( models controllers helpers ).each do |app_part| - assert ActiveSupport::Dependencies.load_paths.include?( - File.join(plugin_fixture_path('engines/engine'), 'app', app_part) - ), "Couldn't find #{app_part} in load path" - end + %w( models controllers helpers ).each do |app_part| + assert ActiveSupport::Dependencies.load_paths.include?( + File.join(plugin_fixture_path('engines/engine'), 'app', app_part) + ), "Couldn't find #{app_part} in load path" end + end + + def test_engine_controllers_should_have_their_view_path_set_when_loaded + only_load_the_following_plugins!([ :engine ]) - def test_should_add_plugin_load_paths_to_Dependencies_load_once_paths - only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] - - @loader.add_plugin_load_paths + @loader.send :add_engine_view_paths + + assert_equal [ File.join(plugin_fixture_path('engines/engine'), 'app', 'views') ], ActionController::Base.view_paths + end - assert ActiveSupport::Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) - assert ActiveSupport::Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) - end + def test_should_add_plugin_load_paths_to_Dependencies_load_once_paths + only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] - def test_should_add_all_load_paths_from_a_plugin_to_LOAD_PATH_array - plugin_load_paths = ["a", "b"] - plugin = stub(:load_paths => plugin_load_paths) - @loader.stubs(:plugins).returns([plugin]) + @loader.add_plugin_load_paths - @loader.add_plugin_load_paths + assert ActiveSupport::Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) + assert ActiveSupport::Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) + end - plugin_load_paths.each { |path| assert $LOAD_PATH.include?(path) } - end + def test_should_add_all_load_paths_from_a_plugin_to_LOAD_PATH_array + plugin_load_paths = ["a", "b"] + plugin = stub(:load_paths => plugin_load_paths) + @loader.stubs(:plugins).returns([plugin]) - private + @loader.add_plugin_load_paths - def reset_load_path! - $LOAD_PATH.clear - ORIGINAL_LOAD_PATH.each { |path| $LOAD_PATH << path } - end + plugin_load_paths.each { |path| assert $LOAD_PATH.include?(path) } end -end + + private + def reset_load_path! + $LOAD_PATH.clear + ORIGINAL_LOAD_PATH.each { |path| $LOAD_PATH << path } + end +end
\ No newline at end of file |