Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Revert "Super lazy load view paths in development mode (no indexing o…
…r caching at all). Switch layout finders to use view path api to take advantage of cache." as it killed dev mode reloading.

This reverts commit 4d910b0.
  • Loading branch information
dhh committed Nov 29, 2008
1 parent 6fa9957 commit 3490567
Show file tree
Hide file tree
Showing 11 changed files with 74 additions and 92 deletions.
5 changes: 2 additions & 3 deletions actionpack/lib/action_controller/base.rb
Expand Up @@ -867,9 +867,8 @@ def render(options = nil, extra_options = {}, &block) #:doc:
end
end

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
response.layout = layout = pick_layout(options)
logger.info("Rendering template within #{layout}") if logger && layout

if content_type = options[:content_type]
response.content_type = content_type.to_s
Expand Down
1 change: 1 addition & 0 deletions actionpack/lib/action_controller/dispatcher.rb
Expand Up @@ -137,6 +137,7 @@ def reload_application
run_callbacks :prepare_dispatch

Routing::Routes.reload
ActionController::Base.view_paths.reload!
ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear
end

Expand Down
38 changes: 28 additions & 10 deletions actionpack/lib/action_controller/layout.rb
Expand Up @@ -175,20 +175,21 @@ def layout_conditions #:nodoc:
def default_layout(format) #:nodoc:
layout = read_inheritable_attribute(:layout)
return layout unless read_inheritable_attribute(:auto_layout)
find_layout(layout, format)
@default_layout ||= {}
@default_layout[format] ||= default_layout_with_format(format, layout)
@default_layout[format]
end

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)
def layout_list #:nodoc:
Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] }
end

private
def inherited_with_layout(child)
inherited_without_layout(child)
unless child.name.blank?
layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
child.layout(layout_match, {}, true) if child.find_layout(layout_match, :all)
child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty?
end
end

Expand All @@ -199,6 +200,15 @@ def add_layout_conditions(conditions)
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
Expand All @@ -207,18 +217,20 @@ def normalize_conditions(conditions)
# 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 layout = self.class.find_layout(active_layout, @template.template_format)
layout
if active_layout.include?('/') && ! layout_directory?(active_layout)
active_layout
else
raise ActionView::MissingTemplate.new(self.class.view_paths, active_layout)
"layouts/#{active_layout}"
end
end
end
Expand Down Expand Up @@ -259,6 +271,12 @@ def action_has_layout?
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
Expand Down
4 changes: 3 additions & 1 deletion actionpack/lib/action_view/base.rb
Expand Up @@ -322,7 +322,9 @@ def _pick_template(template_path)
end

# OPTIMIZE: Checks to lookup template in view path
if template = self.view_paths.find_template(template_file_name, template_format)
if template = self.view_paths["#{template_file_name}.#{template_format}"]
template
elsif template = self.view_paths[template_file_name]
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}"])
Expand Down
67 changes: 17 additions & 50 deletions actionpack/lib/action_view/paths.rb
Expand Up @@ -40,10 +40,18 @@ def unshift(*objs)
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 = false)
def initialize(path, load = true)
raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
@path = path.freeze
reload! if load
Expand All @@ -57,35 +65,9 @@ def eql?(path)
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)
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
raise "Unloaded view path! #{@path}" unless @loaded
@paths[path]
end

def loaded?
Expand All @@ -102,7 +84,9 @@ def reload!
@paths = {}

templates_in_path do |template|
template.freeze
# Eager load memoized methods and freeze cached template
template.freeze if self.class.eager_load_templates?

@paths[template.path] = template
@paths[template.path_without_extension] ||= template
end
Expand All @@ -114,13 +98,11 @@ def reload!
private
def templates_in_path
(Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
yield create_template(file) unless File.directory?(file)
unless File.directory?(file)
yield Template.new(file.split("#{self}/").last, self)
end
end
end

def create_template(file)
Template.new(file.split("#{self}/").last, self)
end
end

def load
Expand All @@ -139,20 +121,5 @@ def [](template_path)
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
2 changes: 1 addition & 1 deletion actionpack/lib/action_view/renderable.rb
Expand Up @@ -96,7 +96,7 @@ def #{render_symbol}(local_assigns)
# 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)
!(frozen? && Base::CompiledTemplates.method_defined?(symbol))
!(ActionView::PathSet::Path.eager_load_templates? && Base::CompiledTemplates.method_defined?(symbol))
end
end
end
2 changes: 1 addition & 1 deletion actionpack/test/abstract_unit.rb
Expand Up @@ -30,8 +30,8 @@
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
Expand Down
14 changes: 7 additions & 7 deletions actionpack/test/controller/layout_test.rb
Expand Up @@ -3,10 +3,6 @@
# 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
Expand Down Expand Up @@ -35,6 +31,9 @@ class ControllerNameSpace::NestedController < LayoutTest
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"
Expand All @@ -53,9 +52,10 @@ def test_controller_name_layout_name_match
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.mab', @controller.active_layout.to_s
assert_equal 'layouts/third_party_template_library', @controller.active_layout
assert_equal 'layouts/third_party_template_library', @response.layout
assert_response :success
assert_equal 'Mab', @response.body
Expand All @@ -64,14 +64,14 @@ def test_third_party_template_library_auto_discovers_layout
def test_namespaced_controllers_auto_detect_layouts
@controller = ControllerNameSpace::NestedController.new
get :hello
assert_equal 'layouts/controller_name_space/nested', @controller.active_layout.to_s
assert_equal 'layouts/controller_name_space/nested', @controller.active_layout
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.html.erb', @controller.active_layout.to_s
assert_equal 'layouts/multiple_extensions', @controller.active_layout
assert_equal 'multiple_extensions.html.erb hello.rhtml', @response.body.strip
end
end
Expand Down
9 changes: 9 additions & 0 deletions actionpack/test/template/compiled_templates_test.rb
Expand Up @@ -30,6 +30,15 @@ def test_compiled_template_will_not_be_recompiled_when_rendered_with_identical_l
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)
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)
3.times { assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb") }
assert_equal 1, @compiled_templates.instance_methods.size
end

private
def render(*args)
ActionView::Base.new(ActionController::Base.view_paths, {}).render(*args)
Expand Down
17 changes: 2 additions & 15 deletions actionpack/test/template/render_test.rb
Expand Up @@ -4,9 +4,7 @@
class ViewRenderTest < Test::Unit::TestCase
def setup
@assigns = { :secret => 'in the sauce' }
view_paths = ActionController::Base.view_paths
@view = ActionView::Base.new(view_paths, @assigns)
assert view_paths.first.loaded?
@view = ActionView::Base.new(ActionController::Base.view_paths, @assigns)
end

def test_render_file
Expand Down Expand Up @@ -159,7 +157,7 @@ def test_render_inline_with_locals
end

def test_render_fallbacks_to_erb_for_unknown_types
assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :bar)
assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :foo)
end

CustomHandler = lambda do |template|
Expand Down Expand Up @@ -198,14 +196,3 @@ def test_render_with_nested_layout
@view.render(:file => "test/nested_layout.erb", :layout => "layouts/yield")
end
end

class LazyViewRenderTest < ViewRenderTest
# Test the same thing as above, but make sure the view path
# is not eager loaded
def setup
@assigns = { :secret => 'in the sauce' }
view_paths = ActionView::Base.process_view_paths(FIXTURE_LOAD_PATH)
@view = ActionView::Base.new(view_paths, @assigns)
assert !view_paths.first.loaded?
end
end
7 changes: 3 additions & 4 deletions railties/lib/initializer.rb
Expand Up @@ -372,10 +372,9 @@ def load_observers

def load_view_paths
if configuration.frameworks.include?(:action_view)
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
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)
end
end

Expand Down

0 comments on commit 3490567

Please sign in to comment.