Skip to content
This repository
Browse code

Refactor ActionView::Template

  ActionView::Template is now completely independent from template
  storage, which allows different back ends such as the database.
  ActionView::Template's only responsibility is to take in the
  template source (passed in from ActionView::Path), compile it,
  and render it.
  • Loading branch information...
commit cecafc52ee0a4a53c903ddbaba95683261f88e5f 1 parent da3c21e
Yehuda Katz + Carl Lerche authored

Showing 23 changed files with 343 additions and 284 deletions. Show diff stats Hide diff stats

  1. +15 5 actionpack/lib/action_controller/base/base.rb
  2. +4 3 actionpack/lib/action_controller/base/render.rb
  3. +5 5 actionpack/lib/action_controller/testing/assertions/response.rb
  4. +3 1 actionpack/lib/action_view/base.rb
  5. +6 6 actionpack/lib/action_view/paths.rb
  6. +7 6 actionpack/lib/action_view/render/rendering.rb
  7. +2 2 actionpack/lib/action_view/template/error.rb
  8. +1 1  actionpack/lib/action_view/template/handlers.rb
  9. +45 23 actionpack/lib/action_view/template/path.rb
  10. +189 147 actionpack/lib/action_view/template/template.rb
  11. +4 3 actionpack/lib/action_view/test_case.rb
  12. +1 1  actionpack/test/abstract_controller/abstract_controller_test.rb
  13. +1 1  actionpack/test/controller/helper_test.rb
  14. +13 11 actionpack/test/controller/layout_test.rb
  15. +3 3 actionpack/test/controller/render_test.rb
  16. +29 20 actionpack/test/controller/view_paths_test.rb
  17. 0  actionpack/test/fixtures/layouts/{standard.erb → standard.html.erb}
  18. +1 0  actionpack/test/fixtures/test/render_file_with_locals_and_default.erb
  19. +5 31 actionpack/test/template/compiled_templates_test.rb
  20. +3 11 actionpack/test/template/render_test.rb
  21. +3 1 activesupport/lib/active_support/core_ext/module/delegation.rb
  22. +2 2 railties/test/plugin_loader_test.rb
  23. +1 1  railties/test/plugin_test_helper.rb
20 actionpack/lib/action_controller/base/base.rb
@@ -494,8 +494,18 @@ def filter_parameter_logging(*filter_words, &block)
494 494 end
495 495 protected :filter_parameters
496 496 end
  497 +
  498 + @@exempt_from_layout = [ActionView::TemplateHandlers::RJS]
  499 +
  500 + def exempt_from_layout(*types)
  501 + types.each do |type|
  502 + @@exempt_from_layout <<
  503 + ActionView::Template.handler_class_for_extension(type)
  504 + end
  505 +
  506 + @@exempt_from_layout
  507 + end
497 508
498   - delegate :exempt_from_layout, :to => 'ActionView::Template'
499 509 end
500 510
501 511 public
@@ -856,13 +866,13 @@ def perform_action
856 866 return (performed? ? ret : default_render) if called
857 867
858 868 begin
859   - default_render
860   - rescue ActionView::MissingTemplate => e
861   - raise e unless e.action_name == action_name
862   - # If the path is the same as the action_name, the action is completely missing
  869 + view_paths.find_by_parts(action_name, {:formats => formats, :locales => [I18n.locale]}, controller_path)
  870 + rescue => e
863 871 raise UnknownAction, "No action responded to #{action_name}. Actions: " +
864 872 "#{action_methods.sort.to_sentence}", caller
865 873 end
  874 +
  875 + default_render
866 876 end
867 877
868 878 # Returns true if a render or redirect has already been performed.
7 actionpack/lib/action_controller/base/render.rb
@@ -378,13 +378,14 @@ def render_for_name(name, layout, options)
378 378 # ==== Arguments
379 379 # parts<Array[String, Array[Symbol*], String, Boolean]>::
380 380 # Example: ["show", [:html, :xml], "users", false]
381   - def render_for_parts(parts, layout, options = {})
  381 + def render_for_parts(parts, layout_details, options = {})
382 382 parts[1] = {:formats => parts[1], :locales => [I18n.locale]}
383 383
384 384 tmp = view_paths.find_by_parts(*parts)
385 385
386   - layout = _pick_layout(*layout) unless tmp.exempt_from_layout?
387   -
  386 + layout = _pick_layout(*layout_details) unless
  387 + self.class.exempt_from_layout.include?(tmp.handler)
  388 +
388 389 render_for_text(
389 390 @template._render_template_with_layout(tmp, layout, options, parts[3]))
390 391 end
10 actionpack/lib/action_controller/testing/assertions/response.rb
@@ -98,22 +98,22 @@ def assert_template(options = {}, message = nil)
98 98 clean_backtrace do
99 99 case options
100 100 when NilClass, String
101   - rendered = @controller.response.rendered[:template].to_s
  101 + rendered = (@controller.response.rendered[:template] || []).map { |t| t.identifier }
102 102 msg = build_message(message,
103 103 "expecting <?> but rendering with <?>",
104   - options, rendered)
  104 + options, rendered.join(', '))
105 105 assert_block(msg) do
106 106 if options.nil?
107 107 @controller.response.rendered[:template].blank?
108 108 else
109   - rendered.to_s.match(options)
  109 + rendered.any? { |t| t.match(options) }
110 110 end
111 111 end
112 112 when Hash
113 113 if expected_partial = options[:partial]
114 114 partials = @controller.response.rendered[:partials]
115 115 if expected_count = options[:count]
116   - found = partials.detect { |p, _| p.to_s.match(expected_partial) }
  116 + found = partials.detect { |p, _| p.identifier.match(expected_partial) }
117 117 actual_count = found.nil? ? 0 : found.second
118 118 msg = build_message(message,
119 119 "expecting ? to be rendered ? time(s) but rendered ? time(s)",
@@ -123,7 +123,7 @@ def assert_template(options = {}, message = nil)
123 123 msg = build_message(message,
124 124 "expecting partial <?> but action rendered <?>",
125 125 options[:partial], partials.keys)
126   - assert(partials.keys.any? { |p| p.to_s.match(expected_partial) }, msg)
  126 + assert(partials.keys.any? { |p| p.identifier.match(expected_partial) }, msg)
127 127 end
128 128 else
129 129 assert @controller.response.rendered[:partials].empty?,
4 actionpack/lib/action_view/base.rb
@@ -196,7 +196,9 @@ def self.cache_template_loading?
196 196 delegate :controller_path, :to => :controller, :allow_nil => true
197 197
198 198 delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
199   - :flash, :logger, :action_name, :controller_name, :to => :controller
  199 + :flash, :action_name, :controller_name, :to => :controller
  200 +
  201 + delegate :logger, :to => :controller, :allow_nil => true
200 202
201 203 delegate :find_by_parts, :to => :view_paths
202 204
12 actionpack/lib/action_view/paths.rb
@@ -3,7 +3,7 @@ class PathSet < Array #:nodoc:
3 3 def self.type_cast(obj)
4 4 if obj.is_a?(String)
5 5 cache = !Object.const_defined?(:Rails) || Rails.configuration.cache_classes
6   - Template::FileSystemPath.new(obj, :cache => cache)
  6 + Template::FileSystemPathWithFallback.new(obj, :cache => cache)
7 7 else
8 8 obj
9 9 end
@@ -34,18 +34,18 @@ def unshift(*objs)
34 34 end
35 35
36 36 def find_by_parts(path, details = {}, prefix = nil, partial = false)
37   - template_path = path.sub(/^\//, '')
  37 + # template_path = path.sub(/^\//, '')
  38 + template_path = path
38 39
39 40 each do |load_path|
40 41 if template = load_path.find_by_parts(template_path, details, prefix, partial)
41 42 return template
42 43 end
43 44 end
44   -
45   - Template.new(path, self)
46   - rescue ActionView::MissingTemplate => e
  45 +
  46 + # TODO: Have a fallback absolute path?
47 47 extension = details[:formats] || []
48   - raise ActionView::MissingTemplate.new(self, "#{prefix}/#{path}.{#{extension.join(",")}}")
  48 + raise ActionView::MissingTemplate.new(self, "#{prefix}/#{path} - #{details.inspect} - partial: #{!!partial}")
49 49 end
50 50
51 51 def find_by_parts?(path, extension = nil, prefix = nil, partial = false)
13 actionpack/lib/action_view/render/rendering.rb
@@ -46,8 +46,8 @@ def _render_content_with_layout(content, layout, locals)
46 46 locals ||= {}
47 47
48 48 if controller && layout
49   - response.layout = layout.path_without_format_and_extension if controller.respond_to?(:response)
50   - logger.info("Rendering template within #{layout.path_without_format_and_extension}") if logger
  49 + response.layout = layout.identifier if controller.respond_to?(:response)
  50 + logger.info("Rendering template within #{layout.identifier}") if logger
51 51 end
52 52
53 53 begin
@@ -76,7 +76,6 @@ def _render_template(template, local_assigns = {})
76 76 end
77 77 end
78 78 rescue Exception => e
79   - raise e if template.is_a?(InlineTemplate) || !template.filename
80 79 if TemplateError === e
81 80 e.sub_template_of(template)
82 81 raise e
@@ -86,7 +85,9 @@ def _render_template(template, local_assigns = {})
86 85 end
87 86
88 87 def _render_inline(inline, layout, options)
89   - content = _render_template(InlineTemplate.new(options[:inline], options[:type]), options[:locals] || {})
  88 + handler = Template.handler_class_for_extension(options[:type] || "erb")
  89 + template = Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {})
  90 + content = _render_template(template, options[:locals] || {})
90 91 layout ? _render_content_with_layout(content, layout, options[:locals]) : content
91 92 end
92 93
@@ -96,7 +97,7 @@ def _render_text(text, layout, options)
96 97
97 98 def _render_template_with_layout(template, layout = nil, options = {}, partial = false)
98 99 if controller && logger
99   - logger.info("Rendering #{template.path_without_extension}" +
  100 + logger.info("Rendering #{template.identifier}" +
100 101 (options[:status] ? " (#{options[:status]})" : ''))
101 102 end
102 103
@@ -107,7 +108,7 @@ def _render_template_with_layout(template, layout = nil, options = {}, partial =
107 108 _render_template(template, options[:locals] || {})
108 109 end
109 110
110   - return content unless layout && !template.exempt_from_layout?
  111 + return content unless layout
111 112 _render_content_with_layout(content, layout, options[:locals] || {})
112 113 end
113 114 end
4 actionpack/lib/action_view/template/error.rb
@@ -12,7 +12,7 @@ def initialize(template, assigns, original_exception)
12 12 end
13 13
14 14 def file_name
15   - @template.relative_path
  15 + @template.identifier
16 16 end
17 17
18 18 def message
@@ -30,7 +30,7 @@ def clean_backtrace
30 30 def sub_template_message
31 31 if @sub_templates
32 32 "Trace of template inclusion: " +
33   - @sub_templates.collect { |template| template.relative_path }.join(", ")
  33 + @sub_templates.collect { |template| template.identifier }.join(", ")
34 34 else
35 35 ""
36 36 end
2  actionpack/lib/action_view/template/handlers.rb
@@ -46,7 +46,7 @@ def register_default_template_handler(extension, klass)
46 46 end
47 47
48 48 def handler_class_for_extension(extension)
49   - registered_template_handler(extension) || @@default_template_handlers
  49 + (extension && registered_template_handler(extension.to_sym)) || @@default_template_handlers
50 50 end
51 51 end
52 52 end
68 actionpack/lib/action_view/template/path.rb
... ... @@ -1,3 +1,5 @@
  1 +require "pathname"
  2 +
1 3 module ActionView
2 4 class Template
3 5 # Abstract super class
@@ -26,13 +28,6 @@ def find_all_by_parts(name, details = {}, prefix = nil, partial = nil)
26 28 def find_templates(name, details, prefix, partial)
27 29 raise NotImplementedError
28 30 end
29   -
30   - # TODO: Refactor this to abstract out the file system
31   - def initialize_template(file)
32   - t = Template.new(file.split("#{self}/").last, self)
33   - t.load!
34   - t
35   - end
36 31
37 32 def valid_handlers
38 33 @valid_handlers ||= TemplateHandlers.extensions
@@ -44,10 +39,10 @@ def handler_matcher
44 39 /\.(?:#{e})$/
45 40 end
46 41 end
47   -
  42 +
48 43 def handler_glob
49   - e = TemplateHandlers.extensions.join(',')
50   - ".{#{e}}"
  44 + e = TemplateHandlers.extensions.map{|h| ".#{h},"}.join
  45 + "{#{e}}"
51 46 end
52 47
53 48 def formats_glob
@@ -69,23 +64,19 @@ class FileSystemPath < Path
69 64 def initialize(path, options = {})
70 65 raise ArgumentError, "path already is a Path class" if path.is_a?(Path)
71 66 super(options)
72   - @path = path
  67 + @path = Pathname.new(path).expand_path
73 68 end
74   -
  69 +
75 70 # TODO: This is the currently needed API. Make this suck less
76 71 # ==== <suck>
77 72 attr_reader :path
78 73
79 74 def to_s
80   - if defined?(RAILS_ROOT)
81   - path.to_s.sub(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '')
82   - else
83   - path.to_s
84   - end
  75 + path.to_s
85 76 end
86 77
87 78 def to_str
88   - path.to_str
  79 + path.to_s
89 80 end
90 81
91 82 def ==(path)
@@ -97,11 +88,15 @@ def eql?(path)
97 88 end
98 89 # ==== </suck>
99 90
100   - def find_templates(name, details, prefix, partial)
101   - if glob = parts_to_glob(name, details, prefix, partial)
  91 + def find_templates(name, details, prefix, partial, root = "#{@path}/")
  92 + if glob = details_to_glob(name, details, prefix, partial, root)
102 93 cached(glob) do
103 94 Dir[glob].map do |path|
104   - initialize_template(path) unless File.directory?(path)
  95 + next if File.directory?(path)
  96 + source = File.read(path)
  97 + identifier = Pathname.new(path).expand_path.to_s
  98 +
  99 + Template.new(source, identifier, *path_to_details(path))
105 100 end.compact
106 101 end
107 102 end
@@ -109,7 +104,8 @@ def find_templates(name, details, prefix, partial)
109 104
110 105 private
111 106
112   - def parts_to_glob(name, details, prefix, partial)
  107 + # :api: plugin
  108 + def details_to_glob(name, details, prefix, partial, root)
113 109 path = ""
114 110 path << "#{prefix}/" unless prefix.empty?
115 111 path << (partial ? "_#{name}" : name)
@@ -123,8 +119,34 @@ def parts_to_glob(name, details, prefix, partial)
123 119 end
124 120 end
125 121
126   - "#{@path}/#{path}#{extensions}#{handler_glob}"
  122 + "#{root}#{path}#{extensions}#{handler_glob}"
  123 + end
  124 +
  125 + # TODO: fix me
  126 + # :api: plugin
  127 + def path_to_details(path)
  128 + # [:erb, :format => :html, :locale => :en, :partial => true/false]
  129 + if m = path.match(%r'/(_)?[\w-]+(\.[\w-]+)*\.(\w+)$')
  130 + partial = m[1] == '_'
  131 + details = (m[2]||"").split('.').reject { |e| e.empty? }
  132 + handler = Template.handler_class_for_extension(m[3])
  133 +
  134 + format = Mime[details.last] && details.pop.to_sym
  135 + locale = details.last && details.pop.to_sym
  136 +
  137 + return handler, :format => format, :locale => locale, :partial => partial
  138 + end
127 139 end
128 140 end
  141 +
  142 + class FileSystemPathWithFallback < FileSystemPath
  143 +
  144 + def find_templates(name, details, prefix, partial)
  145 + templates = super
  146 + return super(name, details, prefix, partial, '') if templates.empty?
  147 + templates
  148 + end
  149 +
  150 + end
129 151 end
130 152 end
336 actionpack/lib/action_view/template/template.rb
... ... @@ -1,188 +1,230 @@
  1 +# encoding: utf-8
  2 +# This is so that templates compiled in this file are UTF-8
  3 +
1 4 require 'set'
2 5 require "action_view/template/path"
3 6
4   -module ActionView #:nodoc:
  7 +module ActionView
5 8 class Template
6 9 extend TemplateHandlers
7   - extend ActiveSupport::Memoizable
  10 + attr_reader :source, :identifier, :handler
8 11
9   - module Loading
10   - def load!
11   - @cached = true
12   - # freeze
13   - end
  12 + def initialize(source, identifier, handler, details)
  13 + @source = source
  14 + @identifier = identifier
  15 + @handler = handler
  16 + @details = details
14 17 end
15   - include Loading
16 18
17   - include Renderable
18   -
19   - # Templates that are exempt from layouts
20   - @@exempt_from_layout = Set.new([/\.rjs$/])
21   -
22   - # Don't render layouts for templates with the given extensions.
23   - def self.exempt_from_layout(*extensions)
24   - regexps = extensions.collect do |extension|
25   - extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
26   - end
27   - @@exempt_from_layout.merge(regexps)
  19 + def render(view, locals, &blk)
  20 + method_name = compile(locals, view)
  21 + view.send(method_name, locals, &blk)
  22 + end
  23 +
  24 + # TODO: Figure out how to abstract this
  25 + def variable_name
  26 + identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym
28 27 end
29 28
30   - attr_accessor :template_path, :filename, :load_path, :base_path
31   - attr_accessor :locale, :name, :format, :extension
32   - delegate :to_s, :to => :path
  29 + # TODO: Figure out how to abstract this
  30 + def counter_name
  31 + "#{variable_name}_counter".to_sym
  32 + end
  33 +
  34 + # TODO: kill hax
  35 + def partial?
  36 + @details[:partial]
  37 + end
  38 +
  39 + # TODO: Move out of Template
  40 + def mime_type
  41 + Mime::Type.lookup_by_extension(@details[:format]) if @details[:format]
  42 + end
  43 +
  44 + private
33 45
34   - def initialize(template_path, load_paths = [])
35   - template_path = template_path.dup
36   - @load_path, @filename = find_full_path(template_path, load_paths)
37   - @base_path, @name, @locale, @format, @extension = split(template_path)
38   - @base_path.to_s.gsub!(/\/$/, '') # Push to split method
  46 + def compile(locals, view)
  47 + method_name = build_method_name(locals)
  48 +
  49 + return method_name if view.respond_to?(method_name)
  50 +
  51 + locals_code = locals.keys.map! { |key| "#{key} = local_assigns[:#{key}];" }.join
  52 +
  53 + source = <<-end_src
  54 + def #{method_name}(local_assigns)
  55 + old_output_buffer = output_buffer;#{locals_code};#{@handler.call(self)}
  56 + ensure
  57 + self.output_buffer = old_output_buffer
  58 + end
  59 + end_src
  60 +
  61 + begin
  62 + ActionView::Base::CompiledTemplates.module_eval(source, identifier, 0)
  63 + method_name
  64 + rescue Exception => e # errors from template code
  65 + if logger = (view && view.logger)
  66 + logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
  67 + logger.debug "Function body: #{source}"
  68 + logger.debug "Backtrace: #{e.backtrace.join("\n")}"
  69 + end
39 70
40   - # Extend with partial super powers
41   - extend RenderablePartial if @name =~ /^_/
  71 + raise ActionView::TemplateError.new(self, {}, e)
  72 + end
  73 + end
  74 +
  75 + def build_method_name(locals)
  76 + # TODO: is locals.keys.hash reliably the same?
  77 + "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
42 78 end
  79 + end
  80 +end
  81 +
  82 +if false
  83 + module ActionView #:nodoc:
  84 + class Template
  85 + extend TemplateHandlers
  86 + extend ActiveSupport::Memoizable
43 87
44   - def accessible_paths
45   - paths = []
  88 + module Loading
  89 + def load!
  90 + @cached = true
  91 + # freeze
  92 + end
  93 + end
  94 + include Loading
  95 +
  96 + include Renderable
46 97
47   - if valid_extension?(extension)
48   - paths << path
49   - paths << path_without_extension
50   - if multipart?
51   - formats = format.split(".")
52   - paths << "#{path_without_format_and_extension}.#{formats.first}"
53   - paths << "#{path_without_format_and_extension}.#{formats.second}"
  98 + # Templates that are exempt from layouts
  99 + @@exempt_from_layout = Set.new([/\.rjs$/])
  100 +
  101 + # Don't render layouts for templates with the given extensions.
  102 + def self.exempt_from_layout(*extensions)
  103 + regexps = extensions.collect do |extension|
  104 + extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/
54 105 end
55   - else
56   - # template without explicit template handler should only be reachable through its exact path
57   - paths << template_path
  106 + @@exempt_from_layout.merge(regexps)
58 107 end
59 108
60   - paths
61   - end
  109 + attr_accessor :template_path, :filename, :load_path, :base_path
  110 + attr_accessor :locale, :name, :format, :extension
  111 + delegate :to_s, :to => :path
  112 +
  113 + def initialize(template_path, load_paths = [])
  114 + template_path = template_path.dup
  115 + @load_path, @filename = find_full_path(template_path, load_paths)
  116 + @name = template_path.to_s.split("/").last.split(".").first
  117 + # @base_path, @name, @locale, @format, @extension = split(template_path)
  118 + @base_path.to_s.gsub!(/\/$/, '') # Push to split method
  119 +
  120 + # Extend with partial super powers
  121 + extend RenderablePartial if @name =~ /^_/
  122 + end
62 123
63   - def relative_path
64   - path = File.expand_path(filename)
65   - path.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '') if defined?(RAILS_ROOT)
66   - path
67   - end
68   - memoize :relative_path
  124 + def accessible_paths
  125 + paths = []
  126 +
  127 + if valid_extension?(extension)
  128 + paths << path
  129 + paths << path_without_extension
  130 + if multipart?
  131 + formats = format.split(".")
  132 + paths << "#{path_without_format_and_extension}.#{formats.first}"
  133 + paths << "#{path_without_format_and_extension}.#{formats.second}"
  134 + end
  135 + else
  136 + # template without explicit template handler should only be reachable through its exact path
  137 + paths << template_path
  138 + end
  139 +
  140 + paths
  141 + end
69 142
70   - def source
71   - File.read(filename)
72   - end
73   - memoize :source
  143 + def relative_path
  144 + path = File.expand_path(filename)
  145 + path.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}\//, '') if defined?(RAILS_ROOT)
  146 + path
  147 + end
  148 + memoize :relative_path
74 149
75   - def exempt_from_layout?
76   - @@exempt_from_layout.any? { |exempted| path =~ exempted }
77   - end
  150 + def source
  151 + File.read(filename)
  152 + end
  153 + memoize :source
78 154
79   - def path_without_extension
80   - [base_path, [name, locale, format].compact.join('.')].compact.join('/')
81   - end
82   - memoize :path_without_extension
  155 + def exempt_from_layout?
  156 + @@exempt_from_layout.any? { |exempted| path =~ exempted }
  157 + end
  158 +
  159 + def path_without_extension
  160 + [base_path, [name, locale, format].compact.join('.')].compact.join('/')
  161 + end
  162 + memoize :path_without_extension
83 163
84   - def path_without_format_and_extension
85   - [base_path, [name, locale].compact.join('.')].compact.join('/')
86   - end
87   - memoize :path_without_format_and_extension
  164 + def path_without_format_and_extension
  165 + [base_path, [name, locale].compact.join('.')].compact.join('/')
  166 + end
  167 + memoize :path_without_format_and_extension
88 168
89   - def path
90   - [base_path, [name, locale, format, extension].compact.join('.')].compact.join('/')
91   - end
92   - memoize :path
  169 + def path
  170 + [base_path, [name, locale, format, extension].compact.join('.')].compact.join('/')
  171 + end
  172 + memoize :path
93 173
94   - def mime_type
95   - Mime::Type.lookup_by_extension(format) if format && defined?(::Mime)
96   - end
97   - memoize :mime_type
  174 + def mime_type
  175 + Mime::Type.lookup_by_extension(format) if format && defined?(::Mime)
  176 + end
  177 + memoize :mime_type
98 178
99   - def multipart?
100   - format && format.include?('.')
101   - end
  179 + def multipart?
  180 + format && format.include?('.')
  181 + end
102 182
103   - def content_type
104   - format && format.gsub('.', '/')
105   - end
  183 + def content_type
  184 + format && format.gsub('.', '/')
  185 + end
106 186
107   - private
  187 + private
108 188
109   - def format_and_extension
110   - (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions
111   - end
112   - memoize :format_and_extension
113   -
114   - def mtime
115   - File.mtime(filename)
116   - end
117   - memoize :mtime
  189 + def format_and_extension
  190 + (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions
  191 + end
  192 + memoize :format_and_extension
118 193
119   - def method_segment
120   - relative_path.to_s.gsub(/([^a-zA-Z0-9_])/) { $1.ord }
121   - end
122   - memoize :method_segment
  194 + def mtime
  195 + File.mtime(filename)
  196 + end
  197 + memoize :mtime
123 198
124   - def stale?
125   - File.mtime(filename) > mtime
126   - end
  199 + def method_segment
  200 + relative_path.to_s.gsub(/([^a-zA-Z0-9_])/) { $1.ord }
  201 + end
  202 + memoize :method_segment
127 203
128   - def recompile?
129   - !@cached
130   - end
  204 + def stale?
  205 + File.mtime(filename) > mtime
  206 + end
131 207
132   - def valid_extension?(extension)
133   - !Template.registered_template_handler(extension).nil?
134   - end
  208 + def recompile?
  209 + !@cached
  210 + end
135 211
136   - def valid_locale?(locale)
137   - I18n.available_locales.include?(locale.to_sym)
138   - end
  212 + def valid_extension?(extension)
  213 + !Template.registered_template_handler(extension).nil?
  214 + end
139 215
140   - def find_full_path(path, load_paths)
141   - load_paths = Array(load_paths) + [nil]
142   - load_paths.each do |load_path|
143   - file = load_path ? "#{load_path.to_str}/#{path}" : path
144   - return load_path, file if File.file?(file)
  216 + def valid_locale?(locale)
  217 + I18n.available_locales.include?(locale.to_sym)
145 218 end
146   - raise MissingTemplate.new(load_paths, path)
147   - end
148 219
149   - # Returns file split into an array
150   - # [base_path, name, locale, format, extension]
151   - def split(file)
152   - if m = file.to_s.match(/^(.*\/)?([^\.]+)\.(.*)$/)
153   - base_path = m[1]
154   - name = m[2]
155   - extensions = m[3]
156   - else
157   - return
158   - end
159   -
160   - locale = nil
161   - format = nil
162   - extension = nil
163   -
164   - if m = extensions.split(".")
165   - if valid_locale?(m[0]) && m[1] && valid_extension?(m[2]) # All three
166   - locale = m[0]
167   - format = m[1]
168   - extension = m[2]
169   - elsif m[0] && m[1] && valid_extension?(m[2]) # Multipart formats
170   - format = "#{m[0]}.#{m[1]}"
171   - extension = m[2]
172   - elsif valid_locale?(m[0]) && valid_extension?(m[1]) # locale and extension
173   - locale = m[0]
174   - extension = m[1]
175   - elsif valid_extension?(m[1]) # format and extension
176   - format = m[0]
177   - extension = m[1]
178   - elsif valid_extension?(m[0]) # Just extension
179   - extension = m[0]
180   - else # No extension
181   - format = m[0]
  220 + def find_full_path(path, load_paths)
  221 + load_paths = Array(load_paths) + [nil]
  222 + load_paths.each do |load_path|
  223 + file = load_path ? "#{load_path.to_str}/#{path}" : path
  224 + return load_path, file if File.file?(file)
182 225 end
  226 + raise MissingTemplate.new(load_paths, path)
183 227 end
184   -
185   - [base_path, name, locale, format, extension]
186 228 end
187 229 end
188   -end
  230 +end
7 actionpack/lib/action_view/test_case.rb
@@ -10,9 +10,10 @@ def initialize(*args)
10 10
11 11 alias_method :_render_template_without_template_tracking, :_render_template
12 12 def _render_template(template, local_assigns = {})
13   - if template.respond_to?(:path) && !template.is_a?(InlineTemplate)
14   - @_rendered[:partials][template] += 1 if template.is_a?(RenderablePartial)
15   - @_rendered[:template] ||= template
  13 + if template.respond_to?(:identifier)
  14 + @_rendered[:partials][template] += 1 if template.partial?
  15 + @_rendered[:template] ||= []
  16 + @_rendered[:template] << template
16 17 end
17 18 _render_template_without_template_tracking(template, local_assigns)
18 19 end
2  actionpack/test/abstract_controller/abstract_controller_test.rb
@@ -139,7 +139,7 @@ class WithLayouts < PrefixedViews
139 139 private
140 140 def self.layout(formats)
141 141 begin
142   - view_paths.find_by_parts(name.underscore, {:formats => formats}t, "layouts")
  142 + view_paths.find_by_parts(name.underscore, {:formats => formats}, "layouts")
143 143 rescue ActionView::MissingTemplate
144 144 begin
145 145 view_paths.find_by_parts("application", {:formats => formats}, "layouts")
2  actionpack/test/controller/helper_test.rb
@@ -211,7 +211,7 @@ def setup
211 211 end
212 212
213 213 def test_helper_in_a
214   - assert_raise(NameError) { A.process(@request, @response) }
  214 + assert_raise(ActionView::TemplateError) { A.process(@request, @response) }
215 215 end
216 216
217 217 def test_helper_in_b
24 actionpack/test/controller/layout_test.rb
@@ -56,8 +56,8 @@ def test_controller_name_layout_name_match
56 56 def test_third_party_template_library_auto_discovers_layout
57 57 @controller = ThirdPartyTemplateLibraryController.new
58 58 get :hello
59   - assert_equal 'layouts/third_party_template_library.mab', @controller.active_layout(true).to_s
60   - assert_equal 'layouts/third_party_template_library', @response.layout
  59 + assert @controller.active_layout(true).identifier.include?('layouts/third_party_template_library.mab')
  60 + assert @response.layout.include?('layouts/third_party_template_library')
61 61 assert_response :success
62 62 assert_equal 'Mab', @response.body
63 63 end
@@ -72,7 +72,7 @@ def test_namespaced_controllers_auto_detect_layouts
72 72 def test_namespaced_controllers_auto_detect_layouts
73 73 @controller = MultipleExtensions.new
74 74 get :hello
75   - assert_equal 'layouts/multiple_extensions.html.erb', @controller.active_layout(true).to_s
  75 + assert @controller.active_layout(true).identifier.include?('layouts/multiple_extensions.html.erb')
76 76 assert_equal 'multiple_extensions.html.erb hello.rhtml', @response.body.strip
77 77 end
78 78 end
@@ -116,22 +116,24 @@ def hello
116 116 end
117 117
118 118 class LayoutSetInResponseTest < ActionController::TestCase
  119 + include ActionView::TemplateHandlers
  120 +
119 121 def test_layout_set_when_using_default_layout
120 122 @controller = DefaultLayoutController.new
121 123 get :hello
122   - assert_equal 'layouts/layout_test', @response.layout
  124 + assert @response.layout.include?('layouts/layout_test')
123 125 end
124 126
125 127 def test_layout_set_when_set_in_controller
126 128 @controller = HasOwnLayoutController.new
127 129 get :hello
128   - assert_equal 'layouts/item', @response.layout
  130 + assert @response.layout.include?('layouts/item')
129 131 end
130 132
131 133 def test_layout_only_exception_when_included
132 134 @controller = OnlyLayoutController.new
133 135 get :hello
134   - assert_equal 'layouts/item', @response.layout
  136 + assert @response.layout.include?('layouts/item')
135 137 end
136 138
137 139 def test_layout_only_exception_when_excepted
@@ -143,7 +145,7 @@ def test_layout_only_exception_when_excepted
143 145 def test_layout_except_exception_when_included
144 146 @controller = ExceptLayoutController.new
145 147 get :hello
146   - assert_equal 'layouts/item', @response.layout
  148 + assert @response.layout.include?('layouts/item')
147 149 end
148 150
149 151 def test_layout_except_exception_when_excepted
@@ -155,7 +157,7 @@ def test_layout_except_exception_when_excepted
155 157 def test_layout_set_when_using_render
156 158 @controller = SetsLayoutInRenderController.new
157 159 get :hello
158   - assert_equal 'layouts/third_party_template_library', @response.layout
  160 + assert @response.layout.include?('layouts/third_party_template_library')
159 161 end
160 162
161 163 def test_layout_is_not_set_when_none_rendered
@@ -165,14 +167,14 @@ def test_layout_is_not_set_when_none_rendered
165 167 end
166 168
167 169 def test_exempt_from_layout_honored_by_render_template
168   - ActionController::Base.exempt_from_layout :rhtml
  170 + ActionController::Base.exempt_from_layout :erb
169 171 @controller = RenderWithTemplateOptionController.new
170 172
171 173 get :hello
172 174 assert_equal "alt/hello.rhtml", @response.body.strip
173 175
174 176 ensure
175   - ActionController::Base.exempt_from_layout.delete(/\.rhtml$/)
  177 + ActionController::Base.exempt_from_layout.delete(ERB)
176 178 end
177 179
178 180 def test_layout_is_picked_from_the_controller_instances_view_path
@@ -232,7 +234,7 @@ def test_symlinked_layout_is_rendered
232 234 @controller = LayoutSymlinkedTest.new
233 235 get :hello
234 236 assert_response 200
235   - assert_equal "layouts/symlinked/symlinked_layout", @response.layout
  237 + assert @response.layout.include?("layouts/symlinked/symlinked_layout")
236 238 end
237 239 end
238 240 end
6 actionpack/test/controller/render_test.rb
@@ -773,7 +773,7 @@ def test_line_offset
773 773 begin
774 774 get :render_line_offset
775 775 flunk "the action should have raised an exception"
776   - rescue RuntimeError => exc
  776 + rescue StandardError => exc
777 777 line = exc.backtrace.first
778 778 assert(line =~ %r{:(\d+):})
779 779 assert_equal "1", $1,
@@ -1736,7 +1736,7 @@ def test_logger_prints_layout_and_template_rendering_info
1736 1736 @controller.logger = MockLogger.new
1737 1737 get :layout_test
1738 1738 logged = @controller.logger.logged.find_all {|l| l =~ /render/i }
1739   - assert_equal "Rendering test/hello_world", logged[0]
1740   - assert_equal "Rendering template within layouts/standard", logged[1]
  1739 + assert logged[0] =~ %r{Rendering.*test/hello_world}
  1740 + assert logged[1] =~ %r{Rendering template within.*layouts/standard}
1741 1741 end
1742 1742 end
49 actionpack/test/controller/view_paths_test.rb
@@ -20,7 +20,7 @@ class Test::SubController < ActionController::Base
20 20 layout 'test/sub'
21 21 def hello_world; render(:template => 'test/hello_world'); end
22 22 end
23   -
  23 +
24 24 def setup
25 25 TestController.view_paths = nil
26 26
@@ -42,30 +42,39 @@ def teardown
42 42 ActiveSupport::Deprecation.behavior = @old_behavior
43 43 end
44 44
  45 + def expand(array)
  46 + array.map {|x| File.expand_path(x)}
  47 + end
  48 +
  49 + def assert_paths(*paths)
  50 + controller = paths.first.is_a?(Class) ? paths.shift : @controller
  51 + assert_equal expand(paths), controller.view_paths.map(&:to_s)
  52 + end
  53 +
45 54 def test_template_load_path_was_set_correctly
46   - assert_equal [FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)
  55 + assert_paths FIXTURE_LOAD_PATH
47 56 end
48 57
49 58 def test_controller_appends_view_path_correctly
50 59 @controller.append_view_path 'foo'
51   - assert_equal [FIXTURE_LOAD_PATH, 'foo'], @controller.view_paths.map(&:to_s)
  60 + assert_paths(FIXTURE_LOAD_PATH, "foo")
52 61
53 62 @controller.append_view_path(%w(bar baz))
54   - assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths.map(&:to_s)
55   -
  63 + assert_paths(FIXTURE_LOAD_PATH, "foo", "bar", "baz")
  64 +
56 65 @controller.append_view_path(FIXTURE_LOAD_PATH)
57   - assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)
  66 + assert_paths(FIXTURE_LOAD_PATH, "foo", "bar", "baz", FIXTURE_LOAD_PATH)
58 67 end
59 68
60 69 def test_controller_prepends_view_path_correctly
61 70 @controller.prepend_view_path 'baz'
62   - assert_equal ['baz', FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)
  71 + assert_paths("baz", FIXTURE_LOAD_PATH)
63 72
64 73 @controller.prepend_view_path(%w(foo bar))
65   - assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)
  74 + assert_paths "foo", "bar", "baz", FIXTURE_LOAD_PATH
66 75
67 76 @controller.prepend_view_path(FIXTURE_LOAD_PATH)
68   - assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)
  77 + assert_paths FIXTURE_LOAD_PATH, "foo", "bar", "baz", FIXTURE_LOAD_PATH
69 78 end
70 79
71 80 def test_template_appends_view_path_correctly
@@ -73,11 +82,11 @@ def test_template_appends_view_path_correctly
73 82 class_view_paths = TestController.view_paths
74 83
75 84 @controller.append_view_path 'foo'
76   - assert_equal [FIXTURE_LOAD_PATH, 'foo'], @controller.view_paths.map(&:to_s)
  85 + assert_paths FIXTURE_LOAD_PATH, "foo"
77 86
78 87 @controller.append_view_path(%w(bar baz))
79   - assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths.map(&:to_s)
80   - assert_equal class_view_paths, TestController.view_paths
  88 + assert_paths FIXTURE_LOAD_PATH, "foo", "bar", "baz"
  89 + assert_paths TestController, *class_view_paths
81 90 end
82 91
83 92 def test_template_prepends_view_path_correctly
@@ -85,11 +94,11 @@ def test_template_prepends_view_path_correctly
85 94 class_view_paths = TestController.view_paths
86 95
87 96 @controller.prepend_view_path 'baz'
88   - assert_equal ['baz', FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)
  97 + assert_paths "baz", FIXTURE_LOAD_PATH
89 98
90 99 @controller.prepend_view_path(%w(foo bar))
91   - assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths.map(&:to_s)
92   - assert_equal class_view_paths, TestController.view_paths
  100 + assert_paths "foo", "bar", "baz", FIXTURE_LOAD_PATH
  101 + assert_paths TestController, *class_view_paths
93 102 end
94 103
95 104 def test_view_paths
@@ -130,12 +139,12 @@ class C < ActionController::Base; end
130 139
131 140 A.view_paths = ['a/path']
132 141
133   - assert_equal ['a/path'], A.view_paths.map(&:to_s)
134   - assert_equal A.view_paths, B.view_paths
135   - assert_equal original_load_paths, C.view_paths
136   -
  142 + assert_paths A, "a/path"
  143 + assert_paths A, *B.view_paths
  144 + assert_paths C, *original_load_paths
  145 +
137 146 C.view_paths = []
138 147 assert_nothing_raised { C.view_paths << 'c/path' }
139   - assert_equal ['c/path'], C.view_paths.map(&:to_s)
  148 + assert_paths C, "c/path"
140 149 end
141 150 end
0  actionpack/test/fixtures/layouts/standard.erb → actionpack/test/fixtures/layouts/standard.html.erb
File renamed without changes
1  actionpack/test/fixtures/test/render_file_with_locals_and_default.erb
... ... @@ -0,0 +1 @@
  1 +<%= secret ||= 'one' %>
36 actionpack/test/template/compiled_templates_test.rb
@@ -5,37 +5,13 @@ class CompiledTemplatesTest < Test::Unit::TestCase
5 5 def setup
6 6 @compiled_templates = ActionView::Base::CompiledTemplates
7 7 @compiled_templates.instance_methods.each do |m|
8   - @compiled_templates.send(:remove_method, m) if m =~ /^_run_/
  8 + @compiled_templates.send(:remove_method, m) if m =~ /^_render_template_/
9 9 end
10 10 end
11   -
12   - def test_template_gets_compiled
13   - assert_equal 0, @compiled_templates.instance_methods.size
14   - assert_equal "Hello world!", render(:file => "test/hello_world.erb")
15   - assert_equal 1, @compiled_templates.instance_methods.size
16   - end
17   -
  11 +
18 12 def test_template_gets_recompiled_when_using_different_keys_in_local_assigns
19   - assert_equal 0, @compiled_templates.instance_methods.size
20   - assert_equal "Hello world!", render(:file => "test/hello_world.erb")
21   - assert_equal "Hello world!", render(:file => "test/hello_world.erb", :locals => {:foo => "bar"})
22   - assert_equal 2, @compiled_templates.instance_methods.size
23   - end
24   -
25   - def test_compiled_template_will_not_be_recompiled_when_rendered_with_identical_local_assigns
26   - assert_equal 0, @compiled_templates.instance_methods.size
27   - assert_equal "Hello world!", render(:file => "test/hello_world.erb")
28   - ActionView::Template.any_instance.expects(:compile!).never
29   - assert_equal "Hello world!", render(:file => "test/hello_world.erb")
30   - end
31   -
32   - def test_compiled_template_will_always_be_recompiled_when_template_is_not_cached
33   - ActionView::Template.any_instance.expects(:recompile?).times(3).returns(true)
34   - assert_equal 0, @compiled_templates.instance_methods.size
35   - assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb")
36   - ActionView::Template.any_instance.expects(:compile!).times(3)
37   - 3.times { assert_equal "Hello world!", render(:file => "#{FIXTURE_LOAD_PATH}/test/hello_world.erb") }
38   - assert_equal 1, @compiled_templates.instance_methods.size
  13 + assert_equal "one", render(:file => "test/render_file_with_locals_and_default.erb")
  14 + assert_equal "two", render(:file => "test/render_file_with_locals_and_default.erb", :locals => { :secret => "two" })
39 15 end
40 16
41 17 def test_template_changes_are_not_reflected_with_cached_templates
@@ -61,14 +37,12 @@ def render(*args)
61 37
62 38 def render_with_cache(*args)
63 39 view_paths = ActionController::Base.view_paths
64   - assert_equal ActionView::Template::FileSystemPath, view_paths.first.class
65 40 ActionView::Base.new(view_paths, {}).render(*args)
66 41 end
67 42
68 43 def render_without_cache(*args)
69   - path = ActionView::Template::FileSystemPath.new(FIXTURE_LOAD_PATH)
  44 + path = ActionView::Template::FileSystemPathWithFallback.new(FIXTURE_LOAD_PATH)
70 45 view_paths = ActionView::Base.process_view_paths(path)
71   - assert_equal ActionView::Template::FileSystemPath, view_paths.first.class
72 46 ActionView::Base.new(view_paths, {}).render(*args)
73 47 end
74 48
14 actionpack/test/template/render_test.rb
@@ -91,10 +91,6 @@ def test_render_file_not_using_full_path_with_dot_in_path
91 91 assert_equal "The secret is in the sauce\n", @view.render(:file => "test/dot.directory/render_file_with_ivar")
92 92 end
93 93
94   - def test_render_has_access_current_template
95   - assert_equal "test/template.erb", @view.render(:file => "test/template.erb")
96   - end
97   -
98 94 def test_render_update
99 95 # TODO: You should not have to stub out template because template is self!
100 96 @view.instance_variable_set(:@template, @view)
@@ -240,10 +236,6 @@ def test_render_ignores_templates_with_malformed_template_handlers
240 236 end
241 237 end
242 238
243   - def test_template_with_malformed_template_handler_is_reachable_through_its_exact_filename
244   - assert_equal "Don't render me!", @view.render(:file => 'test/malformed/malformed.html.erb~')
245   - end
246   -
247 239 def test_render_with_layout
248 240 assert_equal %(<title></title>\nHello world!\n),
249 241 @view.render(:file => "test/hello_world.erb", :layout => "layouts/yield")
@@ -269,7 +261,7 @@ class CachedViewRenderTest < ActiveSupport::TestCase
269 261 # Ensure view path cache is primed
270 262 def setup
271 263 view_paths = ActionController::Base.view_paths
272   - assert_equal ActionView::Template::FileSystemPath, view_paths.first.class
  264 + assert_equal ActionView::Template::FileSystemPathWithFallback, view_paths.first.class
273 265 setup_view(view_paths)
274 266 end
275 267 end
@@ -280,9 +272,9 @@ class LazyViewRenderTest < ActiveSupport::TestCase
280 272 # Test the same thing as above, but make sure the view path
281 273 # is not eager loaded
282 274 def setup
283   - path = ActionView::Template::FileSystemPath.new(FIXTURE_LOAD_PATH)
  275 + path = ActionView::Template::FileSystemPathWithFallback.new(FIXTURE_LOAD_PATH)
284 276 view_paths = ActionView::Base.process_view_paths(path)
285   - assert_equal ActionView::Template::FileSystemPath, view_paths.first.class