aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_view')
-rw-r--r--actionpack/lib/action_view/base.rb11
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb30
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb107
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb20
-rw-r--r--actionpack/lib/action_view/paths.rb18
-rw-r--r--actionpack/lib/action_view/reloadable_template.rb120
-rw-r--r--actionpack/lib/action_view/renderable.rb5
-rw-r--r--actionpack/lib/action_view/template.rb103
10 files changed, 329 insertions, 89 deletions
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 70a0ba91a7..4198725e0d 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -182,6 +182,15 @@ module ActionView #:nodoc:
# that alert()s the caught exception (and then re-raises it).
cattr_accessor :debug_rjs
+ # 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,
@@ -222,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:
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index b4c1adbe76..b7ef1fb90d 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -931,7 +931,7 @@ module ActionView
end
def default_datetime(options)
- return if options[:include_blank]
+ return if options[:include_blank] || options[:prompt]
case options[:default]
when nil
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 37400bdbf7..4fef2b443e 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -755,7 +755,9 @@ module ActionView
end
options["checked"] = "checked" if checked
add_default_name_and_id(options)
- tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
+ hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
+ checkbox = tag("input", options)
+ hidden + checkbox
end
def to_boolean_select_tag(options = {})
@@ -964,24 +966,34 @@ module ActionView
def fields_for_with_nested_attributes(association_name, args, block)
name = "#{object_name}[#{association_name}_attributes]"
association = @object.send(association_name)
+ explicit_object = args.first if args.first.respond_to?(:new_record?)
if association.is_a?(Array)
- children = args.first.respond_to?(:new_record?) ? [args.first] : association
+ children = explicit_object ? [explicit_object] : association
+ explicit_child_index = args.last[:child_index] if args.last.is_a?(Hash)
children.map do |child|
- child_name = "#{name}[#{ child.new_record? ? new_child_id : child.id }]"
- @template.fields_for(child_name, child, *args, &block)
+ fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index}]", child, args, block)
end.join
else
- object = args.first.respond_to?(:new_record?) ? args.first : association
+ fields_for_nested_model(name, explicit_object || association, args, block)
+ end
+ end
+
+ def fields_for_nested_model(name, object, args, block)
+ if object.new_record?
@template.fields_for(name, object, *args, &block)
+ else
+ @template.fields_for(name, object, *args) do |builder|
+ @template.concat builder.hidden_field(:id)
+ block.call(builder)
+ end
end
end
- def new_child_id
- value = (@child_counter ||= 1)
- @child_counter += 1
- "new_#{value}"
+ def nested_child_index
+ @nested_child_index ||= -1
+ @nested_child_index += 1
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 54c82cbd1d..6b385ef77d 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -6,9 +6,7 @@ module ActionView
module Helpers
# Provides a number of methods for turning different kinds of containers into a set of option tags.
# == Options
- # The <tt>collection_select</tt>, <tt>country_select</tt>, <tt>select</tt>,
- # and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter,
- # a hash.
+ # The <tt>collection_select</tt>, <tt>select</tt> and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter, a hash:
#
# * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
#
@@ -28,7 +26,7 @@ module ActionView
#
# Example with @post.person_id => 2:
#
- # select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, {:include_blank => 'None'})
+ # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:include_blank => 'None'})
#
# could become:
#
@@ -43,7 +41,7 @@ module ActionView
#
# Example:
#
- # select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, {:prompt => 'Select Person'})
+ # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {:prompt => 'Select Person'})
#
# could become:
#
@@ -68,6 +66,36 @@ module ActionView
# <option value="rock">rock</option>
# <option value="country">country</option>
# </select>
+ #
+ # * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
+ #
+ # Example:
+ #
+ # select("post", "category", Post::CATEGORIES, {:disabled => 'restricted'})
+ #
+ # could become:
+ #
+ # <select name="post[category]">
+ # <option></option>
+ # <option>joke</option>
+ # <option>poem</option>
+ # <option disabled="disabled">restricted</option>
+ # </select>
+ #
+ # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
+ #
+ # Example:
+ #
+ # collection_select(:post, :category_id, Category.all, :id, :name, {:disabled => lambda{|category| category.archived? }})
+ #
+ # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
+ # <select name="post[category_id]">
+ # <option value="1" disabled="disabled">2008 stuff</option>
+ # <option value="2" disabled="disabled">Christmas</option>
+ # <option value="3">Jokes</option>
+ # <option value="4">Poems</option>
+ # </select>
+ #
module FormOptionsHelper
include ERB::Util
@@ -76,7 +104,7 @@ module ActionView
# See options_for_select for the required format of the choices parameter.
#
# Example with @post.person_id => 1:
- # select("post", "person_id", Person.find(:all).collect {|p| [ p.name, p.id ] }, { :include_blank => true })
+ # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
#
# could become:
#
@@ -94,7 +122,8 @@ module ActionView
# In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
#
# By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
- # or <tt>:selected => nil</tt> to leave all options unselected.
+ # or <tt>:selected => nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
+ # tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
def select(object, method, choices, options = {}, html_options = {})
InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
end
@@ -120,7 +149,7 @@ module ActionView
# end
#
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
- # collection_select(:post, :author_id, Author.find(:all), :id, :name_with_initial, {:prompt => true})
+ # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {:prompt => true})
#
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
# <select name="post[author_id]">
@@ -186,14 +215,29 @@ module ActionView
# options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
# <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
#
+ # If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
+ # or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
+ #
+ # Examples:
+ # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => "Super Platinum")
+ # <option value="Free">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ #
+ # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => ["Advanced", "Super Platinum"])
+ # <option value="Free">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced" disabled="disabled">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ #
+ # options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :selected => "Free", :disabled => "Super Platinum")
+ # <option value="Free" selected="selected">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ #
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
def options_for_select(container, selected = nil)
container = container.to_a if Hash === container
+ selected, disabled = extract_selected_and_disabled(selected)
options_for_select = container.inject([]) do |options, element|
text, value = option_text_and_value(element)
selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
- options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}>#{html_escape(text.to_s)}</option>)
+ disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
+ options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}#{disabled_attribute}>#{html_escape(text.to_s)}</option>)
end
options_for_select.join("\n")
@@ -209,8 +253,15 @@ module ActionView
# This is more often than not used inside a #select_tag like this example:
# select_tag 'person', options_from_collection_for_select(@people, 'id', 'name')
#
- # If +selected+ is specified, the element returning a match on +value_method+ will get the selected option tag.
- # Be sure to specify the same class as the +value_method+ when specifying a selected option.
+ # If +selected+ is specified as a value or array of values, the element(s) returning a match on +value_method+
+ # will be selected option tag(s).
+ #
+ # If +selected+ is specified as a Proc, those members of the collection that return true for the anonymous
+ # function are the selected values.
+ #
+ # +selected+ can also be a hash, specifying both <tt>:selected</tt> and/or <tt>:disabled</tt> values as required.
+ #
+ # Be sure to specify the same class as the +value_method+ when specifying selected or disabled options.
# Failure to do this will produce undesired results. Example:
# options_from_collection_for_select(@people, 'id', 'name', '1')
# Will not select a person with the id of 1 because 1 (an Integer) is not the same as '1' (a string)
@@ -220,7 +271,12 @@ module ActionView
options = collection.map do |element|
[element.send(text_method), element.send(value_method)]
end
- options_for_select(options, selected)
+ selected, disabled = extract_selected_and_disabled(selected)
+ select_deselect = {}
+ select_deselect[:selected] = extract_values_from_collection(collection, value_method, selected)
+ select_deselect[:disabled] = extract_values_from_collection(collection, value_method, disabled)
+
+ options_for_select(options, select_deselect)
end
# Returns a string of <tt><option></tt> tags, like <tt>options_from_collection_for_select</tt>, but
@@ -238,7 +294,8 @@ module ActionView
# +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
# * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
# which will have the +selected+ attribute set. Corresponds to the return value of one of the calls
- # to +option_key_method+. If +nil+, no selection is made.
+ # to +option_key_method+. If +nil+, no selection is made. Can also be a hash if disabled values are
+ # to be specified.
#
# Example object structure for use with this method:
# class Continent < ActiveRecord::Base
@@ -388,6 +445,24 @@ module ActionView
value == selected
end
end
+
+ def extract_selected_and_disabled(selected)
+ if selected.is_a?(Hash)
+ [selected[:selected], selected[:disabled]]
+ else
+ [selected, nil]
+ end
+ end
+
+ def extract_values_from_collection(collection, value_method, selected)
+ if selected.is_a?(Proc)
+ collection.map do |element|
+ element.send(value_method) if selected.call(element)
+ end.compact
+ else
+ selected
+ end
+ end
end
class InstanceTag #:nodoc:
@@ -398,16 +473,18 @@ module ActionView
add_default_name_and_id(html_options)
value = value(object)
selected_value = options.has_key?(:selected) ? options[:selected] : value
- content_tag("select", add_options(options_for_select(choices, selected_value), options, selected_value), html_options)
+ disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil
+ content_tag("select", add_options(options_for_select(choices, :selected => selected_value, :disabled => disabled_value), options, selected_value), html_options)
end
def to_collection_select_tag(collection, value_method, text_method, options, html_options)
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
value = value(object)
+ disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil
selected_value = options.has_key?(:selected) ? options[:selected] : value
content_tag(
- "select", add_options(options_from_collection_for_select(collection, value_method, text_method, selected_value), options, value), html_options
+ "select", add_options(options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => disabled_value), options, value), html_options
)
end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index b1eb6891fa..63fe0c1c57 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -107,7 +107,7 @@ module ActionView
text
else
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})(?!(?:[^<]*?)?(?:["'])[^<>]*>)/i, options[:highlighter])
+ text.gsub(/(#{match})(?!(?:[^<]*?)(?:["'])[^<>]*>)/i, options[:highlighter])
end
end
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index dc41ef5305..4aed10f640 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -3,19 +3,37 @@ require 'action_view/helpers/tag_helper'
module ActionView
module Helpers
module TranslationHelper
+ # Delegates to I18n#translate but also performs two additional functions. First, it'll catch MissingTranslationData exceptions
+ # and turn them into inline spans that contains the missing key, such that you can see in a view what is missing where.
+ #
+ # Second, it'll scope the key by the current partial if the key starts with a period. So if you call translate(".foo") from the
+ # people/index.html.erb template, you'll actually be calling I18n.translate("people.index.foo"). This makes it less repetitive
+ # to translate many keys within the same partials and gives you a simple framework for scoping them consistently. If you don't
+ # prepend the key with a period, nothing is converted.
def translate(key, options = {})
options[:raise] = true
- I18n.translate(key, options)
+ I18n.translate(scope_key_by_partial(key), options)
rescue I18n::MissingTranslationData => e
keys = I18n.send(:normalize_translation_keys, e.locale, e.key, e.options[:scope])
content_tag('span', keys.join(', '), :class => 'translation_missing')
end
alias :t :translate
+ # Delegates to I18n.localize with no additional functionality.
def localize(*args)
I18n.localize *args
end
alias :l :localize
+
+
+ private
+ def scope_key_by_partial(key)
+ if key.to_s.first == "."
+ template.path_without_format_and_extension.gsub(%r{/_?}, ".") + key.to_s
+ else
+ key
+ end
+ end
end
end
end \ No newline at end of file
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index c7d6fd696a..41f9f486e5 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -2,16 +2,16 @@ 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)
+ if Base.cache_template_loading?
+ Template::EagerPath.new(obj.to_s)
else
- Template::Path.new(obj)
+ ReloadableTemplate::ReloadablePath.new(obj.to_s)
end
else
obj
end
end
-
+
def initialize(*args)
super(*args).map! { |obj| self.class.type_cast(obj) }
end
@@ -35,6 +35,10 @@ module ActionView #:nodoc:
def unshift(*objs)
super(*objs.map { |obj| self.class.type_cast(obj) })
end
+
+ def load!
+ each(&:load!)
+ end
def find_template(original_template_path, format = nil)
return original_template_path if original_template_path.respond_to?(:render)
@@ -50,12 +54,16 @@ module ActionView #:nodoc:
elsif template = load_path[template_path]
return template
# Try to find html version if the format is javascript
+ elsif format == :js && template = load_path["#{template_path}.#{I18n.locale}.html"]
+ return template
elsif format == :js && template = load_path["#{template_path}.html"]
return template
end
end
- Template.new(original_template_path, self)
+ return Template.new(original_template_path, original_template_path =~ /\A\// ? "" : ".") if File.file?(original_template_path)
+
+ 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 153e14f68b..41080ed629 100644
--- a/actionpack/lib/action_view/renderable.rb
+++ b/actionpack/lib/action_view/renderable.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
module ActionView
# NOTE: The template that this mixin is being included into is frozen
# so you cannot set or modify any instance variables
@@ -16,7 +18,6 @@ module ActionView
def compiled_source
handler.call(self)
end
- memoize :compiled_source
def method_name_without_locals
['_run', extension, method_segment].compact.join('_')
@@ -78,6 +79,8 @@ module ActionView
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}"
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 553158b82a..b8e2165ddf 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -6,7 +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 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
@@ -39,30 +44,26 @@ 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)
- templates_in_path do |template|
- if template.accessible_paths.include?(path)
- return template
- end
- end
- nil
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 create_template(file)
- Template.new(file.split("#{self}/").last, self)
+
+ def load!
+ end
+
+ def self.new_and_loaded(path)
+ returning new(path) do |path|
+ path.load!
end
+ end
end
class EagerPath < Path
def initialize(path)
super
+ end
+ def load!
+ return if @loaded
+
@paths = {}
templates_in_path do |template|
template.load!
@@ -71,11 +72,24 @@ module ActionView #:nodoc:
end
end
@paths.freeze
+ @loaded = true
end
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 create_template(file)
+ Template.new(file.split("#{self}/").last, self)
+ end
end
extend TemplateHandlers
@@ -97,9 +111,9 @@ module ActionView #:nodoc:
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)
+ @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
@@ -163,11 +177,6 @@ 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
@@ -190,16 +199,7 @@ module ActionView #:nodoc:
end
end
- def stale?
- File.mtime(filename) > mtime
- end
-
- def recompile?
- !@cached
- end
-
def load!
- @cached = true
freeze
end
@@ -212,15 +212,6 @@ module ActionView #:nodoc:
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)
@@ -236,24 +227,24 @@ module ActionView #:nodoc:
format = nil
extension = nil
- if m = extensions.match(/^(\w+)?\.?(\w+)?\.?(\w+)?\.?/)
- if valid_locale?(m[1]) && m[2] && valid_extension?(m[3]) # All three
- locale = m[1]
- format = m[2]
- extension = m[3]
- elsif m[1] && m[2] && valid_extension?(m[3]) # Multipart formats
- format = "#{m[1]}.#{m[2]}"
- extension = m[3]
- elsif valid_locale?(m[1]) && valid_extension?(m[2]) # locale and extension
- locale = m[1]
- extension = m[2]
- elsif valid_extension?(m[2]) # format and extension
+ if m = extensions.split(".")
+ if valid_locale?(m[0]) && m[1] && valid_extension?(m[2]) # All three
+ locale = m[0]
format = m[1]
extension = m[2]
- elsif valid_extension?(m[1]) # Just extension
+ elsif m[0] && m[1] && valid_extension?(m[2]) # Multipart formats
+ format = "#{m[0]}.#{m[1]}"
+ extension = m[2]
+ elsif valid_locale?(m[0]) && valid_extension?(m[1]) # locale and extension
+ locale = m[0]
extension = m[1]
+ elsif valid_extension?(m[1]) # format and extension
+ format = m[0]
+ extension = m[1]
+ elsif valid_extension?(m[0]) # Just extension
+ extension = m[0]
else # No extension
- format = m[1]
+ format = m[0]
end
end