PageRenderTime 90ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/rspec/ruby/1.9.1/gems/actionpack-3.2.5/lib/action_view/template.rb

https://github.com/corymatthew/demo_app
Ruby | 337 lines | 149 code | 35 blank | 153 comment | 20 complexity | b1a5cd56923244d4aea975ea120d5551 MD5 | raw file
  1. require 'active_support/core_ext/array/wrap'
  2. require 'active_support/core_ext/object/blank'
  3. require 'active_support/core_ext/object/try'
  4. require 'active_support/core_ext/kernel/singleton_class'
  5. require 'thread'
  6. module ActionView
  7. # = Action View Template
  8. class Template
  9. extend ActiveSupport::Autoload
  10. # === Encodings in ActionView::Template
  11. #
  12. # ActionView::Template is one of a few sources of potential
  13. # encoding issues in Rails. This is because the source for
  14. # templates are usually read from disk, and Ruby (like most
  15. # encoding-aware programming languages) assumes that the
  16. # String retrieved through File IO is encoded in the
  17. # <tt>default_external</tt> encoding. In Rails, the default
  18. # <tt>default_external</tt> encoding is UTF-8.
  19. #
  20. # As a result, if a user saves their template as ISO-8859-1
  21. # (for instance, using a non-Unicode-aware text editor),
  22. # and uses characters outside of the ASCII range, their
  23. # users will see diamonds with question marks in them in
  24. # the browser.
  25. #
  26. # For the rest of this documentation, when we say "UTF-8",
  27. # we mean "UTF-8 or whatever the default_internal encoding
  28. # is set to". By default, it will be UTF-8.
  29. #
  30. # To mitigate this problem, we use a few strategies:
  31. # 1. If the source is not valid UTF-8, we raise an exception
  32. # when the template is compiled to alert the user
  33. # to the problem.
  34. # 2. The user can specify the encoding using Ruby-style
  35. # encoding comments in any template engine. If such
  36. # a comment is supplied, Rails will apply that encoding
  37. # to the resulting compiled source returned by the
  38. # template handler.
  39. # 3. In all cases, we transcode the resulting String to
  40. # the UTF-8.
  41. #
  42. # This means that other parts of Rails can always assume
  43. # that templates are encoded in UTF-8, even if the original
  44. # source of the template was not UTF-8.
  45. #
  46. # From a user's perspective, the easiest thing to do is
  47. # to save your templates as UTF-8. If you do this, you
  48. # do not need to do anything else for things to "just work".
  49. #
  50. # === Instructions for template handlers
  51. #
  52. # The easiest thing for you to do is to simply ignore
  53. # encodings. Rails will hand you the template source
  54. # as the default_internal (generally UTF-8), raising
  55. # an exception for the user before sending the template
  56. # to you if it could not determine the original encoding.
  57. #
  58. # For the greatest simplicity, you can support only
  59. # UTF-8 as the <tt>default_internal</tt>. This means
  60. # that from the perspective of your handler, the
  61. # entire pipeline is just UTF-8.
  62. #
  63. # === Advanced: Handlers with alternate metadata sources
  64. #
  65. # If you want to provide an alternate mechanism for
  66. # specifying encodings (like ERB does via <%# encoding: ... %>),
  67. # you may indicate that you will handle encodings yourself
  68. # by implementing <tt>self.handles_encoding?</tt>
  69. # on your handler.
  70. #
  71. # If you do, Rails will not try to encode the String
  72. # into the default_internal, passing you the unaltered
  73. # bytes tagged with the assumed encoding (from
  74. # default_external).
  75. #
  76. # In this case, make sure you return a String from
  77. # your handler encoded in the default_internal. Since
  78. # you are handling out-of-band metadata, you are
  79. # also responsible for alerting the user to any
  80. # problems with converting the user's data to
  81. # the <tt>default_internal</tt>.
  82. #
  83. # To do so, simply raise the raise +WrongEncodingError+
  84. # as follows:
  85. #
  86. # raise WrongEncodingError.new(
  87. # problematic_string,
  88. # expected_encoding
  89. # )
  90. eager_autoload do
  91. autoload :Error
  92. autoload :Handlers
  93. autoload :Text
  94. end
  95. extend Template::Handlers
  96. attr_accessor :locals, :formats, :virtual_path
  97. attr_reader :source, :identifier, :handler, :original_encoding, :updated_at
  98. # This finalizer is needed (and exactly with a proc inside another proc)
  99. # otherwise templates leak in development.
  100. Finalizer = proc do |method_name, mod|
  101. proc do
  102. mod.module_eval do
  103. remove_possible_method method_name
  104. end
  105. end
  106. end
  107. def initialize(source, identifier, handler, details)
  108. format = details[:format] || (handler.default_format if handler.respond_to?(:default_format))
  109. @source = source
  110. @identifier = identifier
  111. @handler = handler
  112. @compiled = false
  113. @original_encoding = nil
  114. @locals = details[:locals] || []
  115. @virtual_path = details[:virtual_path]
  116. @updated_at = details[:updated_at] || Time.now
  117. @formats = Array.wrap(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f }
  118. @compile_mutex = Mutex.new
  119. end
  120. # Returns if the underlying handler supports streaming. If so,
  121. # a streaming buffer *may* be passed when it start rendering.
  122. def supports_streaming?
  123. handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
  124. end
  125. # Render a template. If the template was not compiled yet, it is done
  126. # exactly before rendering.
  127. #
  128. # This method is instrumented as "!render_template.action_view". Notice that
  129. # we use a bang in this instrumentation because you don't want to
  130. # consume this in production. This is only slow if it's being listened to.
  131. def render(view, locals, buffer=nil, &block)
  132. ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
  133. compile!(view)
  134. view.send(method_name, locals, buffer, &block)
  135. end
  136. rescue Exception => e
  137. handle_render_error(view, e)
  138. end
  139. def mime_type
  140. @mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first
  141. end
  142. # Receives a view object and return a template similar to self by using @virtual_path.
  143. #
  144. # This method is useful if you have a template object but it does not contain its source
  145. # anymore since it was already compiled. In such cases, all you need to do is to call
  146. # refresh passing in the view object.
  147. #
  148. # Notice this method raises an error if the template to be refreshed does not have a
  149. # virtual path set (true just for inline templates).
  150. def refresh(view)
  151. raise "A template needs to have a virtual path in order to be refreshed" unless @virtual_path
  152. lookup = view.lookup_context
  153. pieces = @virtual_path.split("/")
  154. name = pieces.pop
  155. partial = !!name.sub!(/^_/, "")
  156. lookup.disable_cache do
  157. lookup.find_template(name, [ pieces.join('/') ], partial, @locals)
  158. end
  159. end
  160. def inspect
  161. @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier
  162. end
  163. # This method is responsible for properly setting the encoding of the
  164. # source. Until this point, we assume that the source is BINARY data.
  165. # If no additional information is supplied, we assume the encoding is
  166. # the same as <tt>Encoding.default_external</tt>.
  167. #
  168. # The user can also specify the encoding via a comment on the first
  169. # line of the template (# encoding: NAME-OF-ENCODING). This will work
  170. # with any template engine, as we process out the encoding comment
  171. # before passing the source on to the template engine, leaving a
  172. # blank line in its stead.
  173. def encode!
  174. return unless source.encoding_aware? && source.encoding == Encoding::BINARY
  175. # Look for # encoding: *. If we find one, we'll encode the
  176. # String in that encoding, otherwise, we'll use the
  177. # default external encoding.
  178. if source.sub!(/\A#{ENCODING_FLAG}/, '')
  179. encoding = magic_encoding = $1
  180. else
  181. encoding = Encoding.default_external
  182. end
  183. # Tag the source with the default external encoding
  184. # or the encoding specified in the file
  185. source.force_encoding(encoding)
  186. # If the user didn't specify an encoding, and the handler
  187. # handles encodings, we simply pass the String as is to
  188. # the handler (with the default_external tag)
  189. if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
  190. source
  191. # Otherwise, if the String is valid in the encoding,
  192. # encode immediately to default_internal. This means
  193. # that if a handler doesn't handle encodings, it will
  194. # always get Strings in the default_internal
  195. elsif source.valid_encoding?
  196. source.encode!
  197. # Otherwise, since the String is invalid in the encoding
  198. # specified, raise an exception
  199. else
  200. raise WrongEncodingError.new(source, encoding)
  201. end
  202. end
  203. protected
  204. # Compile a template. This method ensures a template is compiled
  205. # just once and removes the source after it is compiled.
  206. def compile!(view) #:nodoc:
  207. return if @compiled
  208. # Templates can be used concurrently in threaded environments
  209. # so compilation and any instance variable modification must
  210. # be synchronized
  211. @compile_mutex.synchronize do
  212. # Any thread holding this lock will be compiling the template needed
  213. # by the threads waiting. So re-check the @compiled flag to avoid
  214. # re-compilation
  215. return if @compiled
  216. if view.is_a?(ActionView::CompiledTemplates)
  217. mod = ActionView::CompiledTemplates
  218. else
  219. mod = view.singleton_class
  220. end
  221. compile(view, mod)
  222. # Just discard the source if we have a virtual path. This
  223. # means we can get the template back.
  224. @source = nil if @virtual_path
  225. @compiled = true
  226. end
  227. end
  228. # Among other things, this method is responsible for properly setting
  229. # the encoding of the compiled template.
  230. #
  231. # If the template engine handles encodings, we send the encoded
  232. # String to the engine without further processing. This allows
  233. # the template engine to support additional mechanisms for
  234. # specifying the encoding. For instance, ERB supports <%# encoding: %>
  235. #
  236. # Otherwise, after we figure out the correct encoding, we then
  237. # encode the source into <tt>Encoding.default_internal</tt>.
  238. # In general, this means that templates will be UTF-8 inside of Rails,
  239. # regardless of the original source encoding.
  240. def compile(view, mod) #:nodoc:
  241. encode!
  242. method_name = self.method_name
  243. code = @handler.call(self)
  244. # Make sure that the resulting String to be evalled is in the
  245. # encoding of the code
  246. source = <<-end_src
  247. def #{method_name}(local_assigns, output_buffer)
  248. _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
  249. ensure
  250. @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
  251. end
  252. end_src
  253. if source.encoding_aware?
  254. # Make sure the source is in the encoding of the returned code
  255. source.force_encoding(code.encoding)
  256. # In case we get back a String from a handler that is not in
  257. # BINARY or the default_internal, encode it to the default_internal
  258. source.encode!
  259. # Now, validate that the source we got back from the template
  260. # handler is valid in the default_internal. This is for handlers
  261. # that handle encoding but screw up
  262. unless source.valid_encoding?
  263. raise WrongEncodingError.new(@source, Encoding.default_internal)
  264. end
  265. end
  266. begin
  267. mod.module_eval(source, identifier, 0)
  268. ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
  269. rescue Exception => e # errors from template code
  270. if logger = (view && view.logger)
  271. logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
  272. logger.debug "Function body: #{source}"
  273. logger.debug "Backtrace: #{e.backtrace.join("\n")}"
  274. end
  275. raise ActionView::Template::Error.new(self, {}, e)
  276. end
  277. end
  278. def handle_render_error(view, e) #:nodoc:
  279. if e.is_a?(Template::Error)
  280. e.sub_template_of(self)
  281. raise e
  282. else
  283. assigns = view.respond_to?(:assigns) ? view.assigns : {}
  284. template = self
  285. unless template.source
  286. template = refresh(view)
  287. template.encode!
  288. end
  289. raise Template::Error.new(template, assigns, e)
  290. end
  291. end
  292. def locals_code #:nodoc:
  293. @locals.map { |key| "#{key} = local_assigns[:#{key}];" }.join
  294. end
  295. def method_name #:nodoc:
  296. @method_name ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".gsub('-', "_")
  297. end
  298. def identifier_method_name #:nodoc:
  299. inspect.gsub(/[^a-z_]/, '_')
  300. end
  301. end
  302. end