omg-actionview 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/app/assets/javascripts/rails-ujs.esm.js +686 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +316 -0
  8. data/lib/action_view/buffers.rb +165 -0
  9. data/lib/action_view/cache_expiry.rb +69 -0
  10. data/lib/action_view/context.rb +32 -0
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
  12. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  13. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  14. data/lib/action_view/dependency_tracker.rb +41 -0
  15. data/lib/action_view/deprecator.rb +7 -0
  16. data/lib/action_view/digestor.rb +130 -0
  17. data/lib/action_view/flows.rb +75 -0
  18. data/lib/action_view/gem_version.rb +17 -0
  19. data/lib/action_view/helpers/active_model_helper.rb +54 -0
  20. data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
  21. data/lib/action_view/helpers/asset_url_helper.rb +473 -0
  22. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  23. data/lib/action_view/helpers/cache_helper.rb +315 -0
  24. data/lib/action_view/helpers/capture_helper.rb +236 -0
  25. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  26. data/lib/action_view/helpers/controller_helper.rb +42 -0
  27. data/lib/action_view/helpers/csp_helper.rb +26 -0
  28. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  29. data/lib/action_view/helpers/date_helper.rb +1266 -0
  30. data/lib/action_view/helpers/debug_helper.rb +38 -0
  31. data/lib/action_view/helpers/form_helper.rb +2765 -0
  32. data/lib/action_view/helpers/form_options_helper.rb +927 -0
  33. data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
  34. data/lib/action_view/helpers/javascript_helper.rb +96 -0
  35. data/lib/action_view/helpers/number_helper.rb +165 -0
  36. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  37. data/lib/action_view/helpers/rendering_helper.rb +218 -0
  38. data/lib/action_view/helpers/sanitize_helper.rb +201 -0
  39. data/lib/action_view/helpers/tag_helper.rb +621 -0
  40. data/lib/action_view/helpers/tags/base.rb +138 -0
  41. data/lib/action_view/helpers/tags/check_box.rb +65 -0
  42. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  43. data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
  44. data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
  45. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  46. data/lib/action_view/helpers/tags/collection_select.rb +33 -0
  47. data/lib/action_view/helpers/tags/color_field.rb +26 -0
  48. data/lib/action_view/helpers/tags/date_field.rb +14 -0
  49. data/lib/action_view/helpers/tags/date_select.rb +75 -0
  50. data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
  51. data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
  52. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  53. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  54. data/lib/action_view/helpers/tags/file_field.rb +26 -0
  55. data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
  56. data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/label.rb +84 -0
  58. data/lib/action_view/helpers/tags/month_field.rb +14 -0
  59. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  60. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  61. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  62. data/lib/action_view/helpers/tags/radio_button.rb +32 -0
  63. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  64. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  65. data/lib/action_view/helpers/tags/select.rb +45 -0
  66. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  67. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  68. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  69. data/lib/action_view/helpers/tags/text_field.rb +33 -0
  70. data/lib/action_view/helpers/tags/time_field.rb +23 -0
  71. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  72. data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
  73. data/lib/action_view/helpers/tags/translator.rb +39 -0
  74. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  75. data/lib/action_view/helpers/tags/week_field.rb +14 -0
  76. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  77. data/lib/action_view/helpers/tags.rb +47 -0
  78. data/lib/action_view/helpers/text_helper.rb +568 -0
  79. data/lib/action_view/helpers/translation_helper.rb +161 -0
  80. data/lib/action_view/helpers/url_helper.rb +812 -0
  81. data/lib/action_view/helpers.rb +68 -0
  82. data/lib/action_view/layouts.rb +434 -0
  83. data/lib/action_view/locale/en.yml +56 -0
  84. data/lib/action_view/log_subscriber.rb +132 -0
  85. data/lib/action_view/lookup_context.rb +299 -0
  86. data/lib/action_view/model_naming.rb +14 -0
  87. data/lib/action_view/path_registry.rb +57 -0
  88. data/lib/action_view/path_set.rb +84 -0
  89. data/lib/action_view/railtie.rb +132 -0
  90. data/lib/action_view/record_identifier.rb +118 -0
  91. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  92. data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
  93. data/lib/action_view/render_parser.rb +40 -0
  94. data/lib/action_view/renderer/abstract_renderer.rb +186 -0
  95. data/lib/action_view/renderer/collection_renderer.rb +204 -0
  96. data/lib/action_view/renderer/object_renderer.rb +34 -0
  97. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
  98. data/lib/action_view/renderer/partial_renderer.rb +267 -0
  99. data/lib/action_view/renderer/renderer.rb +107 -0
  100. data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
  101. data/lib/action_view/renderer/template_renderer.rb +115 -0
  102. data/lib/action_view/rendering.rb +190 -0
  103. data/lib/action_view/routing_url_for.rb +149 -0
  104. data/lib/action_view/tasks/cache_digests.rake +25 -0
  105. data/lib/action_view/template/error.rb +264 -0
  106. data/lib/action_view/template/handlers/builder.rb +25 -0
  107. data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
  108. data/lib/action_view/template/handlers/erb.rb +157 -0
  109. data/lib/action_view/template/handlers/html.rb +11 -0
  110. data/lib/action_view/template/handlers/raw.rb +11 -0
  111. data/lib/action_view/template/handlers.rb +66 -0
  112. data/lib/action_view/template/html.rb +33 -0
  113. data/lib/action_view/template/inline.rb +22 -0
  114. data/lib/action_view/template/raw_file.rb +25 -0
  115. data/lib/action_view/template/renderable.rb +30 -0
  116. data/lib/action_view/template/resolver.rb +212 -0
  117. data/lib/action_view/template/sources/file.rb +17 -0
  118. data/lib/action_view/template/sources.rb +13 -0
  119. data/lib/action_view/template/text.rb +32 -0
  120. data/lib/action_view/template/types.rb +50 -0
  121. data/lib/action_view/template.rb +580 -0
  122. data/lib/action_view/template_details.rb +66 -0
  123. data/lib/action_view/template_path.rb +66 -0
  124. data/lib/action_view/test_case.rb +449 -0
  125. data/lib/action_view/testing/resolvers.rb +44 -0
  126. data/lib/action_view/unbound_template.rb +67 -0
  127. data/lib/action_view/version.rb +10 -0
  128. data/lib/action_view/view_paths.rb +117 -0
  129. data/lib/action_view.rb +104 -0
  130. metadata +275 -0
@@ -0,0 +1,299 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/map"
4
+ require "active_support/core_ext/module/attribute_accessors"
5
+ require "action_view/template/resolver"
6
+
7
+ module ActionView
8
+ # = Action View Lookup Context
9
+ #
10
+ # <tt>LookupContext</tt> is the object responsible for holding all information
11
+ # required for looking up templates, i.e. view paths and details.
12
+ # <tt>LookupContext</tt> is also responsible for generating a key, given to
13
+ # view paths, used in the resolver cache lookup. Since this key is generated
14
+ # only once during the request, it speeds up all cache accesses.
15
+ class LookupContext # :nodoc:
16
+ attr_accessor :prefixes
17
+
18
+ singleton_class.attr_accessor :registered_details
19
+ self.registered_details = []
20
+
21
+ def self.register_detail(name, &block)
22
+ registered_details << name
23
+ Accessors::DEFAULT_PROCS[name] = block
24
+
25
+ Accessors.define_method(:"default_#{name}", &block)
26
+ Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
27
+ def #{name}
28
+ @details[:#{name}] || []
29
+ end
30
+
31
+ def #{name}=(value)
32
+ value = value.present? ? Array(value) : default_#{name}
33
+ _set_detail(:#{name}, value) if value != @details[:#{name}]
34
+ end
35
+ METHOD
36
+ end
37
+
38
+ # Holds accessors for the registered details.
39
+ module Accessors # :nodoc:
40
+ DEFAULT_PROCS = {}
41
+ end
42
+
43
+ register_detail(:locale) do
44
+ locales = [I18n.locale]
45
+ locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
46
+ locales << I18n.default_locale
47
+ locales.uniq!
48
+ locales
49
+ end
50
+ register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
51
+ register_detail(:variants) { [] }
52
+ register_detail(:handlers) { Template::Handlers.extensions }
53
+
54
+ class DetailsKey # :nodoc:
55
+ alias :eql? :equal?
56
+
57
+ @details_keys = Concurrent::Map.new
58
+ @digest_cache = Concurrent::Map.new
59
+ @view_context_mutex = Mutex.new
60
+
61
+ def self.digest_cache(details)
62
+ @digest_cache[details_cache_key(details)] ||= Concurrent::Map.new
63
+ end
64
+
65
+ def self.details_cache_key(details)
66
+ @details_keys.fetch(details) do
67
+ if formats = details[:formats]
68
+ unless Template::Types.valid_symbols?(formats)
69
+ details = details.dup
70
+ details[:formats] &= Template::Types.symbols
71
+ end
72
+ end
73
+ @details_keys[details] ||= TemplateDetails::Requested.new(**details)
74
+ end
75
+ end
76
+
77
+ def self.clear
78
+ ActionView::PathRegistry.all_resolvers.each do |resolver|
79
+ resolver.clear_cache
80
+ end
81
+ @view_context_class = nil
82
+ @details_keys.clear
83
+ @digest_cache.clear
84
+ end
85
+
86
+ def self.digest_caches
87
+ @digest_cache.values
88
+ end
89
+
90
+ def self.view_context_class
91
+ @view_context_mutex.synchronize do
92
+ @view_context_class ||= ActionView::Base.with_empty_template_cache
93
+ end
94
+ end
95
+ end
96
+
97
+ # Add caching behavior on top of Details.
98
+ module DetailsCache
99
+ attr_accessor :cache
100
+
101
+ # Calculate the details key. Remove the handlers from calculation to improve performance
102
+ # since the user cannot modify it explicitly.
103
+ def details_key # :nodoc:
104
+ @details_key ||= DetailsKey.details_cache_key(@details) if @cache
105
+ end
106
+
107
+ # Temporary skip passing the details_key forward.
108
+ def disable_cache
109
+ old_value, @cache = @cache, false
110
+ yield
111
+ ensure
112
+ @cache = old_value
113
+ end
114
+
115
+ private
116
+ def _set_detail(key, value) # :doc:
117
+ @details = @details.dup if @digest_cache || @details_key
118
+ @digest_cache = nil
119
+ @details_key = nil
120
+ @details[key] = value
121
+ end
122
+ end
123
+
124
+ # Helpers related to template lookup using the lookup context information.
125
+ module ViewPaths
126
+ attr_reader :view_paths, :html_fallback_for_js
127
+
128
+ def find(name, prefixes = [], partial = false, keys = [], options = {})
129
+ name, prefixes = normalize_name(name, prefixes)
130
+ details, details_key = detail_args_for(options)
131
+ @view_paths.find(name, prefixes, partial, details, details_key, keys)
132
+ end
133
+ alias :find_template :find
134
+
135
+ def find_all(name, prefixes = [], partial = false, keys = [], options = {})
136
+ name, prefixes = normalize_name(name, prefixes)
137
+ details, details_key = detail_args_for(options)
138
+ @view_paths.find_all(name, prefixes, partial, details, details_key, keys)
139
+ end
140
+
141
+ def exists?(name, prefixes = [], partial = false, keys = [], **options)
142
+ name, prefixes = normalize_name(name, prefixes)
143
+ details, details_key = detail_args_for(options)
144
+ @view_paths.exists?(name, prefixes, partial, details, details_key, keys)
145
+ end
146
+ alias :template_exists? :exists?
147
+
148
+ def any?(name, prefixes = [], partial = false)
149
+ name, prefixes = normalize_name(name, prefixes)
150
+ details, details_key = detail_args_for_any
151
+ @view_paths.exists?(name, prefixes, partial, details, details_key, [])
152
+ end
153
+ alias :any_templates? :any?
154
+
155
+ def append_view_paths(paths)
156
+ @view_paths = build_view_paths(@view_paths.to_a + paths)
157
+ end
158
+
159
+ def prepend_view_paths(paths)
160
+ @view_paths = build_view_paths(paths + @view_paths.to_a)
161
+ end
162
+
163
+ private
164
+ # Whenever setting view paths, makes a copy so that we can manipulate them in
165
+ # instance objects as we wish.
166
+ def build_view_paths(paths)
167
+ if ActionView::PathSet === paths
168
+ paths
169
+ else
170
+ ActionView::PathSet.new(Array(paths))
171
+ end
172
+ end
173
+
174
+ # Compute details hash and key according to user options (e.g. passed from #render).
175
+ def detail_args_for(options) # :doc:
176
+ return @details, details_key if options.empty? # most common path.
177
+ user_details = @details.merge(options)
178
+
179
+ if @cache
180
+ details_key = DetailsKey.details_cache_key(user_details)
181
+ else
182
+ details_key = nil
183
+ end
184
+
185
+ [user_details, details_key]
186
+ end
187
+
188
+ def detail_args_for_any
189
+ @detail_args_for_any ||= begin
190
+ details = {}
191
+
192
+ LookupContext.registered_details.each do |k|
193
+ if k == :variants
194
+ details[k] = :any
195
+ else
196
+ details[k] = Accessors::DEFAULT_PROCS[k].call
197
+ end
198
+ end
199
+
200
+ if @cache
201
+ [details, DetailsKey.details_cache_key(details)]
202
+ else
203
+ [details, nil]
204
+ end
205
+ end
206
+ end
207
+
208
+ # Fix when prefix is specified as part of the template name
209
+ def normalize_name(name, prefixes)
210
+ name = name.to_s
211
+ idx = name.rindex("/")
212
+ return name, prefixes.presence || [""] unless idx
213
+
214
+ path_prefix = name[0, idx]
215
+ path_prefix = path_prefix.from(1) if path_prefix.start_with?("/")
216
+ name = name.from(idx + 1)
217
+
218
+ if !prefixes || prefixes.empty?
219
+ prefixes = [path_prefix]
220
+ else
221
+ prefixes = prefixes.map { |p| "#{p}/#{path_prefix}" }
222
+ end
223
+
224
+ return name, prefixes
225
+ end
226
+ end
227
+
228
+ include Accessors
229
+ include DetailsCache
230
+ include ViewPaths
231
+
232
+ def initialize(view_paths, details = {}, prefixes = [])
233
+ @details_key = nil
234
+ @digest_cache = nil
235
+ @cache = true
236
+ @prefixes = prefixes
237
+
238
+ @details = initialize_details({}, details)
239
+ @view_paths = build_view_paths(view_paths)
240
+ end
241
+
242
+ def digest_cache
243
+ @digest_cache ||= DetailsKey.digest_cache(@details)
244
+ end
245
+
246
+ def with_prepended_formats(formats)
247
+ details = @details.dup
248
+ details[:formats] = formats
249
+
250
+ self.class.new(@view_paths, details, @prefixes)
251
+ end
252
+
253
+ def initialize_details(target, details)
254
+ LookupContext.registered_details.each do |k|
255
+ target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call
256
+ end
257
+ target
258
+ end
259
+ private :initialize_details
260
+
261
+ # Override formats= to expand ["*/*"] values and automatically
262
+ # add :html as fallback to :js.
263
+ def formats=(values)
264
+ if values
265
+ values = values.dup
266
+ values.concat(default_formats) if values.delete "*/*"
267
+ values.uniq!
268
+
269
+ unless Template::Types.valid_symbols?(values)
270
+ invalid_values = values - Template::Types.symbols
271
+ raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}"
272
+ end
273
+
274
+ if (values.length == 1) && (values[0] == :js)
275
+ values << :html
276
+ @html_fallback_for_js = true
277
+ end
278
+ end
279
+ super(values)
280
+ end
281
+
282
+ # Override locale to return a symbol instead of array.
283
+ def locale
284
+ @details[:locale].first
285
+ end
286
+
287
+ # Overload locale= to also set the I18n.locale. If the current I18n.config object responds
288
+ # to original_config, it means that it has a copy of the original I18n configuration and it's
289
+ # acting as proxy, which we need to skip.
290
+ def locale=(value)
291
+ if value
292
+ config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config
293
+ config.locale = value
294
+ end
295
+
296
+ super(default_locale)
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module ModelNaming # :nodoc:
5
+ # Converts the given object to an Active Model compliant one.
6
+ def convert_to_model(object)
7
+ object.respond_to?(:to_model) ? object.to_model : object
8
+ end
9
+
10
+ def model_name_from_record_or_class(record_or_class)
11
+ convert_to_model(record_or_class).model_name
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView # :nodoc:
4
+ module PathRegistry # :nodoc:
5
+ @view_paths_by_class = {}
6
+ @file_system_resolvers = {}
7
+ @file_system_resolver_mutex = Mutex.new
8
+ @file_system_resolver_hooks = []
9
+
10
+ class << self
11
+ attr_reader :file_system_resolver_hooks
12
+ end
13
+
14
+ def self.get_view_paths(klass)
15
+ @view_paths_by_class[klass] || get_view_paths(klass.superclass)
16
+ end
17
+
18
+ def self.set_view_paths(klass, paths)
19
+ @view_paths_by_class[klass] = paths
20
+ end
21
+
22
+ def self.cast_file_system_resolvers(paths)
23
+ paths = Array(paths)
24
+
25
+ @file_system_resolver_mutex.synchronize do
26
+ built_resolver = false
27
+ paths = paths.map do |path|
28
+ case path
29
+ when String, Pathname
30
+ path = File.expand_path(path)
31
+ @file_system_resolvers[path] ||=
32
+ begin
33
+ built_resolver = true
34
+ FileSystemResolver.new(path)
35
+ end
36
+ else
37
+ path
38
+ end
39
+ end
40
+
41
+ file_system_resolver_hooks.each(&:call) if built_resolver
42
+ end
43
+
44
+ paths
45
+ end
46
+
47
+ def self.all_resolvers
48
+ resolvers = [all_file_system_resolvers]
49
+ resolvers.concat @view_paths_by_class.values.map(&:to_a)
50
+ resolvers.flatten.uniq
51
+ end
52
+
53
+ def self.all_file_system_resolvers
54
+ @file_system_resolvers.values
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView # :nodoc:
4
+ # = Action View PathSet
5
+ #
6
+ # This class is used to store and access paths in Action View. A number of
7
+ # operations are defined so that you can search among the paths in this
8
+ # set and also perform operations on other +PathSet+ objects.
9
+ #
10
+ # A +LookupContext+ will use a +PathSet+ to store the paths in its context.
11
+ class PathSet # :nodoc:
12
+ include Enumerable
13
+
14
+ attr_reader :paths
15
+
16
+ delegate :[], :include?, :size, :each, to: :paths
17
+
18
+ def initialize(paths = [])
19
+ @paths = typecast(paths).freeze
20
+ end
21
+
22
+ def initialize_copy(other)
23
+ @paths = other.paths.dup.freeze
24
+ self
25
+ end
26
+
27
+ def to_ary
28
+ paths.dup
29
+ end
30
+
31
+ def compact
32
+ PathSet.new paths.compact
33
+ end
34
+
35
+ def +(other)
36
+ array = Array === other ? other : other.paths
37
+ PathSet.new(paths + array)
38
+ end
39
+
40
+ def find(path, prefixes, partial, details, details_key, locals)
41
+ find_all(path, prefixes, partial, details, details_key, locals).first ||
42
+ raise(MissingTemplate.new(self, path, prefixes, partial, details, details_key, locals))
43
+ end
44
+
45
+ def find_all(path, prefixes, partial, details, details_key, locals)
46
+ search_combinations(prefixes) do |resolver, prefix|
47
+ templates = resolver.find_all(path, prefix, partial, details, details_key, locals)
48
+ return templates unless templates.empty?
49
+ end
50
+ []
51
+ end
52
+
53
+ def exists?(path, prefixes, partial, details, details_key, locals)
54
+ find_all(path, prefixes, partial, details, details_key, locals).any?
55
+ end
56
+
57
+ private
58
+ def search_combinations(prefixes)
59
+ prefixes = Array(prefixes)
60
+ prefixes.each do |prefix|
61
+ paths.each do |resolver|
62
+ yield resolver, prefix
63
+ end
64
+ end
65
+ end
66
+
67
+ def typecast(paths)
68
+ paths.map do |path|
69
+ case path
70
+ when Pathname, String
71
+ # This path should only be reached by "direct" users of
72
+ # ActionView::Base (not using the ViewPaths or Renderer modules).
73
+ # We can't cache/de-dup the file system resolver in this case as we
74
+ # don't know which compiled_method_container we'll be rendering to.
75
+ FileSystemResolver.new(path)
76
+ when Resolver
77
+ path
78
+ else
79
+ raise TypeError, "#{path.inspect} is not a valid path: must be a String, Pathname, or Resolver"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view"
4
+ require "rails"
5
+
6
+ module ActionView
7
+ # = Action View Railtie
8
+ class Railtie < Rails::Engine # :nodoc:
9
+ config.action_view = ActiveSupport::OrderedOptions.new
10
+ config.action_view.embed_authenticity_token_in_remote_forms = nil
11
+ config.action_view.debug_missing_translation = true
12
+ config.action_view.default_enforce_utf8 = nil
13
+ config.action_view.image_loading = nil
14
+ config.action_view.image_decoding = nil
15
+ config.action_view.apply_stylesheet_media_default = true
16
+ config.action_view.prepend_content_exfiltration_prevention = false
17
+
18
+ config.eager_load_namespaces << ActionView
19
+
20
+ config.after_initialize do |app|
21
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms =
22
+ app.config.action_view.delete(:embed_authenticity_token_in_remote_forms)
23
+ end
24
+
25
+ config.after_initialize do |app|
26
+ form_with_generates_remote_forms = app.config.action_view.delete(:form_with_generates_remote_forms)
27
+ ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms
28
+ end
29
+
30
+ config.after_initialize do |app|
31
+ form_with_generates_ids = app.config.action_view.delete(:form_with_generates_ids)
32
+ unless form_with_generates_ids.nil?
33
+ ActionView::Helpers::FormHelper.form_with_generates_ids = form_with_generates_ids
34
+ end
35
+ end
36
+
37
+ config.after_initialize do |app|
38
+ default_enforce_utf8 = app.config.action_view.delete(:default_enforce_utf8)
39
+ unless default_enforce_utf8.nil?
40
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8
41
+ end
42
+ end
43
+
44
+ config.after_initialize do |app|
45
+ prepend_content_exfiltration_prevention = app.config.action_view.delete(:prepend_content_exfiltration_prevention)
46
+ ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = prepend_content_exfiltration_prevention
47
+ end
48
+
49
+ config.after_initialize do |app|
50
+ if klass = app.config.action_view.delete(:sanitizer_vendor)
51
+ ActionView::Helpers::SanitizeHelper.sanitizer_vendor = klass
52
+ end
53
+ end
54
+
55
+ config.after_initialize do |app|
56
+ button_to_generates_button_tag = app.config.action_view.delete(:button_to_generates_button_tag)
57
+ unless button_to_generates_button_tag.nil?
58
+ ActionView::Helpers::UrlHelper.button_to_generates_button_tag = button_to_generates_button_tag
59
+ end
60
+ end
61
+
62
+ config.after_initialize do |app|
63
+ frozen_string_literal = app.config.action_view.delete(:frozen_string_literal)
64
+ ActionView::Template.frozen_string_literal = frozen_string_literal
65
+ end
66
+
67
+ config.after_initialize do |app|
68
+ ActionView::Helpers::AssetTagHelper.image_loading = app.config.action_view.delete(:image_loading)
69
+ ActionView::Helpers::AssetTagHelper.image_decoding = app.config.action_view.delete(:image_decoding)
70
+ ActionView::Helpers::AssetTagHelper.preload_links_header = app.config.action_view.delete(:preload_links_header)
71
+ ActionView::Helpers::AssetTagHelper.apply_stylesheet_media_default = app.config.action_view.delete(:apply_stylesheet_media_default)
72
+ end
73
+
74
+ config.after_initialize do |app|
75
+ ActiveSupport.on_load(:action_view) do
76
+ app.config.action_view.each do |k, v|
77
+ send "#{k}=", v
78
+ end
79
+ end
80
+ end
81
+
82
+ initializer "action_view.deprecator", before: :load_environment_config do |app|
83
+ app.deprecators[:action_view] = ActionView.deprecator
84
+ end
85
+
86
+ initializer "action_view.logger" do
87
+ ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
88
+ end
89
+
90
+ initializer "action_view.caching" do |app|
91
+ ActiveSupport.on_load(:action_view) do
92
+ if app.config.action_view.cache_template_loading.nil?
93
+ ActionView::Resolver.caching = !app.config.reloading_enabled?
94
+ end
95
+ end
96
+ end
97
+
98
+ initializer "action_view.setup_action_pack" do |app|
99
+ ActiveSupport.on_load(:action_controller) do
100
+ ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
101
+ end
102
+ end
103
+
104
+ initializer "action_view.collection_caching", after: "action_controller.set_configs" do |app|
105
+ PartialRenderer.collection_cache = app.config.action_controller.cache_store
106
+ end
107
+
108
+ config.after_initialize do |app|
109
+ enable_caching = if app.config.action_view.cache_template_loading.nil?
110
+ !app.config.reloading_enabled?
111
+ else
112
+ app.config.action_view.cache_template_loading
113
+ end
114
+
115
+ unless enable_caching
116
+ view_reloader = ActionView::CacheExpiry::ViewReloader.new(watcher: app.config.file_watcher)
117
+
118
+ app.reloaders << view_reloader
119
+ app.reloader.to_run do
120
+ require_unload_lock!
121
+ view_reloader.execute
122
+ end
123
+ end
124
+ end
125
+
126
+ rake_tasks do |app|
127
+ unless app.config.api_only
128
+ load "action_view/tasks/cache_digests.rake"
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module"
4
+ require "action_view/model_naming"
5
+
6
+ module ActionView
7
+ # = Action View \Record \Identifier
8
+ #
9
+ # RecordIdentifier encapsulates methods used by various ActionView helpers
10
+ # to associate records with DOM elements.
11
+ #
12
+ # Consider for example the following code that form of post:
13
+ #
14
+ # <%= form_for(post) do |f| %>
15
+ # <%= f.text_field :body %>
16
+ # <% end %>
17
+ #
18
+ # When +post+ is a new, unsaved ActiveRecord::Base instance, the resulting HTML
19
+ # is:
20
+ #
21
+ # <form class="new_post" id="new_post" action="/posts" accept-charset="UTF-8" method="post">
22
+ # <input type="text" name="post[body]" id="post_body" />
23
+ # </form>
24
+ #
25
+ # When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML
26
+ # is:
27
+ #
28
+ # <form class="edit_post" id="edit_post_42" action="/posts/42" accept-charset="UTF-8" method="post">
29
+ # <input type="text" value="What a wonderful world!" name="post[body]" id="post_body" />
30
+ # </form>
31
+ #
32
+ # In both cases, the +id+ and +class+ of the wrapping DOM element are
33
+ # automatically generated, following naming conventions encapsulated by the
34
+ # RecordIdentifier methods #dom_id and #dom_class:
35
+ #
36
+ # dom_id(Post) # => "new_post"
37
+ # dom_class(Post) # => "post"
38
+ # dom_id(Post.new) # => "new_post"
39
+ # dom_class(Post.new) # => "post"
40
+ # dom_id(Post.find 42) # => "post_42"
41
+ # dom_class(Post.find 42) # => "post"
42
+ #
43
+ # Note that these methods do not strictly require +Post+ to be a subclass of
44
+ # ActiveRecord::Base.
45
+ # Any +Post+ class will work as long as its instances respond to +to_key+
46
+ # and +model_name+, given that +model_name+ responds to +param_key+.
47
+ # For instance:
48
+ #
49
+ # class Post
50
+ # attr_accessor :to_key
51
+ #
52
+ # def model_name
53
+ # OpenStruct.new param_key: 'post'
54
+ # end
55
+ #
56
+ # def self.find(id)
57
+ # new.tap { |post| post.to_key = [id] }
58
+ # end
59
+ # end
60
+ module RecordIdentifier
61
+ extend self
62
+ extend ModelNaming
63
+
64
+ include ModelNaming
65
+
66
+ JOIN = "_"
67
+ NEW = "new"
68
+
69
+ # The DOM class convention is to use the singular form of an object or class.
70
+ #
71
+ # dom_class(post) # => "post"
72
+ # dom_class(Person) # => "person"
73
+ #
74
+ # If you need to address multiple instances of the same class in the same view, you can prefix the dom_class:
75
+ #
76
+ # dom_class(post, :edit) # => "edit_post"
77
+ # dom_class(Person, :edit) # => "edit_person"
78
+ def dom_class(record_or_class, prefix = nil)
79
+ singular = model_name_from_record_or_class(record_or_class).param_key
80
+ prefix ? "#{prefix}#{JOIN}#{singular}" : singular
81
+ end
82
+
83
+ # The DOM id convention is to use the singular form of an object or class with the id following an underscore.
84
+ # If no id is found, prefix with "new_" instead.
85
+ #
86
+ # dom_id(Post.find(45)) # => "post_45"
87
+ # dom_id(Post) # => "new_post"
88
+ #
89
+ # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
90
+ #
91
+ # dom_id(Post.find(45), :edit) # => "edit_post_45"
92
+ # dom_id(Post, :custom) # => "custom_post"
93
+ def dom_id(record_or_class, prefix = nil)
94
+ raise ArgumentError, "dom_id must be passed a record_or_class as the first argument, you passed #{record_or_class.inspect}" unless record_or_class
95
+
96
+ record_id = record_key_for_dom_id(record_or_class) unless record_or_class.is_a?(Class)
97
+ if record_id
98
+ "#{dom_class(record_or_class, prefix)}#{JOIN}#{record_id}"
99
+ else
100
+ dom_class(record_or_class, prefix || NEW)
101
+ end
102
+ end
103
+
104
+ private
105
+ # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id.
106
+ # This can be overwritten to customize the default generated string representation if desired.
107
+ # If you need to read back a key from a dom_id in order to query for the underlying database record,
108
+ # you should write a helper like 'person_record_from_dom_id' that will extract the key either based
109
+ # on the default implementation (which just joins all key attributes with '_') or on your own
110
+ # overwritten version of the method. By default, this implementation passes the key string through a
111
+ # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
112
+ # make sure yourself that your dom ids are valid, in case you override this method.
113
+ def record_key_for_dom_id(record) # :doc:
114
+ key = convert_to_model(record).to_key
115
+ key && key.all? ? key.join(JOIN) : nil
116
+ end
117
+ end
118
+ end