Skip to content

Commit

Permalink
Improve view rendering performance in development mode and reinstate …
Browse files Browse the repository at this point in the history
…template recompiling in production [#1909 state:resolved]

Signed-off-by: Joshua Peek <josh@joshpeek.com>
  • Loading branch information
pixeltrix authored and josh committed Feb 9, 2009
1 parent 5120429 commit 893e9eb
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 102 deletions.
2 changes: 1 addition & 1 deletion actionpack/lib/action_controller/rescue.rb
Expand Up @@ -38,7 +38,7 @@ module Rescue
'ActionView::TemplateError' => 'template_error'
}

RESCUES_TEMPLATE_PATH = ActionView::Template::EagerPath.new(
RESCUES_TEMPLATE_PATH = ActionView::Template::Path.new(
File.join(File.dirname(__FILE__), "templates"))

def self.included(base) #:nodoc:
Expand Down
8 changes: 6 additions & 2 deletions actionpack/lib/action_view/base.rb
Expand Up @@ -182,6 +182,10 @@ class << self
# that alert()s the caught exception (and then re-raises it).
cattr_accessor :debug_rjs

# Specify whether to check whether modified templates are recompiled without a restart
@@cache_template_loading = false
cattr_accessor :cache_template_loading

attr_internal :request

delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
Expand Down Expand Up @@ -243,8 +247,8 @@ def render(options = {}, local_assigns = {}, &block) #:nodoc:
if options[:layout]
_render_with_layout(options, local_assigns, &block)
elsif options[:file]
tempalte = self.view_paths.find_template(options[:file], template_format)
tempalte.render_template(self, options[:locals])
template = self.view_paths.find_template(options[:file], template_format)
template.render_template(self, options[:locals])
elsif options[:partial]
render_partial(options)
elsif options[:inline]
Expand Down
1 change: 0 additions & 1 deletion actionpack/lib/action_view/partials.rb
Expand Up @@ -235,6 +235,5 @@ def _pick_partial_template(partial_path) #:nodoc:

self.view_paths.find_template(path, self.template_format)
end
memoize :_pick_partial_template
end
end
17 changes: 8 additions & 9 deletions actionpack/lib/action_view/paths.rb
Expand Up @@ -2,11 +2,7 @@ module ActionView #:nodoc:
class PathSet < Array #:nodoc:
def self.type_cast(obj)
if obj.is_a?(String)
if !Object.const_defined?(:Rails) || Rails.configuration.cache_classes
Template::EagerPath.new(obj)
else
Template::Path.new(obj)
end
Template::Path.new(obj)
else
obj
end
Expand Down Expand Up @@ -36,9 +32,8 @@ def unshift(*objs)
super(*objs.map { |obj| self.class.type_cast(obj) })
end

def find_template(original_template_path, format = nil)
return original_template_path if original_template_path.respond_to?(:render)
template_path = original_template_path.sub(/^\//, '')
def find_template(template_path, format = nil)
return template_path if template_path.respond_to?(:render)

each do |load_path|
if format && (template = load_path["#{template_path}.#{I18n.locale}.#{format}"])
Expand All @@ -57,7 +52,11 @@ def find_template(original_template_path, format = nil)
end
end

Template.new(original_template_path, self)
if File.exist?(template_path)
return Template.new(template_path, template_path[0] == 47 ? "" : ".")
end

raise MissingTemplate.new(self, template_path, format)
end
end
end
7 changes: 2 additions & 5 deletions actionpack/lib/action_view/renderable.rb
Expand Up @@ -18,7 +18,6 @@ def handler
def compiled_source
handler.call(self)
end
memoize :compiled_source

def method_name_without_locals
['_run', extension, method_segment].compact.join('_')
Expand Down Expand Up @@ -80,6 +79,8 @@ def #{render_symbol}(local_assigns)

begin
ActionView::Base::CompiledTemplates.module_eval(source, filename, 0)
rescue Errno::ENOENT => e
raise e # Missing template file, re-raise for Base to rescue
rescue Exception => e # errors from template code
if logger = defined?(ActionController) && Base.logger
logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
Expand All @@ -90,9 +91,5 @@ def #{render_symbol}(local_assigns)
raise ActionView::TemplateError.new(self, {}, e)
end
end

def recompile?
false
end
end
end
80 changes: 42 additions & 38 deletions actionpack/lib/action_view/template.rb
Expand Up @@ -7,6 +7,11 @@ class Path
def initialize(path)
raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
@path = path.freeze

@paths = {}
templates_in_path do |template|
load_template(template)
end
end

def to_s
Expand Down Expand Up @@ -39,12 +44,7 @@ def eql?(path)
# etc. A format must be supplied to match a formated file. +hello/index+
# will never match +hello/index.html.erb+.
def [](path)
templates_in_path do |template|
if template.accessible_paths.include?(path)
return template
end
end
nil
@paths[path] || find_template(path)
end

private
Expand All @@ -57,25 +57,30 @@ def templates_in_path
def create_template(file)
Template.new(file.split("#{self}/").last, self)
end
end

class EagerPath < Path
def initialize(path)
super

@paths = {}
templates_in_path do |template|
def load_template(template)
template.load!
template.accessible_paths.each do |path|
@paths[path] = template
end
end
@paths.freeze
end

def [](path)
@paths[path]
end
def matching_templates(template_path)
Dir.glob("#{@path}/#{template_path}.*").each do |file|
yield create_template(file) unless File.directory?(file)
end
end

def find_template(path)
return nil if Base.cache_template_loading || ActionController::Base.allow_concurrency
matching_templates(path) do |template|
if template.accessible_paths.include?(path)
load_template(template)
return template
end
end
nil
end
end

extend TemplateHandlers
Expand All @@ -97,9 +102,9 @@ def self.exempt_from_layout(*extensions)
attr_accessor :locale, :name, :format, :extension
delegate :to_s, :to => :path

def initialize(template_path, load_paths = [])
def initialize(template_path, load_path)
template_path = template_path.dup
@load_path, @filename = find_full_path(template_path, load_paths)

This comment has been minimized.

Copy link
@tekkub

tekkub Mar 23, 2009

This change is causing issues for me with files in the public path, like my 401 error page. Specifically, it seems to be appending “./” onto the path when it shouldn’t, and I get an error when trying to load “./c:/rails_apps/my_app/public/401.html”

@load_path, @filename = load_path, File.join(load_path, template_path)
@base_path, @name, @locale, @format, @extension = split(template_path)
@base_path.to_s.gsub!(/\/$/, '') # Push to split method

Expand Down Expand Up @@ -171,7 +176,6 @@ def mtime
def source
File.read(filename)
end
memoize :source

def method_segment
relative_path.to_s.gsub(/([^a-zA-Z0-9_])/) { $1.ord }
Expand All @@ -185,25 +189,34 @@ def render_template(view, local_assigns = {})
if TemplateError === e
e.sub_template_of(self)
raise e
elsif Errno::ENOENT === e
raise MissingTemplate.new(view.view_paths, filename.sub("#{RAILS_ROOT}/#{load_path}/", ""))
else
raise TemplateError.new(self, view.assigns, e)
end
end

def stale?
File.mtime(filename) > mtime
end

def recompile?
!@cached
!frozen? && mtime < mtime(:reload)
end

def load!
@cached = true
freeze
reloadable? ? memoize_all : freeze
end

private
def cached?
Base.cache_template_loading || ActionController::Base.allow_concurrency
end

def reloadable?
!cached?
end

def recompile?
reloadable? ? stale? : false
end

def valid_extension?(extension)
!Template.registered_template_handler(extension).nil?
end
Expand All @@ -212,15 +225,6 @@ def valid_locale?(locale)
I18n.available_locales.include?(locale.to_sym)
end

def find_full_path(path, load_paths)
load_paths = Array(load_paths) + [nil]
load_paths.each do |load_path|
file = load_path ? "#{load_path.to_str}/#{path}" : path
return load_path, file if File.file?(file)
end
raise MissingTemplate.new(load_paths, path)
end

# Returns file split into an array
# [base_path, name, locale, format, extension]
def split(file)
Expand Down Expand Up @@ -259,5 +263,5 @@ def split(file)

[base_path, name, locale, format, extension]
end
end
end
end
42 changes: 23 additions & 19 deletions actionpack/test/template/compiled_templates_test.rb
Expand Up @@ -39,35 +39,29 @@ def test_compiled_template_will_always_be_recompiled_when_template_is_not_cached
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
with_caching(true) do
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
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")
def test_template_changes_are_reflected_without_cached_templates
with_caching(false) do
assert_equal "Hello world!", render(:file => "test/hello_world.erb")
modify_template "test/hello_world.erb", "Goodbye world!" do
assert_equal "Goodbye world!", render(:file => "test/hello_world.erb")
sleep(1) # Need to sleep so that the timestamp actually changes
end
assert_equal "Hello world!", render(:file => "test/hello_world.erb")
end
assert_equal "Hello world!", render_without_cache(:file => "test/hello_world.erb")
end

private
def render(*args)
render_with_cache(*args)
end

def render_with_cache(*args)
view_paths = ActionController::Base.view_paths
assert_equal ActionView::Template::EagerPath, view_paths.first.class
ActionView::Base.new(view_paths, {}).render(*args)
end

def render_without_cache(*args)
path = ActionView::Template::Path.new(FIXTURE_LOAD_PATH)
view_paths = ActionView::Base.process_view_paths(path)
assert_equal ActionView::Template::Path, view_paths.first.class
ActionView::Base.new(view_paths, {}).render(*args)
end
Expand All @@ -82,4 +76,14 @@ def modify_template(template, content)
File.open(filename, "wb+") { |f| f.write(old_content) }
end
end

def with_caching(caching_enabled)
old_caching_enabled = ActionView::Base.cache_template_loading
begin
ActionView::Base.cache_template_loading = caching_enabled
yield
ensure
ActionView::Base.cache_template_loading = old_caching_enabled
end
end
end
15 changes: 1 addition & 14 deletions actionpack/test/template/render_test.rb
Expand Up @@ -243,25 +243,12 @@ def test_render_utf8_template
end
end

class CachedViewRenderTest < Test::Unit::TestCase
class CachedRenderTest < Test::Unit::TestCase
include RenderTestCases

# Ensure view path cache is primed
def setup
view_paths = ActionController::Base.view_paths
assert_equal ActionView::Template::EagerPath, view_paths.first.class
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
path = ActionView::Template::Path.new(FIXTURE_LOAD_PATH)
view_paths = ActionView::Base.process_view_paths(path)
assert_equal ActionView::Template::Path, view_paths.first.class
setup_view(view_paths)
end
Expand Down
1 change: 1 addition & 0 deletions railties/environments/production.rb
Expand Up @@ -7,6 +7,7 @@
# Full error reports are disabled and caching is turned on
config.action_controller.consider_all_requests_local = false
config.action_controller.perform_caching = true
config.action_view.cache_template_loading = true

# See everything in the log (default is :info)
# config.log_level = :debug
Expand Down
13 changes: 0 additions & 13 deletions railties/lib/initializer.rb
Expand Up @@ -181,9 +181,6 @@ def process
# Observers are loaded after plugins in case Observers or observed models are modified by plugins.
load_observers

# Load view path cache
load_view_paths

# Load application classes
load_application_classes

Expand Down Expand Up @@ -367,16 +364,6 @@ def load_observers
end
end

def load_view_paths
if configuration.frameworks.include?(:action_view)
if configuration.cache_classes
view_path = ActionView::Template::EagerPath.new(configuration.view_path)
ActionController::Base.view_paths = view_path if configuration.frameworks.include?(:action_controller)
ActionMailer::Base.template_root = view_path if configuration.frameworks.include?(:action_mailer)
end
end
end

# Eager load application classes
def load_application_classes
return if $rails_rake_task
Expand Down

7 comments on commit 893e9eb

@lackac
Copy link
Contributor

@lackac lackac commented on 893e9eb Feb 10, 2009

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit gave me a lot of headache, since Haml templates stopped working. The issue is that the haml plugin or gem is only initialized after the the templates are processed so ActionView will not know about the available template handler until it’s too late. I guess this would be true for other template handlers in plugin or gem form too.

@petelacey
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirming that Haml is no longer working after this commit.

@pixeltrix
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it work with this patch?
http://rails.lighthouseapp.com/attachments/87152/local_assigns_and_template_handlers.diff

@rolfb
Copy link
Contributor

@rolfb rolfb commented on 893e9eb Feb 11, 2009

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice if someone fixed this quickly :)

@dbackeus
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My views don’t reload at all in development after this update…

@masterkain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

views reloading in dev mode broke

@josh
Copy link
Contributor

@josh josh commented on 893e9eb Feb 12, 2009

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we’ve finally got it fix. Sorry about that.

Please test it again.

Please sign in to comment.