Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 2368

Show
Ignore:
Timestamp:
09/27/05 16:45:39 (3 years ago)
Author:
ulysses
Message:

Streamline render process, code cleaning. Closes #2294.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/CHANGELOG

    r2363 r2368  
    11*SVN* 
     2 
     3* Streamline render process, code cleaning. Closes #2294. [skae] 
    24 
    35* Keep flash after components are rendered. #2291 [Rick Olson, Scott] 
  • trunk/actionpack/lib/action_view.rb

    r2058 r2368  
    2525require 'action_view/vendor/builder' 
    2626 
    27 require 'action_view/compiled_templates' 
    2827require 'action_view/base' 
    2928require 'action_view/partials' 
  • trunk/actionpack/lib/action_view/base.rb

    r2160 r2368  
    130130    cattr_accessor :erb_trim_mode 
    131131 
    132     @@cache_template_loading = false # Unused at the moment 
     132    # Specify whether file modification times should be checked to see if a template needs recompilation 
     133    @@cache_template_loading = false 
    133134    cattr_accessor :cache_template_loading 
    134135 
    135136    @@template_handlers = {} 
    136  
    137     @@compiled_templates = CompiledTemplates.new 
    138     include @@compiled_templates 
     137  
     138    module CompiledTemplates 
     139      # holds compiled template code 
     140    end 
     141    include CompiledTemplates 
     142 
     143    # maps inline templates to their method names  
     144    @@method_names = {} 
     145    # map method names to their compile time 
     146    @@compile_time = {} 
     147    # map method names to the names passed in local assigns so far 
     148    @@template_args = {} 
     149    # count the number of inline templates 
     150    @@inline_template_count = 0     
    139151 
    140152    class ObjectWrapper < Struct.new(:value) #:nodoc: 
     
    222234    end 
    223235 
    224     # Render the privded template with the given local assigns. If the template has not been rendered with the provided 
     236    # Render the provided template with the given local assigns. If the template has not been rendered with the provided 
    225237    # local assigns yet, or if the template has been updated on disk, then the template will be compiled to a method. 
    226238    # 
    227     # Either, but not both, of template and file_path may be nil. If file_path is given but template is nil, the template 
     239 
     240    # Either, but not both, of template and file_path may be nil. If file_path is given, the template 
    228241    # will only be read if it has to be compiled. 
    229242    # 
    230243    def compile_and_render_template(extension, template = nil, file_path = nil, local_assigns = {}) 
    231       file_path = File.expand_path(file_path) if file_path 
    232       identifier = file_path || template # either might be nil. Prefer to use the file_path as a key 
    233       names, params = split_locals(local_assigns) 
    234        
    235       compile = ! @@compiled_templates.compiled?(identifier, names) # Compile the template if it hasn't been done 
    236       if ! compile && file_path # If the file path is given, recompile if the mtime is new. 
    237         compiled_at = @@compiled_templates.mtime(identifier, names) 
    238         compile = compiled_at.nil? || (mtime = File.mtime(file_path)).nil? || compiled_at < mtime 
    239       end 
    240        
    241       if compile 
     244      # compile the given template, if necessary 
     245      if compile_template?(template, file_path, local_assigns) 
    242246        template ||= read_template_file(file_path, extension) 
    243         compile_template(extension, file_path || 'inline-template', identifier, template, names) 
    244       end 
    245  
    246       # Get the selector for this template and names, then call the method. 
    247       selector = @@compiled_templates.selector(identifier, names) 
     247        compile_template(extension, template, file_path, local_assigns) 
     248      end 
     249 
     250      # Get the method name for this template and run it 
     251      method_name = @@method_names[file_path || template] 
    248252      evaluate_assigns                                     
    249       send(selector, *params) do |*name| 
     253      send(method_name, local_assigns) do |*name| 
    250254        instance_variable_get "@content_for_#{name.first || 'layout'}" 
    251255      end 
     
    269273 
    270274    def erb_template_exists?(template_path)#:nodoc: 
    271       template_exists?(template_path, 'rhtml'
     275      template_exists?(template_path, :rhtml
    272276    end 
    273277 
    274278    def builder_template_exists?(template_path)#:nodoc: 
    275       template_exists?(template_path, 'rxml'
     279      template_exists?(template_path, :rxml
    276280    end 
    277281 
     
    291295 
    292296      def template_exists?(template_path, extension) 
    293         File.file?(full_template_path(template_path, extension)
    294       end 
    295  
    296       # This method reads a template file. No, it doesn't check mtimes, look to check if the template 
    297       # has been compiled, or check your date of birth. It reads the template file. Crazy, I know
     297        file_path = full_template_path(template_path, extension
     298        @@method_names.has_key?(file_path) || FileTest.exists?(file_path) 
     299      end 
     300 
     301      # This method reads a template file
    298302      def read_template_file(template_path, extension) 
    299303        File.read(template_path) 
    300       end 
    301  
    302       # Split the provided hash of local assigns into two arrays, one of the names, and another of the value 
    303       # The arrays are guarenteed to be in matching order, and also ordered the same for different hashes. 
    304       def split_locals(assigns) 
    305         names, values = [], [] 
    306         assigns.to_a.sort_by {|pair| pair.first.to_s}.each do |name, value| 
    307           names << name 
    308           values << value 
    309         end 
    310         return [names, values] 
    311304      end 
    312305 
     
    318311      end 
    319312 
    320       # Compile the template to a method using a CompiledTemplates instance. 
    321       def compile_template(extension, file_path, identifier, template, local_names = []) 
    322         line_no = 0 
    323  
    324         case extension && extension.to_sym 
    325           when :rxml 
    326             # Initialize the xml variable to the builder instance. 
    327             source_code = \ 
    328 "xml = Builder::XmlMarkup.new(:indent => 2) 
    329 @controller.headers['Content-Type'] ||= 'text/xml'\n" + template 
    330             line_no = -2 # offset extra line. 
    331           else # Assume rhtml 
    332             source_code = ERB.new(template, nil, @@erb_trim_mode).src 
    333         end 
    334  
    335         @@compiled_templates.compile_source(identifier, local_names, source_code, line_no, file_path) 
    336       end 
    337  
    338313      def delegate_render(handler, template, local_assigns) 
    339314        handler.new(self).render(template, local_assigns) 
     
    343318        @assigns.each { |key, value| instance_variable_set("@#{key}", value) } 
    344319      end 
     320 
     321 
     322      # Return true if the given template was compiled for a superset of the keys in local_assigns 
     323      def supports_local_assigns?(render_symbol, local_assigns) 
     324        local_assigns.empty? || 
     325          ((args = @@template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) }) 
     326      end 
     327       
     328      # Check whether compilation is necessary. 
     329      # Compile if the inline template or file has not been compiled yet. 
     330      # Or if local_assigns has a new key, which isn't supported by the compiled code yet. 
     331      # Or if the file has changed on disk and checking file mods hasn't been disabled.  
     332      def compile_template?(template, file_name, local_assigns) 
     333        method_key = file_name || template 
     334        render_symbol = @@method_names[method_key] 
     335        if @@compile_time[render_symbol] && supports_local_assigns?(render_symbol, local_assigns) 
     336          if file_name && !@@cache_template_loading  
     337            @@compile_time[render_symbol] < File.mtime(file_name) 
     338          end 
     339        else 
     340          true 
     341        end 
     342      end 
     343 
     344      # Create source code for given template 
     345      def create_template_source(extension, template, render_symbol, locals) 
     346        # logger.debug "Creating source for :#{render_symbol}" if logger 
     347        if extension && (extension.to_sym == :rxml) 
     348          body = "xml = Builder::XmlMarkup.new(:indent => 2)\n" + 
     349                 "@controller.headers['Content-Type'] ||= 'text/xml'\n" + 
     350                 template 
     351        else 
     352          body = ERB.new(template, nil, @@erb_trim_mode).src 
     353        end 
     354        @@template_args[render_symbol] ||= {} 
     355        locals_keys = @@template_args[render_symbol].keys | locals 
     356        @@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h } 
     357        locals_code = locals_keys.inject("") do |code, key| 
     358          code << "#{key} = local_assigns[:#{key}] if local_assigns.has_key?(:#{key})\n" 
     359        end 
     360        "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend" 
     361      end 
     362 
     363      def assign_method_name(extension, template, file_name) 
     364        method_name = '_run_' 
     365        if extension && (extension.to_sym == :rxml) 
     366          method_name << 'xml_' 
     367        else 
     368          method_name << 'html_' 
     369        end 
     370        if file_name 
     371          file_path = File.expand_path(file_name) 
     372          base_path = File.expand_path(@base_path) 
     373          i = file_path.index(base_path) 
     374          l = base_path.length 
     375          method_name_file_part = i ? file_path[i+l+1,file_path.length-l-1] : file_path.clone 
     376          method_name_file_part.sub!(/\.r(ht|x)ml$/,'') 
     377          method_name_file_part.tr!('/:-', '_') 
     378          method_name_file_part.gsub!(/[^a-zA-Z0-9_]/){|s| s[0].to_s} 
     379          method_name += method_name_file_part 
     380        else 
     381          @@inline_template_count += 1 
     382          method_name << @@inline_template_count.to_s 
     383        end 
     384        @@method_names[file_name || template] = method_name.intern 
     385      end 
     386 
     387      def compile_template(extension, template, file_name, local_assigns) 
     388        method_key = file_name || template 
     389        render_symbol = @@method_names[method_key] || assign_method_name(extension, template, file_name) 
     390        render_source = create_template_source(extension, template, render_symbol, local_assigns.keys) 
     391        line_offset = @@template_args[render_symbol].size 
     392        line_offset += 2 if extension && (extension.to_sym == :rxml) 
     393        begin 
     394          if file_name 
     395            CompiledTemplates.module_eval(render_source, file_name, -line_offset) 
     396          else 
     397            CompiledTemplates.module_eval(render_source) 
     398          end 
     399        rescue Object => e 
     400          if logger 
     401            logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" 
     402            logger.debug "Function body: #{render_source}" 
     403            logger.debug "Backtrace: #{e.backtrace.join("\n")}" 
     404          end 
     405          raise TemplateError.new(@base_path, method_key, @assigns, template, e) 
     406        end 
     407 
     408        @@compile_time[render_symbol] = Time.now 
     409        logger.debug "Compiled template #{method_key}\n  ==> #{render_symbol}" if logger 
     410        # logger.debug  "CompiledTemplates methods: #{CompiledTemplates.instance_methods.sort.inspect}" if logger 
     411      end 
     412 
    345413  end 
    346414end 
  • trunk/actionpack/lib/action_view/partials.rb

    r2160 r2368  
    9090 
    9191      def partial_counter_name(partial_name) 
    92         "#{partial_name.split('/').last}_counter" 
     92        "#{partial_name.split('/').last}_counter".intern 
    9393      end 
    9494       
  • trunk/actionpack/test/controller/custom_handler_test.rb

    r1874 r2368  
    2020 
    2121  def test_custom_render 
    22     result = @view.render_template( "foo", "hello <%= one %>", nil, "one" => "two" ) 
     22    result = @view.render_template( "foo", "hello <%= one %>", nil, :one => "two" ) 
    2323    assert_equal( 
    24       [ "hello <%= one %>", { "one" => "two" }, @view ], 
     24      [ "hello <%= one %>", { :one => "two" }, @view ], 
    2525      result ) 
    2626  end 
     
    2828  def test_unhandled_extension 
    2929    # uses the ERb handler by default if the extension isn't recognized 
    30     result = @view.render_template( "bar", "hello <%= one %>", nil, "one" => "two" ) 
     30    result = @view.render_template( "bar", "hello <%= one %>", nil, :one => "two" ) 
    3131    assert_equal "hello two", result 
    3232  end