actionview 5.2.4.rc1 → 6.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +182 -77
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -2
  5. data/lib/action_view.rb +3 -2
  6. data/lib/action_view/base.rb +107 -10
  7. data/lib/action_view/buffers.rb +15 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +5 -9
  10. data/lib/action_view/digestor.rb +8 -17
  11. data/lib/action_view/gem_version.rb +4 -4
  12. data/lib/action_view/helpers.rb +0 -2
  13. data/lib/action_view/helpers/asset_tag_helper.rb +7 -30
  14. data/lib/action_view/helpers/asset_url_helper.rb +4 -3
  15. data/lib/action_view/helpers/cache_helper.rb +18 -10
  16. data/lib/action_view/helpers/capture_helper.rb +4 -0
  17. data/lib/action_view/helpers/csp_helper.rb +4 -2
  18. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  19. data/lib/action_view/helpers/date_helper.rb +69 -25
  20. data/lib/action_view/helpers/form_helper.rb +238 -6
  21. data/lib/action_view/helpers/form_options_helper.rb +23 -15
  22. data/lib/action_view/helpers/form_tag_helper.rb +12 -11
  23. data/lib/action_view/helpers/javascript_helper.rb +9 -8
  24. data/lib/action_view/helpers/number_helper.rb +5 -0
  25. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  26. data/lib/action_view/helpers/rendering_helper.rb +6 -4
  27. data/lib/action_view/helpers/sanitize_helper.rb +3 -3
  28. data/lib/action_view/helpers/tag_helper.rb +7 -6
  29. data/lib/action_view/helpers/tags/base.rb +9 -5
  30. data/lib/action_view/helpers/tags/color_field.rb +1 -1
  31. data/lib/action_view/helpers/tags/translator.rb +1 -6
  32. data/lib/action_view/helpers/text_helper.rb +3 -3
  33. data/lib/action_view/helpers/translation_helper.rb +16 -12
  34. data/lib/action_view/helpers/url_helper.rb +14 -14
  35. data/lib/action_view/layouts.rb +5 -5
  36. data/lib/action_view/log_subscriber.rb +6 -6
  37. data/lib/action_view/lookup_context.rb +73 -31
  38. data/lib/action_view/path_set.rb +5 -10
  39. data/lib/action_view/railtie.rb +24 -1
  40. data/lib/action_view/record_identifier.rb +2 -2
  41. data/lib/action_view/renderer/abstract_renderer.rb +56 -3
  42. data/lib/action_view/renderer/partial_renderer.rb +66 -52
  43. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +62 -16
  44. data/lib/action_view/renderer/renderer.rb +16 -4
  45. data/lib/action_view/renderer/streaming_template_renderer.rb +5 -5
  46. data/lib/action_view/renderer/template_renderer.rb +24 -18
  47. data/lib/action_view/rendering.rb +51 -31
  48. data/lib/action_view/routing_url_for.rb +12 -11
  49. data/lib/action_view/template.rb +102 -70
  50. data/lib/action_view/template/error.rb +21 -1
  51. data/lib/action_view/template/handlers.rb +27 -1
  52. data/lib/action_view/template/handlers/builder.rb +2 -2
  53. data/lib/action_view/template/handlers/erb.rb +17 -7
  54. data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
  55. data/lib/action_view/template/handlers/html.rb +1 -1
  56. data/lib/action_view/template/handlers/raw.rb +2 -2
  57. data/lib/action_view/template/html.rb +14 -5
  58. data/lib/action_view/template/inline.rb +22 -0
  59. data/lib/action_view/template/raw_file.rb +28 -0
  60. data/lib/action_view/template/resolver.rb +136 -133
  61. data/lib/action_view/template/sources.rb +13 -0
  62. data/lib/action_view/template/sources/file.rb +17 -0
  63. data/lib/action_view/template/text.rb +5 -3
  64. data/lib/action_view/test_case.rb +1 -1
  65. data/lib/action_view/testing/resolvers.rb +12 -11
  66. data/lib/action_view/unbound_template.rb +32 -0
  67. data/lib/action_view/view_paths.rb +25 -1
  68. data/lib/assets/compiled/rails-ujs.js +27 -4
  69. metadata +19 -14
  70. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -11,47 +11,93 @@ module ActionView
11
11
  end
12
12
 
13
13
  private
14
- def cache_collection_render(instrumentation_payload)
14
+ def cache_collection_render(instrumentation_payload, view, template)
15
15
  return yield unless @options[:cached]
16
16
 
17
- keyed_collection = collection_by_cache_keys
18
- cached_partials = collection_cache.read_multi(*keyed_collection.keys)
17
+ # Result is a hash with the key represents the
18
+ # key used for cache lookup and the value is the item
19
+ # on which the partial is being rendered
20
+ keyed_collection, ordered_keys = collection_by_cache_keys(view, template)
21
+
22
+ # Pull all partials from cache
23
+ # Result is a hash, key matches the entry in
24
+ # `keyed_collection` where the cache was retrieved and the
25
+ # value is the value that was present in the cache
26
+ cached_partials = collection_cache.read_multi(*keyed_collection.keys)
19
27
  instrumentation_payload[:cache_hits] = cached_partials.size
20
28
 
29
+ # Extract the items for the keys that are not found
30
+ # Set the uncached values to instance variable @collection
31
+ # which is used by the caller
21
32
  @collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
33
+
34
+ # If all elements are already in cache then
35
+ # rendered partials will be an empty array
36
+ #
37
+ # If the cache is missing elements then
38
+ # the block will be called against the remaining items
39
+ # in the @collection.
22
40
  rendered_partials = @collection.empty? ? [] : yield
23
41
 
24
42
  index = 0
25
- fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do
43
+ keyed_partials = fetch_or_cache_partial(cached_partials, template, order_by: keyed_collection.each_key) do
44
+ # This block is called once
45
+ # for every cache miss while preserving order.
26
46
  rendered_partials[index].tap { index += 1 }
27
47
  end
48
+
49
+ ordered_keys.map do |key|
50
+ keyed_partials[key]
51
+ end
28
52
  end
29
53
 
30
54
  def callable_cache_key?
31
55
  @options[:cached].respond_to?(:call)
32
56
  end
33
57
 
34
- def collection_by_cache_keys
58
+ def collection_by_cache_keys(view, template)
35
59
  seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
36
60
 
37
- @collection.each_with_object({}) do |item, hash|
38
- hash[expanded_cache_key(seed.call(item))] = item
61
+ digest_path = view.digest_path_from_template(template)
62
+
63
+ @collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)|
64
+ key = expanded_cache_key(seed.call(item), view, template, digest_path)
65
+ ordered_keys << key
66
+ hash[key] = item
39
67
  end
40
68
  end
41
69
 
42
- def expanded_cache_key(key)
43
- key = @view.combined_fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
70
+ def expanded_cache_key(key, view, template, digest_path)
71
+ key = view.combined_fragment_cache_key(view.cache_fragment_name(key, virtual_path: template.virtual_path, digest_path: digest_path))
44
72
  key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
45
73
  end
46
74
 
47
- def fetch_or_cache_partial(cached_partials, order_by:)
48
- order_by.map do |cache_key|
49
- cached_partials.fetch(cache_key) do
50
- yield.tap do |rendered_partial|
51
- collection_cache.write(cache_key, rendered_partial)
52
- end
75
+ # `order_by` is an enumerable object containing keys of the cache,
76
+ # all keys are passed in whether found already or not.
77
+ #
78
+ # `cached_partials` is a hash. If the value exists
79
+ # it represents the rendered partial from the cache
80
+ # otherwise `Hash#fetch` will take the value of its block.
81
+ #
82
+ # This method expects a block that will return the rendered
83
+ # partial. An example is to render all results
84
+ # for each element that was not found in the cache and store it as an array.
85
+ # Order it so that the first empty cache element in `cached_partials`
86
+ # corresponds to the first element in `rendered_partials`.
87
+ #
88
+ # If the partial is not already cached it will also be
89
+ # written back to the underlying cache store.
90
+ def fetch_or_cache_partial(cached_partials, template, order_by:)
91
+ order_by.each_with_object({}) do |cache_key, hash|
92
+ hash[cache_key] =
93
+ if content = cached_partials[cache_key]
94
+ build_rendered_template(content, template)
95
+ else
96
+ yield.tap do |rendered_partial|
97
+ collection_cache.write(cache_key, rendered_partial.body)
98
+ end
99
+ end
53
100
  end
54
- end
55
101
  end
56
102
  end
57
103
  end
@@ -19,10 +19,14 @@ module ActionView
19
19
 
20
20
  # Main render entry point shared by Action View and Action Controller.
21
21
  def render(context, options)
22
+ render_to_object(context, options).body
23
+ end
24
+
25
+ def render_to_object(context, options) # :nodoc:
22
26
  if options.key?(:partial)
23
- render_partial(context, options)
27
+ render_partial_to_object(context, options)
24
28
  else
25
- render_template(context, options)
29
+ render_template_to_object(context, options)
26
30
  end
27
31
  end
28
32
 
@@ -41,16 +45,24 @@ module ActionView
41
45
 
42
46
  # Direct access to template rendering.
43
47
  def render_template(context, options) #:nodoc:
44
- TemplateRenderer.new(@lookup_context).render(context, options)
48
+ render_template_to_object(context, options).body
45
49
  end
46
50
 
47
51
  # Direct access to partial rendering.
48
52
  def render_partial(context, options, &block) #:nodoc:
49
- PartialRenderer.new(@lookup_context).render(context, options, block)
53
+ render_partial_to_object(context, options, &block).body
50
54
  end
51
55
 
52
56
  def cache_hits # :nodoc:
53
57
  @cache_hits ||= {}
54
58
  end
59
+
60
+ def render_template_to_object(context, options) #:nodoc:
61
+ TemplateRenderer.new(@lookup_context).render(context, options)
62
+ end
63
+
64
+ def render_partial_to_object(context, options, &block) #:nodoc:
65
+ PartialRenderer.new(@lookup_context).render(context, options, block)
66
+ end
55
67
  end
56
68
  end
@@ -33,8 +33,8 @@ module ActionView
33
33
  logger = ActionView::Base.logger
34
34
  return unless logger
35
35
 
36
- message = "\n#{exception.class} (#{exception.message}):\n".dup
37
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
36
+ message = +"\n#{exception.class} (#{exception.message}):\n"
37
+ message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_source_code)
38
38
  message << " " << exception.backtrace.join("\n ")
39
39
  logger.fatal("#{message}\n\n")
40
40
  end
@@ -43,14 +43,14 @@ module ActionView
43
43
  # For streaming, instead of rendering a given a template, we return a Body
44
44
  # object that responds to each. This object is initialized with a block
45
45
  # that knows how to render the template.
46
- def render_template(template, layout_name = nil, locals = {}) #:nodoc:
47
- return [super] unless layout_name && template.supports_streaming?
46
+ def render_template(view, template, layout_name = nil, locals = {}) #:nodoc:
47
+ return [super.body] unless layout_name && template.supports_streaming?
48
48
 
49
49
  locals ||= {}
50
50
  layout = layout_name && find_layout(layout_name, locals.keys, [formats.first])
51
51
 
52
52
  Body.new do |buffer|
53
- delayed_render(buffer, template, layout, @view, locals)
53
+ delayed_render(buffer, template, layout, view, locals)
54
54
  end
55
55
  end
56
56
 
@@ -5,15 +5,12 @@ require "active_support/core_ext/object/try"
5
5
  module ActionView
6
6
  class TemplateRenderer < AbstractRenderer #:nodoc:
7
7
  def render(context, options)
8
- @view = context
9
8
  @details = extract_details(options)
10
9
  template = determine_template(options)
11
10
 
12
- prepend_formats(template.formats)
11
+ prepend_formats(template.format)
13
12
 
14
- @lookup_context.rendered_format ||= (template.formats.first || formats.first)
15
-
16
- render_template(template, options[:layout], options[:locals])
13
+ render_template(context, template, options[:layout], options[:locals] || {})
17
14
  end
18
15
 
19
16
  private
@@ -29,15 +26,25 @@ module ActionView
29
26
  elsif options.key?(:html)
30
27
  Template::HTML.new(options[:html], formats.first)
31
28
  elsif options.key?(:file)
32
- with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
29
+ if File.exist?(options[:file])
30
+ Template::RawFile.new(options[:file])
31
+ else
32
+ ActiveSupport::Deprecation.warn "render file: should be given the absolute path to a file"
33
+ @lookup_context.with_fallbacks.find_template(options[:file], nil, false, keys, @details)
34
+ end
33
35
  elsif options.key?(:inline)
34
36
  handler = Template.handler_for_extension(options[:type] || "erb")
35
- Template.new(options[:inline], "inline template", handler, locals: keys)
37
+ format = if handler.respond_to?(:default_format)
38
+ handler.default_format
39
+ else
40
+ @lookup_context.formats.first
41
+ end
42
+ Template::Inline.new(options[:inline], "inline template", handler, locals: keys, format: format)
36
43
  elsif options.key?(:template)
37
44
  if options[:template].respond_to?(:render)
38
45
  options[:template]
39
46
  else
40
- find_template(options[:template], options[:prefixes], false, keys, @details)
47
+ @lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details)
41
48
  end
42
49
  else
43
50
  raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
@@ -46,27 +53,25 @@ module ActionView
46
53
 
47
54
  # Renders the given template. A string representing the layout can be
48
55
  # supplied as well.
49
- def render_template(template, layout_name = nil, locals = nil)
50
- view, locals = @view, locals || {}
51
-
52
- render_with_layout(layout_name, locals) do |layout|
56
+ def render_template(view, template, layout_name, locals)
57
+ render_with_layout(view, template, layout_name, locals) do |layout|
53
58
  instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
54
59
  template.render(view, locals) { |*name| view._layout_for(*name) }
55
60
  end
56
61
  end
57
62
  end
58
63
 
59
- def render_with_layout(path, locals)
64
+ def render_with_layout(view, template, path, locals)
60
65
  layout = path && find_layout(path, locals.keys, [formats.first])
61
66
  content = yield(layout)
62
67
 
63
- if layout
64
- view = @view
68
+ body = if layout
65
69
  view.view_flow.set(:layout, content)
66
70
  layout.render(view, locals) { |*name| view._layout_for(*name) }
67
71
  else
68
72
  content
69
73
  end
74
+ build_rendered_template(body, template, layout)
70
75
  end
71
76
 
72
77
  # This is the method which actually finds the layout using details in the lookup
@@ -84,16 +89,17 @@ module ActionView
84
89
  when String
85
90
  begin
86
91
  if layout.start_with?("/")
87
- with_fallbacks { find_template(layout, nil, false, [], details) }
92
+ ActiveSupport::Deprecation.warn "Rendering layouts from an absolute path is deprecated."
93
+ @lookup_context.with_fallbacks.find_template(layout, nil, false, [], details)
88
94
  else
89
- find_template(layout, nil, false, [], details)
95
+ @lookup_context.find_template(layout, nil, false, [], details)
90
96
  end
91
97
  rescue ActionView::MissingTemplate
92
98
  all_details = @details.merge(formats: @lookup_context.default_formats)
93
99
  raise unless template_exists?(layout, nil, false, [], all_details)
94
100
  end
95
101
  when Proc
96
- resolve_layout(layout.call(formats), keys, formats)
102
+ resolve_layout(layout.call(@lookup_context, formats), keys, formats)
97
103
  else
98
104
  layout
99
105
  end
@@ -26,6 +26,13 @@ module ActionView
26
26
  extend ActiveSupport::Concern
27
27
  include ActionView::ViewPaths
28
28
 
29
+ attr_reader :rendered_format
30
+
31
+ def initialize
32
+ @rendered_format = nil
33
+ super
34
+ end
35
+
29
36
  # Overwrite process to setup I18n proxy.
30
37
  def process(*) #:nodoc:
31
38
  old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
@@ -35,47 +42,59 @@ module ActionView
35
42
  end
36
43
 
37
44
  module ClassMethods
38
- def view_context_class
39
- @view_context_class ||= begin
40
- supports_path = supports_path?
41
- routes = respond_to?(:_routes) && _routes
42
- helpers = respond_to?(:_helpers) && _helpers
43
-
44
- Class.new(ActionView::Base) do
45
- if routes
46
- include routes.url_helpers(supports_path)
47
- include routes.mounted_helpers
48
- end
49
-
50
- if helpers
51
- include helpers
52
- end
45
+ def _routes
46
+ end
47
+
48
+ def _helpers
49
+ end
50
+
51
+ def build_view_context_class(klass, supports_path, routes, helpers)
52
+ Class.new(klass) do
53
+ if routes
54
+ include routes.url_helpers(supports_path)
55
+ include routes.mounted_helpers
56
+ end
57
+
58
+ if helpers
59
+ include helpers
53
60
  end
54
61
  end
55
62
  end
56
- end
57
63
 
58
- attr_internal_writer :view_context_class
64
+ def view_context_class
65
+ klass = ActionView::LookupContext::DetailsKey.view_context_class(ActionView::Base)
66
+
67
+ @view_context_class ||= build_view_context_class(klass, supports_path?, _routes, _helpers)
68
+
69
+ if klass.changed?(@view_context_class)
70
+ @view_context_class = build_view_context_class(klass, supports_path?, _routes, _helpers)
71
+ end
72
+
73
+ @view_context_class
74
+ end
75
+ end
59
76
 
60
77
  def view_context_class
61
- @_view_context_class ||= self.class.view_context_class
78
+ self.class.view_context_class
62
79
  end
63
80
 
64
81
  # An instance of a view class. The default view class is ActionView::Base.
65
82
  #
66
83
  # The view class must have the following methods:
67
- # View.new[lookup_context, assigns, controller]
68
- # Create a new ActionView instance for a controller and we can also pass the arguments.
69
- # View#render(option)
70
- # Returns String with the rendered template
84
+ #
85
+ # * <tt>View.new(lookup_context, assigns, controller)</tt> Create a new
86
+ # ActionView instance for a controller and we can also pass the arguments.
87
+ #
88
+ # * <tt>View#render(option)</tt> — Returns String with the rendered template.
71
89
  #
72
90
  # Override this method in a module to change the default behavior.
73
91
  def view_context
74
- view_context_class.new(view_renderer, view_assigns, self)
92
+ view_context_class.new(lookup_context, view_assigns, self)
75
93
  end
76
94
 
77
95
  # Returns an object that is able to render templates.
78
96
  def view_renderer # :nodoc:
97
+ # Lifespan: Per controller
79
98
  @_view_renderer ||= ActionView::Renderer.new(lookup_context)
80
99
  end
81
100
 
@@ -84,10 +103,6 @@ module ActionView
84
103
  _render_template(options)
85
104
  end
86
105
 
87
- def rendered_format
88
- Template::Types[lookup_context.rendered_format]
89
- end
90
-
91
106
  private
92
107
 
93
108
  # Find and render a template based on the options given.
@@ -97,17 +112,22 @@ module ActionView
97
112
  context = view_context
98
113
 
99
114
  context.assign assigns if assigns
100
- lookup_context.rendered_format = nil if options[:formats]
101
115
  lookup_context.variants = variant if variant
102
116
 
103
- view_renderer.render(context, options)
117
+ rendered_template = context.in_rendering_context(options) do |renderer|
118
+ renderer.render_to_object(context, options)
119
+ end
120
+
121
+ rendered_format = rendered_template.format || lookup_context.formats.first
122
+ @rendered_format = Template::Types[rendered_format]
123
+
124
+ rendered_template.body
104
125
  end
105
126
 
106
127
  # Assign the rendered format to look up context.
107
128
  def _process_format(format)
108
129
  super
109
- lookup_context.formats = [format.to_sym]
110
- lookup_context.rendered_format = lookup_context.formats.first
130
+ lookup_context.formats = [format.to_sym] if format.to_sym
111
131
  end
112
132
 
113
133
  # Normalize args by converting render "foo" to render :action => "foo" and
@@ -84,25 +84,24 @@ module ActionView
84
84
  super(only_path: _generate_paths_by_default)
85
85
  when Hash
86
86
  options = options.symbolize_keys
87
- unless options.key?(:only_path)
88
- options[:only_path] = only_path?(options[:host])
89
- end
87
+ ensure_only_path_option(options)
90
88
 
91
89
  super(options)
92
90
  when ActionController::Parameters
93
- unless options.key?(:only_path)
94
- options[:only_path] = only_path?(options[:host])
95
- end
91
+ ensure_only_path_option(options)
96
92
 
97
93
  super(options)
98
94
  when :back
99
95
  _back_url
100
96
  when Array
101
97
  components = options.dup
102
- if _generate_paths_by_default
103
- polymorphic_path(components, components.extract_options!)
98
+ options = components.extract_options!
99
+ ensure_only_path_option(options)
100
+
101
+ if options[:only_path]
102
+ polymorphic_path(components, options)
104
103
  else
105
- polymorphic_url(components, components.extract_options!)
104
+ polymorphic_url(components, options)
106
105
  end
107
106
  else
108
107
  method = _generate_paths_by_default ? :path : :url
@@ -138,8 +137,10 @@ module ActionView
138
137
  true
139
138
  end
140
139
 
141
- def only_path?(host)
142
- _generate_paths_by_default unless host
140
+ def ensure_only_path_option(options)
141
+ unless options.key?(:only_path)
142
+ options[:only_path] = _generate_paths_by_default unless options[:host]
143
+ end
143
144
  end
144
145
  end
145
146
  end