From 3942cb406e1d5db0ac00e03153809cc8dc4cc4db Mon Sep 17 00:00:00 2001 From: thedarkone Date: Thu, 12 Feb 2009 19:35:14 +0100 Subject: Port fast reloadable templates from rails-dev-boost. --- actionpack/lib/action_view/base.rb | 13 ++- actionpack/lib/action_view/partials.rb | 1 + actionpack/lib/action_view/paths.rb | 23 +++-- actionpack/lib/action_view/reloadable_template.rb | 120 ++++++++++++++++++++++ actionpack/lib/action_view/renderable.rb | 30 +----- actionpack/lib/action_view/template.rb | 88 +++++++--------- 6 files changed, 187 insertions(+), 88 deletions(-) create mode 100644 actionpack/lib/action_view/reloadable_template.rb (limited to 'actionpack/lib/action_view') diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 3134807a08..4198725e0d 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -182,10 +182,15 @@ module ActionView #:nodoc: # 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, @@ -226,6 +231,8 @@ module ActionView #:nodoc: 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: @@ -247,8 +254,8 @@ module ActionView #: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) + tempalte.render_template(self, options[:locals]) elsif options[:partial] render_partial(options) elsif options[:inline] diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index 6fe4dbf375..9e5e0f786e 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -235,5 +235,6 @@ module ActionView self.view_paths.find_template(path, self.template_format) end + memoize :_pick_partial_template end end diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index e14b21221c..41f9f486e5 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -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 @@ -31,9 +35,14 @@ module ActionView #:nodoc: 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}"]) @@ -52,11 +61,9 @@ module ActionView #:nodoc: 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 diff --git a/actionpack/lib/action_view/reloadable_template.rb b/actionpack/lib/action_view/reloadable_template.rb new file mode 100644 index 0000000000..3081be60fd --- /dev/null +++ b/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 diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index 16cdd0162e..41080ed629 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -16,18 +16,8 @@ module ActionView 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('_') @@ -71,12 +61,8 @@ module ActionView 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 @@ -93,7 +79,6 @@ module ActionView 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 @@ -107,17 +92,8 @@ module ActionView 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 diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index f2d3998d16..b8e2165ddf 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -6,14 +6,12 @@ module ActionView #:nodoc: def initialize(path) raise ArgumentError, "path already is a Path class" if path.is_a?(Path) - @path = path.freeze + @path = expand_path(path).freeze end - def load! - @paths = {} - templates_in_path do |template| - load_template(template) - end + def expand_path(path) + # collapse any directory dots in path ('.' or '..') + path.starts_with?('/') ? File.expand_path(path) : File.expand_path(path, '/').from(1) end def to_s @@ -46,43 +44,51 @@ module ActionView #:nodoc: # etc. A format must be supplied to match a formated file. +hello/index+ # will never match +hello/index.html.erb+. def [](path) - load! if @paths.nil? - @paths[path] || find_template(path) end - - private - def templates_in_path - (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file| - yield create_template(file) unless File.directory?(file) - end + + def load! + end + + def self.new_and_loaded(path) + returning new(path) do |path| + path.load! end + end + end - def create_template(file) - Template.new(file.split("#{self}/").last, self) - end + class EagerPath < Path + def initialize(path) + super + end - def load_template(template) + def load! + return if @loaded + + @paths = {} + templates_in_path do |template| template.load! template.accessible_paths.each do |path| @paths[path] = template end end + @paths.freeze + @loaded = true + end - def matching_templates(template_path) - Dir.glob("#{@path}/#{template_path}.*").each do |file| + def [](path) + load! unless @loaded + @paths[path] + end + + private + def templates_in_path + (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@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 + def create_template(file) + Template.new(file.split("#{self}/").last, self) end end @@ -171,14 +177,10 @@ module ActionView #:nodoc: @@exempt_from_layout.any? { |exempted| path =~ exempted } end - def mtime - File.mtime(filename) - end - memoize :mtime - def source File.read(filename) end + memoize :source def method_segment relative_path.to_s.gsub(/([^a-zA-Z0-9_])/) { $1.ord } @@ -192,27 +194,13 @@ module ActionView #:nodoc: 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? - reloadable? && (mtime < mtime(:reload)) - end - def load! - reloadable? ? memoize_all : freeze - end - - def reloadable? - !(Base.cache_template_loading || ActionController::Base.allow_concurrency) - end - - def cached? - ActionController::Base.perform_caching || !reloadable? + freeze end private @@ -262,5 +250,5 @@ module ActionView #:nodoc: [base_path, name, locale, format, extension] end - end + end end -- cgit v1.2.3