Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Port fast reloadable templates from rails-dev-boost.
  • Loading branch information
thedarkone authored and josh committed Feb 12, 2009
1 parent b1d41bd commit 3942cb4
Show file tree
Hide file tree
Showing 17 changed files with 269 additions and 124 deletions.
1 change: 1 addition & 0 deletions actionmailer/test/abstract_unit.rb
Expand Up @@ -19,6 +19,7 @@

$:.unshift "#{File.dirname(__FILE__)}/fixtures/helpers"

ActionView::Base.cache_template_loading = true
FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
ActionMailer::Base.template_root = FIXTURE_LOAD_PATH

Expand Down
2 changes: 1 addition & 1 deletion actionmailer/test/mail_service_test.rb
Expand Up @@ -975,7 +975,7 @@ def test_starttls_is_not_enabled

class InheritableTemplateRootTest < Test::Unit::TestCase
def test_attr
expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
expected = ("#{File.dirname(__FILE__)}/fixtures/path.with.dots").sub(/\.\//, '')
assert_equal expected, FunkyPathMailer.template_root.to_s

sub = Class.new(FunkyPathMailer)
Expand Down
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::Path.new(
RESCUES_TEMPLATE_PATH = ActionView::Template::EagerPath.new_and_loaded(
File.join(File.dirname(__FILE__), "templates"))

def self.included(base) #:nodoc:
Expand Down
1 change: 1 addition & 0 deletions actionpack/lib/action_view.rb
Expand Up @@ -44,6 +44,7 @@ def self.load_all!
autoload :Renderable, 'action_view/renderable'
autoload :RenderablePartial, 'action_view/renderable_partial'
autoload :Template, 'action_view/template'
autoload :ReloadableTemplate, 'action_view/reloadable_template'
autoload :TemplateError, 'action_view/template_error'
autoload :TemplateHandler, 'action_view/template_handler'
autoload :TemplateHandlers, 'action_view/template_handlers'
Expand Down
13 changes: 10 additions & 3 deletions actionpack/lib/action_view/base.rb
Expand Up @@ -182,10 +182,15 @@ 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
# Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed.
# Automaticaly reloading templates are not thread safe and should only be used in development mode.
@@cache_template_loading = false
cattr_accessor :cache_template_loading

def self.cache_template_loading?
ActionController::Base.allow_concurrency || cache_template_loading
end

attr_internal :request

delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
Expand Down Expand Up @@ -226,6 +231,8 @@ def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)

def view_paths=(paths)
@view_paths = self.class.process_view_paths(paths)
# we might be using ReloadableTemplates, so we need to let them know this a new request
@view_paths.load!
end

# Returns the result of a render that's dictated by the options hash. The primary options are:
Expand All @@ -247,8 +254,8 @@ def render(options = {}, local_assigns = {}, &block) #:nodoc:
if options[:layout]
_render_with_layout(options, local_assigns, &block)
elsif options[:file]
template = self.view_paths.find_template(options[:file], template_format)
template.render_template(self, options[:locals])
tempalte = self.view_paths.find_template(options[:file], template_format)

This comment has been minimized.

Copy link
@joshpencheon

joshpencheon Feb 19, 2009

Contributor

Is this a spelling mistake?

Should tempalte be template?

This comment has been minimized.

Copy link
@joshpencheon

joshpencheon Feb 19, 2009

Contributor

If avoiding a conflict with the method template (L288), would it not be better to use a contraction rather than a mis-spelling?

Sorry if I’m missing something!

tempalte.render_template(self, options[:locals])
elsif options[:partial]
render_partial(options)
elsif options[:inline]
Expand Down
1 change: 1 addition & 0 deletions actionpack/lib/action_view/partials.rb
Expand Up @@ -235,5 +235,6 @@ def _pick_partial_template(partial_path) #:nodoc:

self.view_paths.find_template(path, self.template_format)
end
memoize :_pick_partial_template
end
end
23 changes: 15 additions & 8 deletions actionpack/lib/action_view/paths.rb
Expand Up @@ -2,12 +2,16 @@ module ActionView #:nodoc:
class PathSet < Array #:nodoc:
def self.type_cast(obj)
if obj.is_a?(String)
Template::Path.new(obj)
if Base.cache_template_loading?
Template::EagerPath.new(obj.to_s)
else
ReloadableTemplate::ReloadablePath.new(obj.to_s)
end
else
obj
end
end

def initialize(*args)
super(*args).map! { |obj| self.class.type_cast(obj) }
end
Expand All @@ -31,9 +35,14 @@ def push(*objs)
def unshift(*objs)
super(*objs.map { |obj| self.class.type_cast(obj) })
end

def load!
each(&:load!)
end

def find_template(template_path, format = nil)
return template_path if template_path.respond_to?(:render)
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(/^\//, '')

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

if File.exist?(template_path)
return Template.new(template_path, template_path[0] == 47 ? "" : ".")
end
return Template.new(original_template_path, original_template_path =~ /\A\// ? "" : ".") if File.file?(original_template_path)

raise MissingTemplate.new(self, template_path, format)
raise MissingTemplate.new(self, original_template_path, format)
end
end
end
120 changes: 120 additions & 0 deletions actionpack/lib/action_view/reloadable_template.rb
@@ -0,0 +1,120 @@
module ActionView #:nodoc:
class ReloadableTemplate < Template

class TemplateDeleted < ActionView::ActionViewError
end

class ReloadablePath < Template::Path

def initialize(path)
super
@paths = {}
new_request!
end

def new_request!
@disk_cache = {}
end
alias_method :load!, :new_request!

def [](path)
if found_template = @paths[path]
begin
found_template.reset_cache_if_stale!
rescue TemplateDeleted
unregister_template(found_template)
self[path]
end
else
load_all_templates_from_dir(templates_dir_from_path(path))
@paths[path]
end
end

def register_template_from_file(template_file_path)
if !@paths[template_relative_path = template_file_path.split("#{@path}/").last] && File.file?(template_file_path)
register_template(ReloadableTemplate.new(template_relative_path, self))
end
end

def register_template(template)
template.accessible_paths.each do |path|
@paths[path] = template
end
end

# remove (probably deleted) template from cache
def unregister_template(template)
template.accessible_paths.each do |template_path|
@paths.delete(template_path) if @paths[template_path] == template
end
# fill in any newly created gaps
@paths.values.uniq.each do |template|
template.accessible_paths.each {|path| @paths[path] ||= template}
end
end

# load all templates from the directory of the requested template
def load_all_templates_from_dir(dir)
# hit disk only once per template-dir/request
@disk_cache[dir] ||= template_files_from_dir(dir).each {|template_file| register_template_from_file(template_file)}
end

def templates_dir_from_path(path)
dirname = File.dirname(path)
File.join(@path, dirname == '.' ? '' : dirname)
end

# get all the template filenames from the dir
def template_files_from_dir(dir)
Dir.glob(File.join(dir, '*'))
end

end

module Unfreezable
def freeze; self; end
end

def initialize(*args)
super
@compiled_methods = []

# we don't ever want to get frozen
extend Unfreezable
end

def mtime
File.mtime(filename)
end

attr_accessor :previously_last_modified

def stale?
previously_last_modified.nil? || previously_last_modified < mtime
rescue Errno::ENOENT => e
undef_my_compiled_methods!
raise TemplateDeleted
end

def reset_cache_if_stale!
if stale?
flush_cache 'source', 'compiled_source'
undef_my_compiled_methods!
@previously_last_modified = mtime
end
self
end

def undef_my_compiled_methods!
@compiled_methods.each {|comp_method| ActionView::Base::CompiledTemplates.send(:remove_method, comp_method)}
@compiled_methods.clear
end

def compile!(render_symbol, local_assigns)
super
@compiled_methods << render_symbol
end

end
end
30 changes: 3 additions & 27 deletions actionpack/lib/action_view/renderable.rb
Expand Up @@ -16,18 +16,8 @@ def handler
memoize :handler

def compiled_source
@compiled_at = Time.now
handler.call(self)
end
memoize :compiled_source

def compiled_at
@compiled_at
end

def defined_at
@defined_at ||= {}
end

def method_name_without_locals
['_run', extension, method_segment].compact.join('_')
Expand Down Expand Up @@ -71,12 +61,8 @@ def method_name(local_assigns)
def compile(local_assigns)
render_symbol = method_name(local_assigns)

if self.is_a?(InlineTemplate)
if !Base::CompiledTemplates.method_defined?(render_symbol) || recompile?
compile!(render_symbol, local_assigns)
else
if !Base::CompiledTemplates.method_defined?(render_symbol) || recompile?(render_symbol)
recompile!(render_symbol, local_assigns)
end
end
end

Expand All @@ -93,7 +79,6 @@ def #{render_symbol}(local_assigns)

begin
ActionView::Base::CompiledTemplates.module_eval(source, filename, 0)
defined_at[render_symbol] = Time.now if respond_to?(:reloadable?) && reloadable?
rescue Errno::ENOENT => e
raise e # Missing template file, re-raise for Base to rescue
rescue Exception => e # errors from template code
Expand All @@ -107,17 +92,8 @@ def #{render_symbol}(local_assigns)
end
end

def recompile?(render_symbol)
!cached? || redefine?(render_symbol) || stale?
end

def recompile!(render_symbol, local_assigns)
compiled_source(:reload) if compiled_at.nil? || compiled_at < mtime
compile!(render_symbol, local_assigns)
end

def redefine?(render_symbol)
compiled_at && defined_at[render_symbol] && compiled_at > defined_at[render_symbol]
def recompile?
false
end
end
end

4 comments on commit 3942cb4

@openhood
Copy link

Choose a reason for hiding this comment

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

We just have to hope that it work better than when using rails-dev-boost, because the later doesn’t seem to reload models after a git pull, which cause me a headache followed by the quick removal of this plugin.

@thedarkone
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a small clarification to avoid more headaches :). Only faster reloading templates have been ported, not the constants trickery. Also my fork of rails-dev-boost is not compatible with Rails edge, but only with Rails 2.3.0-RC1 and Rails 2.2.

@alloy
Copy link
Contributor

@alloy alloy commented on 3942cb4 Feb 13, 2009

Choose a reason for hiding this comment

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

Don’t hope, try and give feedback :-)

@thedarkone
Copy link
Contributor

Choose a reason for hiding this comment

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

It does look like a typo and was previously fixed by pixeltrix in 893e9eb9950 only to be unintentionally reintroduced back by me (I was automaticaly reverting some of the commits). It should be fixed in the next patch.

Please sign in to comment.