diff options
Diffstat (limited to 'actionpack/lib/action_view')
23 files changed, 461 insertions, 590 deletions
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 64e0ab575f..fb82443060 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -3,6 +3,12 @@ module ActionView #:nodoc: end class MissingTemplate < ActionViewError #:nodoc: + def initialize(paths, path, template_format = nil) + full_template_path = path.include?('.') ? path : "#{path}.erb" + display_paths = paths.join(':') + template_type = (path =~ /layouts/i) ? 'layout' : 'template' + super("Missing #{template_type} #{full_template_path} in view path #{display_paths}") + end end # Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb @@ -151,7 +157,6 @@ module ActionView #:nodoc: # # See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details. class Base - extend TemplateHandlers include ERB::Util attr_accessor :base_path, :assigns, :template_extension, :first_render @@ -166,7 +171,7 @@ module ActionView #:nodoc: delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB' end - # Specify whether file modification times should be checked to see if a template needs recompilation + # Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed. @@cache_template_loading = false cattr_accessor :cache_template_loading @@ -180,6 +185,10 @@ module ActionView #:nodoc: @@debug_rjs = false cattr_accessor :debug_rjs + # A warning will be displayed whenever an action results in a cache miss on your view paths. + @@warn_cache_misses = false + cattr_accessor :warn_cache_misses + attr_internal :request delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers, @@ -190,12 +199,6 @@ module ActionView #:nodoc: end include CompiledTemplates - # Maps inline templates to their method names - cattr_accessor :method_names - @@method_names = {} - # Map method names to the names passed in local assigns so far - @@template_args = {} - # Cache public asset paths cattr_reader :computed_public_paths @@computed_public_paths = {} @@ -213,6 +216,10 @@ module ActionView #:nodoc: return helpers end + def self.process_view_paths(value) + ActionView::PathSet.new(Array(value)) + end + def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc: @assigns = assigns_for_first_render @assigns_added = nil @@ -223,12 +230,14 @@ module ActionView #:nodoc: attr_reader :view_paths def view_paths=(paths) - @view_paths = ViewLoadPaths.new(Array(paths)) + @view_paths = self.class.process_view_paths(paths) end # Renders the template present at <tt>template_path</tt> (relative to the view_paths array). # The hash in <tt>local_assigns</tt> is made available as local variables. def render(options = {}, local_assigns = {}, &block) #:nodoc: + local_assigns ||= {} + if options.is_a?(String) render_file(options, nil, local_assigns) elsif options == :update @@ -263,13 +272,9 @@ module ActionView #:nodoc: template_path.split('/').last[0,1] != '_' end - # Returns a symbolized version of the <tt>:format</tt> parameter of the request, - # or <tt>:html</tt> by default. - # - # EXCEPTION: If the <tt>:format</tt> parameter is not set, the Accept header will be examined for - # whether it contains the JavaScript mime type as its first priority. If that's the case, - # it will be used. This ensures that Ajax applications can use the same URL to support both - # JavaScript and non-JavaScript users. + # The format to be used when choosing between multiple templates with + # the same name but differing formats. See +Request#template_format+ + # for more details. def template_format return @template_format if @template_format @@ -281,21 +286,50 @@ module ActionView #:nodoc: end def file_exists?(template_path) - view_paths.template_exists?(template_file_from_name(template_path)) + pick_template(template_path) ? true : false + rescue MissingTemplate + false end # Gets the extension for an existing template with the given template_path. # Returns the format with the extension if that template exists. # - # pick_template_extension('users/show') - # # => 'html.erb' + # pick_template('users/show') + # # => 'users/show.html.erb' # - # pick_template_extension('users/legacy') - # # => "rhtml" + # pick_template('users/legacy') + # # => 'users/legacy.rhtml' # - def pick_template_extension(template_path) - if template = template_file_from_name(template_path) - template.extension + def pick_template(template_path) + path = template_path.sub(/^\//, '') + if m = path.match(/(.*)\.(\w+)$/) + template_file_name, template_file_extension = m[1], m[2] + else + template_file_name = path + 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] + template + elsif first_render && template = self.view_paths["#{template_file_name}.#{first_render.extension}"] + template + elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"] + @template_format = :html + template + else + template = Template.new(template_path, view_paths) + + if self.class.warn_cache_misses && logger = ActionController::Base.logger + logger.debug "[PERFORMANCE] Rendering a template that was " + + "not found in view path. Templates outside the view path are " + + "not cached and result in expensive disk operations. Move this " + + "file into #{view_paths.join(':')} or add the folder to your " + + "view path list" + end + + template end end @@ -303,6 +337,10 @@ module ActionView #:nodoc: # Renders the template present at <tt>template_path</tt>. The hash in <tt>local_assigns</tt> # is made available as local variables. def render_file(template_path, use_full_path = nil, local_assigns = {}) #:nodoc: + unless use_full_path == nil + ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller) + end + if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/") raise ActionViewError, <<-END_ERROR Due to changes in ActionMailer, you need to provide the mailer_name along with the template name. @@ -316,11 +354,12 @@ module ActionView #:nodoc: END_ERROR end - Template.new(self, template_path, use_full_path, local_assigns).render_template + template = pick_template(template_path) + template.render_template(self, local_assigns) end def render_inline(text, local_assigns = {}, type = nil) - InlineTemplate.new(self, text, local_assigns, type).render + InlineTemplate.new(text, type).render(self, local_assigns) end def wrap_content_for_layout(content) @@ -343,33 +382,10 @@ module ActionView #:nodoc: @assigns.each { |key, value| instance_variable_set("@#{key}", value) } end - def execute(template) - send(template.method, template.locals) do |*names| + def execute(template, local_assigns = {}) + send(template.method(local_assigns), local_assigns) do |*names| instance_variable_get "@content_for_#{names.first || 'layout'}" end end - - def template_file_from_name(template_name) - template_name = TemplateFile.from_path(template_name) - pick_template(template_name) unless template_name.extension - end - - def pick_template(file) - if f = self.view_paths.find_template_file_for_path(file.dup_with_extension(template_format)) || file_from_first_render(file) - f - elsif template_format == :js && f = self.view_paths.find_template_file_for_path(file.dup_with_extension(:html)) - @template_format = :html - f - else - nil - end - end - - # Determine the template extension from the <tt>@first_render</tt> filename - def file_from_first_render(file) - if extension = File.basename(@first_render.to_s)[/^[^.]+\.(.+)$/, 1] - file.dup_with_extension(extension) - end - end end end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 0122de47af..bf13945844 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -209,6 +209,10 @@ module ActionView # Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to # all subsequently included files. # + # If you want Rails to search in all the subdirectories under javascripts, you should explicitly set <tt>:recursive</tt>: + # + # javascript_include_tag :all, :recursive => true + # # == Caching multiple javascripts into one # # You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be @@ -235,18 +239,23 @@ module ActionView # # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is true => # <script type="text/javascript" src="/javascripts/shop.js"></script> + # + # The <tt>:recursive</tt> option is also available for caching: + # + # javascript_include_tag :all, :cache => true, :recursive => true def javascript_include_tag(*sources) options = sources.extract_options!.stringify_keys cache = options.delete("cache") + recursive = options.delete("recursive") if ActionController::Base.perform_caching && cache joined_javascript_name = (cache == true ? "all" : cache) + ".js" joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name) - write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources)) unless File.exists?(joined_javascript_path) + write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) unless File.exists?(joined_javascript_path) javascript_src_tag(joined_javascript_name, options) else - expand_javascript_sources(sources).collect { |source| javascript_src_tag(source, options) }.join("\n") + expand_javascript_sources(sources, recursive).collect { |source| javascript_src_tag(source, options) }.join("\n") end end @@ -332,13 +341,17 @@ module ActionView # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" /> # <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" /> # - # You can also include all styles in the stylesheet directory using <tt>:all</tt> as the source: + # You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source: # # stylesheet_link_tag :all # => # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" /> # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" /> # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" /> # + # If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>: + # + # stylesheet_link_tag :all, :recursive => true + # # == Caching multiple stylesheets into one # # You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be @@ -362,18 +375,23 @@ module ActionView # # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true => # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" /> + # + # The <tt>:recursive</tt> option is also available for caching: + # + # stylesheet_link_tag :all, :cache => true, :recursive => true def stylesheet_link_tag(*sources) options = sources.extract_options!.stringify_keys cache = options.delete("cache") + recursive = options.delete("recursive") if ActionController::Base.perform_caching && cache joined_stylesheet_name = (cache == true ? "all" : cache) + ".css" joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name) - write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources)) unless File.exists?(joined_stylesheet_path) + write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) unless File.exists?(joined_stylesheet_path) stylesheet_tag(joined_stylesheet_name, options) else - expand_stylesheet_sources(sources).collect { |source| stylesheet_tag(source, options) }.join("\n") + expand_stylesheet_sources(sources, recursive).collect { |source| stylesheet_tag(source, options) }.join("\n") end end @@ -556,18 +574,19 @@ module ActionView tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false) end - def compute_javascript_paths(sources) - expand_javascript_sources(sources).collect { |source| compute_public_path(source, 'javascripts', 'js', false) } + def compute_javascript_paths(*args) + expand_javascript_sources(*args).collect { |source| compute_public_path(source, 'javascripts', 'js', false) } end - def compute_stylesheet_paths(sources) - expand_stylesheet_sources(sources).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) } + def compute_stylesheet_paths(*args) + expand_stylesheet_sources(*args).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) } end - def expand_javascript_sources(sources) + def expand_javascript_sources(sources, recursive = false) if sources.include?(:all) - all_javascript_files = Dir[File.join(JAVASCRIPTS_DIR, '*.js')].collect { |file| File.basename(file).gsub(/\.\w+$/, '') }.sort - @@all_javascript_sources ||= ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq + all_javascript_files = collect_asset_files(JAVASCRIPTS_DIR, ('**' if recursive), '*.js') + @@all_javascript_sources ||= {} + @@all_javascript_sources[recursive] ||= ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq else expanded_sources = sources.collect do |source| determine_source(source, @@javascript_expansions) @@ -577,9 +596,10 @@ module ActionView end end - def expand_stylesheet_sources(sources) + def expand_stylesheet_sources(sources, recursive) if sources.first == :all - @@all_stylesheet_sources ||= Dir[File.join(STYLESHEETS_DIR, '*.css')].collect { |file| File.basename(file).gsub(/\.\w+$/, '') }.sort + @@all_stylesheet_sources ||= {} + @@all_stylesheet_sources[recursive] ||= collect_asset_files(STYLESHEETS_DIR, ('**' if recursive), '*.css') else sources.collect do |source| determine_source(source, @@stylesheet_expansions) @@ -604,6 +624,14 @@ module ActionView FileUtils.mkdir_p(File.dirname(joined_asset_path)) File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) } end + + def collect_asset_files(*path) + dir = path.first + + Dir[File.join(*path.compact)].collect do |file| + file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '') + end.sort + end end end end diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index c2aab5aa72..930c397785 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -32,7 +32,7 @@ module ActionView # <i>Topics listed alphabetically</i> # <% end %> def cache(name = {}, options = nil, &block) - handler = Base.handler_class_for_extension(current_render_extension.to_sym) + handler = Template.handler_class_for_extension(current_render_extension.to_sym) handler.new(@controller).cache_fragment(block, name, options) end end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 990c30b90d..720e2da8cc 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -34,9 +34,8 @@ module ActionView # Return captured buffer in erb. if block_called_from_erb?(block) with_output_buffer { block.call(*args) } - - # Return block result otherwise, but protect buffer also. else + # Return block result otherwise, but protect buffer also. with_output_buffer { return block.call(*args) } end end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 17497481e6..0735ed07ee 100755 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -159,7 +159,10 @@ module ActionView # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified # time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+). # You can include the seconds with <tt>:include_seconds</tt>. - # + # + # This method will also generate 3 input hidden tags, for the actual year, month and day unless the option + # <tt>:ignore_date</tt> is set to +true+. + # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # # ==== Examples @@ -655,7 +658,7 @@ module ActionView order.reverse.each do |param| # Send hidden fields for discarded elements once output has started # This ensures AR can reconstruct valid dates using ParseDate - next if discard[param] && date_or_time_select.empty? + next if discard[param] && (date_or_time_select.empty? || options[:ignore_date]) date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param])), html_options)) date_or_time_select.insert(0, diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 9f6e550c09..576ca84bcc 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -397,7 +397,7 @@ module ActionView # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create', # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); # # return false;" type="button" value="Create" /> - # <%= submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %> + # <%= button_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %> # # # Submit to the remote action update and update the DIV succeed or fail based # # on the success or failure of the request @@ -405,11 +405,13 @@ module ActionView # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'}, # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); # # return false;" type="button" value="Update" /> - # <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' }, + # <%= button_to_remote 'update_btn', 'Update', :url => { :action => 'update' }, # :update => { :success => "succeed", :failure => "fail" } # # <tt>options</tt> argument is the same as in form_remote_tag. - def submit_to_remote(name, value, options = {}) + # + # Note: This method used to be called submit_to_remote, but that's now just an alias for button_to_remote + def button_to_remote(name, value, options = {}) options[:with] ||= 'Form.serialize(this.form)' options[:html] ||= {} @@ -420,6 +422,7 @@ module ActionView tag("input", options[:html], false) end + alias_method :submit_to_remote, :button_to_remote # Returns '<tt>eval(request.responseText)</tt>' which is the JavaScript function # that +form_remote_tag+ can call in <tt>:complete</tt> to evaluate a multiple diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index a6c48737e9..3e3452b615 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -27,7 +27,7 @@ module ActionView # %> def concat(string, unused_binding = nil) if unused_binding - ActiveSupport::Deprecation.warn("The binding argument of #concat is no longer needed. Please remove it from your views and helpers.") + ActiveSupport::Deprecation.warn("The binding argument of #concat is no longer needed. Please remove it from your views and helpers.", caller) end output_buffer << string diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 89166fe19a..94e1f1d33a 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -63,17 +63,15 @@ module ActionView # # calls @workshop.to_s # # => /workshops/5 def url_for(options = {}) + options ||= {} case options when Hash - show_path = options[:host].nil? ? true : false - options = { :only_path => show_path }.update(options.symbolize_keys) + options = { :only_path => options[:host].nil? }.update(options.symbolize_keys) escape = options.key?(:escape) ? options.delete(:escape) : true url = @controller.send(:url_for, options) when String escape = true url = options - when NilClass - url = @controller.send(:url_for, nil) else escape = false url = polymorphic_path(options) @@ -468,7 +466,7 @@ module ActionView email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot") if encode == "javascript" - "document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c| + "document.write('#{content_tag("a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c| string << sprintf("%%%x", c) end "<script type=\"#{Mime::JS}\">eval(unescape('#{string}'))</script>" diff --git a/actionpack/lib/action_view/inline_template.rb b/actionpack/lib/action_view/inline_template.rb index 19ab92ce1a..5e00cef13f 100644 --- a/actionpack/lib/action_view/inline_template.rb +++ b/actionpack/lib/action_view/inline_template.rb @@ -1,16 +1,19 @@ module ActionView #:nodoc: class InlineTemplate #:nodoc: - include Renderer + include Renderable - def initialize(view, source, locals = {}, type = nil) - @view = view + attr_reader :source, :extension, :method_segment + def initialize(source, type = nil) @source = source @extension = type - @locals = locals || {} - - @method_key = @source - @handler = Base.handler_class_for_extension(@extension).new(@view) + @method_segment = "inline_#{@source.hash.abs}" end + + private + # Always recompile inline templates + def recompile?(local_assigns) + true + end end end diff --git a/actionpack/lib/action_view/partial_template.rb b/actionpack/lib/action_view/partial_template.rb deleted file mode 100644 index 72f831e937..0000000000 --- a/actionpack/lib/action_view/partial_template.rb +++ /dev/null @@ -1,69 +0,0 @@ -module ActionView #:nodoc: - class PartialTemplate < Template #:nodoc: - attr_reader :variable_name, :object, :as - - def initialize(view, partial_path, object = nil, locals = {}, as = nil) - @view_controller = view.controller if view.respond_to?(:controller) - @as = as - set_path_and_variable_name!(partial_path) - super(view, @path, nil, locals) - add_object_to_local_assigns!(object) - - # This is needed here in order to compile template with knowledge of 'counter' - initialize_counter! - - # Prepare early. This is a performance optimization for partial collections - prepare! - end - - def render - ActionController::Base.benchmark("Rendered #{@path.path_without_format_and_extension}", Logger::DEBUG, false) do - super - end - end - - def render_member(object) - @locals[:object] = @locals[@variable_name] = object - @locals[as] = object if as - - template = render_template - @locals[@counter_name] += 1 - @locals.delete(as) - @locals.delete(@variable_name) - @locals.delete(:object) - - template - end - - def counter=(num) - @locals[@counter_name] = num - end - - private - def add_object_to_local_assigns!(object) - @locals[:object] ||= - @locals[@variable_name] ||= object || @view_controller.instance_variable_get("@#{variable_name}") - @locals[as] ||= @locals[:object] if as - end - - def set_path_and_variable_name!(partial_path) - if partial_path.include?('/') - @variable_name = File.basename(partial_path) - @path = "#{File.dirname(partial_path)}/_#{@variable_name}" - elsif @view_controller - @variable_name = partial_path - @path = "#{@view_controller.class.controller_path}/_#{@variable_name}" - else - @variable_name = partial_path - @path = "_#{@variable_name}" - end - - @variable_name = @variable_name.sub(/\..*$/, '').to_sym - end - - def initialize_counter! - @counter_name ||= "#{@variable_name}_counter".to_sym - @locals[@counter_name] = 0 - end - end -end diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index 7c6c98d611..116d61e13b 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -104,10 +104,12 @@ module ActionView module Partials private def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc: + local_assigns ||= {} + case partial_path when String, Symbol, NilClass - # Render the template - ActionView::PartialTemplate.new(self, partial_path, object_assigns, local_assigns).render_template + variable_name, path = partial_pieces(partial_path) + pick_template(path).render_partial(self, variable_name, object_assigns, local_assigns) when ActionView::Helpers::FormBuilder builder_partial_path = partial_path.class.to_s.demodulize.underscore.sub(/_builder$/, '') render_partial(builder_partial_path, object_assigns, (local_assigns || {}).merge(builder_partial_path.to_sym => partial_path)) @@ -128,31 +130,43 @@ module ActionView local_assigns = local_assigns ? local_assigns.clone : {} spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : '' + _partial_pieces = {} + _templates = {} - if partial_path.nil? - render_partial_collection_with_unknown_partial_path(collection, local_assigns, as) - else - render_partial_collection_with_known_partial_path(collection, partial_path, local_assigns, as) - end.join(spacer) - end + index = 0 + collection.map do |object| + _partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path) + variable_name, path = _partial_pieces[_partial_path] ||= partial_pieces(_partial_path) + template = _templates[path] ||= pick_template(path) - def render_partial_collection_with_known_partial_path(collection, partial_path, local_assigns, as) - template = ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns, as) - collection.map do |element| - template.render_member(element) - end + local_assigns["#{variable_name}_counter".to_sym] = index + local_assigns[:object] = local_assigns[variable_name] = object + local_assigns[as] = object if as + + result = template.render_partial(self, variable_name, object, local_assigns) + + local_assigns.delete(as) + local_assigns.delete(variable_name) + local_assigns.delete(:object) + index += 1 + + result + end.join(spacer) end - def render_partial_collection_with_unknown_partial_path(collection, local_assigns, as) - templates = Hash.new - i = 0 - collection.map do |element| - partial_path = ActionController::RecordIdentifier.partial_path(element, controller.class.controller_path) - template = templates[partial_path] ||= ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns, as) - template.counter = i - i += 1 - template.render_member(element) + def partial_pieces(partial_path) + if partial_path.include?('/') + variable_name = File.basename(partial_path) + path = "#{File.dirname(partial_path)}/_#{variable_name}" + elsif respond_to?(:controller) + variable_name = partial_path + path = "#{controller.class.controller_path}/_#{variable_name}" + else + variable_name = partial_path + path = "_#{variable_name}" end + variable_name = variable_name.sub(/\..*$/, '').to_sym + return variable_name, path end end end diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb new file mode 100644 index 0000000000..b0ab7d0c67 --- /dev/null +++ b/actionpack/lib/action_view/paths.rb @@ -0,0 +1,96 @@ +module ActionView #:nodoc: + class PathSet < Array #:nodoc: + def self.type_cast(obj) + if obj.is_a?(String) + if Base.warn_cache_misses && defined?(Rails) && Rails.initialized? + Rails.logger.debug "[PERFORMANCE] Processing view path during a " + + "request. This an expense disk operation that should be done at " + + "boot. You can manually process this view path with " + + "ActionView::Base.process_view_paths(#{obj.inspect}) and set it " + + "as your view path" + end + Path.new(obj) + else + obj + end + end + + class Path #:nodoc: + attr_reader :path, :paths + delegate :to_s, :to_str, :inspect, :to => :path + + def initialize(path) + @path = path.freeze + reload! + end + + def ==(path) + to_str == path.to_str + end + + def [](path) + @paths[path] + end + + # Rebuild load path directory cache + def reload! + @paths = {} + + templates_in_path do |template| + @paths[template.path] = template + @paths[template.path_without_extension] ||= template + end + + @paths.freeze + end + + private + def templates_in_path + (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file| + unless File.directory?(file) + template = Template.new(file.split("#{self}/").last, self) + # Eager load memoized methods and freeze cached template + template.freeze if Base.cache_template_loading + yield template + end + end + end + end + + def initialize(*args) + super(*args).map! { |obj| self.class.type_cast(obj) } + end + + def reload! + each { |path| path.reload! } + end + + def <<(obj) + super(self.class.type_cast(obj)) + end + + def push(*objs) + delete_paths!(objs) + super(*objs.map { |obj| self.class.type_cast(obj) }) + end + + def unshift(*objs) + delete_paths!(objs) + super(*objs.map { |obj| self.class.type_cast(obj) }) + end + + def [](template_path) + each do |path| + if template = path[template_path] + return template + end + end + nil + end + + private + def delete_paths!(paths) + paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } } + end + end +end diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb new file mode 100644 index 0000000000..2c4302146f --- /dev/null +++ b/actionpack/lib/action_view/renderable.rb @@ -0,0 +1,79 @@ +module ActionView + module Renderable + # NOTE: The template that this mixin is beening include into is frozen + # So you can not set or modify any instance variables + + def self.included(base) + @@mutex = Mutex.new + end + + # NOTE: Exception to earlier notice. Ensure this is called before freeze + def handler + @handler ||= Template.handler_class_for_extension(extension) + end + + # NOTE: Exception to earlier notice. Ensure this is called before freeze + def compiled_source + @compiled_source ||= handler.new(nil).compile(self) if handler.compilable? + end + + def render(view, local_assigns = {}) + view.first_render ||= self + view.send(:evaluate_assigns) + view.current_render_extension = extension + compile(local_assigns) if handler.compilable? + handler.new(view).render(self, local_assigns) + end + + def method(local_assigns) + if local_assigns && local_assigns.any? + local_assigns_keys = "locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}" + end + ['_run', extension, method_segment, local_assigns_keys].compact.join('_').to_sym + end + + private + # Compile and evaluate the template's code + def compile(local_assigns) + render_symbol = method(local_assigns) + + @@mutex.synchronize do + return false unless recompile?(render_symbol) + + locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join + + source = <<-end_src + def #{render_symbol}(local_assigns) + old_output_buffer = output_buffer;#{locals_code};#{compiled_source} + ensure + self.output_buffer = old_output_buffer + end + end_src + + begin + file_name = respond_to?(:filename) ? filename : 'compiled-template' + ActionView::Base::CompiledTemplates.module_eval(source, file_name, 0) + rescue Exception => e # errors from template code + if logger = ActionController::Base.logger + logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" + logger.debug "Function body: #{source}" + logger.debug "Backtrace: #{e.backtrace.join("\n")}" + end + + raise ActionView::TemplateError.new(self, {}, e) + end + end + end + + # Method to check whether template compilation is necessary. + # 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) + unless Base::CompiledTemplates.instance_methods.include?(symbol) && Base.cache_template_loading + true + else + false + end + end + end +end diff --git a/actionpack/lib/action_view/renderable_partial.rb b/actionpack/lib/action_view/renderable_partial.rb new file mode 100644 index 0000000000..6a17b50a14 --- /dev/null +++ b/actionpack/lib/action_view/renderable_partial.rb @@ -0,0 +1,19 @@ +module ActionView + module RenderablePartial + # NOTE: The template that this mixin is beening include into is frozen + # So you can not set or modify any instance variables + + def render(view, local_assigns = {}) + ActionController::Base.benchmark("Rendered #{path_without_format_and_extension}", Logger::DEBUG, false) do + super + end + end + + def render_partial(view, variable_name, object = nil, local_assigns = {}, as = nil) + object ||= view.controller.instance_variable_get("@#{variable_name}") if view.respond_to?(:controller) + local_assigns[:object] ||= local_assigns[variable_name] ||= object + local_assigns[as] ||= local_assigns[:object] if as + render_template(view, local_assigns) + end + end +end diff --git a/actionpack/lib/action_view/renderer.rb b/actionpack/lib/action_view/renderer.rb deleted file mode 100644 index e6c64d2749..0000000000 --- a/actionpack/lib/action_view/renderer.rb +++ /dev/null @@ -1,29 +0,0 @@ -module ActionView - module Renderer - # TODO: Local assigns should not be tied to template instance - attr_accessor :locals - - # TODO: These readers should be private - attr_reader :filename, :source, :handler, :method_key, :method - - def render - prepare! - @handler.render(self) - end - - private - def prepare! - unless @prepared - @view.send(:evaluate_assigns) - @view.current_render_extension = @extension - - if @handler.compilable? - @handler.compile_template(self) # compile the given template, if necessary - @method = @view.method_names[method_key] # Set the method name for this template and run it - end - - @prepared = true - end - end - end -end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 8142232c8f..03f9234289 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -1,79 +1,112 @@ module ActionView #:nodoc: - class Template #:nodoc: - include Renderer + class Template + extend TemplateHandlers + include Renderable - class << self - # TODO: Deprecate - delegate :register_template_handler, :to => 'ActionView::Base' + attr_accessor :filename, :load_path, :base_path, :name, :format, :extension + delegate :to_s, :to => :path + + def initialize(template_path, load_paths = []) + template_path = template_path.dup + @base_path, @name, @format, @extension = split(template_path) + @base_path.to_s.gsub!(/\/$/, '') # Push to split method + @load_path, @filename = find_full_path(template_path, load_paths) + + # Extend with partial super powers + extend RenderablePartial if @name =~ /^_/ end - attr_reader :path, :extension + def freeze + # Eager load memoized methods + format_and_extension + path + path_without_extension + path_without_format_and_extension + source + method_segment - def initialize(view, path, use_full_path = nil, locals = {}) - unless use_full_path == nil - ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller) - end + # Eager load memoized methods from Renderable + handler + compiled_source - @view = view - @paths = view.view_paths + instance_variables.each { |ivar| ivar.freeze } - @original_path = path - @path = TemplateFile.from_path(path) - @view.first_render ||= @path.to_s + super + end - set_extension_and_file_name + def format_and_extension + @format_and_extension ||= (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions + end + + def path + @path ||= [base_path, [name, format, extension].compact.join('.')].compact.join('/') + end - @method_key = @filename - @locals = locals || {} - @handler = Base.handler_class_for_extension(@extension).new(@view) + def path_without_extension + @path_without_extension ||= [base_path, [name, format].compact.join('.')].compact.join('/') end - def render_template - render + def path_without_format_and_extension + @path_without_format_and_extension ||= [base_path, name].compact.join('/') + end + + def source + @source ||= File.read(@filename) + end + + def method_segment + unless @method_segment + segment = File.expand_path(@filename) + segment.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT) + segment.gsub!(/([^a-zA-Z0-9_])/) { $1.ord } + @method_segment = segment + end + + @method_segment + end + + def render_template(view, local_assigns = {}) + render(view, local_assigns) rescue Exception => e raise e unless filename if TemplateError === e e.sub_template_of(filename) raise e else - raise TemplateError.new(self, @view.assigns, e) + raise TemplateError.new(self, view.assigns, e) end end - def source - @source ||= File.read(self.filename) - end - - def base_path_for_exception - (@paths.find_load_path_for_path(@path) || @paths.first).to_s - end - private - def set_extension_and_file_name - @extension = @path.extension - - unless @extension - @path = @view.send(:template_file_from_name, @path) - raise_missing_template_exception unless @path - @extension = @path.extension - end + def valid_extension?(extension) + Template.template_handler_extensions.include?(extension) + end - if p = @paths.find_template_file_for_path(path) - @path = p - @filename = @path.full_path - @extension = @path.extension - raise_missing_template_exception if @filename.blank? - else - @filename = @original_path - raise_missing_template_exception unless File.exist?(@filename) + def find_full_path(path, load_paths) + load_paths = Array(load_paths) + [nil] + load_paths.each do |load_path| + file = [load_path, path].compact.join('/') + return load_path, file if File.exist?(file) end + raise MissingTemplate.new(load_paths, path) end - def raise_missing_template_exception - full_template_path = @original_path.include?('.') ? @original_path : "#{@original_path}.#{@view.template_format}.erb" - display_paths = @paths.join(':') - template_type = (@original_path =~ /layouts/i) ? 'layout' : 'template' - raise MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}" + # Returns file split into an array + # [base_path, name, format, extension] + def split(file) + if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/) + if m[5] # Mulipart formats + [m[1], m[2], "#{m[3]}.#{m[4]}", m[5]] + elsif m[4] # Single format + [m[1], m[2], m[3], m[4]] + else + if valid_extension?(m[3]) # No format + [m[1], m[2], nil, m[3]] + else # No extension + [m[1], m[2], m[3], nil] + end + end + end end end end diff --git a/actionpack/lib/action_view/template_error.rb b/actionpack/lib/action_view/template_error.rb index 65d80362b5..35fc07bdb2 100644 --- a/actionpack/lib/action_view/template_error.rb +++ b/actionpack/lib/action_view/template_error.rb @@ -7,7 +7,7 @@ module ActionView attr_reader :original_exception def initialize(template, assigns, original_exception) - @base_path = template.base_path_for_exception + @base_path = template.base_path @assigns, @source, @original_exception = assigns.dup, template.source, original_exception @file_path = template.filename @backtrace = compute_backtrace @@ -105,6 +105,6 @@ module ActionView end if defined?(Exception::TraceSubstitutions) - Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, ''] + Exception::TraceSubstitutions << [/:in\s+`_run_.*'\s*$/, ''] Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}/}, ''] if defined?(RAILS_ROOT) end diff --git a/actionpack/lib/action_view/template_file.rb b/actionpack/lib/action_view/template_file.rb deleted file mode 100644 index c38e8ed122..0000000000 --- a/actionpack/lib/action_view/template_file.rb +++ /dev/null @@ -1,88 +0,0 @@ -module ActionView #:nodoc: - # TemplateFile abstracts the pattern of querying a file path for its - # path with or without its extension. The path is only the partial path - # from the load path root e.g. "hello/index.html.erb" not - # "app/views/hello/index.html.erb" - class TemplateFile - def self.from_path(path) - path.is_a?(self) ? path : new(path) - end - - def self.from_full_path(load_path, full_path) - file = new(full_path.split(load_path).last) - file.load_path = load_path - file.freeze - end - - attr_accessor :load_path, :base_path, :name, :format, :extension - delegate :to_s, :inspect, :to => :path - - def initialize(path) - path = path.dup - - # Clear the forward slash in the beginning - trim_forward_slash!(path) - - @base_path, @name, @format, @extension = split(path) - end - - def freeze - @load_path.freeze - @base_path.freeze - @name.freeze - @format.freeze - @extension.freeze - super - end - - def format_and_extension - extensions = [format, extension].compact.join(".") - extensions.blank? ? nil : extensions - end - - def full_path - if load_path - "#{load_path}/#{path}" - else - path - end - end - - def path - base_path.to_s + [name, format, extension].compact.join(".") - end - - def path_without_extension - base_path.to_s + [name, format].compact.join(".") - end - - def path_without_format_and_extension - "#{base_path}#{name}" - end - - def dup_with_extension(extension) - file = dup - file.extension = extension ? extension.to_s : nil - file - end - - private - def trim_forward_slash!(path) - path.sub!(/^\//, '') - end - - # Returns file split into an array - # [base_path, name, format, extension] - def split(file) - if m = file.match(/^(.*\/)?(\w+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/) - if m[5] # Mulipart formats - [m[1], m[2], "#{m[3]}.#{m[4]}", m[5]] - elsif m[4] # Single format - [m[1], m[2], m[3], m[4]] - else # No format - [m[1], m[2], nil, m[3]] - end - end - end - end -end diff --git a/actionpack/lib/action_view/template_handler.rb b/actionpack/lib/action_view/template_handler.rb index 39e578e586..1afea21f67 100644 --- a/actionpack/lib/action_view/template_handler.rb +++ b/actionpack/lib/action_view/template_handler.rb @@ -1,9 +1,5 @@ module ActionView class TemplateHandler - def self.line_offset - 0 - end - def self.compilable? false end @@ -12,7 +8,7 @@ module ActionView @view = view end - def render(template) + def render(template, local_assigns = {}) end def compile(template) @@ -22,10 +18,6 @@ module ActionView self.class.compilable? end - def line_offset - self.class.line_offset - end - # Called by CacheHelper#cache def cache_fragment(block, name = {}, options = nil) end diff --git a/actionpack/lib/action_view/template_handlers/builder.rb b/actionpack/lib/action_view/template_handlers/builder.rb index ee02ce1a6f..cbe53e11d8 100644 --- a/actionpack/lib/action_view/template_handlers/builder.rb +++ b/actionpack/lib/action_view/template_handlers/builder.rb @@ -5,17 +5,12 @@ module ActionView class Builder < TemplateHandler include Compilable - def self.line_offset - 2 - end - def compile(template) - content_type_handler = (@view.send!(:controller).respond_to?(:response) ? "controller.response" : "controller") - - "#{content_type_handler}.content_type ||= Mime::XML\n" + - "xml = ::Builder::XmlMarkup.new(:indent => 2)\n" + + # ActionMailer does not have a response + "controller.respond_to?(:response) && controller.response.content_type ||= Mime::XML;" + + "xml = ::Builder::XmlMarkup.new(:indent => 2);" + template.source + - "\nxml.target!\n" + ";xml.target!;" end def cache_fragment(block, name = {}, options = nil) diff --git a/actionpack/lib/action_view/template_handlers/compilable.rb b/actionpack/lib/action_view/template_handlers/compilable.rb index f436ebbe45..a0ebaefeef 100644 --- a/actionpack/lib/action_view/template_handlers/compilable.rb +++ b/actionpack/lib/action_view/template_handlers/compilable.rb @@ -1,21 +1,8 @@ module ActionView module TemplateHandlers module Compilable - def self.included(base) base.extend ClassMethod - - # Map method names to their compile time - base.cattr_accessor :compile_time - base.compile_time = {} - - # Map method names to the names passed in local assigns so far - base.cattr_accessor :template_args - base.template_args = {} - - # Count the number of inline templates - base.cattr_accessor :inline_template_count - base.inline_template_count = 0 end module ClassMethod @@ -24,111 +11,10 @@ module ActionView true end end - - def render(template) - @view.send :execute, template - end - - # Compile and evaluate the template's code - def compile_template(template) - return unless compile_template?(template) - - render_symbol = assign_method_name(template) - render_source = create_template_source(template, render_symbol) - line_offset = self.template_args[render_symbol].size + self.line_offset - - begin - file_name = template.filename || 'compiled-template' - ActionView::Base::CompiledTemplates.module_eval(render_source, file_name, -line_offset) - rescue Exception => e # errors from template code - if Base.logger - Base.logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" - Base.logger.debug "Function body: #{render_source}" - Base.logger.debug "Backtrace: #{e.backtrace.join("\n")}" - end - - raise ActionView::TemplateError.new(template, @view.assigns, e) - end - - self.compile_time[render_symbol] = Time.now - # logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger - end - - private - - # Method to check whether template compilation is necessary. - # The template will be compiled if the inline template or file has not been compiled yet, - # if local_assigns has a new key, which isn't supported by the compiled code yet, - # or if the file has changed on disk and checking file mods hasn't been disabled. - def compile_template?(template) - method_key = template.method_key - render_symbol = @view.method_names[method_key] - - compile_time = self.compile_time[render_symbol] - if compile_time && supports_local_assigns?(render_symbol, template.locals) - if template.filename && !@view.cache_template_loading - template_changed_since?(template.filename, compile_time) - end - else - true - end - end - - def assign_method_name(template) - @view.method_names[template.method_key] ||= compiled_method_name(template) - end - def compiled_method_name(template) - ['_run', self.class.to_s.demodulize.underscore, compiled_method_name_file_path_segment(template.filename)].compact.join('_').to_sym + def render(template, local_assigns = {}) + @view.send(:execute, template, local_assigns) end - - def compiled_method_name_file_path_segment(file_name) - if file_name - s = File.expand_path(file_name) - s.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT) - s.gsub!(/([^a-zA-Z0-9_])/) { $1.ord } - s - else - (self.inline_template_count += 1).to_s - end - end - - # Method to create the source code for a given template. - def create_template_source(template, render_symbol) - body = compile(template) - - self.template_args[render_symbol] ||= {} - locals_keys = self.template_args[render_symbol].keys | template.locals.keys - self.template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h } - - locals_code = "" - locals_keys.each do |key| - locals_code << "#{key} = local_assigns[:#{key}]\n" - end - - <<-end_src - def #{render_symbol}(local_assigns) - old_output_buffer = output_buffer;#{locals_code}#{body} - ensure - self.output_buffer = old_output_buffer - end - end_src - end - - # Return true if the given template was compiled for a superset of the keys in local_assigns - def supports_local_assigns?(render_symbol, local_assigns) - local_assigns.empty? || - ((args = self.template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) }) - end - - # Method to handle checking a whether a template has changed since last compile; isolated so that templates - # not stored on the file system can hook and extend appropriately. - def template_changed_since?(file_name, compile_time) - lstat = File.lstat(file_name) - compile_time < lstat.mtime || - (lstat.symlink? && compile_time < File.stat(file_name).mtime) - end - end end end diff --git a/actionpack/lib/action_view/template_handlers/rjs.rb b/actionpack/lib/action_view/template_handlers/rjs.rb index 5854e33fed..3892bf1d6e 100644 --- a/actionpack/lib/action_view/template_handlers/rjs.rb +++ b/actionpack/lib/action_view/template_handlers/rjs.rb @@ -3,13 +3,9 @@ module ActionView class RJS < TemplateHandler include Compilable - def self.line_offset - 2 - end - def compile(template) - "controller.response.content_type ||= Mime::JS\n" + - "update_page do |page|\n#{template.source}\nend" + "controller.response.content_type ||= Mime::JS;" + + "update_page do |page|;#{template.source}\nend" end def cache_fragment(block, name = {}, options = nil) #:nodoc: diff --git a/actionpack/lib/action_view/view_load_paths.rb b/actionpack/lib/action_view/view_load_paths.rb deleted file mode 100644 index 6e439a009c..0000000000 --- a/actionpack/lib/action_view/view_load_paths.rb +++ /dev/null @@ -1,103 +0,0 @@ -module ActionView #:nodoc: - class ViewLoadPaths < Array #:nodoc: - def self.type_cast(obj) - obj.is_a?(String) ? LoadPath.new(obj) : obj - end - - class LoadPath #:nodoc: - attr_reader :path, :paths - delegate :to_s, :to_str, :inspect, :to => :path - - def initialize(path) - @path = path.freeze - reload! - end - - def ==(path) - to_str == path.to_str - end - - # Rebuild load path directory cache - def reload! - @paths = {} - - files.each do |file| - @paths[file.path] = file - @paths[file.path_without_extension] ||= file - end - - @paths.freeze - end - - def find_template_file_for_partial_path(template_path, template_format) - @paths["#{template_path}.#{template_format}"] || - @paths[template_path] || - @paths[template_path.gsub(/\..*$/, '')] - end - - private - # Get all the files and directories in the path - def files_in_path - Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**") - end - - # Create an array of all the files within the path - def files - files_in_path.map do |file| - TemplateFile.from_full_path(@path, file) unless File.directory?(file) - end.compact - end - end - - def initialize(*args) - super(*args).map! { |obj| self.class.type_cast(obj) } - end - - def reload! - each { |path| path.reload! } - end - - def <<(obj) - super(self.class.type_cast(obj)) - end - - def push(*objs) - delete_paths!(objs) - super(*objs.map { |obj| self.class.type_cast(obj) }) - end - - def unshift(*objs) - delete_paths!(objs) - super(*objs.map { |obj| self.class.type_cast(obj) }) - end - - def template_exists?(file) - find_load_path_for_path(file) ? true : false - end - - def find_load_path_for_path(file) - find { |path| path.paths[file.to_s] } - end - - def find_template_file_for_path(template_path) - template_path_without_extension, template_extension = path_and_extension(template_path.to_s) - each do |path| - if f = path.find_template_file_for_partial_path(template_path_without_extension, template_extension) - return f - end - end - nil - end - - private - def delete_paths!(paths) - paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } } - end - - # Splits the path and extension from the given template_path and returns as an array. - def path_and_extension(template_path) - template_path_without_extension = template_path.sub(/\.(\w+)$/, '') - [template_path_without_extension, $1] - end - end -end |
