Skip to content
This repository
tree: 2703d55935
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 643 lines (562 sloc) 27.175 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
module ActionView #:nodoc:
  class ActionViewError < StandardError #:nodoc:
  end

  # Action View templates can be written in three ways. If the template file has a +.erb+ (or +.rhtml+) extension then it uses a mixture of ERb
  # (included in Ruby) and HTML. If the template file has a +.builder+ (or +.rxml+) extension then Jim Weirich's Builder::XmlMarkup library is used.
  # If the template file has a +.rjs+ extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.
  #
  # = ERb
  #
  # You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
  # following loop for names:
  #
  # <b>Names of all the people</b>
  # <% for person in @people %>
  # Name: <%= person.name %><br/>
  # <% end %>
  #
  # The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this
  # is not just a usage suggestion. Regular output functions like print or puts won't work with ERb templates. So this would be wrong:
  #
  # Hi, Mr. <% puts "Frodo" %>
  #
  # If you absolutely must write from within a function, you can use the TextHelper#concat
  #
  # <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>.
  #
  # == Using sub templates
  #
  # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The
  # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts):
  #
  # <%= render "shared/header" %>
  # Something really specific and terrific
  # <%= render "shared/footer" %>
  #
  # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the
  # result of the rendering. The output embedding writes it to the current template.
  #
  # But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance
  # variables defined using the regular embedding tags. Like this:
  #
  # <% @page_title = "A Wonderful Hello" %>
  # <%= render "shared/header" %>
  #
  # Now the header can pick up on the @page_title variable and use it for outputting a title tag:
  #
  # <title><%= @page_title %></title>
  #
  # == Passing local variables to sub templates
  #
  # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
  #
  # <%= render "shared/header", { :headline => "Welcome", :person => person } %>
  #
  # These can now be accessed in shared/header with:
  #
  # Headline: <%= headline %>
  # First name: <%= person.first_name %>
  #
  # If you need to find out whether a certain local variable has been assigned a value in a particular render call,
  # you need to use the following pattern:
  #
  # <% if local_assigns.has_key? :headline %>
  # Headline: <%= headline %>
  # <% end %>
  #
  # Testing using <tt>defined? headline</tt> will not work. This is an implementation restriction.
  #
  # == Template caching
  #
  # By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will
  # check the file's modification time and recompile it.
  #
  # == Builder
  #
  # Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An +XmlMarkup+ object
  # named +xml+ is automatically made available to templates with a +.builder+ extension.
  #
  # Here are some basic examples:
  #
  # xml.em("emphasized") # => <em>emphasized</em>
  # xml.em { xml.b("emph & bold") } # => <em><b>emph &amp; bold</b></em>
  # xml.a("A Link", "href"=>"http://onestepback.org") # => <a href="http://onestepback.org">A Link</a>
  # xml.target("name"=>"compile", "option"=>"fast") # => <target option="fast" name="compile"\>
  # # NOTE: order of attributes is not specified.
  #
  # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
  #
  # xml.div {
  # xml.h1(@person.name)
  # xml.p(@person.bio)
  # }
  #
  # would produce something like:
  #
  # <div>
  # <h1>David Heinemeier Hansson</h1>
  # <p>A product of Danish Design during the Winter of '79...</p>
  # </div>
  #
  # A full-length RSS example actually used on Basecamp:
  #
  # xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
  # xml.channel do
  # xml.title(@feed_title)
  # xml.link(@url)
  # xml.description "Basecamp: Recent items"
  # xml.language "en-us"
  # xml.ttl "40"
  #
  # for item in @recent_items
  # xml.item do
  # xml.title(item_title(item))
  # xml.description(item_description(item)) if item_description(item)
  # xml.pubDate(item_pubDate(item))
  # xml.guid(@person.firm.account.url + @recent_items.url(item))
  # xml.link(@person.firm.account.url + @recent_items.url(item))
  #
  # xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
  # end
  # end
  # end
  # end
  #
  # More builder documentation can be found at http://builder.rubyforge.org.
  #
  # == JavaScriptGenerator
  #
  # JavaScriptGenerator templates end in +.rjs+. Unlike conventional templates which are used to
  # render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to
  # modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax
  # and make updates to the page where the request originated from.
  #
  # An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block.
  #
  # When an .rjs action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example:
  #
  # link_to_remote :url => {:action => 'delete'}
  #
  # The subsequently rendered +delete.rjs+ might look like:
  #
  # page.replace_html 'sidebar', :partial => 'sidebar'
  # page.remove "person-#{@person.id}"
  # page.visual_effect :highlight, 'user-list'
  #
  # This refreshes the sidebar, removes a person element and highlights the user list.
  #
  # See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
  class Base
    include ERB::Util

    attr_reader :first_render
    attr_accessor :base_path, :assigns, :template_extension
    attr_accessor :controller, :view_paths

    attr_reader :logger, :response, :headers
    attr_internal :cookies, :flash, :headers, :params, :request, :response, :session
    
    attr_writer :template_format

    # Specify trim mode for the ERB compiler. Defaults to '-'.
    # See ERb documentation for suitable values.
    @@erb_trim_mode = '-'
    cattr_accessor :erb_trim_mode

    # Specify whether file modification times should be checked to see if a template needs recompilation
    @@cache_template_loading = false
    cattr_accessor :cache_template_loading

    # Specify whether file extension lookup should be cached, and whether template base path lookup should be cached.
    # 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).
    @@debug_rjs = false
    cattr_accessor :debug_rjs
    
    @@erb_variable = '_erbout'
    cattr_accessor :erb_variable
    
    delegate :request_forgery_protection_token, :to => :controller

    @@template_handlers = HashWithIndifferentAccess.new
 
    module CompiledTemplates #:nodoc:
      # holds compiled template code
    end
    include CompiledTemplates

    # Maps inline templates to their method names
    @@method_names = {}
    # Map method names to their compile time
    @@compile_time = {}
    # Map method names to the names passed in local assigns so far
    @@template_args = {}
    # Count the number of inline templates
    @@inline_template_count = 0
    # Maps template paths without extension to their file extension returned by pick_template_extension.
    # If for a given path, path.ext1 and path.ext2 exist on the file system, the order of extensions
    # used by pick_template_extension determines whether ext1 or ext2 will be stored.
    @@cached_template_extension = {}
    # Maps template paths / extensions to
    @@cached_base_paths = {}

    # Cache public asset paths
    cattr_reader :computed_public_paths
    @@computed_public_paths = {}

    @@template_handlers = {}
    @@default_template_handlers = nil

    class ObjectWrapper < Struct.new(:value) #:nodoc:
    end

    def self.load_helpers #:nodoc:
      Dir.entries("#{File.dirname(__FILE__)}/helpers").sort.each do |file|
        next unless file =~ /^([a-z][a-z_]*_helper).rb$/
        require "action_view/helpers/#{$1}"
        helper_module_name = $1.camelize
        if Helpers.const_defined?(helper_module_name)
          include Helpers.const_get(helper_module_name)
        end
      end
    end

    # Register a class that knows how to handle template files with the given
    # extension. This can be used to implement new template types.
    # The constructor for the class must take the ActiveView::Base instance
    # as a parameter, and the class must implement a #render method that
    # takes the contents of the template to render as well as the Hash of
    # local assigns available to the template. The #render method ought to
    # return the rendered template as a string.
    def self.register_template_handler(extension, klass)
      @@template_handlers[extension.to_sym] = klass
    end

    def self.template_handler_extensions
      @@template_handler_extensions ||= @@template_handlers.keys.map(&:to_s).sort
    end

    def self.register_default_template_handler(extension, klass)
      register_template_handler(extension, klass)
      @@default_template_handlers = klass
    end

    def self.handler_for_extension(extension)
      (extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers
    end

    register_default_template_handler :erb, TemplateHandlers::ERB
    register_template_handler :rjs, TemplateHandlers::RJS
    register_template_handler :builder, TemplateHandlers::Builder

    # TODO: Depreciate old template extensions
    register_template_handler :rhtml, TemplateHandlers::ERB
    register_template_handler :rxml, TemplateHandlers::Builder

    def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc:
      @view_paths = view_paths.respond_to?(:find) ? view_paths.dup : [*view_paths].compact
      @assigns = assigns_for_first_render
      @assigns_added = nil
      @controller = controller
      @logger = controller && controller.logger
    end

    # Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true,
    # it's relative to the view_paths array, otherwise it's absolute. The hash in <tt>local_assigns</tt>
    # is made available as local variables.
    def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc:
      if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/")
        raise ActionViewError, <<-END_ERROR
Due to changes in ActionMailer, you need to provide the mailer_name along with the template name.

render "user_mailer/signup"
render :file => "user_mailer/signup"

If you are rendering a subtemplate, you must now use controller-like partial syntax:

render :partial => 'signup' # no mailer_name necessary
END_ERROR
      end
      
      @first_render ||= template_path
      template_path_without_extension, template_extension = path_and_extension(template_path)
      if use_full_path
        if template_extension
          template_file_name = full_template_path(template_path_without_extension, template_extension)
        else
          template_extension = pick_template_extension(template_path).to_s
          unless template_extension
            raise ActionViewError, "No template found for #{template_path} in #{view_paths.inspect}"
          end
          template_file_name = full_template_path(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 #{view_paths.inspect}"
      end

      begin
        render_template(template_extension, template_source, template_file_name, local_assigns)
      rescue Exception => e
        if TemplateError === e
          e.sub_template_of(template_file_name)
          raise e
        else
          raise TemplateError.new(find_base_path_for("#{template_path_without_extension}.#{template_extension}") || view_paths.first, template_file_name, @assigns, template_source, e)
        end
      end
    end
    
    # Renders the template present at <tt>template_path</tt> (relative to the view_paths array).
    # The hash in <tt>local_assigns</tt> is made available as local variables.
    def render(options = {}, old_local_assigns = {}, &block) #:nodoc:
      if options.is_a?(String)
        render_file(options, true, old_local_assigns)
      elsif options == :update
        update_page(&block)
      elsif options.is_a?(Hash)
        options = options.reverse_merge(:locals => {}, :use_full_path => true)

        if options[:layout]
          path, partial_name = partial_pieces(options.delete(:layout))

          if block_given?
            @content_for_layout = capture(&block)
            concat(render(options.merge(:partial => "#{path}/#{partial_name}")), block.binding)
          else
            @content_for_layout = render(options)
            render(options.merge(:partial => "#{path}/#{partial_name}"))
          end
        elsif options[:file]
          render_file(options[:file], options[:use_full_path], options[:locals])
        elsif options[:partial] && options[:collection]
          render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals])
        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])
        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_for_extension(template_extension)

      if template_handler_is_compilable?(handler)
        compile_and_render_template(handler, template, file_path, local_assigns)
      else
        template ||= read_template_file(file_path, template_extension) # Make sure that a lazyily-read template is loaded.
        delegate_render(handler, template, local_assigns)
      end
    end

    # Gets the full template path with base path for the given template_path and extension.
    #
    # full_template_path('users/show', 'html.erb')
    # # => '~/rails/app/views/users/show.html.erb
    #
    def full_template_path(template_path, extension)
      if @@cache_template_extensions
        (@@cached_base_paths[template_path] ||= {})[extension.to_s] ||= find_full_template_path(template_path, extension)
      else
        find_full_template_path(template_path, extension)
      end
    end

    # Gets the extension for an existing template with the given template_path.
    # Returns the format with the extension if that template exists.
    #
    # pick_template_extension('users/show')
    # # => 'html.erb'
    #
    # pick_template_extension('users/legacy')
    # # => "rhtml"
    #
    def pick_template_extension(template_path)#:nodoc:
      if @@cache_template_extensions
        (@@cached_template_extension[template_path] ||= {})[template_format] ||= find_template_extension_for(template_path)
      else
        find_template_extension_for(template_path)
      end
    end

    def file_exists?(template_path)#:nodoc:
      template_file_name, template_file_extension = path_and_extension(template_path)
      if template_file_extension
        template_exists?(template_file_name, template_file_extension)
      else
        template_exists?(template_file_name, pick_template_extension(template_path))
      end
    end

    # Returns true is the file may be rendered implicitly.
    def file_public?(template_path)#:nodoc:
      template_path.split('/').last[0,1] != '_'
    end

    # symbolized version of the :format parameter of the request, or :html by default.
    def template_format
      return @template_format if @template_format
      format = controller && controller.respond_to?(:request) && controller.request.parameters[:format]
      @template_format = format.blank? ? :html : format.to_sym
    end

    # Adds a view_path to the front of the view_paths array.
    # This change affects the current request only.
    #
    # @template.prepend_view_path("views/default")
    # @template.prepend_view_path(["views/default", "views/custom"])
    #
    def prepend_view_path(path)
      @view_paths.unshift(*path)
    end
    
    # Adds a view_path to the end of the view_paths array.
    # This change affects the current request only.
    #
    # @template.append_view_path("views/default")
    # @template.append_view_path(["views/default", "views/custom"])
    #
    def append_view_path(path)
      @view_paths.push(*path)
    end

    private
      def find_full_template_path(template_path, extension)
        file_name = "#{template_path}.#{extension}"
        base_path = find_base_path_for(file_name)
        base_path.blank? ? "" : "#{base_path}/#{file_name}"
      end

      # Asserts the existence of a template.
      def template_exists?(template_path, extension)
        file_path = full_template_path(template_path, extension)
        !file_path.blank? && @@method_names.has_key?(file_path) || File.exist?(file_path)
      end

      # Splits the path and extension from the given template_path and returns as an array.
      def path_and_extension(template_path)
        template_path_without_extension = template_path.sub(/\.(\w+)$/, '')
        [ template_path_without_extension, $1 ]
      end
      
      # Returns the view path that contains the given relative template path.
      def find_base_path_for(template_file_name)
        view_paths.find { |p| File.file?(File.join(p, template_file_name)) }
      end

      # Returns the view path that the full path resides in.
      def extract_base_path_from(full_path)
        view_paths.find { |p| full_path[0..p.size - 1] == p }
      end

      # Determines the template's file extension, such as rhtml, rxml, or rjs.
      def find_template_extension_for(template_path)
        find_template_extension_from_handler(template_path, true) ||
        find_template_extension_from_handler(template_path) ||
        find_template_extension_from_first_render()
      end

      def find_template_extension_from_handler(template_path, formatted = nil)
        checked_template_path = formatted ? "#{template_path}.#{template_format}" : template_path

        self.class.template_handler_extensions.each do |extension|
          if template_exists?(checked_template_path, extension)
            return formatted ? "#{template_format}.#{extension}" : extension.to_s
          end
        end
        nil
      end
      
      # Determine the template extension from the <tt>@first_render</tt> filename
      def find_template_extension_from_first_render
        File.basename(@first_render.to_s)[/^[^.]+\.(.+)$/, 1]
      end

      # This method reads a template file.
      def read_template_file(template_path, extension)
        File.read(template_path)
      end

      # Evaluate the local assigns and pushes them to the view.
      def evaluate_assigns
        unless @assigns_added
          assign_variables_from_controller
          @assigns_added = true
        end
      end

      def delegate_render(handler, template, local_assigns)
        handler.new(self).render(template, local_assigns)
      end

      def delegate_compile(handler, template)
        handler.new(self).compile(template)
      end

      def template_handler_is_compilable?(handler)
        handler.new(self).respond_to?(:compile)
      end

      # Assigns instance variables from the controller to the view.
      def assign_variables_from_controller
        @assigns.each { |key, value| instance_variable_set("@#{key}", value) }
      end


      # Return true if the given template was compiled for a superset of the keys in local_assigns
      def supports_local_assigns?(render_symbol, local_assigns)
        local_assigns.empty? ||
          ((args = @@template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) })
      end

      # Method to check whether template compilation is necessary.
      # 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
        render_symbol = @@method_names[method_key]

        compile_time = @@compile_time[render_symbol]
        if compile_time && supports_local_assigns?(render_symbol, local_assigns)
          if file_name && !@@cache_template_loading
            template_changed_since?(file_name, compile_time)
          end
        else
          true
        end
      end

      # Method to handle checking a whether a template has changed since last compile; isolated so that templates
      # not stored on the file system can hook and extend appropriately.
      def template_changed_since?(file_name, compile_time)
        lstat = File.lstat(file_name)
        compile_time < lstat.mtime ||
          (lstat.symlink? && compile_time < File.stat(file_name).mtime)
      end

      # Method to create the source code for a given template.
      def create_template_source(handler, template, render_symbol, locals)
        body = delegate_compile(handler, template)

        @@template_args[render_symbol] ||= {}
        locals_keys = @@template_args[render_symbol].keys | locals
        @@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h }

        locals_code = ""
        locals_keys.each do |key|
          locals_code << "#{key} = local_assigns[:#{key}]\n"
        end

        "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend"
      end

      def assign_method_name(handler, template, file_name)
        method_key = file_name || template
        @@method_names[method_key] ||= compiled_method_name(handler, template, file_name)
      end

      def compiled_method_name(handler, template, file_name)
        ['_run', handler.to_s.demodulize.underscore, compiled_method_name_file_path_segment(file_name)].compact.join('_').to_sym
      end

      def compiled_method_name_file_path_segment(file_name)
        if file_name
          s = File.expand_path(file_name)
          s.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT)
          s.gsub!(/([^a-zA-Z0-9_])/) { $1.ord }
          s
        else
          (@@inline_template_count += 1).to_s
        end
      end

      # Compile and evaluate the template's code
      def compile_template(handler, template, file_name, local_assigns)
        render_symbol = assign_method_name(handler, template, file_name)
        render_source = create_template_source(handler, template, render_symbol, local_assigns.keys)
        line_offset = @@template_args[render_symbol].size + handler.line_offset

        begin
          file_name = 'compiled-template' if file_name.blank?
          CompiledTemplates.module_eval(render_source, file_name, -line_offset)
        rescue Exception => e # errors from template code
          if logger
            logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
            logger.debug "Function body: #{render_source}"
            logger.debug "Backtrace: #{e.backtrace.join("\n")}"
          end

          raise TemplateError.new(extract_base_path_from(file_name) || view_paths.first, file_name || template, @assigns, template, e)
        end

        @@compile_time[render_symbol] = Time.now
        # logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger
      end

      # Render the provided template with the given local assigns. If the template has not been rendered with the provided
      # local assigns yet, or if the template has been updated on disk, then the template will be compiled to a method.
      #
      # 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

        # compile the given template, if necessary
        if compile_template?(template, file_path, local_assigns)
          template ||= read_template_file(file_path, nil)
          compile_template(handler, template, file_path, local_assigns)
        end

        # Get the method name for this template and run it
        method_name = @@method_names[file_path || template]
        evaluate_assigns

        send(method_name, local_assigns) do |*name|
          instance_variable_get "@content_for_#{name.first || 'layout'}"
        end
      end
  end
end
Something went wrong with that request. Please try again.