omg-actionview 8.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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