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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +25 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +40 -0
- data/app/assets/javascripts/rails-ujs.esm.js +686 -0
- data/app/assets/javascripts/rails-ujs.js +630 -0
- data/lib/action_view/base.rb +316 -0
- data/lib/action_view/buffers.rb +165 -0
- data/lib/action_view/cache_expiry.rb +69 -0
- data/lib/action_view/context.rb +32 -0
- data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
- data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
- data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +41 -0
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +130 -0
- data/lib/action_view/flows.rb +75 -0
- data/lib/action_view/gem_version.rb +17 -0
- data/lib/action_view/helpers/active_model_helper.rb +54 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
- data/lib/action_view/helpers/asset_url_helper.rb +473 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
- data/lib/action_view/helpers/cache_helper.rb +315 -0
- data/lib/action_view/helpers/capture_helper.rb +236 -0
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +42 -0
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +35 -0
- data/lib/action_view/helpers/date_helper.rb +1266 -0
- data/lib/action_view/helpers/debug_helper.rb +38 -0
- data/lib/action_view/helpers/form_helper.rb +2765 -0
- data/lib/action_view/helpers/form_options_helper.rb +927 -0
- data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
- data/lib/action_view/helpers/javascript_helper.rb +96 -0
- data/lib/action_view/helpers/number_helper.rb +165 -0
- data/lib/action_view/helpers/output_safety_helper.rb +70 -0
- data/lib/action_view/helpers/rendering_helper.rb +218 -0
- data/lib/action_view/helpers/sanitize_helper.rb +201 -0
- data/lib/action_view/helpers/tag_helper.rb +621 -0
- data/lib/action_view/helpers/tags/base.rb +138 -0
- data/lib/action_view/helpers/tags/check_box.rb +65 -0
- data/lib/action_view/helpers/tags/checkable.rb +18 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
- data/lib/action_view/helpers/tags/collection_select.rb +33 -0
- data/lib/action_view/helpers/tags/color_field.rb +26 -0
- data/lib/action_view/helpers/tags/date_field.rb +14 -0
- data/lib/action_view/helpers/tags/date_select.rb +75 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
- data/lib/action_view/helpers/tags/email_field.rb +10 -0
- data/lib/action_view/helpers/tags/file_field.rb +26 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
- data/lib/action_view/helpers/tags/label.rb +84 -0
- data/lib/action_view/helpers/tags/month_field.rb +14 -0
- data/lib/action_view/helpers/tags/number_field.rb +20 -0
- data/lib/action_view/helpers/tags/password_field.rb +14 -0
- data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
- data/lib/action_view/helpers/tags/radio_button.rb +32 -0
- data/lib/action_view/helpers/tags/range_field.rb +10 -0
- data/lib/action_view/helpers/tags/search_field.rb +27 -0
- data/lib/action_view/helpers/tags/select.rb +45 -0
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/tel_field.rb +10 -0
- data/lib/action_view/helpers/tags/text_area.rb +24 -0
- data/lib/action_view/helpers/tags/text_field.rb +33 -0
- data/lib/action_view/helpers/tags/time_field.rb +23 -0
- data/lib/action_view/helpers/tags/time_select.rb +10 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
- data/lib/action_view/helpers/tags/translator.rb +39 -0
- data/lib/action_view/helpers/tags/url_field.rb +10 -0
- data/lib/action_view/helpers/tags/week_field.rb +14 -0
- data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
- data/lib/action_view/helpers/tags.rb +47 -0
- data/lib/action_view/helpers/text_helper.rb +568 -0
- data/lib/action_view/helpers/translation_helper.rb +161 -0
- data/lib/action_view/helpers/url_helper.rb +812 -0
- data/lib/action_view/helpers.rb +68 -0
- data/lib/action_view/layouts.rb +434 -0
- data/lib/action_view/locale/en.yml +56 -0
- data/lib/action_view/log_subscriber.rb +132 -0
- data/lib/action_view/lookup_context.rb +299 -0
- data/lib/action_view/model_naming.rb +14 -0
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +84 -0
- data/lib/action_view/railtie.rb +132 -0
- data/lib/action_view/record_identifier.rb +118 -0
- data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
- data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
- data/lib/action_view/render_parser.rb +40 -0
- data/lib/action_view/renderer/abstract_renderer.rb +186 -0
- data/lib/action_view/renderer/collection_renderer.rb +204 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
- data/lib/action_view/renderer/partial_renderer.rb +267 -0
- data/lib/action_view/renderer/renderer.rb +107 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
- data/lib/action_view/renderer/template_renderer.rb +115 -0
- data/lib/action_view/rendering.rb +190 -0
- data/lib/action_view/routing_url_for.rb +149 -0
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +264 -0
- data/lib/action_view/template/handlers/builder.rb +25 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
- data/lib/action_view/template/handlers/erb.rb +157 -0
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/handlers.rb +66 -0
- data/lib/action_view/template/html.rb +33 -0
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +30 -0
- data/lib/action_view/template/resolver.rb +212 -0
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +32 -0
- data/lib/action_view/template/types.rb +50 -0
- data/lib/action_view/template.rb +580 -0
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +66 -0
- data/lib/action_view/test_case.rb +449 -0
- data/lib/action_view/testing/resolvers.rb +44 -0
- data/lib/action_view/unbound_template.rb +67 -0
- data/lib/action_view/version.rb +10 -0
- data/lib/action_view/view_paths.rb +117 -0
- data/lib/action_view.rb +104 -0
- 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
|