actionview 4.2.11.3 → 5.0.0.beta1

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 (65) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +136 -255
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/action_view.rb +1 -1
  6. data/lib/action_view/base.rb +14 -2
  7. data/lib/action_view/dependency_tracker.rb +46 -15
  8. data/lib/action_view/digestor.rb +13 -9
  9. data/lib/action_view/flows.rb +1 -1
  10. data/lib/action_view/gem_version.rb +4 -4
  11. data/lib/action_view/helpers/asset_tag_helper.rb +15 -5
  12. data/lib/action_view/helpers/asset_url_helper.rb +51 -12
  13. data/lib/action_view/helpers/atom_feed_helper.rb +5 -4
  14. data/lib/action_view/helpers/cache_helper.rb +75 -20
  15. data/lib/action_view/helpers/capture_helper.rb +3 -2
  16. data/lib/action_view/helpers/controller_helper.rb +1 -0
  17. data/lib/action_view/helpers/date_helper.rb +39 -10
  18. data/lib/action_view/helpers/debug_helper.rb +1 -1
  19. data/lib/action_view/helpers/form_helper.rb +81 -35
  20. data/lib/action_view/helpers/form_options_helper.rb +74 -35
  21. data/lib/action_view/helpers/form_tag_helper.rb +46 -19
  22. data/lib/action_view/helpers/javascript_helper.rb +4 -4
  23. data/lib/action_view/helpers/number_helper.rb +10 -12
  24. data/lib/action_view/helpers/record_tag_helper.rb +12 -99
  25. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  26. data/lib/action_view/helpers/sanitize_helper.rb +1 -2
  27. data/lib/action_view/helpers/tag_helper.rb +20 -13
  28. data/lib/action_view/helpers/tags/base.rb +33 -28
  29. data/lib/action_view/helpers/tags/collection_check_boxes.rb +2 -30
  30. data/lib/action_view/helpers/tags/collection_helpers.rb +28 -0
  31. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -9
  32. data/lib/action_view/helpers/tags/file_field.rb +15 -0
  33. data/lib/action_view/helpers/tags/label.rb +1 -1
  34. data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
  35. data/lib/action_view/helpers/tags/search_field.rb +12 -9
  36. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  37. data/lib/action_view/helpers/tags/translator.rb +1 -1
  38. data/lib/action_view/helpers/text_helper.rb +25 -9
  39. data/lib/action_view/helpers/translation_helper.rb +56 -26
  40. data/lib/action_view/helpers/url_helper.rb +40 -65
  41. data/lib/action_view/layouts.rb +11 -10
  42. data/lib/action_view/lookup_context.rb +14 -40
  43. data/lib/action_view/model_naming.rb +1 -1
  44. data/lib/action_view/path_set.rb +15 -18
  45. data/lib/action_view/railtie.rb +20 -3
  46. data/lib/action_view/record_identifier.rb +44 -19
  47. data/lib/action_view/renderer/abstract_renderer.rb +1 -1
  48. data/lib/action_view/renderer/partial_renderer.rb +27 -26
  49. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +70 -0
  50. data/lib/action_view/renderer/renderer.rb +2 -6
  51. data/lib/action_view/renderer/streaming_template_renderer.rb +1 -1
  52. data/lib/action_view/renderer/template_renderer.rb +12 -11
  53. data/lib/action_view/rendering.rb +8 -5
  54. data/lib/action_view/routing_url_for.rb +18 -6
  55. data/lib/action_view/template.rb +50 -13
  56. data/lib/action_view/template/error.rb +14 -7
  57. data/lib/action_view/template/handlers.rb +3 -3
  58. data/lib/action_view/template/handlers/erb.rb +25 -0
  59. data/lib/action_view/template/handlers/raw.rb +1 -1
  60. data/lib/action_view/template/resolver.rb +36 -58
  61. data/lib/action_view/template/types.rb +1 -1
  62. data/lib/action_view/test_case.rb +13 -8
  63. data/lib/action_view/testing/resolvers.rb +3 -4
  64. data/lib/action_view/view_paths.rb +6 -22
  65. metadata +17 -14
@@ -0,0 +1,70 @@
1
+ require 'active_support/core_ext/object/try'
2
+
3
+ module ActionView
4
+ module CollectionCaching # :nodoc:
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # Fallback cache store if Action View is used without Rails.
9
+ # Otherwise overridden in Railtie to use Rails.cache.
10
+ mattr_accessor(:collection_cache) { ActiveSupport::Cache::MemoryStore.new }
11
+ end
12
+
13
+ private
14
+ def cache_collection_render
15
+ return yield unless cache_collection?
16
+
17
+ keyed_collection = collection_by_cache_keys
18
+ partial_cache = collection_cache.read_multi(*keyed_collection.keys)
19
+
20
+ @collection = keyed_collection.reject { |key, _| partial_cache.key?(key) }.values
21
+ rendered_partials = @collection.any? ? yield.dup : []
22
+
23
+ fetch_or_cache_partial(partial_cache, order_by: keyed_collection.each_key) do
24
+ rendered_partials.shift
25
+ end
26
+ end
27
+
28
+ def cache_collection?
29
+ @options.fetch(:cache, automatic_cache_eligible?)
30
+ end
31
+
32
+ def automatic_cache_eligible?
33
+ single_template_render? && !callable_cache_key? &&
34
+ @template.eligible_for_collection_caching?(as: @options[:as])
35
+ end
36
+
37
+ def single_template_render?
38
+ @template # Template is only set when a collection renders one template.
39
+ end
40
+
41
+ def callable_cache_key?
42
+ @options[:cache].respond_to?(:call)
43
+ end
44
+
45
+ def collection_by_cache_keys
46
+ seed = callable_cache_key? ? @options[:cache] : ->(i) { i }
47
+
48
+ @collection.each_with_object({}) do |item, hash|
49
+ hash[expanded_cache_key(seed.call(item))] = item
50
+ end
51
+ end
52
+
53
+ def expanded_cache_key(key)
54
+ key = @view.fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
55
+ key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
56
+ end
57
+
58
+ def fetch_or_cache_partial(cached_partials, order_by:)
59
+ cache_options = @options[:cache_options] || @locals[:cache_options] || {}
60
+
61
+ order_by.map do |key|
62
+ cached_partials.fetch(key) do
63
+ yield.tap do |rendered_partial|
64
+ collection_cache.write(key, rendered_partial, cache_options)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -15,12 +15,8 @@ module ActionView
15
15
  @lookup_context = lookup_context
16
16
  end
17
17
 
18
- # Main render entry point shared by AV and AC.
18
+ # Main render entry point shared by Action View and Action Controller.
19
19
  def render(context, options)
20
- if options.respond_to?(:permitted?) && !options.permitted?
21
- raise ArgumentError, "render parameters are not permitted"
22
- end
23
-
24
20
  if options.key?(:partial)
25
21
  render_partial(context, options)
26
22
  else
@@ -41,7 +37,7 @@ module ActionView
41
37
  end
42
38
  end
43
39
 
44
- # Direct accessor to template rendering.
40
+ # Direct access to template rendering.
45
41
  def render_template(context, options) #:nodoc:
46
42
  TemplateRenderer.new(@lookup_context).render(context, options)
47
43
  end
@@ -47,7 +47,7 @@ module ActionView
47
47
  return [super] unless layout_name && template.supports_streaming?
48
48
 
49
49
  locals ||= {}
50
- layout = layout_name && find_layout(layout_name, locals.keys)
50
+ layout = layout_name && find_layout(layout_name, locals.keys, [formats.first])
51
51
 
52
52
  Body.new do |buffer|
53
53
  delayed_render(buffer, template, layout, @view, locals)
@@ -29,7 +29,7 @@ module ActionView
29
29
  elsif options.key?(:html)
30
30
  Template::HTML.new(options[:html], formats.first)
31
31
  elsif options.key?(:file)
32
- with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
32
+ with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
33
33
  elsif options.key?(:inline)
34
34
  handler = Template.handler_for_extension(options[:type] || "erb")
35
35
  Template.new(options[:inline], "inline template", handler, :locals => keys)
@@ -40,7 +40,7 @@ module ActionView
40
40
  find_template(options[:template], options[:prefixes], false, keys, @details)
41
41
  end
42
42
  else
43
- raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option."
43
+ raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html, :text or :body option."
44
44
  end
45
45
  end
46
46
 
@@ -57,7 +57,7 @@ module ActionView
57
57
  end
58
58
 
59
59
  def render_with_layout(path, locals) #:nodoc:
60
- layout = path && find_layout(path, locals.keys)
60
+ layout = path && find_layout(path, locals.keys, [formats.first])
61
61
  content = yield(layout)
62
62
 
63
63
  if layout
@@ -72,27 +72,28 @@ module ActionView
72
72
  # This is the method which actually finds the layout using details in the lookup
73
73
  # context object. If no layout is found, it checks if at least a layout with
74
74
  # the given name exists across all details before raising the error.
75
- def find_layout(layout, keys)
76
- with_layout_format { resolve_layout(layout, keys) }
75
+ def find_layout(layout, keys, formats)
76
+ resolve_layout(layout, keys, formats)
77
77
  end
78
78
 
79
- def resolve_layout(layout, keys)
79
+ def resolve_layout(layout, keys, formats)
80
+ details = @details.dup
81
+ details[:formats] = formats
82
+
80
83
  case layout
81
84
  when String
82
85
  begin
83
86
  if layout =~ /^\//
84
- with_fallbacks { find_template(layout, nil, false, keys, @details) }
87
+ with_fallbacks { find_template(layout, nil, false, keys, details) }
85
88
  else
86
- find_template(layout, nil, false, keys, @details)
89
+ find_template(layout, nil, false, keys, details)
87
90
  end
88
91
  rescue ActionView::MissingTemplate
89
92
  all_details = @details.merge(:formats => @lookup_context.default_formats)
90
93
  raise unless template_exists?(layout, nil, false, keys, all_details)
91
94
  end
92
95
  when Proc
93
- resolve_layout(layout.call, keys)
94
- when FalseClass
95
- nil
96
+ resolve_layout(layout.call(formats), keys, formats)
96
97
  else
97
98
  layout
98
99
  end
@@ -59,7 +59,7 @@ module ActionView
59
59
  @_view_context_class ||= self.class.view_context_class
60
60
  end
61
61
 
62
- # An instance of a view class. The default view class is ActionView::Base
62
+ # An instance of a view class. The default view class is ActionView::Base.
63
63
  #
64
64
  # The view class must have the following methods:
65
65
  # View.new[lookup_context, assigns, controller]
@@ -92,16 +92,19 @@ module ActionView
92
92
  # Find and render a template based on the options given.
93
93
  # :api: private
94
94
  def _render_template(options) #:nodoc:
95
- variant = options[:variant]
95
+ variant = options.delete(:variant)
96
+ assigns = options.delete(:assigns)
97
+ context = view_context
96
98
 
99
+ context.assign assigns if assigns
97
100
  lookup_context.rendered_format = nil if options[:formats]
98
101
  lookup_context.variants = variant if variant
99
102
 
100
- view_renderer.render(view_context, options)
103
+ view_renderer.render(context, options)
101
104
  end
102
105
 
103
- # Assign the rendered format to lookup context.
104
- def _process_format(format, options = {}) #:nodoc:
106
+ # Assign the rendered format to look up context.
107
+ def _process_format(format) #:nodoc:
105
108
  super
106
109
  lookup_context.formats = [format.to_sym]
107
110
  lookup_context.rendered_format = lookup_context.formats.first
@@ -32,7 +32,7 @@ module ActionView
32
32
  #
33
33
  # ==== Examples
34
34
  # <%= url_for(action: 'index') %>
35
- # # => /blog/
35
+ # # => /blogs/
36
36
  #
37
37
  # <%= url_for(action: 'find', controller: 'books') %>
38
38
  # # => /books/find
@@ -84,11 +84,13 @@ module ActionView
84
84
  when Hash
85
85
  options = options.symbolize_keys
86
86
  unless options.key?(:only_path)
87
- if options[:host].nil?
88
- options[:only_path] = _generate_paths_by_default
89
- else
90
- options[:only_path] = false
91
- end
87
+ options[:only_path] = only_path?(options[:host])
88
+ end
89
+
90
+ super(options)
91
+ when ActionController::Parameters
92
+ unless options.key?(:only_path)
93
+ options[:only_path] = only_path?(options[:host])
92
94
  end
93
95
 
94
96
  super(options)
@@ -131,5 +133,15 @@ module ActionView
131
133
  controller.optimize_routes_generation? : super
132
134
  end
133
135
  protected :optimize_routes_generation?
136
+
137
+ private
138
+
139
+ def _generate_paths_by_default
140
+ true
141
+ end
142
+
143
+ def only_path?(host)
144
+ _generate_paths_by_default unless host
145
+ end
134
146
  end
135
147
  end
@@ -87,6 +87,19 @@ module ActionView
87
87
  # expected_encoding
88
88
  # )
89
89
 
90
+ ##
91
+ # :method: local_assigns
92
+ #
93
+ # Returns a hash with the defined local variables.
94
+ #
95
+ # Given this sub template rendering:
96
+ #
97
+ # <%= render "shared/header", { headline: "Welcome", person: person } %>
98
+ #
99
+ # You can use +local_assigns+ in the sub templates to access the local variables:
100
+ #
101
+ # local_assigns[:headline] # => "Welcome"
102
+
90
103
  eager_autoload do
91
104
  autoload :Error
92
105
  autoload :Handlers
@@ -103,7 +116,7 @@ module ActionView
103
116
 
104
117
  # This finalizer is needed (and exactly with a proc inside another proc)
105
118
  # otherwise templates leak in development.
106
- Finalizer = proc do |method_name, mod|
119
+ Finalizer = proc do |method_name, mod| # :nodoc:
107
120
  proc do
108
121
  mod.module_eval do
109
122
  remove_possible_method method_name
@@ -117,6 +130,7 @@ module ActionView
117
130
  @source = source
118
131
  @identifier = identifier
119
132
  @handler = handler
133
+ @cache_name = extract_resource_cache_name
120
134
  @compiled = false
121
135
  @original_encoding = nil
122
136
  @locals = details[:locals] || []
@@ -127,7 +141,7 @@ module ActionView
127
141
  @compile_mutex = Mutex.new
128
142
  end
129
143
 
130
- # Returns if the underlying handler supports streaming. If so,
144
+ # Returns whether the underlying handler supports streaming. If so,
131
145
  # a streaming buffer *may* be passed when it start rendering.
132
146
  def supports_streaming?
133
147
  handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
@@ -140,7 +154,7 @@ module ActionView
140
154
  # we use a bang in this instrumentation because you don't want to
141
155
  # consume this in production. This is only slow if it's being listened to.
142
156
  def render(view, locals, buffer=nil, &block)
143
- instrument("!render_template") do
157
+ instrument("!render_template".freeze) do
144
158
  compile!(view)
145
159
  view.send(method_name, locals, buffer, &block)
146
160
  end
@@ -152,6 +166,10 @@ module ActionView
152
166
  @type ||= Types[@formats.first] if @formats.first
153
167
  end
154
168
 
169
+ def eligible_for_collection_caching?(as: nil)
170
+ @cache_name == (as || inferred_cache_name).to_s
171
+ end
172
+
155
173
  # Receives a view object and return a template similar to self by using @virtual_path.
156
174
  #
157
175
  # This method is useful if you have a template object but it does not contain its source
@@ -172,7 +190,7 @@ module ActionView
172
190
  end
173
191
 
174
192
  def inspect
175
- @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier
193
+ @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", ''.freeze) : identifier
176
194
  end
177
195
 
178
196
  # This method is responsible for properly setting the encoding of the
@@ -307,34 +325,53 @@ module ActionView
307
325
  template = refresh(view)
308
326
  template.encode!
309
327
  end
310
- raise Template::Error.new(template, e)
328
+ raise Template::Error.new(template)
311
329
  end
312
330
  end
313
331
 
314
332
  def locals_code #:nodoc:
315
- # Only locals with valid variable names get set directly. Others will
316
- # still be available in local_assigns.
317
- locals = @locals.to_set - Module::DELEGATION_RESERVED_METHOD_NAMES
318
- locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
319
333
  # Double assign to suppress the dreaded 'assigned but unused variable' warning
320
- locals.each_with_object('') { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" }
334
+ @locals.each_with_object('') { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" }
321
335
  end
322
336
 
323
337
  def method_name #:nodoc:
324
338
  @method_name ||= begin
325
339
  m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
326
- m.tr!('-', '_')
340
+ m.tr!('-'.freeze, '_'.freeze)
327
341
  m
328
342
  end
329
343
  end
330
344
 
331
345
  def identifier_method_name #:nodoc:
332
- inspect.tr('^a-z_', '_')
346
+ inspect.tr('^a-z_'.freeze, '_'.freeze)
333
347
  end
334
348
 
335
349
  def instrument(action, &block)
336
350
  payload = { virtual_path: @virtual_path, identifier: @identifier }
337
- ActiveSupport::Notifications.instrument("#{action}.action_view", payload, &block)
351
+ case action
352
+ when "!render_template".freeze
353
+ ActiveSupport::Notifications.instrument("!render_template.action_view".freeze, payload, &block)
354
+ else
355
+ ActiveSupport::Notifications.instrument("#{action}.action_view".freeze, payload, &block)
356
+ end
357
+ end
358
+
359
+ EXPLICIT_COLLECTION = /# Template Collection: (?<resource_name>\w+)/
360
+
361
+ def extract_resource_cache_name
362
+ if match = @source.match(EXPLICIT_COLLECTION) || resource_cache_call_match
363
+ match[:resource_name]
364
+ end
365
+ end
366
+
367
+ def resource_cache_call_match
368
+ if @handler.respond_to?(:resource_cache_call_pattern)
369
+ @source.match(@handler.resource_cache_call_pattern)
370
+ end
371
+ end
372
+
373
+ def inferred_cache_name
374
+ @inferred_cache_name ||= @virtual_path.split('/'.freeze).last.sub('_'.freeze, ''.freeze)
338
375
  end
339
376
  end
340
377
  end
@@ -59,13 +59,20 @@ module ActionView
59
59
  class Error < ActionViewError #:nodoc:
60
60
  SOURCE_CODE_RADIUS = 3
61
61
 
62
- attr_reader :original_exception
62
+ def initialize(template, original_exception = nil)
63
+ if original_exception
64
+ ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
65
+ "Exceptions will automatically capture the original exception.", caller)
66
+ end
67
+
68
+ super($!.message)
69
+ set_backtrace($!.backtrace)
70
+ @template, @sub_templates = template, nil
71
+ end
63
72
 
64
- def initialize(template, original_exception)
65
- super(original_exception.message)
66
- @template, @original_exception = template, original_exception
67
- @sub_templates = nil
68
- set_backtrace(original_exception.backtrace)
73
+ def original_exception
74
+ ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
75
+ cause
69
76
  end
70
77
 
71
78
  def file_name
@@ -75,7 +82,7 @@ module ActionView
75
82
  def sub_template_message
76
83
  if @sub_templates
77
84
  "Trace of template inclusion: " +
78
- @sub_templates.collect { |template| template.inspect }.join(", ")
85
+ @sub_templates.collect(&:inspect).join(", ")
79
86
  else
80
87
  ""
81
88
  end
@@ -7,9 +7,9 @@ module ActionView #:nodoc:
7
7
  autoload :Raw, 'action_view/template/handlers/raw'
8
8
 
9
9
  def self.extended(base)
10
- base.register_default_template_handler :erb, ERB.new
10
+ base.register_default_template_handler :raw, Raw.new
11
+ base.register_template_handler :erb, ERB.new
11
12
  base.register_template_handler :builder, Builder.new
12
- base.register_template_handler :raw, Raw.new
13
13
  base.register_template_handler :ruby, :source.to_proc
14
14
  end
15
15
 
@@ -42,7 +42,7 @@ module ActionView #:nodoc:
42
42
  end
43
43
 
44
44
  def template_handler_extensions
45
- @@template_handlers.keys.map {|key| key.to_s }.sort
45
+ @@template_handlers.keys.map(&:to_s).sort
46
46
  end
47
47
 
48
48
  def registered_template_handler(extension)
@@ -123,6 +123,31 @@ module ActionView
123
123
  ).src
124
124
  end
125
125
 
126
+ # Returns Regexp to extract a cached resource's name from a cache call at the
127
+ # first line of a template.
128
+ # The extracted cache name is captured as :resource_name.
129
+ #
130
+ # <% cache notification do %> # => notification
131
+ #
132
+ # The pattern should support templates with a beginning comment:
133
+ #
134
+ # <%# Still extractable even though there's a comment %>
135
+ # <% cache notification do %> # => notification
136
+ #
137
+ # But fail to extract a name if a resource association is cached.
138
+ #
139
+ # <% cache notification.event do %> # => nil
140
+ def resource_cache_call_pattern
141
+ /\A
142
+ (?:<%\#.*%>)* # optional initial comment
143
+ \s* # followed by optional spaces or newlines
144
+ <%\s*cache[\(\s] # followed by an ERB call to cache
145
+ \s* # followed by optional spaces or newlines
146
+ (?<resource_name>\w+) # capture the cache call argument as :resource_name
147
+ [\s\)] # followed by a space or close paren
148
+ /xm
149
+ end
150
+
126
151
  private
127
152
 
128
153
  def valid_encoding(string, encoding)