eager_autoload do
autoload :Error
autoload :Handlers
autoload :HTML
autoload :Text
autoload :Types
end
extend Template::Handlers
attr_accessor :locals, :formats, :variants, :virtual_path
attr_reader :source, :identifier, :handler, :original_encoding, :updated_at
Finalizer = proc do |method_name, mod|
proc do
mod.module_eval do
remove_possible_method method_name
end
end
end
def initialize(source, identifier, handler, details)
format = details[:format] || (handler.default_format if handler.respond_to?(:default_format))
@source = source
@identifier = identifier
@handler = handler
@compiled = false
@original_encoding = nil
@locals = details[:locals] || []
@virtual_path = details[:virtual_path]
@updated_at = details[:updated_at] || Time.now
@formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f }
@variants = [details[:variant]]
@compile_mutex = Mutex.new
end
def supports_streaming?
handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
end
def render(view, locals, buffer = nil, &block)
instrument_render_template do
compile!(view)
view.send(method_name, locals, buffer, &block)
end
rescue => e
handle_render_error(view, e)
end
def type
@type ||= Types[@formats.first] if @formats.first
end
def refresh(view)
raise "A template needs to have a virtual path in order to be refreshed" unless @virtual_path
lookup = view.lookup_context
pieces = @virtual_path.split("/")
name = pieces.pop
partial = !!name.sub!(/^_/, "")
lookup.disable_cache do
lookup.find_template(name, [ pieces.join("/") ], partial, @locals)
end
end
def inspect
@inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "".freeze) : identifier
end
def encode!
return unless source.encoding == Encoding::BINARY
if source.sub!(/\A#{ENCODING_FLAG}/, "")
encoding = magic_encoding = $1
else
encoding = Encoding.default_external
end
source.force_encoding(encoding)
if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
source
elsif source.valid_encoding?
source.encode!
else
raise WrongEncodingError.new(source, encoding)
end
end
private
def compile!(view)
return if @compiled
@compile_mutex.synchronize do
return if @compiled
if view.is_a?(ActionView::CompiledTemplates)
mod = ActionView::CompiledTemplates
else
mod = view.singleton_class
end
instrument("!compile_template") do
compile(mod)
end
@source = nil if @virtual_path
@compiled = true
end
end
def compile(mod)
encode!
code = @handler.call(self)
source = def #{method_name}(local_assigns, output_buffer) _old_virtual_path, @virtual_path = @virtual_path,
source.force_encoding(code.encoding)
source.encode!
unless source.valid_encoding?
raise WrongEncodingError.new(@source, Encoding.default_internal)
end
mod.module_eval(source, identifier, 0)
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
end
def handle_render_error(view, e)
if e.is_a?(Template::Error)
e.sub_template_of(self)
raise e
else
template = self
unless template.source
template = refresh(view)
template.encode!
end
raise Template::Error.new(template)
end
end
def locals_code
locals = @locals - Module::RUBY_RESERVED_KEYWORDS
locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\00--\1177])+\z/)
locals.each_with_object("".dup) { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
end
def method_name
@method_name ||= begin
m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".dup
m.tr!("-".freeze, "_".freeze)
m
end
end
def identifier_method_name
inspect.tr("^a-z_".freeze, "_".freeze)
end
def instrument(action, &block)
ActiveSupport::Notifications.instrument("#{action}.action_view", instrument_payload, &block)
end
def instrument_render_template(&block)
ActiveSupport::Notifications.instrument("!render_template.action_view".freeze, instrument_payload, &block)
end
def instrument_payload
{ virtual_path: @virtual_path, identifier: @identifier }
end
end