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.rb33
-rw-r--r--actionpack/lib/action_view/helpers/active_record_helper.rb1
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb13
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb9
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb9
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb20
-rw-r--r--actionpack/lib/action_view/helpers/scriptaculous_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb20
-rw-r--r--actionpack/lib/action_view/paths.rb20
-rw-r--r--actionpack/lib/action_view/render/partials.rb37
-rw-r--r--actionpack/lib/action_view/render/rendering.rb22
-rw-r--r--actionpack/lib/action_view/template/error.rb4
-rw-r--r--actionpack/lib/action_view/template/handler.rb6
-rw-r--r--actionpack/lib/action_view/template/handlers.rb8
-rw-r--r--actionpack/lib/action_view/template/handlers/builder.rb2
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb9
-rw-r--r--actionpack/lib/action_view/template/handlers/rjs.rb6
-rw-r--r--actionpack/lib/action_view/template/path.rb173
-rw-r--r--actionpack/lib/action_view/template/template.rb227
-rw-r--r--actionpack/lib/action_view/template/text.rb16
-rw-r--r--actionpack/lib/action_view/test_case.rb14
23 files changed, 357 insertions, 306 deletions
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index efed19a21d..4ab568b44c 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -1,3 +1,6 @@
+require 'active_support/core_ext/module/attr_internal'
+require 'active_support/core_ext/module/delegation'
+
module ActionView #:nodoc:
class ActionViewError < StandardError #:nodoc:
end
@@ -191,12 +194,14 @@ module ActionView #:nodoc:
ActionController::Base.allow_concurrency || (cache_template_loading.nil? ? !ActiveSupport::Dependencies.load? : cache_template_loading)
end
- attr_internal :request
+ attr_internal :request, :layout
delegate :controller_path, :to => :controller, :allow_nil => true
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
- :flash, :logger, :action_name, :controller_name, :to => :controller
+ :flash, :action_name, :controller_name, :to => :controller
+
+ delegate :logger, :to => :controller, :allow_nil => true
delegate :find_by_parts, :to => :view_paths
@@ -264,15 +269,16 @@ module ActionView #:nodoc:
nil
end
- private
- # Evaluates the local assigns and controller ivars, pushes them to the view.
- def _evaluate_assigns_and_ivars #:nodoc:
- unless @assigns_added
- @assigns.each { |key, value| instance_variable_set("@#{key}", value) }
- _copy_ivars_from_controller
- @assigns_added = true
- end
+ # Evaluates the local assigns and controller ivars, pushes them to the view.
+ def _evaluate_assigns_and_ivars #:nodoc:
+ unless @assigns_added
+ @assigns.each { |key, value| instance_variable_set("@#{key}", value) }
+ _copy_ivars_from_controller
+ @assigns_added = true
end
+ end
+
+ private
def _copy_ivars_from_controller #:nodoc:
if @controller
@@ -283,8 +289,11 @@ module ActionView #:nodoc:
end
def _set_controller_content_type(content_type) #:nodoc:
- if controller.respond_to?(:response)
- controller.response.content_type ||= content_type
+ # TODO: Remove this method when new base is switched
+ unless defined?(ActionController::Http)
+ if controller.respond_to?(:response)
+ controller.response.content_type ||= content_type
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb
index 7c0dfdab10..b4b9f6e34b 100644
--- a/actionpack/lib/action_view/helpers/active_record_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -1,5 +1,6 @@
require 'cgi'
require 'action_view/helpers/form_helper'
+require 'active_support/core_ext/class/attribute_accessors'
module ActionView
class Base
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 9e39536653..b4197479a0 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -124,7 +124,11 @@ module ActionView
# Use an alternate output buffer for the duration of the block.
# Defaults to a new empty string.
- def with_output_buffer(buf = '') #:nodoc:
+ def with_output_buffer(buf = nil) #:nodoc:
+ unless buf
+ buf = ''
+ buf.force_encoding(output_buffer.encoding) if buf.respond_to?(:force_encoding)
+ end
self.output_buffer, old_buffer = buf, output_buffer
yield
output_buffer
@@ -134,9 +138,12 @@ module ActionView
# Add the output buffer to the response body and start a new one.
def flush_output_buffer #:nodoc:
- if output_buffer && output_buffer != ''
+ if output_buffer && !output_buffer.empty?
response.body_parts << output_buffer
- self.output_buffer = ''
+ new = ''
+ new.force_encoding(output_buffer.encoding) if new.respond_to?(:force_encoding)
+ self.output_buffer = new
+ nil
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index a59829b23f..beef661a37 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -2,6 +2,7 @@ require 'cgi'
require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
+require 'active_support/core_ext/class/inheritable_attributes'
module ActionView
module Helpers
@@ -1039,8 +1040,8 @@ module ActionView
end
end
- class Base
- cattr_accessor :default_form_builder
- self.default_form_builder = ::ActionView::Helpers::FormBuilder
+ class << Base
+ attr_accessor :default_form_builder
end
-end \ No newline at end of file
+ Base.default_form_builder = ::ActionView::Helpers::FormBuilder
+end
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 6b385ef77d..6adbab175f 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -230,6 +230,8 @@ module ActionView
#
# 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)
+ return container if String === container
+
container = container.to_a if Hash === container
selected, disabled = extract_selected_and_disabled(selected)
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index dea958deaf..999d5b34fc 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/float/rounding'
+
module ActionView
module Helpers #:nodoc:
# Provides methods for converting numbers into formatted strings.
@@ -246,6 +248,11 @@ module ActionView
# number_to_human_size(483989, :precision => 0) # => 473 KB
# number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,18 MB
#
+ # Zeros after the decimal point are always stripped out, regardless of the
+ # specified precision:
+ # helper.number_to_human_size(1234567890123, :precision => 5) # => "1.12283 TB"
+ # helper.number_to_human_size(524288000, :precision=>5) # => "500 MB"
+ #
# You can still use <tt>number_to_human_size</tt> with the old API that accepts the
# +precision+ as its optional second parameter:
# number_to_human_size(1234567, 2) # => 1.18 MB
@@ -291,7 +298,7 @@ module ActionView
:precision => precision,
:separator => separator,
:delimiter => delimiter
- ).sub(/(\d)(#{escaped_separator}[1-9]*)?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
+ ).sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
rescue
number
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index 6bad11e354..c0f5df3468 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -1,5 +1,6 @@
require 'set'
require 'active_support/json'
+require 'active_support/core_ext/object/extending'
module ActionView
module Helpers
@@ -572,6 +573,7 @@ module ActionView
# #include_helpers_from_context has nothing to overwrite.
class JavaScriptGenerator #:nodoc:
def initialize(context, &block) #:nodoc:
+ context._evaluate_assigns_and_ivars
@context, @lines = context, []
include_helpers_from_context
@context.with_output_buffer(@lines) do
@@ -686,7 +688,7 @@ module ActionView
# Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
# expression as an argument to another JavaScriptGenerator method.
def literal(code)
- ActiveSupport::JSON::Variable.new(code.to_s)
+ ::ActiveSupport::JSON::Variable.new(code.to_s)
end
# Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
@@ -973,7 +975,7 @@ module ActionView
def loop_on_multiple_args(method, ids)
record(ids.size>1 ?
"#{javascript_object_for(ids)}.each(#{method})" :
- "#{method}(#{ids.first.to_json})")
+ "#{method}(#{javascript_object_for(ids.first)})")
end
def page
@@ -997,7 +999,7 @@ module ActionView
end
def javascript_object_for(object)
- object.respond_to?(:to_json) ? object.to_json : object.inspect
+ ::ActiveSupport::JSON.encode(object)
end
def arguments_for_call(arguments, block = nil)
@@ -1139,7 +1141,7 @@ module ActionView
class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
def initialize(generator, id)
@id = id
- super(generator, "$(#{id.to_json})")
+ super(generator, "$(#{::ActiveSupport::JSON.encode(id)})")
end
# Allows access of element attributes through +attribute+. Examples:
@@ -1180,11 +1182,11 @@ module ActionView
# The JSON Encoder calls this to check for the +to_json+ method
# Since it's a blank slate object, I suppose it responds to anything.
- def respond_to?(method)
+ def respond_to?(*)
true
end
- def to_json(options = nil)
+ def rails_to_json(*)
@variable
end
@@ -1211,7 +1213,7 @@ module ActionView
enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
else
add_variable_assignment!(variable)
- append_enumerable_function!("eachSlice(#{number.to_json});")
+ append_enumerable_function!("eachSlice(#{::ActiveSupport::JSON.encode(number)});")
end
end
@@ -1232,7 +1234,7 @@ module ActionView
def pluck(variable, property)
add_variable_assignment!(variable)
- append_enumerable_function!("pluck(#{property.to_json});")
+ append_enumerable_function!("pluck(#{::ActiveSupport::JSON.encode(property)});")
end
def zip(variable, *arguments, &block)
@@ -1296,7 +1298,7 @@ module ActionView
class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
def initialize(generator, pattern)
- super(generator, "$$(#{pattern.to_json})")
+ super(generator, "$$(#{::ActiveSupport::JSON.encode(pattern)})")
end
end
end
diff --git a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
index e16935ea87..04af2781d7 100644
--- a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
+++ b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
@@ -43,7 +43,7 @@ module ActionView
# You can change the behaviour with various options, see
# http://script.aculo.us for more documentation.
def visual_effect(name, element_id = false, js_options = {})
- element = element_id ? element_id.to_json : "element"
+ element = element_id ? ActiveSupport::JSON.encode(element_id) : "element"
js_options[:queue] = if js_options[:queue].is_a?(Hash)
'{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
@@ -138,7 +138,7 @@ module ActionView
end
def sortable_element_js(element_id, options = {}) #:nodoc:
- options[:with] ||= "Sortable.serialize(#{element_id.to_json})"
+ options[:with] ||= "Sortable.serialize(#{ActiveSupport::JSON.encode(element_id)})"
options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
@@ -149,7 +149,7 @@ module ActionView
options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
- %(Sortable.create(#{element_id.to_json}, #{options_for_javascript(options)});)
+ %(Sortable.create(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
end
# Makes the element with the DOM ID specified by +element_id+ draggable.
@@ -164,7 +164,7 @@ module ActionView
end
def draggable_element_js(element_id, options = {}) #:nodoc:
- %(new Draggable(#{element_id.to_json}, #{options_for_javascript(options)});)
+ %(new Draggable(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
end
# Makes the element with the DOM ID specified by +element_id+ receive
@@ -219,7 +219,7 @@ module ActionView
# Confirmation happens during the onDrop callback, so it can be removed from the options
options.delete(:confirm) if options[:confirm]
- %(Droppables.add(#{element_id.to_json}, #{options_for_javascript(options)});)
+ %(Droppables.add(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index af8c4d5e21..66d7592874 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -9,7 +9,7 @@ module ActionView
include ERB::Util
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked).to_set
- BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
+ BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attr| attr.to_sym })
# Returns an empty HTML tag of type +name+ which by default is XHTML
# compliant. Set +open+ to true to create an open tag compatible
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 573b99b96e..ad0733a7e1 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -34,12 +34,16 @@ module ActionView
# Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
# (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...").
+ # Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
#
# ==== Examples
#
# truncate("Once upon a time in a world far far away")
# # => Once upon a time in a world f...
#
+ # truncate("Once upon a time in a world far far away", :separator => ' ')
+ # # => Once upon a time in a world...
+ #
# truncate("Once upon a time in a world far far away", :length => 14)
# # => Once upon a...
#
@@ -71,7 +75,8 @@ module ActionView
if text
l = options[:length] - options[:omission].mb_chars.length
chars = text.mb_chars
- (chars.length > options[:length] ? chars[0...l] + options[:omission] : text).to_s
+ stop = options[:separator] ? (chars.rindex(options[:separator].mb_chars, l) || l) : l
+ (chars.length > options[:length] ? chars[0...stop] + options[:omission] : text).to_s
end
end
@@ -535,7 +540,7 @@ module ActionView
link_attributes = html_options.stringify_keys
text.gsub(AUTO_LINK_RE) do
href = $&
- punctuation = ''
+ punctuation = []
left, right = $`, $'
# detect already linked URLs and URLs in the middle of a tag
if left =~ /<[^>]+$/ && right =~ /^[^>]*>/
@@ -543,17 +548,18 @@ module ActionView
href
else
# don't include trailing punctuation character as part of the URL
- if href.sub!(/[^\w\/-]$/, '') and punctuation = $& and opening = BRACKETS[punctuation]
- if href.scan(opening).size > href.scan(punctuation).size
- href << punctuation
- punctuation = ''
+ while href.sub!(/[^\w\/-]$/, '')
+ punctuation.push $&
+ if opening = BRACKETS[punctuation.last] and href.scan(opening).size > href.scan(punctuation.last).size
+ href << punctuation.pop
+ break
end
end
link_text = block_given?? yield(href) : href
href = 'http://' + href unless href.index('http') == 0
- content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation
+ content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation.reverse.join('')
end
end
end
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index 1d0279889c..95c56faf9c 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -2,8 +2,8 @@ module ActionView #:nodoc:
class PathSet < Array #:nodoc:
def self.type_cast(obj)
if obj.is_a?(String)
- cache = !Object.const_defined?(:Rails) || Rails.configuration.cache_classes
- Template::FileSystemPath.new(obj, :cache => cache)
+ cache = !defined?(Rails) || !Rails.respond_to?(:configuration) || Rails.configuration.cache_classes
+ Template::FileSystemPathWithFallback.new(obj, :cache => cache)
else
obj
end
@@ -33,19 +33,19 @@ module ActionView #:nodoc:
super(*objs.map { |obj| self.class.type_cast(obj) })
end
- def find_by_parts(path, extension = nil, prefix = nil, partial = false)
- template_path = path.sub(/^\//, '')
+ def find_by_parts(path, details = {}, prefix = nil, partial = false)
+ # template_path = path.sub(/^\//, '')
+ template_path = path
each do |load_path|
- if template = load_path.find_by_parts(template_path, extension, prefix, partial)
+ if template = load_path.find_by_parts(template_path, details, prefix, partial)
return template
end
end
-
- Template.new(path, self)
- rescue ActionView::MissingTemplate => e
- extension ||= []
- raise ActionView::MissingTemplate.new(self, "#{prefix}/#{path}.{#{extension.join(",")}}")
+
+ # TODO: Have a fallback absolute path?
+ extension = details[:formats] || []
+ raise ActionView::MissingTemplate.new(self, "#{prefix}/#{path} - #{details.inspect} - partial: #{!!partial}")
end
def find_by_parts?(path, extension = nil, prefix = nil, partial = false)
diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb
index e337dcb63b..eacf117bea 100644
--- a/actionpack/lib/action_view/render/partials.rb
+++ b/actionpack/lib/action_view/render/partials.rb
@@ -171,11 +171,21 @@ module ActionView
# <% end %>
module Partials
extend ActiveSupport::Memoizable
+ extend ActiveSupport::Concern
+
+ included do
+ attr_accessor :_partial
+ end
+
+ def _render_partial_from_controller(*args)
+ @assigns_added = false
+ _render_partial(*args)
+ end
def _render_partial(options = {}) #:nodoc:
options[:locals] ||= {}
- case path = partial = options[:partial]
+ case path = partial = options[:partial]
when *_array_like_objects
return _render_partial_collection(partial, options)
else
@@ -187,6 +197,7 @@ module ActionView
path = ActionController::RecordIdentifier.partial_path(object, controller_path)
end
_, _, prefix, object = parts = partial_parts(path, options)
+ parts[1] = {:formats => parts[1]}
template = find_by_parts(*parts)
_render_partial_object(template, options, (object unless object == true))
end
@@ -221,16 +232,7 @@ module ActionView
ensure
@_proc_for_layout = nil
end
-
- def _render_partial_with_layout(layout, options)
- if layout
- prefix = controller && !layout.include?("/") ? controller.controller_path : nil
- layout = find_by_parts(layout, formats, prefix, true)
- end
- content = _render_partial(options)
- return _render_content_with_layout(content, layout, options[:locals])
- end
-
+
def _deprecated_ivar_assign(template)
if respond_to?(:controller)
ivar = :"@#{template.variable_name}"
@@ -253,7 +255,7 @@ module ActionView
def _render_partial_with_layout(layout, options)
if layout
prefix = controller && !layout.include?("/") ? controller.controller_path : nil
- layout = find_by_parts(layout, formats, prefix, true)
+ layout = find_by_parts(layout, {:formats => formats}, prefix, true)
end
content = _render_partial(options)
return _render_content_with_layout(content, layout, options[:locals])
@@ -286,13 +288,17 @@ module ActionView
locals = (options[:locals] ||= {})
object ||= locals[:object] || locals[template.variable_name]
- _set_locals(object, locals, template, options)
+ _set_locals(object, locals, template, options)
+
+ self._partial = template
+
_render_template(template, locals)
end
end
def _set_locals(object, locals, template, options)
object ||= _deprecated_ivar_assign(template)
+
locals[:object] = locals[template.variable_name] = object
locals[options[:as]] = object if options[:as]
end
@@ -315,13 +321,16 @@ module ActionView
locals[template.counter_name] = index
index += 1
+
+ self._partial = template
+
_render_template(template, locals)
end.join(spacer)
end
def _pick_partial_template(partial_path) #:nodoc:
prefix = controller_path unless partial_path.include?('/')
- find_by_parts(partial_path, formats, prefix, true)
+ find_by_parts(partial_path, {:formats => formats}, prefix, true)
end
memoize :_pick_partial_template
end
diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb
index a9b2acecd5..fe785e7b20 100644
--- a/actionpack/lib/action_view/render/rendering.rb
+++ b/actionpack/lib/action_view/render/rendering.rb
@@ -23,10 +23,10 @@ module ActionView
return _render_partial_with_layout(layout, options) if options.key?(:partial)
return _render_partial_with_block(layout, block, options) if block_given?
- layout = find_by_parts(layout, formats) if layout
+ layout = find_by_parts(layout, {:formats => formats}) if layout
if file = options[:file]
- template = find_by_parts(file, formats)
+ template = find_by_parts(file, {:formats => formats})
_render_template_with_layout(template, layout, :locals => options[:locals])
elsif inline = options[:inline]
_render_inline(inline, layout, options)
@@ -46,8 +46,8 @@ module ActionView
locals ||= {}
if controller && layout
- response.layout = layout.path_without_format_and_extension if controller.respond_to?(:response)
- logger.info("Rendering template within #{layout.path_without_format_and_extension}") if logger
+ @_layout = layout.identifier
+ logger.info("Rendering template within #{layout.identifier}") if logger
end
begin
@@ -76,7 +76,6 @@ module ActionView
end
end
rescue Exception => e
- raise e if template.is_a?(InlineTemplate) || !template.filename
if TemplateError === e
e.sub_template_of(template)
raise e
@@ -86,7 +85,9 @@ module ActionView
end
def _render_inline(inline, layout, options)
- content = _render_template(InlineTemplate.new(options[:inline], options[:type]), options[:locals] || {})
+ handler = Template.handler_class_for_extension(options[:type] || "erb")
+ template = Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {})
+ content = _render_template(template, options[:locals] || {})
layout ? _render_content_with_layout(content, layout, options[:locals]) : content
end
@@ -94,9 +95,14 @@ module ActionView
layout ? _render_content_with_layout(text, layout, options[:locals]) : text
end
+ def _render_template_from_controller(*args)
+ @assigns_added = nil
+ _render_template_with_layout(*args)
+ end
+
def _render_template_with_layout(template, layout = nil, options = {}, partial = false)
if controller && logger
- logger.info("Rendering #{template.path_without_extension}" +
+ logger.info("Rendering #{template.identifier}" +
(options[:status] ? " (#{options[:status]})" : ''))
end
@@ -107,7 +113,7 @@ module ActionView
_render_template(template, options[:locals] || {})
end
- return content unless layout && !template.exempt_from_layout?
+ return content unless layout
_render_content_with_layout(content, layout, options[:locals] || {})
end
end
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index 37cb1c7c6c..a06e80b294 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -12,7 +12,7 @@ module ActionView
end
def file_name
- @template.relative_path
+ @template.identifier
end
def message
@@ -30,7 +30,7 @@ module ActionView
def sub_template_message
if @sub_templates
"Trace of template inclusion: " +
- @sub_templates.collect { |template| template.relative_path }.join(", ")
+ @sub_templates.collect { |template| template.identifier }.join(", ")
else
""
end
diff --git a/actionpack/lib/action_view/template/handler.rb b/actionpack/lib/action_view/template/handler.rb
index 672da0ed2b..3071c78174 100644
--- a/actionpack/lib/action_view/template/handler.rb
+++ b/actionpack/lib/action_view/template/handler.rb
@@ -1,3 +1,6 @@
+require "active_support/core_ext/class/inheritable_attributes"
+require "action_dispatch/http/mime_type"
+
# Legacy TemplateHandler stub
module ActionView
module TemplateHandlers #:nodoc:
@@ -19,6 +22,9 @@ module ActionView
end
class TemplateHandler
+ extlib_inheritable_accessor :default_format
+ self.default_format = Mime::HTML
+
def self.call(template)
"#{name}.new(self).render(template, local_assigns)"
end
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
index fb85f28851..faf54b9fe5 100644
--- a/actionpack/lib/action_view/template/handlers.rb
+++ b/actionpack/lib/action_view/template/handlers.rb
@@ -16,6 +16,10 @@ module ActionView #:nodoc:
@@template_handlers = {}
@@default_template_handlers = nil
+
+ def self.extensions
+ @@template_handlers.keys
+ end
# Register a class that knows how to handle template files with the given
# extension. This can be used to implement new template types.
@@ -29,7 +33,7 @@ module ActionView #:nodoc:
end
def template_handler_extensions
- @@template_handlers.keys.map(&:to_s).sort
+ @@template_handlers.keys.map {|key| key.to_s }.sort
end
def registered_template_handler(extension)
@@ -42,7 +46,7 @@ module ActionView #:nodoc:
end
def handler_class_for_extension(extension)
- registered_template_handler(extension) || @@default_template_handlers
+ (extension && registered_template_handler(extension.to_sym)) || @@default_template_handlers
end
end
end
diff --git a/actionpack/lib/action_view/template/handlers/builder.rb b/actionpack/lib/action_view/template/handlers/builder.rb
index 788dc93326..f412228752 100644
--- a/actionpack/lib/action_view/template/handlers/builder.rb
+++ b/actionpack/lib/action_view/template/handlers/builder.rb
@@ -5,6 +5,8 @@ module ActionView
class Builder < TemplateHandler
include Compilable
+ self.default_format = Mime::XML
+
def compile(template)
"_set_controller_content_type(Mime::XML);" +
"xml = ::Builder::XmlMarkup.new(:indent => 2);" +
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index a20b1b0cd3..d773df7d29 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -1,4 +1,5 @@
require 'erb'
+require 'active_support/core_ext/class/attribute_accessors'
module ActionView
module TemplateHandlers
@@ -12,12 +13,10 @@ module ActionView
cattr_accessor :erb_trim_mode
self.erb_trim_mode = '-'
- def compile(template)
- src = ::ERB.new("<% __in_erb_template=true %>#{template.source}", nil, erb_trim_mode, '@output_buffer').src
+ self.default_format = Mime::HTML
- # Ruby 1.9 prepends an encoding to the source. However this is
- # useless because you can only set an encoding on the first line
- RUBY_VERSION >= '1.9' ? src.sub(/\A#coding:.*\n/, '') : src
+ def compile(template)
+ ::ERB.new("<% __in_erb_template=true %>#{template.source}", nil, erb_trim_mode, '@output_buffer').src
end
end
end
diff --git a/actionpack/lib/action_view/template/handlers/rjs.rb b/actionpack/lib/action_view/template/handlers/rjs.rb
index 802a79b3fc..a36744c2b7 100644
--- a/actionpack/lib/action_view/template/handlers/rjs.rb
+++ b/actionpack/lib/action_view/template/handlers/rjs.rb
@@ -3,11 +3,17 @@ module ActionView
class RJS < TemplateHandler
include Compilable
+ self.default_format = Mime::JS
+
def compile(template)
"@formats = [:html];" +
"controller.response.content_type ||= Mime::JS;" +
"update_page do |page|;#{template.source}\nend"
end
+
+ def default_format
+ Mime::JS
+ end
end
end
end
diff --git a/actionpack/lib/action_view/template/path.rb b/actionpack/lib/action_view/template/path.rb
index 9709549b70..478bf96c9a 100644
--- a/actionpack/lib/action_view/template/path.rb
+++ b/actionpack/lib/action_view/template/path.rb
@@ -1,23 +1,82 @@
+require "pathname"
+
module ActionView
class Template
+ # Abstract super class
class Path
- attr_reader :path, :paths
- delegate :hash, :inspect, :to => :path
-
def initialize(options)
- @cache = options[:cache]
+ @cache = options[:cache]
+ @cached = {}
+ end
+
+ # Normalizes the arguments and passes it on to find_template
+ def find_by_parts(*args)
+ find_all_by_parts(*args).first
+ end
+
+ def find_all_by_parts(name, details = {}, prefix = nil, partial = nil)
+ details[:locales] = [I18n.locale]
+ name = name.to_s.gsub(handler_matcher, '').split("/")
+ find_templates(name.pop, details, [prefix, *name].compact.join("/"), partial)
+ end
+
+ private
+
+ # This is what child classes implement. No defaults are needed
+ # because Path guarentees that the arguments are present and
+ # normalized.
+ def find_templates(name, details, prefix, partial)
+ raise NotImplementedError
+ end
+
+ def valid_handlers
+ @valid_handlers ||= TemplateHandlers.extensions
+ end
+
+ def handler_matcher
+ @handler_matcher ||= begin
+ e = valid_handlers.join('|')
+ /\.(?:#{e})$/
+ end
+ end
+
+ def handler_glob
+ e = TemplateHandlers.extensions.map{|h| ".#{h},"}.join
+ "{#{e}}"
+ end
+
+ def formats_glob
+ @formats_glob ||= begin
+ formats = Mime::SET.map { |m| m.symbol }
+ '{' + formats.map { |l| ".#{l}," }.join + '}'
+ end
+ end
+
+ def cached(key)
+ return yield unless @cache
+ return @cached[key] if @cached.key?(key)
+ @cached[key] = yield
+ end
+ end
+
+ class FileSystemPath < Path
+
+ def initialize(path, options = {})
+ raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
+ super(options)
+ @path = Pathname.new(path).expand_path
end
+ # TODO: This is the currently needed API. Make this suck less
+ # ==== <suck>
+ attr_reader :path
+
def to_s
- if defined?(RAILS_ROOT)
- path.to_s.sub(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '')
- else
- path.to_s
- end
+ path.to_s
end
def to_str
- path.to_str
+ path.to_s
end
def ==(path)
@@ -27,59 +86,65 @@ module ActionView
def eql?(path)
to_str == path.to_str
end
-
- def find_by_parts(name, extensions = nil, prefix = nil, partial = nil)
- path = prefix ? "#{prefix}/" : ""
-
- name = name.to_s.split("/")
- name[-1] = "_#{name[-1]}" if partial
+ # ==== </suck>
- path << name.join("/")
-
- template = nil
-
- Array(extensions).each do |extension|
- extensioned_path = extension ? "#{path}.#{extension}" : path
- break if (template = find_template(extensioned_path))
+ def find_templates(name, details, prefix, partial, root = "#{@path}/")
+ if glob = details_to_glob(name, details, prefix, partial, root)
+ cached(glob) do
+ Dir[glob].map do |path|
+ next if File.directory?(path)
+ source = File.read(path)
+ identifier = Pathname.new(path).expand_path.to_s
+
+ Template.new(source, identifier, *path_to_details(path))
+ end.compact
+ end
end
- template || find_template(path)
end
-
+
private
- def create_template(file)
- Template.new(file.split("#{self}/").last, self)
- end
- end
-
- class FileSystemPath < Path
- def initialize(path, options = {})
- raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
-
- super(options)
- @path, @paths = path, {}
+
+ # :api: plugin
+ def details_to_glob(name, details, prefix, partial, root)
+ path = ""
+ path << "#{prefix}/" unless prefix.empty?
+ path << (partial ? "_#{name}" : name)
+
+ extensions = ""
+ [:locales, :formats].each do |k|
+ extensions << if exts = details[k]
+ '{' + exts.map {|e| ".#{e},"}.join + '}'
+ else
+ k == :formats ? formats_glob : ''
+ end
+ end
- # **/*/** is a hax for symlinked directories
- load_templates("#{@path}/{**/*,**}/**") if @cache
+ "#{root}#{path}#{extensions}#{handler_glob}"
end
+
+ # TODO: fix me
+ # :api: plugin
+ def path_to_details(path)
+ # [:erb, :format => :html, :locale => :en, :partial => true/false]
+ if m = path.match(%r'/(_)?[\w-]+(\.[\w-]+)*\.(\w+)$')
+ partial = m[1] == '_'
+ details = (m[2]||"").split('.').reject { |e| e.empty? }
+ handler = Template.handler_class_for_extension(m[3])
- private
-
- def load_template(template)
- template.load!
- template.accessible_paths.each do |path|
- @paths[path] = template
+ format = Mime[details.last] && details.pop.to_sym
+ locale = details.last && details.pop.to_sym
+
+ return handler, :format => format, :locale => locale, :partial => partial
end
end
-
- def find_template(path)
- load_templates("#{@path}/#{path}{,.*}") unless @cache
- @paths[path]
- end
-
- def load_templates(glob)
- Dir[glob].each do |file|
- load_template(create_template(file)) unless File.directory?(file)
- end
+ end
+
+ class FileSystemPathWithFallback < FileSystemPath
+
+ def find_templates(name, details, prefix, partial)
+ templates = super
+ return super(name, details, prefix, partial, '') if templates.empty?
+ templates
end
end
diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb
index 0d2f201458..a9897258d2 100644
--- a/actionpack/lib/action_view/template/template.rb
+++ b/actionpack/lib/action_view/template/template.rb
@@ -1,187 +1,92 @@
+# encoding: utf-8
+# This is so that templates compiled in this file are UTF-8
+
+require 'set'
require "action_view/template/path"
-module ActionView #:nodoc:
+module ActionView
class Template
extend TemplateHandlers
- extend ActiveSupport::Memoizable
-
- module Loading
- def load!
- @cached = true
- # freeze
- end
- end
- include Loading
+ attr_reader :source, :identifier, :handler, :mime_type, :details
- include Renderable
-
- # Templates that are exempt from layouts
- @@exempt_from_layout = Set.new([/\.rjs$/])
-
- # Don't render layouts for templates with the given extensions.
- def self.exempt_from_layout(*extensions)
- regexps = extensions.collect do |extension|
- extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
+ def initialize(source, identifier, handler, details)
+ @source = source
+ @identifier = identifier
+ @handler = handler
+ @details = details
+
+ format = details.delete(:format) || begin
+ # TODO: Clean this up
+ handler.respond_to?(:default_format) ? handler.default_format.to_sym.to_s : "html"
end
- @@exempt_from_layout.merge(regexps)
- end
-
- attr_accessor :template_path, :filename, :load_path, :base_path
- attr_accessor :locale, :name, :format, :extension
- delegate :to_s, :to => :path
-
- def initialize(template_path, load_paths = [])
- template_path = template_path.dup
- @load_path, @filename = find_full_path(template_path, load_paths)
- @base_path, @name, @locale, @format, @extension = split(template_path)
- @base_path.to_s.gsub!(/\/$/, '') # Push to split method
-
- # Extend with partial super powers
- extend RenderablePartial if @name =~ /^_/
+ @mime_type = Mime::Type.lookup_by_extension(format.to_s)
+ @details[:formats] = Array.wrap(format && format.to_sym)
end
- def accessible_paths
- paths = []
-
- if valid_extension?(extension)
- paths << path
- paths << path_without_extension
- if multipart?
- formats = format.split(".")
- paths << "#{path_without_format_and_extension}.#{formats.first}"
- paths << "#{path_without_format_and_extension}.#{formats.second}"
- end
- else
- # template without explicit template handler should only be reachable through its exact path
- paths << template_path
- end
-
- paths
- end
-
- def relative_path
- path = File.expand_path(filename)
- path.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '') if defined?(RAILS_ROOT)
- path
+ def render(view, locals, &blk)
+ method_name = compile(locals, view)
+ view.send(method_name, locals, &blk)
end
- memoize :relative_path
- def source
- File.read(filename)
+ # TODO: Figure out how to abstract this
+ def variable_name
+ identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym
end
- memoize :source
-
- def exempt_from_layout?
- @@exempt_from_layout.any? { |exempted| path =~ exempted }
- end
-
- def path_without_extension
- [base_path, [name, locale, format].compact.join('.')].compact.join('/')
- end
- memoize :path_without_extension
- def path_without_format_and_extension
- [base_path, [name, locale].compact.join('.')].compact.join('/')
- end
- memoize :path_without_format_and_extension
-
- def path
- [base_path, [name, locale, format, extension].compact.join('.')].compact.join('/')
- end
- memoize :path
-
- def mime_type
- Mime::Type.lookup_by_extension(format) if format && defined?(::Mime)
+ # TODO: Figure out how to abstract this
+ def counter_name
+ "#{variable_name}_counter".to_sym
end
- memoize :mime_type
- def multipart?
- format && format.include?('.')
- end
-
- def content_type
- format.gsub('.', '/')
- end
-
- private
-
- def format_and_extension
- (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions
- end
- memoize :format_and_extension
-
- def mtime
- File.mtime(filename)
- end
- memoize :mtime
-
- def method_segment
- relative_path.to_s.gsub(/([^a-zA-Z0-9_])/) { $1.ord }
- end
- memoize :method_segment
-
- def stale?
- File.mtime(filename) > mtime
- end
-
- def recompile?
- !@cached
+ # TODO: kill hax
+ def partial?
+ @details[:partial]
end
- def valid_extension?(extension)
- !Template.registered_template_handler(extension).nil?
- end
-
- def valid_locale?(locale)
- I18n.available_locales.include?(locale.to_sym)
- end
+ private
- 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
+ def compile(locals, view)
+ method_name = build_method_name(locals)
+
+ return method_name if view.respond_to?(method_name)
+
+ locals_code = locals.keys.map! { |key| "#{key} = local_assigns[:#{key}];" }.join
+
+ code = @handler.call(self)
+ encoding_comment = $1 if code.sub!(/\A(#.*coding.*)\n/, '')
+
+ source = <<-end_src
+ def #{method_name}(local_assigns)
+ old_output_buffer = output_buffer;#{locals_code};#{code}
+ ensure
+ self.output_buffer = old_output_buffer
+ end
+ end_src
- # Returns file split into an array
- # [base_path, name, locale, format, extension]
- def split(file)
- if m = file.to_s.match(/^(.*\/)?([^\.]+)\.(.*)$/)
- base_path = m[1]
- name = m[2]
- extensions = m[3]
+ if encoding_comment
+ source = "#{encoding_comment}\n#{source}"
+ line = -1
else
- return
+ line = 0
end
- locale = nil
- format = nil
- extension = nil
-
- 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 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[0]
+ begin
+ ActionView::Base::CompiledTemplates.module_eval(source, identifier, line)
+ method_name
+ rescue Exception => e # errors from template code
+ if logger = (view && view.logger)
+ logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
+ logger.debug "Function body: #{source}"
+ logger.debug "Backtrace: #{e.backtrace.join("\n")}"
end
- end
- [base_path, name, locale, format, extension]
+ raise ActionView::TemplateError.new(self, {}, e)
+ end
+ end
+
+ def build_method_name(locals)
+ # TODO: is locals.keys.hash reliably the same?
+ "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
end
end
end
diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb
index f81174d707..fd57b1677e 100644
--- a/actionpack/lib/action_view/template/text.rb
+++ b/actionpack/lib/action_view/template/text.rb
@@ -1,9 +1,21 @@
module ActionView #:nodoc:
class TextTemplate < String #:nodoc:
+
+ def initialize(string, content_type = Mime[:html])
+ super(string.to_s)
+ @content_type = Mime[content_type]
+ end
+
+ def details
+ {:formats => [@content_type.to_sym]}
+ end
+
+ def identifier() self end
def render(*) self end
- def exempt_from_layout?() false end
-
+ def mime_type() @content_type end
+
+ def partial?() false end
end
end
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index c8f204046b..7355af4192 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -7,19 +7,21 @@ module ActionView
@_rendered = { :template => nil, :partials => Hash.new(0) }
initialize_without_template_tracking(*args)
end
-
+
+ attr_internal :rendered
alias_method :_render_template_without_template_tracking, :_render_template
def _render_template(template, local_assigns = {})
- if template.respond_to?(:path) && !template.is_a?(InlineTemplate)
- @_rendered[:partials][template] += 1 if template.is_a?(RenderablePartial)
- @_rendered[:template] ||= template
+ if template.respond_to?(:identifier) && template.present?
+ @_rendered[:partials][template] += 1 if template.partial?
+ @_rendered[:template] ||= []
+ @_rendered[:template] << template
end
_render_template_without_template_tracking(template, local_assigns)
- end
+ end
end
class TestCase < ActiveSupport::TestCase
- include ActionController::TestCase::Assertions
+ include ActionDispatch::Assertions
include ActionController::TestProcess
class_inheritable_accessor :helper_class