Introduce a Template class to ActionView. Closes #11024 [lifofifo]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8805 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Michael Koziarski
2008-02-06 04:26:40 +00:00
parent 8bc9018882
commit 692dbbf793
11 changed files with 145 additions and 123 deletions

View File

@@ -1,5 +1,7 @@
*SVN*
* Introduce a Template class to ActionView. #11024 [lifofifo]
* Introduce the :index option for form_for and fields_for to simplify multi-model forms (see http://railscasts.com/episodes/75). #9883 [rmm5t]
* Introduce map.resources :cards, :as => 'tarjetas' to use a custom resource name in the URL: cards_path == '/tarjetas'. #10578 [blj]

View File

@@ -5,6 +5,7 @@ require 'action_controller/routing'
require 'action_controller/resources'
require 'action_controller/url_rewriter'
require 'action_controller/status_codes'
require 'action_view/template'
require 'action_view/template_finder'
require 'drb'
require 'set'
@@ -866,7 +867,8 @@ module ActionController #:nodoc:
elsif inline = options[:inline]
add_variables_to_assigns
render_for_text(@template.render_template(options[:type], inline, nil, options[:locals] || {}), options[:status])
tmpl = ActionView::Template.new(@template, options[:inline], false, options[:locals], true, options[:type])
render_for_text(@template.render_template(tmpl), options[:status])
elsif action_name = options[:action]
template = default_template_name(action_name.to_s)

View File

@@ -28,6 +28,7 @@ require 'action_view/template_handlers/erb'
require 'action_view/template_handlers/rjs'
require 'action_view/template_finder'
require 'action_view/template'
require 'action_view/base'
require 'action_view/partials'

View File

@@ -150,8 +150,8 @@ module ActionView #:nodoc:
class Base
include ERB::Util
attr_reader :first_render, :finder
attr_accessor :base_path, :assigns, :template_extension
attr_reader :finder
attr_accessor :base_path, :assigns, :template_extension, :first_render
attr_accessor :controller
attr_reader :logger, :response, :headers
@@ -173,12 +173,6 @@ module ActionView #:nodoc:
# Should be +false+ for development environments. Defaults to +true+.
@@cache_template_extensions = true
cattr_accessor :cache_template_extensions
# Specify whether local_assigns should be able to use string keys.
# Defaults to +true+. String keys are deprecated and will be removed
# shortly.
@@local_assigns_support_string_keys = true
cattr_accessor :local_assigns_support_string_keys
# Specify whether RJS responses should be wrapped in a try/catch block
# that alert()s the caught exception (and then re-raises it).
@@ -282,41 +276,16 @@ If you are rendering a subtemplate, you must now use controller-like partial syn
END_ERROR
end
# Clear the forward slash at the beginning if exists
template_path = template_path.sub(/^\//, '') if use_full_path
@first_render ||= template_path
template_path_without_extension, template_extension = @finder.path_and_extension(template_path)
if use_full_path
if template_extension
template_file_name = @finder.pick_template(template_path_without_extension, template_extension)
else
template_extension = @finder.pick_template_extension(template_path).to_s
unless template_extension
raise ActionViewError, "No template found for #{template_path} in #{@finder.view_paths.inspect}"
end
template_file_name = @finder.pick_template(template_path, template_extension)
template_extension = template_extension.gsub(/^.+\./, '') # strip off any formats
end
else
template_file_name = template_path
end
template_source = nil # Don't read the source until we know that it is required
if template_file_name.blank?
raise ActionViewError, "Couldn't find template file for #{template_path} in #{@finder.view_paths.inspect}"
end
template = Template.new(self, template_path, use_full_path, local_assigns)
begin
render_template(template_extension, template_source, template_file_name, local_assigns)
render_template(template)
rescue Exception => e
if TemplateError === e
e.sub_template_of(template_file_name)
e.sub_template_of(template.filename)
raise e
else
raise TemplateError.new(@finder.find_base_path_for("#{template_path_without_extension}.#{template_extension}") ||
@finder.view_paths.first, template_file_name, @assigns, template_source, e)
raise TemplateError.new(template, @assigns, e)
end
end
end
@@ -350,22 +319,22 @@ If you are rendering a subtemplate, you must now use controller-like partial syn
elsif options[:partial]
render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals])
elsif options[:inline]
render_template(options[:type], options[:inline], nil, options[:locals])
template = Template.new(self, options[:inline], false, options[:locals], true, options[:type])
render_template(template)
end
end
end
# Renders the +template+ which is given as a string as either erb or builder depending on <tt>template_extension</tt>.
# The hash in <tt>local_assigns</tt> is made available as local variables.
def render_template(template_extension, template, file_path = nil, local_assigns = {}) #:nodoc:
handler = self.class.handler_class_for_extension(template_extension).new(self)
@current_render_extension = template_extension
def render_template(template) #:nodoc:
handler = template.handler
@current_render_extension = template.extension
if handler.compilable?
compile_and_render_template(handler, template, file_path, local_assigns)
compile_and_render_template(handler, template)
else
template ||= handler.read_template_file(file_path, template_extension) # Make sure that a lazyily-read template is loaded.
handler.render(template, local_assigns)
handler.render(template.source, template.locals)
end
end
@@ -407,18 +376,15 @@ If you are rendering a subtemplate, you must now use controller-like partial syn
# Either, but not both, of template and file_path may be nil. If file_path is given, the template
# will only be read if it has to be compiled.
#
def compile_and_render_template(handler, template = nil, file_path = nil, local_assigns = {}) #:nodoc:
# convert string keys to symbols if requested
local_assigns = local_assigns.symbolize_keys if @@local_assigns_support_string_keys
def compile_and_render_template(handler, template) #:nodoc:
# compile the given template, if necessary
handler.compile_template(template, file_path, local_assigns)
handler.compile_template(template)
# Get the method name for this template and run it
method_name = @@method_names[file_path || template]
method_name = @@method_names[template.method_key]
evaluate_assigns
send(method_name, local_assigns) do |*name|
send(method_name, template.locals) do |*name|
instance_variable_get "@content_for_#{name.first || 'layout'}"
end
end

View File

@@ -0,0 +1,65 @@
module ActionView #:nodoc:
class Template #:nodoc:
attr_accessor :locals
attr_reader :handler, :path, :source, :extension, :filename, :path_without_extension
def initialize(view, path_or_source, use_full_path, locals = {}, inline = false, inline_type = nil)
@view = view
@finder = @view.finder
unless inline
# Clear the forward slash at the beginning if exists
@path = use_full_path ? path_or_source.sub(/^\//, '') : path_or_source
@view.first_render ||= @path
@source = nil # Don't read the source until we know that it is required
set_extension_and_file_name(use_full_path)
else
@source = path_or_source
@extension = inline_type
end
@locals = locals || {}
end
def source
@source ||= File.read(self.filename)
end
def method_key
@method_key ||= (@filename || @source)
end
def handler
@handler ||= @view.class.handler_class_for_extension(@extension).new(@view)
end
def base_path_for_exception
@finder.find_base_path_for("#{@path_without_extension}.#{@extension}") || @finder.view_paths.first
end
private
def set_extension_and_file_name(use_full_path)
@path_without_extension, @extension = @finder.path_and_extension(@path)
if use_full_path
if @extension
@filename = @finder.pick_template(@path_without_extension, @extension)
else
@extension = @finder.pick_template_extension(@path).to_s
unless @extension
raise ActionViewError, "No template found for #{@path} in #{@finder.view_paths.inspect}"
end
@filename = @finder.pick_template(@path, @extension)
@extension = @extension.gsub(/^.+\./, '') # strip off any formats
end
else
@filename = @path
end
if @filename.blank?
raise ActionViewError, "Couldn't find template file for #{@path} in #{@finder.view_paths.inspect}"
end
end
end
end

View File

@@ -6,10 +6,10 @@ module ActionView
attr_reader :original_exception
def initialize(base_path, file_path, assigns, source, original_exception)
@base_path, @assigns, @source, @original_exception =
base_path, assigns.dup, source, original_exception
@file_path = file_path
def initialize(template, assigns, original_exception)
@base_path = template.base_path_for_exception
@assigns, @source, @original_exception = assigns.dup, template.source, original_exception
@file_path = template.filename
@backtrace = compute_backtrace
end

View File

@@ -30,10 +30,5 @@ module ActionView
# Called by CacheHelper#cache
def cache_fragment(block, name = {}, options = nil)
end
# This method reads a template file.
def read_template_file(template_path, extension)
File.read(template_path)
end
end
end

View File

@@ -26,17 +26,15 @@ module ActionView
end
# Compile and evaluate the template's code
def compile_template(template, file_name, local_assigns)
return unless compile_template?(template, file_name, local_assigns)
def compile_template(template)
return unless compile_template?(template)
template ||= read_template_file(file_name, nil)
render_symbol = assign_method_name(template, file_name)
render_source = create_template_source(template, render_symbol, local_assigns.keys)
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 = 'compiled-template' if file_name.blank?
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 @view.logger
@@ -45,8 +43,7 @@ module ActionView
@view.logger.debug "Backtrace: #{e.backtrace.join("\n")}"
end
raise ActionView::TemplateError.new(@view.finder.extract_base_path_from(file_name) ||
@view.finder.view_paths.first, file_name || template, @view.assigns, template, e)
raise ActionView::TemplateError.new(template, @view.assigns, e)
end
self.compile_time[render_symbol] = Time.now
@@ -59,27 +56,26 @@ module ActionView
# 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, file_name, local_assigns)
method_key = file_name || template
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, local_assigns)
if file_name && !@view.cache_template_loading
template_changed_since?(file_name, compile_time)
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, file_name)
method_key = file_name || template
@view.method_names[method_key] ||= compiled_method_name(template, file_name)
def assign_method_name(template)
@view.method_names[template.method_key] ||= compiled_method_name(template)
end
def compiled_method_name(template, file_name)
['_run', self.class.to_s.demodulize.underscore, compiled_method_name_file_path_segment(file_name)].compact.join('_').to_sym
def compiled_method_name(template)
['_run', self.class.to_s.demodulize.underscore, compiled_method_name_file_path_segment(template.filename)].compact.join('_').to_sym
end
def compiled_method_name_file_path_segment(file_name)
@@ -94,11 +90,11 @@ module ActionView
end
# Method to create the source code for a given template.
def create_template_source(template, render_symbol, locals)
body = compile(template)
def create_template_source(template, render_symbol)
body = compile(template.source)
self.template_args[render_symbol] ||= {}
locals_keys = self.template_args[render_symbol].keys | locals
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 = ""

View File

@@ -20,14 +20,17 @@ class CustomHandlerTest < Test::Unit::TestCase
end
def test_custom_render
result = @view.render_template( "foo", "hello <%= one %>", nil, :one => "two" )
template = ActionView::Template.new(@view, "hello <%= one %>", false, { :one => "two" }, true, "foo")
result = @view.render_template(template)
assert_equal(
[ "hello <%= one %>", { :one => "two" }, @view ],
result )
end
def test_custom_render2
result = @view.render_template( "foo2", "hello <%= one %>", nil, :one => "two" )
template = ActionView::Template.new(@view, "hello <%= one %>", false, { :one => "two" }, true, "foo2")
result = @view.render_template(template)
assert_equal(
[ "hello <%= one %>", { :one => "two" }, @view ],
result )
@@ -35,7 +38,8 @@ class CustomHandlerTest < Test::Unit::TestCase
def test_unhandled_extension
# uses the ERb handler by default if the extension isn't recognized
result = @view.render_template( "bar", "hello <%= one %>", nil, :one => "two" )
template = ActionView::Template.new(@view, "hello <%= one %>", false, { :one => "two" }, true, "bar")
result = @view.render_template(template)
assert_equal "hello two", result
end
end

View File

@@ -140,14 +140,6 @@ class TestController < ActionController::Base
:locals => { :local_name => name }
end
def accessing_local_assigns_in_inline_template_with_string_keys
name = params[:local_name]
ActionView::Base.local_assigns_support_string_keys = true
render :inline => "<%= 'Goodbye, ' + local_name %>",
:locals => { "local_name" => name }
ActionView::Base.local_assigns_support_string_keys = false
end
def formatted_html_erb
end
@@ -387,11 +379,6 @@ class RenderTest < Test::Unit::TestCase
assert_equal "Goodbye, Local David", @response.body
end
def test_accessing_local_assigns_in_inline_template_with_string_keys
get :accessing_local_assigns_in_inline_template_with_string_keys, :local_name => "Local David"
assert_equal "Goodbye, Local David", @response.body
end
def test_render_200_should_set_etag
get :render_hello_world_from_variable
assert_equal etag_for("hello david"), @response.headers['ETag']

View File

@@ -87,6 +87,10 @@ class CompiledTemplateTests < Test::Unit::TestCase
v.base_path = '.'
v.cache_template_loading = false
ta = ActionView::Template.new(v, @a, false, {})
tb = ActionView::Template.new(v, @b, false, {})
ts = ActionView::Template.new(v, @s, false, {})
@handler_class = ActionView::Base.handler_class_for_extension(:rhtml)
@handler = @handler_class.new(v)
@@ -99,15 +103,15 @@ class CompiledTemplateTests < Test::Unit::TestCase
assert @handler.send(:template_changed_since?, @b, t)
assert @handler.send(:template_changed_since?, @s, t) unless windows
assert @handler.send(:compile_template?, nil, @a, {})
assert @handler.send(:compile_template?, nil, @b, {})
assert @handler.send(:compile_template?, nil, @s, {}) unless windows
assert @handler.send(:compile_template?, ta)
assert @handler.send(:compile_template?, tb)
assert @handler.send(:compile_template?, ts) unless windows
# All templates are rendered at t+2
Time.expects(:now).times(windows ? 2 : 3).returns(t + 2.seconds)
v.send(:compile_and_render_template, @handler, '', @a)
v.send(:compile_and_render_template, @handler, '', @b)
v.send(:compile_and_render_template, @handler, '', @s) unless windows
v.send(:compile_and_render_template, @handler, ta)
v.send(:compile_and_render_template, @handler, tb)
v.send(:compile_and_render_template, @handler, ts) unless windows
a_n = v.method_names[@a]
b_n = v.method_names[@b]
s_n = v.method_names[@s] unless windows
@@ -122,12 +126,12 @@ class CompiledTemplateTests < Test::Unit::TestCase
assert !@handler.send(:template_changed_since?, @a, @handler.compile_time[a_n])
assert !@handler.send(:template_changed_since?, @b, @handler.compile_time[b_n])
assert !@handler.send(:template_changed_since?, @s, @handler.compile_time[s_n]) unless windows
assert !@handler.send(:compile_template?, nil, @a, {})
assert !@handler.send(:compile_template?, nil, @b, {})
assert !@handler.send(:compile_template?, nil, @s, {}) unless windows
v.send(:compile_and_render_template, @handler, '', @a)
v.send(:compile_and_render_template, @handler, '', @b)
v.send(:compile_and_render_template, @handler, '', @s) unless windows
assert !@handler.send(:compile_template?, ta)
assert !@handler.send(:compile_template?, tb)
assert !@handler.send(:compile_template?, ts) unless windows
v.send(:compile_and_render_template, @handler, ta)
v.send(:compile_and_render_template, @handler, tb)
v.send(:compile_and_render_template, @handler, ts) unless windows
# none of the files have changed since last compile
assert @handler.compile_time[a_n] < t + 3.seconds
assert @handler.compile_time[b_n] < t + 3.seconds
@@ -144,15 +148,15 @@ class CompiledTemplateTests < Test::Unit::TestCase
assert !@handler.send(:template_changed_since?, @a, @handler.compile_time[a_n])
assert !@handler.send(:template_changed_since?, @b, @handler.compile_time[b_n])
assert @handler.send(:template_changed_since?, @s, @handler.compile_time[s_n]) unless windows
assert !@handler.send(:compile_template?, nil, @a, {})
assert !@handler.send(:compile_template?, nil, @b, {})
assert @handler.send(:compile_template?, nil, @s, {}) unless windows
assert !@handler.send(:compile_template?, ta)
assert !@handler.send(:compile_template?, tb)
assert @handler.send(:compile_template?, ts) unless windows
# Only the symlink template gets rendered at t+3
Time.stubs(:now).returns(t + 3.seconds) unless windows
v.send(:compile_and_render_template, @handler, '', @a)
v.send(:compile_and_render_template, @handler, '', @b)
v.send(:compile_and_render_template, @handler, '', @s) unless windows
v.send(:compile_and_render_template, @handler, ta)
v.send(:compile_and_render_template, @handler, tb)
v.send(:compile_and_render_template, @handler, ts) unless windows
# the symlink has changed since last compile
assert @handler.compile_time[a_n] < t + 3.seconds
assert @handler.compile_time[b_n] < t + 3.seconds
@@ -170,14 +174,14 @@ class CompiledTemplateTests < Test::Unit::TestCase
assert !@handler.send(:template_changed_since?, @a, @handler.compile_time[a_n])
assert @handler.send(:template_changed_since?, @b, @handler.compile_time[b_n])
assert @handler.send(:template_changed_since?, @s, @handler.compile_time[s_n]) unless windows
assert !@handler.send(:compile_template?, nil, @a, {})
assert @handler.send(:compile_template?, nil, @b, {})
assert @handler.send(:compile_template?, nil, @s, {}) unless windows
assert !@handler.send(:compile_template?, ta)
assert @handler.send(:compile_template?, tb)
assert @handler.send(:compile_template?, ts) unless windows
Time.expects(:now).times(windows ? 1 : 2).returns(t + 5.seconds)
v.send(:compile_and_render_template, @handler, '', @a)
v.send(:compile_and_render_template, @handler, '', @b)
v.send(:compile_and_render_template, @handler, '', @s) unless windows
v.send(:compile_and_render_template, @handler, ta)
v.send(:compile_and_render_template, @handler, tb)
v.send(:compile_and_render_template, @handler, ts) unless windows
# the file at the end of the symlink has changed since last compile
# both the symlink and the file at the end of it should be recompiled
assert @handler.compile_time[a_n] < t + 5.seconds