From 692dbbf79387b56e241e1acd05f74f7d71ff79a6 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Wed, 6 Feb 2008 04:26:40 +0000 Subject: 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 --- actionpack/CHANGELOG | 2 + actionpack/lib/action_controller/base.rb | 4 +- actionpack/lib/action_view.rb | 1 + actionpack/lib/action_view/base.rb | 68 ++++++---------------- actionpack/lib/action_view/template.rb | 65 +++++++++++++++++++++ actionpack/lib/action_view/template_error.rb | 8 +-- actionpack/lib/action_view/template_handler.rb | 5 -- .../action_view/template_handlers/compilable.rb | 40 ++++++------- actionpack/test/controller/custom_handler_test.rb | 10 +++- actionpack/test/controller/render_test.rb | 13 ----- .../test/template/compiled_templates_test.rb | 52 +++++++++-------- 11 files changed, 145 insertions(+), 123 deletions(-) create mode 100644 actionpack/lib/action_view/template.rb diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index ec983f3ab7..53b32591c0 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -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] diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 1321b002eb..34c3ed3a60 100755 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -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) diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index d2d765787c..c3b846eece 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -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' diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index b362bb9555..9217d5ae0c 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -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 template_extension. # The hash in local_assigns 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 diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb new file mode 100644 index 0000000000..a814d7b4e5 --- /dev/null +++ b/actionpack/lib/action_view/template.rb @@ -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 diff --git a/actionpack/lib/action_view/template_error.rb b/actionpack/lib/action_view/template_error.rb index 54e4cf099d..33357d50cb 100644 --- a/actionpack/lib/action_view/template_error.rb +++ b/actionpack/lib/action_view/template_error.rb @@ -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 diff --git a/actionpack/lib/action_view/template_handler.rb b/actionpack/lib/action_view/template_handler.rb index 70fa02674f..687c0db4c6 100644 --- a/actionpack/lib/action_view/template_handler.rb +++ b/actionpack/lib/action_view/template_handler.rb @@ -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 diff --git a/actionpack/lib/action_view/template_handlers/compilable.rb b/actionpack/lib/action_view/template_handlers/compilable.rb index 6ab4f69558..110c90af0a 100644 --- a/actionpack/lib/action_view/template_handlers/compilable.rb +++ b/actionpack/lib/action_view/template_handlers/compilable.rb @@ -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 = "" diff --git a/actionpack/test/controller/custom_handler_test.rb b/actionpack/test/controller/custom_handler_test.rb index f3f2625daa..932b8c15c3 100644 --- a/actionpack/test/controller/custom_handler_test.rb +++ b/actionpack/test/controller/custom_handler_test.rb @@ -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 diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 4768dffba2..01a7ad6eec 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -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'] diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb index c46eed0775..ad002e4cce 100644 --- a/actionpack/test/template/compiled_templates_test.rb +++ b/actionpack/test/template/compiled_templates_test.rb @@ -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 -- cgit v1.2.3