5ff5569db6fcb3b4cbddb18de7e4eee14931387b
[depot.git] / renderable.rb
1 module ActionView
2 # NOTE: The template that this mixin is being included into is frozen
3 # so you cannot set or modify any instance variables
4 module Renderable #:nodoc:
5 extend ActiveSupport::Memoizable
6
7 def self.included(base)
8 @@mutex = Mutex.new
9 end
10
11 def filename
12 'compiled-template'
13 end
14
15 def handler
16 Template.handler_class_for_extension(extension)
17 end
18 memoize :handler
19
20 def compiled_source
21 handler.call(self)
22 end
23 memoize :compiled_source
24
25 def render(view, local_assigns = {})
26 compile(local_assigns)
27
28 stack = view.instance_variable_get(:@_render_stack)
29 stack.push(self)
30
31 # This is only used for TestResponse to set rendered_template
32 unless is_a?(InlineTemplate) || view.instance_variable_get(:@_first_render)
33 view.instance_variable_set(:@_first_render, self)
34 end
35
36 view.send(:_evaluate_assigns_and_ivars)
37 view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type)
38
39 result = view.send(method_name(local_assigns), local_assigns) do |*names|
40 ivar = :@_proc_for_layout
41 if !view.instance_variable_defined?(:"@content_for_#{names.first}") && view.instance_variable_defined?(ivar) && (proc = view.instance_variable_get(ivar))
42 view.capture(*names, &proc)
43 elsif view.instance_variable_defined?(ivar = :"@content_for_#{names.first || :layout}")
44 view.instance_variable_get(ivar)
45 end
46 end
47
48 stack.pop
49 result
50 end
51
52 def method_name(local_assigns)
53 if local_assigns && local_assigns.any?
54 local_assigns_keys = "locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}"
55 end
56 ['_run', extension, method_segment, local_assigns_keys].compact.join('_').to_sym
57 end
58
59 private
60 # Compile and evaluate the template's code (if necessary)
61 def compile(local_assigns)
62 render_symbol = method_name(local_assigns)
63
64 @@mutex.synchronize do
65 if recompile?(render_symbol)
66 compile!(render_symbol, local_assigns)
67 end
68 end
69 end
70
71 def compile!(render_symbol, local_assigns)
72 locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join
73
74 source = <<-end_src
75 def #{render_symbol}(local_assigns)
76 old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
77 ensure
78 self.output_buffer = old_output_buffer
79 end
80 end_src
81
82 begin
83 ActionView::Base::CompiledTemplates.module_eval(source, filename, 0)
84 rescue Exception => e # errors from template code
85 if logger = defined?(ActionController) && Base.logger
86 logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
87 logger.debug "Function body: #{source}"
88 logger.debug "Backtrace: #{e.backtrace.join("\n")}"
89 end
90
91 raise ActionView::TemplateError.new(self, {}, e)
92 end
93 end
94
95 # Method to check whether template compilation is necessary.
96 # The template will be compiled if the file has not been compiled yet, or
97 # if local_assigns has a new key, which isn't supported by the compiled code yet.
98 def recompile?(symbol)
99 !(ActionView::PathSet::Path.eager_load_templates? && Base::CompiledTemplates.method_defined?(symbol))
100 end
101 end
102 end