actionview 4.2.11 → 5.0.7

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 (68) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +304 -184
  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 +51 -18
  8. data/lib/action_view/digestor.rb +83 -81
  9. data/lib/action_view/flows.rb +4 -5
  10. data/lib/action_view/gem_version.rb +3 -3
  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 +6 -5
  14. data/lib/action_view/helpers/cache_helper.rb +62 -21
  15. data/lib/action_view/helpers/capture_helper.rb +5 -4
  16. data/lib/action_view/helpers/controller_helper.rb +11 -2
  17. data/lib/action_view/helpers/date_helper.rb +59 -13
  18. data/lib/action_view/helpers/debug_helper.rb +1 -1
  19. data/lib/action_view/helpers/form_helper.rb +74 -72
  20. data/lib/action_view/helpers/form_options_helper.rb +79 -39
  21. data/lib/action_view/helpers/form_tag_helper.rb +74 -44
  22. data/lib/action_view/helpers/javascript_helper.rb +4 -4
  23. data/lib/action_view/helpers/number_helper.rb +28 -13
  24. data/lib/action_view/helpers/output_safety_helper.rb +32 -2
  25. data/lib/action_view/helpers/record_tag_helper.rb +12 -99
  26. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  27. data/lib/action_view/helpers/sanitize_helper.rb +1 -2
  28. data/lib/action_view/helpers/tag_helper.rb +19 -11
  29. data/lib/action_view/helpers/tags/base.rb +45 -29
  30. data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -28
  31. data/lib/action_view/helpers/tags/collection_helpers.rb +32 -0
  32. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -9
  33. data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
  34. data/lib/action_view/helpers/tags/label.rb +1 -1
  35. data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
  36. data/lib/action_view/helpers/tags/search_field.rb +12 -9
  37. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  38. data/lib/action_view/helpers/tags/translator.rb +1 -1
  39. data/lib/action_view/helpers/text_helper.rb +27 -11
  40. data/lib/action_view/helpers/translation_helper.rb +56 -26
  41. data/lib/action_view/helpers/url_helper.rb +108 -79
  42. data/lib/action_view/layouts.rb +11 -10
  43. data/lib/action_view/log_subscriber.rb +35 -1
  44. data/lib/action_view/lookup_context.rb +69 -48
  45. data/lib/action_view/model_naming.rb +1 -1
  46. data/lib/action_view/path_set.rb +9 -0
  47. data/lib/action_view/railtie.rb +18 -3
  48. data/lib/action_view/record_identifier.rb +45 -19
  49. data/lib/action_view/renderer/abstract_renderer.rb +7 -3
  50. data/lib/action_view/renderer/partial_renderer.rb +38 -37
  51. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +49 -0
  52. data/lib/action_view/renderer/renderer.rb +2 -6
  53. data/lib/action_view/renderer/streaming_template_renderer.rb +1 -1
  54. data/lib/action_view/renderer/template_renderer.rb +11 -10
  55. data/lib/action_view/rendering.rb +15 -7
  56. data/lib/action_view/routing_url_for.rb +18 -6
  57. data/lib/action_view/tasks/{dependencies.rake → cache_digests.rake} +2 -2
  58. data/lib/action_view/template.rb +36 -12
  59. data/lib/action_view/template/error.rb +20 -9
  60. data/lib/action_view/template/handlers.rb +6 -4
  61. data/lib/action_view/template/handlers/html.rb +9 -0
  62. data/lib/action_view/template/handlers/raw.rb +1 -3
  63. data/lib/action_view/template/resolver.rb +49 -42
  64. data/lib/action_view/template/types.rb +14 -16
  65. data/lib/action_view/test_case.rb +15 -9
  66. data/lib/action_view/testing/resolvers.rb +1 -2
  67. data/lib/action_view/view_paths.rb +6 -24
  68. metadata +16 -20
@@ -228,7 +228,7 @@ module ActionView
228
228
  # set by the <tt>layout</tt> method.
229
229
  #
230
230
  # ==== Returns
231
- # * <tt> Boolean</tt> - True if the action has a layout definition, false otherwise.
231
+ # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise.
232
232
  def _conditional_layout?
233
233
  return unless super
234
234
 
@@ -262,7 +262,7 @@ module ActionView
262
262
  def layout(layout, conditions = {})
263
263
  include LayoutConditions unless conditions.empty?
264
264
 
265
- conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
265
+ conditions.each {|k, v| conditions[k] = Array(v).map(&:to_s) }
266
266
  self._layout_conditions = conditions
267
267
 
268
268
  self._layout = layout
@@ -277,7 +277,7 @@ module ActionView
277
277
  remove_possible_method(:_layout)
278
278
 
279
279
  prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
280
- default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}).first || super"
280
+ default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, [], { formats: formats }).first || super"
281
281
  name_clause = if name
282
282
  default_behavior
283
283
  else
@@ -316,7 +316,7 @@ module ActionView
316
316
  end
317
317
 
318
318
  self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
319
- def _layout
319
+ def _layout(formats)
320
320
  if _conditional_layout?
321
321
  #{layout_definition}
322
322
  else
@@ -372,7 +372,7 @@ module ActionView
372
372
  end
373
373
 
374
374
  # This will be overwritten by _write_layout_method
375
- def _layout; end
375
+ def _layout(*); end
376
376
 
377
377
  # Determine the layout for a given name, taking into account the name type.
378
378
  #
@@ -382,8 +382,8 @@ module ActionView
382
382
  case name
383
383
  when String then _normalize_layout(name)
384
384
  when Proc then name
385
- when true then Proc.new { _default_layout(true) }
386
- when :default then Proc.new { _default_layout(false) }
385
+ when true then Proc.new { |formats| _default_layout(formats, true) }
386
+ when :default then Proc.new { |formats| _default_layout(formats, false) }
387
387
  when false, nil then nil
388
388
  else
389
389
  raise ArgumentError,
@@ -399,14 +399,15 @@ module ActionView
399
399
  # Optionally raises an exception if the layout could not be found.
400
400
  #
401
401
  # ==== Parameters
402
+ # * <tt>formats</tt> - The formats accepted to this layout
402
403
  # * <tt>require_layout</tt> - If set to true and layout is not found,
403
- # an ArgumentError exception is raised (defaults to false)
404
+ # an +ArgumentError+ exception is raised (defaults to false)
404
405
  #
405
406
  # ==== Returns
406
407
  # * <tt>template</tt> - The template object for the default layout (or nil)
407
- def _default_layout(require_layout = false)
408
+ def _default_layout(formats, require_layout = false)
408
409
  begin
409
- value = _layout if action_has_layout?
410
+ value = _layout(formats) if action_has_layout?
410
411
  rescue NameError => e
411
412
  raise e, "Could not render layout: #{e.message}"
412
413
  end
@@ -20,7 +20,23 @@ module ActionView
20
20
  end
21
21
  end
22
22
  alias :render_partial :render_template
23
- alias :render_collection :render_template
23
+
24
+ def render_collection(event)
25
+ identifier = event.payload[:identifier] || 'templates'
26
+
27
+ info do
28
+ " Rendered collection of #{from_rails_root(identifier)}" \
29
+ " #{render_count(event.payload)} (#{event.duration.round(1)}ms)"
30
+ end
31
+ end
32
+
33
+ def start(name, id, payload)
34
+ if name == "render_template.action_view"
35
+ log_rendering_start(payload)
36
+ end
37
+
38
+ super
39
+ end
24
40
 
25
41
  def logger
26
42
  ActionView::Base.logger
@@ -38,6 +54,24 @@ module ActionView
38
54
  def rails_root
39
55
  @root ||= "#{Rails.root}/"
40
56
  end
57
+
58
+ def render_count(payload)
59
+ if payload[:cache_hits]
60
+ "[#{payload[:cache_hits]} / #{payload[:count]} cache hits]"
61
+ else
62
+ "[#{payload[:count]} times]"
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def log_rendering_start(payload)
69
+ info do
70
+ message = " Rendering #{from_rails_root(payload[:identifier])}"
71
+ message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
72
+ message
73
+ end
74
+ end
41
75
  end
42
76
  end
43
77
 
@@ -1,4 +1,4 @@
1
- require 'thread_safe'
1
+ require 'concurrent/map'
2
2
  require 'active_support/core_ext/module/remove_method'
3
3
  require 'active_support/core_ext/module/attribute_accessors'
4
4
  require 'action_view/template/resolver'
@@ -6,10 +6,11 @@ require 'action_view/template/resolver'
6
6
  module ActionView
7
7
  # = Action View Lookup Context
8
8
  #
9
- # LookupContext is the object responsible to hold all information required to lookup
10
- # templates, i.e. view paths and details. The LookupContext is also responsible to
11
- # generate a key, given to view paths, used in the resolver cache lookup. Since
12
- # this key is generated just once during the request, it speeds up all cache accesses.
9
+ # <tt>LookupContext</tt> is the object responsible for holding all information
10
+ # required for looking up templates, i.e. view paths and details.
11
+ # <tt>LookupContext</tt> is also responsible for generating a key, given to
12
+ # view paths, used in the resolver cache lookup. Since this key is generated
13
+ # only once during the request, it speeds up all cache accesses.
13
14
  class LookupContext #:nodoc:
14
15
  attr_accessor :prefixes, :rendered_format
15
16
 
@@ -19,9 +20,9 @@ module ActionView
19
20
  mattr_accessor :registered_details
20
21
  self.registered_details = []
21
22
 
22
- def self.register_detail(name, options = {}, &block)
23
+ def self.register_detail(name, &block)
23
24
  self.registered_details << name
24
- initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
25
+ Accessors::DEFAULT_PROCS[name] = block
25
26
 
26
27
  Accessors.send :define_method, :"default_#{name}", &block
27
28
  Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
@@ -33,16 +34,12 @@ module ActionView
33
34
  value = value.present? ? Array(value) : default_#{name}
34
35
  _set_detail(:#{name}, value) if value != @details[:#{name}]
35
36
  end
36
-
37
- remove_possible_method :initialize_details
38
- def initialize_details(details)
39
- #{initialize.join("\n")}
40
- end
41
37
  METHOD
42
38
  end
43
39
 
44
40
  # Holds accessors for the registered details.
45
41
  module Accessors #:nodoc:
42
+ DEFAULT_PROCS = {}
46
43
  end
47
44
 
48
45
  register_detail(:locale) do
@@ -54,19 +51,17 @@ module ActionView
54
51
  end
55
52
  register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
56
53
  register_detail(:variants) { [] }
57
- register_detail(:handlers){ Template::Handlers.extensions }
54
+ register_detail(:handlers) { Template::Handlers.extensions }
58
55
 
59
56
  class DetailsKey #:nodoc:
60
57
  alias :eql? :equal?
61
- alias :object_hash :hash
62
58
 
63
- attr_reader :hash
64
- @details_keys = ThreadSafe::Cache.new
59
+ @details_keys = Concurrent::Map.new
65
60
 
66
61
  def self.get(details)
67
62
  if details[:formats]
68
63
  details = details.dup
69
- details[:formats] &= Mime::SET.symbols
64
+ details[:formats] &= Template::Types.symbols
70
65
  end
71
66
  @details_keys[details] ||= new
72
67
  end
@@ -75,8 +70,14 @@ module ActionView
75
70
  @details_keys.clear
76
71
  end
77
72
 
73
+ def self.digest_caches
74
+ @details_keys.values.map(&:digest_cache)
75
+ end
76
+
77
+ attr_reader :digest_cache
78
+
78
79
  def initialize
79
- @hash = object_hash
80
+ @digest_cache = Concurrent::Map.new
80
81
  end
81
82
  end
82
83
 
@@ -130,11 +131,16 @@ module ActionView
130
131
  @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
131
132
  end
132
133
 
133
- def exists?(name, prefixes = [], partial = false, keys = [], options = {})
134
+ def exists?(name, prefixes = [], partial = false, keys = [], **options)
134
135
  @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
135
136
  end
136
137
  alias :template_exists? :exists?
137
138
 
139
+ def any?(name, prefixes = [], partial = false)
140
+ @view_paths.exists?(*args_for_any(name, prefixes, partial))
141
+ end
142
+ alias :any_templates? :any?
143
+
138
144
  # Adds fallbacks to the view paths. Useful in cases when you are rendering
139
145
  # a :file.
140
146
  def with_fallbacks
@@ -171,18 +177,44 @@ module ActionView
171
177
  [user_details, details_key]
172
178
  end
173
179
 
180
+ def args_for_any(name, prefixes, partial) # :nodoc:
181
+ name, prefixes = normalize_name(name, prefixes)
182
+ details, details_key = detail_args_for_any
183
+ [name, prefixes, partial || false, details, details_key]
184
+ end
185
+
186
+ def detail_args_for_any # :nodoc:
187
+ @detail_args_for_any ||= begin
188
+ details = {}
189
+
190
+ registered_details.each do |k|
191
+ if k == :variants
192
+ details[k] = :any
193
+ else
194
+ details[k] = Accessors::DEFAULT_PROCS[k].call
195
+ end
196
+ end
197
+
198
+ if @cache
199
+ [details, DetailsKey.get(details)]
200
+ else
201
+ [details, nil]
202
+ end
203
+ end
204
+ end
205
+
174
206
  # Support legacy foo.erb names even though we now ignore .erb
175
207
  # as well as incorrectly putting part of the path in the template
176
208
  # name instead of the prefix.
177
209
  def normalize_name(name, prefixes) #:nodoc:
178
210
  prefixes = prefixes.presence
179
- parts = name.to_s.split('/')
211
+ parts = name.to_s.split('/'.freeze)
180
212
  parts.shift if parts.first.empty?
181
213
  name = parts.pop
182
214
 
183
215
  return name, prefixes || [""] if parts.empty?
184
216
 
185
- parts = parts.join('/')
217
+ parts = parts.join('/'.freeze)
186
218
  prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
187
219
 
188
220
  return name, prefixes
@@ -194,21 +226,32 @@ module ActionView
194
226
  include ViewPaths
195
227
 
196
228
  def initialize(view_paths, details = {}, prefixes = [])
197
- @details, @details_key = {}, nil
198
- @skip_default_locale = false
229
+ @details_key = nil
199
230
  @cache = true
200
231
  @prefixes = prefixes
201
232
  @rendered_format = nil
202
233
 
234
+ @details = initialize_details({}, details)
203
235
  self.view_paths = view_paths
204
- initialize_details(details)
205
236
  end
206
237
 
238
+ def digest_cache
239
+ details_key.digest_cache
240
+ end
241
+
242
+ def initialize_details(target, details)
243
+ registered_details.each do |k|
244
+ target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call
245
+ end
246
+ target
247
+ end
248
+ private :initialize_details
249
+
207
250
  # Override formats= to expand ["*/*"] values and automatically
208
251
  # add :html as fallback to :js.
209
252
  def formats=(values)
210
253
  if values
211
- values.concat(default_formats) if values.delete "*/*"
254
+ values.concat(default_formats) if values.delete "*/*".freeze
212
255
  if values == [:js]
213
256
  values << :html
214
257
  @html_fallback_for_js = true
@@ -217,12 +260,6 @@ module ActionView
217
260
  super(values)
218
261
  end
219
262
 
220
- # Do not use the default locale on template lookup.
221
- def skip_default_locale!
222
- @skip_default_locale = true
223
- self.locale = nil
224
- end
225
-
226
263
  # Override locale to return a symbol instead of array.
227
264
  def locale
228
265
  @details[:locale].first
@@ -237,23 +274,7 @@ module ActionView
237
274
  config.locale = value
238
275
  end
239
276
 
240
- super(@skip_default_locale ? I18n.locale : default_locale)
241
- end
242
-
243
- # Uses the first format in the formats array for layout lookup.
244
- def with_layout_format
245
- if formats.size == 1
246
- yield
247
- else
248
- old_formats = formats
249
- _set_detail(:formats, formats[0,1])
250
-
251
- begin
252
- yield
253
- ensure
254
- _set_detail(:formats, old_formats)
255
- end
256
- end
277
+ super(default_locale)
257
278
  end
258
279
  end
259
280
  end
@@ -1,5 +1,5 @@
1
1
  module ActionView
2
- module ModelNaming
2
+ module ModelNaming #:nodoc:
3
3
  # Converts the given object to an ActiveModel compliant one.
4
4
  def convert_to_model(object)
5
5
  object.respond_to?(:to_model) ? object.to_model : object
@@ -58,6 +58,15 @@ module ActionView #:nodoc:
58
58
  find_all(path, prefixes, *args).any?
59
59
  end
60
60
 
61
+ def find_all_with_query(query) # :nodoc:
62
+ paths.each do |resolver|
63
+ templates = resolver.find_all_with_query(query)
64
+ return templates unless templates.empty?
65
+ end
66
+
67
+ []
68
+ end
69
+
61
70
  private
62
71
 
63
72
  def _find_all(path, prefixes, args, outside_app)
@@ -6,6 +6,7 @@ module ActionView
6
6
  class Railtie < Rails::Railtie # :nodoc:
7
7
  config.action_view = ActiveSupport::OrderedOptions.new
8
8
  config.action_view.embed_authenticity_token_in_remote_forms = false
9
+ config.action_view.debug_missing_translation = true
9
10
 
10
11
  config.eager_load_namespaces << ActionView
11
12
 
@@ -36,14 +37,28 @@ module ActionView
36
37
  end
37
38
  end
38
39
 
40
+ initializer "action_view.per_request_digest_cache" do |app|
41
+ ActiveSupport.on_load(:action_view) do
42
+ if app.config.consider_all_requests_local
43
+ app.executor.to_run ActionView::Digestor::PerExecutionDigestCacheExpiry
44
+ end
45
+ end
46
+ end
47
+
39
48
  initializer "action_view.setup_action_pack" do |app|
40
49
  ActiveSupport.on_load(:action_controller) do
41
- ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
50
+ ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
42
51
  end
43
52
  end
44
53
 
45
- rake_tasks do
46
- load "action_view/tasks/dependencies.rake"
54
+ initializer "action_view.collection_caching", after: "action_controller.set_configs" do |app|
55
+ PartialRenderer.collection_cache = app.config.action_controller.cache_store
56
+ end
57
+
58
+ rake_tasks do |app|
59
+ unless app.config.api_only
60
+ load "action_view/tasks/cache_digests.rake"
61
+ end
47
62
  end
48
63
  end
49
64
  end
@@ -2,29 +2,55 @@ require 'active_support/core_ext/module'
2
2
  require 'action_view/model_naming'
3
3
 
4
4
  module ActionView
5
- # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
6
- # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
7
- # a higher logical level.
5
+ # RecordIdentifier encapsulates methods used by various ActionView helpers
6
+ # to associate records with DOM elements.
8
7
  #
9
- # # routes
10
- # resources :posts
8
+ # Consider for example the following code that form of post:
11
9
  #
12
- # # view
13
- # <%= div_for(post) do %> <div id="post_45" class="post">
14
- # <%= post.body %> What a wonderful world!
15
- # <% end %> </div>
10
+ # <%= form_for(post) do |f| %>
11
+ # <%= f.text_field :body %>
12
+ # <% end %>
16
13
  #
17
- # # controller
18
- # def update
19
- # post = Post.find(params[:id])
20
- # post.update(params[:post])
14
+ # When +post+ is a new, unsaved ActiveRecord::Base instance, the resulting HTML
15
+ # is:
21
16
  #
22
- # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
23
- # end
17
+ # <form class="new_post" id="new_post" action="/posts" accept-charset="UTF-8" method="post">
18
+ # <input type="text" name="post[body]" id="post_body" />
19
+ # </form>
20
+ #
21
+ # When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML
22
+ # is:
23
+ #
24
+ # <form class="edit_post" id="edit_post_42" action="/posts/42" accept-charset="UTF-8" method="post">
25
+ # <input type="text" value="What a wonderful world!" name="post[body]" id="post_body" />
26
+ # </form>
27
+ #
28
+ # In both cases, the +id+ and +class+ of the wrapping DOM element are
29
+ # automatically generated, following naming conventions encapsulated by the
30
+ # RecordIdentifier methods #dom_id and #dom_class:
31
+ #
32
+ # dom_id(Post.new) # => "new_post"
33
+ # dom_class(Post.new) # => "post"
34
+ # dom_id(Post.find 42) # => "post_42"
35
+ # dom_class(Post.find 42) # => "post"
24
36
  #
25
- # As the example above shows, you can stop caring to a large extent what the actual id of the post is.
26
- # You just know that one is being assigned and that the subsequent calls in redirect_to expect that
27
- # same naming convention and allows you to write less code if you follow it.
37
+ # Note that these methods do not strictly require +Post+ to be a subclass of
38
+ # ActiveRecord::Base.
39
+ # Any +Post+ class will work as long as its instances respond to +to_key+
40
+ # and +model_name+, given that +model_name+ responds to +param_key+.
41
+ # For instance:
42
+ #
43
+ # class Post
44
+ # attr_accessor :to_key
45
+ #
46
+ # def model_name
47
+ # OpenStruct.new param_key: 'post'
48
+ # end
49
+ #
50
+ # def self.find(id)
51
+ # new.tap { |post| post.to_key = [id] }
52
+ # end
53
+ # end
28
54
  module RecordIdentifier
29
55
  extend self
30
56
  extend ModelNaming
@@ -78,7 +104,7 @@ module ActionView
78
104
  # make sure yourself that your dom ids are valid, in case you overwrite this method.
79
105
  def record_key_for_dom_id(record)
80
106
  key = convert_to_model(record).to_key
81
- key ? key.join('_') : key
107
+ key ? key.join(JOIN) : key
82
108
  end
83
109
  end
84
110
  end