template.rb 3.5 KB
Newer Older
1 2
# encoding: utf-8
# This is so that templates compiled in this file are UTF-8
3 4
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
5

6
module ActionView
7
  class Template
C
Carlhuda 已提交
8
    extend ActiveSupport::Autoload
J
Joshua Peek 已提交
9 10 11 12 13 14 15 16

    eager_autoload do
      autoload :Error
      autoload :Handler
      autoload :Handlers
      autoload :Text
    end

C
Carlhuda 已提交
17
    extend Template::Handlers
18 19

    attr_reader :source, :identifier, :handler, :virtual_path, :formats
20

21 22 23 24
    def initialize(source, identifier, handler, details)
      @source     = source
      @identifier = identifier
      @handler    = handler
25 26

      @virtual_path = details[:virtual_path]
27
      @method_names = {}
28

29 30
      format   = details[:format] || :html
      @formats = Array.wrap(format).map(&:to_sym)
31
    end
32

Y
Yehuda Katz 已提交
33
    def render(view, locals, &block)
34 35 36
      # Notice that we use a bang in this instrumentation because you don't want to
      # consume this in production. This is only slow if it's being listened to.
      ActiveSupport::Notifications.instrument("action_view.render_template!", :virtual_path => @virtual_path) do
37 38 39
        method_name = compile(locals, view)
        view.send(method_name, locals, &block)
      end
Y
Yehuda Katz 已提交
40
    rescue Exception => e
C
Carlhuda 已提交
41
      if e.is_a?(Template::Error)
Y
Yehuda Katz 已提交
42 43 44
        e.sub_template_of(self)
        raise e
      else
C
Carlhuda 已提交
45
        raise Template::Error.new(self, view.assigns, e)
Y
Yehuda Katz 已提交
46
      end
47
    end
48

49 50 51 52
    def mime_type
      @mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first
    end

53
    def variable_name
54
      @variable_name ||= @virtual_path[%r'_?(\w+)(\.\w+)*$', 1].to_sym
55 56
    end

57
    def counter_name
58
      @counter_name ||= "#{variable_name}_counter".to_sym
59
    end
60 61 62 63 64 65 66 67

    def inspect
      if defined?(Rails.root)
        identifier.sub("#{Rails.root}/", '')
      else
        identifier
      end
    end
68

69 70 71
    private
      def compile(locals, view)
        method_name = build_method_name(locals)
72

73
        return method_name if view.respond_to?(method_name)
74

75
        locals_code = locals.keys.map! { |key| "#{key} = local_assigns[:#{key}];" }.join
76

77 78 79 80 81 82
        code = @handler.call(self)
        if code.sub!(/\A(#.*coding.*)\n/, '')
          encoding_comment = $1
        elsif defined?(Encoding) && Encoding.respond_to?(:default_external)
          encoding_comment = "#coding:#{Encoding.default_external}"
        end
83

84 85
        source = <<-end_src
          def #{method_name}(local_assigns)
86
            _old_virtual_path, @_virtual_path = @_virtual_path, #{@virtual_path.inspect};_old_output_buffer = output_buffer;#{locals_code};#{code}
87
          ensure
J
José Valim 已提交
88
            @_virtual_path, self.output_buffer = _old_virtual_path, _old_output_buffer
89 90 91 92 93 94 95 96
          end
        end_src

        if encoding_comment
          source = "#{encoding_comment}\n#{source}"
          line = -1
        else
          line = 0
97
        end
98

99 100 101 102 103 104 105 106 107 108
        begin
          ActionView::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

C
Carlhuda 已提交
109
          raise ActionView::Template::Error.new(self, {}, e)
110
        end
111
      end
112

113 114 115 116 117
      def build_method_name(locals)
        # TODO: is locals.keys.hash reliably the same?
        @method_names[locals.keys.hash] ||=
          "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
      end
118
  end
119
end