actionview 6.1.7.2 → 7.1.3
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 +4 -4
- data/CHANGELOG.md +299 -277
- data/MIT-LICENSE +2 -1
- data/README.rdoc +3 -3
- 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 +37 -19
- data/lib/action_view/buffers.rb +107 -9
- data/lib/action_view/cache_expiry.rb +48 -37
- data/lib/action_view/context.rb +1 -1
- data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
- data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
- data/lib/action_view/dependency_tracker.rb +6 -147
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +8 -5
- data/lib/action_view/flows.rb +4 -4
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +200 -60
- data/lib/action_view/helpers/asset_url_helper.rb +22 -21
- data/lib/action_view/helpers/atom_feed_helper.rb +8 -9
- data/lib/action_view/helpers/cache_helper.rb +55 -12
- data/lib/action_view/helpers/capture_helper.rb +34 -14
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +8 -2
- data/lib/action_view/helpers/csp_helper.rb +3 -3
- data/lib/action_view/helpers/csrf_helper.rb +4 -4
- data/lib/action_view/helpers/date_helper.rb +123 -57
- data/lib/action_view/helpers/debug_helper.rb +6 -4
- data/lib/action_view/helpers/form_helper.rb +253 -97
- data/lib/action_view/helpers/form_options_helper.rb +72 -34
- data/lib/action_view/helpers/form_tag_helper.rb +189 -58
- data/lib/action_view/helpers/javascript_helper.rb +4 -5
- data/lib/action_view/helpers/number_helper.rb +43 -335
- data/lib/action_view/helpers/output_safety_helper.rb +6 -6
- data/lib/action_view/helpers/rendering_helper.rb +6 -7
- data/lib/action_view/helpers/sanitize_helper.rb +54 -24
- data/lib/action_view/helpers/tag_helper.rb +42 -35
- data/lib/action_view/helpers/tags/base.rb +16 -77
- data/lib/action_view/helpers/tags/check_box.rb +1 -1
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
- data/lib/action_view/helpers/tags/collection_select.rb +4 -1
- data/lib/action_view/helpers/tags/date_field.rb +1 -1
- data/lib/action_view/helpers/tags/date_select.rb +2 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
- data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
- data/lib/action_view/helpers/tags/file_field.rb +16 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/month_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +4 -1
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/time_field.rb +11 -2
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
- data/lib/action_view/helpers/tags.rb +5 -2
- data/lib/action_view/helpers/text_helper.rb +180 -97
- data/lib/action_view/helpers/translation_helper.rb +14 -45
- data/lib/action_view/helpers/url_helper.rb +230 -132
- data/lib/action_view/helpers.rb +27 -25
- data/lib/action_view/layouts.rb +15 -10
- data/lib/action_view/log_subscriber.rb +49 -32
- data/lib/action_view/lookup_context.rb +58 -61
- data/lib/action_view/model_naming.rb +2 -2
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +28 -35
- data/lib/action_view/railtie.rb +44 -9
- data/lib/action_view/record_identifier.rb +16 -9
- data/lib/action_view/render_parser.rb +188 -0
- data/lib/action_view/renderer/abstract_renderer.rb +3 -3
- data/lib/action_view/renderer/collection_renderer.rb +10 -2
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +21 -3
- data/lib/action_view/renderer/partial_renderer.rb +3 -36
- data/lib/action_view/renderer/renderer.rb +6 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +6 -5
- data/lib/action_view/renderer/template_renderer.rb +9 -4
- data/lib/action_view/rendering.rb +25 -7
- data/lib/action_view/ripper_ast_parser.rb +198 -0
- data/lib/action_view/routing_url_for.rb +8 -5
- data/lib/action_view/template/error.rb +122 -14
- data/lib/action_view/template/handlers/builder.rb +4 -4
- data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
- data/lib/action_view/template/handlers/erb.rb +79 -1
- data/lib/action_view/template/handlers.rb +4 -4
- data/lib/action_view/template/html.rb +4 -4
- data/lib/action_view/template/inline.rb +3 -3
- data/lib/action_view/template/raw_file.rb +4 -4
- data/lib/action_view/template/renderable.rb +1 -1
- data/lib/action_view/template/resolver.rb +96 -313
- data/lib/action_view/template/text.rb +4 -4
- data/lib/action_view/template/types.rb +25 -32
- data/lib/action_view/template.rb +245 -41
- 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 +182 -23
- data/lib/action_view/testing/resolvers.rb +11 -12
- data/lib/action_view/unbound_template.rb +43 -7
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +19 -28
- data/lib/action_view.rb +6 -4
- data/lib/assets/compiled/rails-ujs.js +36 -5
- metadata +32 -25
|
@@ -9,6 +9,9 @@ require "rails-dom-testing"
|
|
|
9
9
|
|
|
10
10
|
module ActionView
|
|
11
11
|
# = Action View Test Case
|
|
12
|
+
#
|
|
13
|
+
# Read more about <tt>ActionView::TestCase</tt> in {Testing Rails Applications}[https://guides.rubyonrails.org/testing.html#testing-view-partials]
|
|
14
|
+
# in the guides.
|
|
12
15
|
class TestCase < ActiveSupport::TestCase
|
|
13
16
|
class TestController < ActionController::Base
|
|
14
17
|
include ActionDispatch::TestProcess
|
|
@@ -24,6 +27,10 @@ module ActionView
|
|
|
24
27
|
self.class.controller_path = path
|
|
25
28
|
end
|
|
26
29
|
|
|
30
|
+
def self.controller_name
|
|
31
|
+
"test"
|
|
32
|
+
end
|
|
33
|
+
|
|
27
34
|
def initialize
|
|
28
35
|
super
|
|
29
36
|
self.class.controller_path = ""
|
|
@@ -53,9 +60,98 @@ module ActionView
|
|
|
53
60
|
include ActiveSupport::Testing::ConstantLookup
|
|
54
61
|
|
|
55
62
|
delegate :lookup_context, to: :controller
|
|
56
|
-
attr_accessor :controller, :
|
|
63
|
+
attr_accessor :controller, :request, :output_buffer
|
|
57
64
|
|
|
58
65
|
module ClassMethods
|
|
66
|
+
def inherited(descendant) # :nodoc:
|
|
67
|
+
super
|
|
68
|
+
|
|
69
|
+
descendant_content_class = content_class.dup
|
|
70
|
+
|
|
71
|
+
if descendant_content_class.respond_to?(:set_temporary_name)
|
|
72
|
+
descendant_content_class.set_temporary_name("rendered_content")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
descendant.content_class = descendant_content_class
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Register a callable to parse rendered content for a given template
|
|
79
|
+
# format.
|
|
80
|
+
#
|
|
81
|
+
# Each registered parser will also define a +#rendered.[FORMAT]+ helper
|
|
82
|
+
# method, where +[FORMAT]+ corresponds to the value of the
|
|
83
|
+
# +format+ argument.
|
|
84
|
+
#
|
|
85
|
+
# By default, ActionView::TestCase defines parsers for:
|
|
86
|
+
#
|
|
87
|
+
# * +:html+ - returns an instance of +Nokogiri::XML::Node+
|
|
88
|
+
# * +:json+ - returns an instance of ActiveSupport::HashWithIndifferentAccess
|
|
89
|
+
#
|
|
90
|
+
# These pre-registered parsers also define corresponding helpers:
|
|
91
|
+
#
|
|
92
|
+
# * +:html+ - defines +rendered.html+
|
|
93
|
+
# * +:json+ - defines +rendered.json+
|
|
94
|
+
#
|
|
95
|
+
# ==== Parameters
|
|
96
|
+
#
|
|
97
|
+
# [+format+]
|
|
98
|
+
# The name (as a +Symbol+) of the format used to render the content.
|
|
99
|
+
#
|
|
100
|
+
# [+callable+]
|
|
101
|
+
# The parser. A callable object that accepts the rendered string as
|
|
102
|
+
# its sole argument. Alternatively, the parser can be specified as a
|
|
103
|
+
# block.
|
|
104
|
+
#
|
|
105
|
+
# ==== Examples
|
|
106
|
+
#
|
|
107
|
+
# test "renders HTML" do
|
|
108
|
+
# article = Article.create!(title: "Hello, world")
|
|
109
|
+
#
|
|
110
|
+
# render partial: "articles/article", locals: { article: article }
|
|
111
|
+
#
|
|
112
|
+
# assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } }
|
|
113
|
+
# end
|
|
114
|
+
#
|
|
115
|
+
# test "renders JSON" do
|
|
116
|
+
# article = Article.create!(title: "Hello, world")
|
|
117
|
+
#
|
|
118
|
+
# render formats: :json, partial: "articles/article", locals: { article: article }
|
|
119
|
+
#
|
|
120
|
+
# assert_pattern { rendered.json => { title: "Hello, world" } }
|
|
121
|
+
# end
|
|
122
|
+
#
|
|
123
|
+
# To parse the rendered content into RSS, register a call to +RSS::Parser.parse+:
|
|
124
|
+
#
|
|
125
|
+
# register_parser :rss, -> rendered { RSS::Parser.parse(rendered) }
|
|
126
|
+
#
|
|
127
|
+
# test "renders RSS" do
|
|
128
|
+
# article = Article.create!(title: "Hello, world")
|
|
129
|
+
#
|
|
130
|
+
# render formats: :rss, partial: article
|
|
131
|
+
#
|
|
132
|
+
# assert_equal "Hello, world", rendered.rss.items.last.title
|
|
133
|
+
# end
|
|
134
|
+
#
|
|
135
|
+
# To parse the rendered content into a +Capybara::Simple::Node+,
|
|
136
|
+
# re-register an +:html+ parser with a call to +Capybara.string+:
|
|
137
|
+
#
|
|
138
|
+
# register_parser :html, -> rendered { Capybara.string(rendered) }
|
|
139
|
+
#
|
|
140
|
+
# test "renders HTML" do
|
|
141
|
+
# article = Article.create!(title: "Hello, world")
|
|
142
|
+
#
|
|
143
|
+
# render partial: article
|
|
144
|
+
#
|
|
145
|
+
# rendered.html.assert_css "h1", text: "Hello, world"
|
|
146
|
+
# end
|
|
147
|
+
#
|
|
148
|
+
def register_parser(format, callable = nil, &block)
|
|
149
|
+
parser = callable || block || :itself.to_proc
|
|
150
|
+
content_class.redefine_method(format) do
|
|
151
|
+
parser.call(to_s)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
59
155
|
def tests(helper_class)
|
|
60
156
|
case helper_class
|
|
61
157
|
when String, Symbol
|
|
@@ -74,11 +170,11 @@ module ActionView
|
|
|
74
170
|
def helper_method(*methods)
|
|
75
171
|
# Almost a duplicate from ActionController::Helpers
|
|
76
172
|
methods.flatten.each do |method|
|
|
77
|
-
_helpers_for_modification.module_eval
|
|
173
|
+
_helpers_for_modification.module_eval <<~end_eval, __FILE__, __LINE__ + 1
|
|
78
174
|
def #{method}(*args, &block) # def current_user(*args, &block)
|
|
79
175
|
_test_case.send(:'#{method}', *args, &block) # _test_case.send(:'current_user', *args, &block)
|
|
80
176
|
end # end
|
|
81
|
-
ruby2_keywords(:'#{method}')
|
|
177
|
+
ruby2_keywords(:'#{method}')
|
|
82
178
|
end_eval
|
|
83
179
|
end
|
|
84
180
|
end
|
|
@@ -101,14 +197,33 @@ module ActionView
|
|
|
101
197
|
end
|
|
102
198
|
end
|
|
103
199
|
|
|
200
|
+
included do
|
|
201
|
+
class_attribute :content_class, instance_accessor: false, default: RenderedViewContent
|
|
202
|
+
|
|
203
|
+
setup :setup_with_controller
|
|
204
|
+
|
|
205
|
+
register_parser :html, -> rendered { Rails::Dom::Testing.html_document.parse(rendered).root }
|
|
206
|
+
register_parser :json, -> rendered { JSON.parse(rendered, object_class: ActiveSupport::HashWithIndifferentAccess) }
|
|
207
|
+
|
|
208
|
+
ActiveSupport.run_load_hooks(:action_view_test_case, self)
|
|
209
|
+
|
|
210
|
+
helper do
|
|
211
|
+
def protect_against_forgery?
|
|
212
|
+
false
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def _test_case
|
|
216
|
+
controller._test_case
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
104
221
|
def setup_with_controller
|
|
105
222
|
controller_class = Class.new(ActionView::TestCase::TestController)
|
|
106
223
|
@controller = controller_class.new
|
|
107
224
|
@request = @controller.request
|
|
108
225
|
@view_flow = ActionView::OutputFlow.new
|
|
109
|
-
|
|
110
|
-
# new without arguments returns ASCII-8BIT encoded buffer like String#new
|
|
111
|
-
@output_buffer = ActiveSupport::SafeBuffer.new ""
|
|
226
|
+
@output_buffer = ActionView::OutputBuffer.new
|
|
112
227
|
@rendered = +""
|
|
113
228
|
|
|
114
229
|
test_case_instance = self
|
|
@@ -129,10 +244,64 @@ module ActionView
|
|
|
129
244
|
@_rendered_views ||= RenderedViewsCollection.new
|
|
130
245
|
end
|
|
131
246
|
|
|
247
|
+
# Returns the content rendered by the last +render+ call.
|
|
248
|
+
#
|
|
249
|
+
# The returned object behaves like a string but also exposes a number of methods
|
|
250
|
+
# that allows you to parse the content string in formats registered using
|
|
251
|
+
# <tt>.register_parser</tt>.
|
|
252
|
+
#
|
|
253
|
+
# By default includes the following parsers:
|
|
254
|
+
#
|
|
255
|
+
# +.html+
|
|
256
|
+
#
|
|
257
|
+
# Parse the <tt>rendered</tt> content String into HTML. By default, this means
|
|
258
|
+
# a <tt>Nokogiri::XML::Node</tt>.
|
|
259
|
+
#
|
|
260
|
+
# test "renders HTML" do
|
|
261
|
+
# article = Article.create!(title: "Hello, world")
|
|
262
|
+
#
|
|
263
|
+
# render partial: "articles/article", locals: { article: article }
|
|
264
|
+
#
|
|
265
|
+
# assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } }
|
|
266
|
+
# end
|
|
267
|
+
#
|
|
268
|
+
# To parse the rendered content into a <tt>Capybara::Simple::Node</tt>,
|
|
269
|
+
# re-register an <tt>:html</tt> parser with a call to
|
|
270
|
+
# <tt>Capybara.string</tt>:
|
|
271
|
+
#
|
|
272
|
+
# register_parser :html, -> rendered { Capybara.string(rendered) }
|
|
273
|
+
#
|
|
274
|
+
# test "renders HTML" do
|
|
275
|
+
# article = Article.create!(title: "Hello, world")
|
|
276
|
+
#
|
|
277
|
+
# render partial: article
|
|
278
|
+
#
|
|
279
|
+
# rendered.html.assert_css "h1", text: "Hello, world"
|
|
280
|
+
# end
|
|
281
|
+
#
|
|
282
|
+
# +.json+
|
|
283
|
+
#
|
|
284
|
+
# Parse the <tt>rendered</tt> content String into JSON. By default, this means
|
|
285
|
+
# a <tt>ActiveSupport::HashWithIndifferentAccess</tt>.
|
|
286
|
+
#
|
|
287
|
+
# test "renders JSON" do
|
|
288
|
+
# article = Article.create!(title: "Hello, world")
|
|
289
|
+
#
|
|
290
|
+
# render formats: :json, partial: "articles/article", locals: { article: article }
|
|
291
|
+
#
|
|
292
|
+
# assert_pattern { rendered.json => { title: "Hello, world" } }
|
|
293
|
+
# end
|
|
294
|
+
def rendered
|
|
295
|
+
@_rendered ||= self.class.content_class.new(@rendered)
|
|
296
|
+
end
|
|
297
|
+
|
|
132
298
|
def _routes
|
|
133
299
|
@controller._routes if @controller.respond_to?(:_routes)
|
|
134
300
|
end
|
|
135
301
|
|
|
302
|
+
class RenderedViewContent < String # :nodoc:
|
|
303
|
+
end
|
|
304
|
+
|
|
136
305
|
# Need to experiment if this priority is the best one: rendered => output_buffer
|
|
137
306
|
class RenderedViewsCollection
|
|
138
307
|
def initialize
|
|
@@ -159,25 +328,10 @@ module ActionView
|
|
|
159
328
|
end
|
|
160
329
|
end
|
|
161
330
|
|
|
162
|
-
included do
|
|
163
|
-
setup :setup_with_controller
|
|
164
|
-
ActiveSupport.run_load_hooks(:action_view_test_case, self)
|
|
165
|
-
|
|
166
|
-
helper do
|
|
167
|
-
def protect_against_forgery?
|
|
168
|
-
false
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def _test_case
|
|
172
|
-
controller._test_case
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
331
|
private
|
|
178
332
|
# Need to experiment if this priority is the best one: rendered => output_buffer
|
|
179
333
|
def document_root_element
|
|
180
|
-
|
|
334
|
+
Rails::Dom::Testing.html_document.parse(@rendered.blank? ? @output_buffer.to_str : @rendered).root
|
|
181
335
|
end
|
|
182
336
|
|
|
183
337
|
module Locals
|
|
@@ -223,6 +377,10 @@ module ActionView
|
|
|
223
377
|
:@_result,
|
|
224
378
|
:@_routes,
|
|
225
379
|
:@controller,
|
|
380
|
+
:@_controller,
|
|
381
|
+
:@_request,
|
|
382
|
+
:@_config,
|
|
383
|
+
:@_default_form_builder,
|
|
226
384
|
:@_layouts,
|
|
227
385
|
:@_files,
|
|
228
386
|
:@_rendered_views,
|
|
@@ -241,7 +399,7 @@ module ActionView
|
|
|
241
399
|
:@view_context_class,
|
|
242
400
|
:@view_flow,
|
|
243
401
|
:@_subscribers,
|
|
244
|
-
:@html_document
|
|
402
|
+
:@html_document,
|
|
245
403
|
]
|
|
246
404
|
|
|
247
405
|
def _user_defined_ivars
|
|
@@ -273,6 +431,7 @@ module ActionView
|
|
|
273
431
|
super
|
|
274
432
|
end
|
|
275
433
|
end
|
|
434
|
+
ruby2_keywords(:method_missing)
|
|
276
435
|
|
|
277
436
|
def respond_to_missing?(name, include_private = false)
|
|
278
437
|
begin
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require "action_view/template/resolver"
|
|
4
4
|
|
|
5
|
-
module ActionView
|
|
5
|
+
module ActionView # :nodoc:
|
|
6
6
|
# Use FixtureResolver in your tests to simulate the presence of files on the
|
|
7
7
|
# file system. This is used internally by Rails' own test suite, and is
|
|
8
8
|
# useful for testing extensions that have no way of knowing what the file
|
|
9
9
|
# system will look like at runtime.
|
|
10
|
-
class FixtureResolver <
|
|
10
|
+
class FixtureResolver < FileSystemResolver
|
|
11
11
|
def initialize(hash = {})
|
|
12
12
|
super("")
|
|
13
13
|
@hash = hash
|
|
@@ -23,23 +23,22 @@ module ActionView #:nodoc:
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
private
|
|
26
|
-
def
|
|
27
|
-
@hash.keys.
|
|
28
|
-
|
|
29
|
-
end.map do |fixture|
|
|
30
|
-
"/#{fixture}"
|
|
26
|
+
def template_glob(glob)
|
|
27
|
+
@hash.keys.filter_map do |path|
|
|
28
|
+
"/#{path}" if File.fnmatch(glob, path)
|
|
31
29
|
end
|
|
32
30
|
end
|
|
33
31
|
|
|
34
32
|
def source_for_template(template)
|
|
35
|
-
@hash[template
|
|
33
|
+
@hash[template.from(1)]
|
|
36
34
|
end
|
|
37
35
|
end
|
|
38
36
|
|
|
39
|
-
class NullResolver <
|
|
40
|
-
def
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
class NullResolver < Resolver
|
|
38
|
+
def find_templates(name, prefix, partial, details, locals = [])
|
|
39
|
+
path = TemplatePath.build(name, prefix, partial)
|
|
40
|
+
handler = ActionView::Template::Handlers::Raw
|
|
41
|
+
[ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: nil, variant: nil, locals: locals)]
|
|
43
42
|
end
|
|
44
43
|
end
|
|
45
44
|
end
|
|
@@ -4,28 +4,64 @@ require "concurrent/map"
|
|
|
4
4
|
|
|
5
5
|
module ActionView
|
|
6
6
|
class UnboundTemplate
|
|
7
|
-
|
|
7
|
+
attr_reader :virtual_path, :details
|
|
8
|
+
delegate :locale, :format, :variant, :handler, to: :@details
|
|
9
|
+
|
|
10
|
+
def initialize(source, identifier, details:, virtual_path:)
|
|
8
11
|
@source = source
|
|
9
12
|
@identifier = identifier
|
|
10
|
-
@
|
|
11
|
-
@
|
|
13
|
+
@details = details
|
|
14
|
+
@virtual_path = virtual_path
|
|
12
15
|
|
|
13
16
|
@templates = Concurrent::Map.new(initial_capacity: 2)
|
|
17
|
+
@write_lock = Mutex.new
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
def bind_locals(locals)
|
|
17
|
-
@templates[locals]
|
|
21
|
+
unless template = @templates[locals]
|
|
22
|
+
@write_lock.synchronize do
|
|
23
|
+
normalized_locals = normalize_locals(locals)
|
|
24
|
+
|
|
25
|
+
# We need ||=, both to dedup on the normalized locals and to check
|
|
26
|
+
# while holding the lock.
|
|
27
|
+
template = (@templates[normalized_locals] ||= build_template(normalized_locals))
|
|
28
|
+
|
|
29
|
+
# This may have already been assigned, but we've already de-dup'd so
|
|
30
|
+
# reassignment is fine.
|
|
31
|
+
@templates[locals.dup] = template
|
|
32
|
+
|
|
33
|
+
if template.strict_locals?
|
|
34
|
+
# Under strict locals, we only need one template.
|
|
35
|
+
# This replaces the @templates Concurrent::Map with a hash which
|
|
36
|
+
# returns this template for every key.
|
|
37
|
+
@templates = Hash.new(template).freeze
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
template
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def built_templates # :nodoc:
|
|
45
|
+
@templates.values
|
|
18
46
|
end
|
|
19
47
|
|
|
20
48
|
private
|
|
21
49
|
def build_template(locals)
|
|
22
|
-
options = @options.merge(locals: locals)
|
|
23
50
|
Template.new(
|
|
24
51
|
@source,
|
|
25
52
|
@identifier,
|
|
26
|
-
|
|
27
|
-
|
|
53
|
+
details.handler_class,
|
|
54
|
+
|
|
55
|
+
format: details.format_or_default,
|
|
56
|
+
variant: variant&.to_s,
|
|
57
|
+
virtual_path: @virtual_path,
|
|
58
|
+
|
|
59
|
+
locals: locals.map(&:to_s)
|
|
28
60
|
)
|
|
29
61
|
end
|
|
62
|
+
|
|
63
|
+
def normalize_locals(locals)
|
|
64
|
+
locals.map(&:to_sym).sort!.freeze
|
|
65
|
+
end
|
|
30
66
|
end
|
|
31
67
|
end
|
data/lib/action_view/version.rb
CHANGED
|
@@ -5,7 +5,7 @@ module ActionView
|
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
6
|
|
|
7
7
|
included do
|
|
8
|
-
|
|
8
|
+
ActionView::PathRegistry.set_view_paths(self, ActionView::PathSet.new.freeze)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=,
|
|
@@ -13,11 +13,11 @@ module ActionView
|
|
|
13
13
|
|
|
14
14
|
module ClassMethods
|
|
15
15
|
def _view_paths
|
|
16
|
-
|
|
16
|
+
ActionView::PathRegistry.get_view_paths(self)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def _view_paths=(paths)
|
|
20
|
-
|
|
20
|
+
ActionView::PathRegistry.set_view_paths(self, paths)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def _prefixes # :nodoc:
|
|
@@ -28,6 +28,13 @@ module ActionView
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def _build_view_paths(paths) # :nodoc:
|
|
32
|
+
return paths if ActionView::PathSet === paths
|
|
33
|
+
|
|
34
|
+
paths = ActionView::PathRegistry.cast_file_system_resolvers(paths)
|
|
35
|
+
ActionView::PathSet.new(paths)
|
|
36
|
+
end
|
|
37
|
+
|
|
31
38
|
# Append a path to the list of view paths for this controller.
|
|
32
39
|
#
|
|
33
40
|
# ==== Parameters
|
|
@@ -35,7 +42,7 @@ module ActionView
|
|
|
35
42
|
# the default view path. You may also provide a custom view path
|
|
36
43
|
# (see ActionView::PathSet for more information)
|
|
37
44
|
def append_view_path(path)
|
|
38
|
-
self._view_paths = view_paths +
|
|
45
|
+
self._view_paths = view_paths + _build_view_paths(path)
|
|
39
46
|
end
|
|
40
47
|
|
|
41
48
|
# Prepend a path to the list of view paths for this controller.
|
|
@@ -45,7 +52,7 @@ module ActionView
|
|
|
45
52
|
# the default view path. You may also provide a custom view path
|
|
46
53
|
# (see ActionView::PathSet for more information)
|
|
47
54
|
def prepend_view_path(path)
|
|
48
|
-
self._view_paths =
|
|
55
|
+
self._view_paths = _build_view_paths(path) + view_paths
|
|
49
56
|
end
|
|
50
57
|
|
|
51
58
|
# A list of all of the default view paths for this controller.
|
|
@@ -59,7 +66,7 @@ module ActionView
|
|
|
59
66
|
# * <tt>paths</tt> - If a PathSet is provided, use that;
|
|
60
67
|
# otherwise, process the parameter into a PathSet.
|
|
61
68
|
def view_paths=(paths)
|
|
62
|
-
self._view_paths =
|
|
69
|
+
self._view_paths = _build_view_paths(paths)
|
|
63
70
|
end
|
|
64
71
|
|
|
65
72
|
private
|
|
@@ -70,30 +77,14 @@ module ActionView
|
|
|
70
77
|
end
|
|
71
78
|
end
|
|
72
79
|
|
|
73
|
-
# :stopdoc:
|
|
74
|
-
@all_view_paths = {}
|
|
75
|
-
|
|
76
|
-
def self.get_view_paths(klass)
|
|
77
|
-
@all_view_paths[klass] || get_view_paths(klass.superclass)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def self.set_view_paths(klass, paths)
|
|
81
|
-
@all_view_paths[klass] = paths
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def self.all_view_paths
|
|
85
|
-
@all_view_paths.values.uniq
|
|
86
|
-
end
|
|
87
|
-
# :startdoc:
|
|
88
|
-
|
|
89
80
|
# The prefixes used in render "foo" shortcuts.
|
|
90
81
|
def _prefixes # :nodoc:
|
|
91
82
|
self.class._prefixes
|
|
92
83
|
end
|
|
93
84
|
|
|
94
|
-
#
|
|
85
|
+
# LookupContext is the object responsible for holding all
|
|
95
86
|
# information required for looking up templates, i.e. view paths and
|
|
96
|
-
# details. Check
|
|
87
|
+
# details. Check ActionView::LookupContext for more information.
|
|
97
88
|
def lookup_context
|
|
98
89
|
@_lookup_context ||=
|
|
99
90
|
ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
|
|
@@ -103,24 +94,24 @@ module ActionView
|
|
|
103
94
|
{}
|
|
104
95
|
end
|
|
105
96
|
|
|
106
|
-
# Append a path to the list of view paths for the current
|
|
97
|
+
# Append a path to the list of view paths for the current LookupContext.
|
|
107
98
|
#
|
|
108
99
|
# ==== Parameters
|
|
109
100
|
# * <tt>path</tt> - If a String is provided, it gets converted into
|
|
110
101
|
# the default view path. You may also provide a custom view path
|
|
111
102
|
# (see ActionView::PathSet for more information)
|
|
112
103
|
def append_view_path(path)
|
|
113
|
-
lookup_context.
|
|
104
|
+
lookup_context.append_view_paths(self.class._build_view_paths(path))
|
|
114
105
|
end
|
|
115
106
|
|
|
116
|
-
# Prepend a path to the list of view paths for the current
|
|
107
|
+
# Prepend a path to the list of view paths for the current LookupContext.
|
|
117
108
|
#
|
|
118
109
|
# ==== Parameters
|
|
119
110
|
# * <tt>path</tt> - If a String is provided, it gets converted into
|
|
120
111
|
# the default view path. You may also provide a custom view path
|
|
121
112
|
# (see ActionView::PathSet for more information)
|
|
122
113
|
def prepend_view_path(path)
|
|
123
|
-
lookup_context.
|
|
114
|
+
lookup_context.prepend_view_paths(self.class._build_view_paths(path))
|
|
124
115
|
end
|
|
125
116
|
end
|
|
126
117
|
end
|
data/lib/action_view.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
#--
|
|
4
|
-
# Copyright (c)
|
|
4
|
+
# Copyright (c) David Heinemeier Hansson
|
|
5
5
|
#
|
|
6
6
|
# Permission is hereby granted, free of charge, to any person obtaining
|
|
7
7
|
# a copy of this software and associated documentation files (the
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
require "active_support"
|
|
27
27
|
require "active_support/rails"
|
|
28
28
|
require "action_view/version"
|
|
29
|
+
require "action_view/deprecator"
|
|
29
30
|
|
|
31
|
+
# :include: actionview/README.rdoc
|
|
30
32
|
module ActionView
|
|
31
33
|
extend ActiveSupport::Autoload
|
|
32
34
|
|
|
@@ -39,11 +41,14 @@ module ActionView
|
|
|
39
41
|
autoload :Helpers
|
|
40
42
|
autoload :LookupContext
|
|
41
43
|
autoload :Layouts
|
|
44
|
+
autoload :PathRegistry
|
|
42
45
|
autoload :PathSet
|
|
43
46
|
autoload :RecordIdentifier
|
|
44
47
|
autoload :Rendering
|
|
45
48
|
autoload :RoutingUrlFor
|
|
46
49
|
autoload :Template
|
|
50
|
+
autoload :TemplateDetails
|
|
51
|
+
autoload :TemplatePath
|
|
47
52
|
autoload :UnboundTemplate
|
|
48
53
|
autoload :ViewPaths
|
|
49
54
|
|
|
@@ -59,10 +64,7 @@ module ActionView
|
|
|
59
64
|
|
|
60
65
|
autoload_at "action_view/template/resolver" do
|
|
61
66
|
autoload :Resolver
|
|
62
|
-
autoload :PathResolver
|
|
63
67
|
autoload :FileSystemResolver
|
|
64
|
-
autoload :OptimizedFileSystemResolver
|
|
65
|
-
autoload :FallbackFileSystemResolver
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
autoload_at "action_view/buffers" do
|
|
@@ -73,6 +73,22 @@ Released under the MIT license
|
|
|
73
73
|
return element[expando][key] = value;
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
+
Rails.isContentEditable = function(element) {
|
|
77
|
+
var isEditable;
|
|
78
|
+
isEditable = false;
|
|
79
|
+
while (true) {
|
|
80
|
+
if (element.isContentEditable) {
|
|
81
|
+
isEditable = true;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
element = element.parentElement;
|
|
85
|
+
if (!element) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return isEditable;
|
|
90
|
+
};
|
|
91
|
+
|
|
76
92
|
Rails.$ = function(selector) {
|
|
77
93
|
return Array.prototype.slice.call(document.querySelectorAll(selector));
|
|
78
94
|
};
|
|
@@ -395,9 +411,9 @@ Released under the MIT license
|
|
|
395
411
|
|
|
396
412
|
}).call(this);
|
|
397
413
|
(function() {
|
|
398
|
-
var disableFormElement, disableFormElements, disableLinkElement, enableFormElement, enableFormElements, enableLinkElement, formElements, getData, isXhrRedirect, matches, setData, stopEverything;
|
|
414
|
+
var disableFormElement, disableFormElements, disableLinkElement, enableFormElement, enableFormElements, enableLinkElement, formElements, getData, isContentEditable, isXhrRedirect, matches, setData, stopEverything;
|
|
399
415
|
|
|
400
|
-
matches = Rails.matches, getData = Rails.getData, setData = Rails.setData, stopEverything = Rails.stopEverything, formElements = Rails.formElements;
|
|
416
|
+
matches = Rails.matches, getData = Rails.getData, setData = Rails.setData, stopEverything = Rails.stopEverything, formElements = Rails.formElements, isContentEditable = Rails.isContentEditable;
|
|
401
417
|
|
|
402
418
|
Rails.handleDisabledElement = function(e) {
|
|
403
419
|
var element;
|
|
@@ -417,6 +433,9 @@ Released under the MIT license
|
|
|
417
433
|
} else {
|
|
418
434
|
element = e;
|
|
419
435
|
}
|
|
436
|
+
if (isContentEditable(element)) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
420
439
|
if (matches(element, Rails.linkDisableSelector)) {
|
|
421
440
|
return enableLinkElement(element);
|
|
422
441
|
} else if (matches(element, Rails.buttonDisableSelector) || matches(element, Rails.formEnableSelector)) {
|
|
@@ -429,6 +448,9 @@ Released under the MIT license
|
|
|
429
448
|
Rails.disableElement = function(e) {
|
|
430
449
|
var element;
|
|
431
450
|
element = e instanceof Event ? e.target : e;
|
|
451
|
+
if (isContentEditable(element)) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
432
454
|
if (matches(element, Rails.linkDisableSelector)) {
|
|
433
455
|
return disableLinkElement(element);
|
|
434
456
|
} else if (matches(element, Rails.buttonDisableSelector) || matches(element, Rails.formDisableSelector)) {
|
|
@@ -513,10 +535,12 @@ Released under the MIT license
|
|
|
513
535
|
|
|
514
536
|
}).call(this);
|
|
515
537
|
(function() {
|
|
516
|
-
var stopEverything;
|
|
538
|
+
var isContentEditable, stopEverything;
|
|
517
539
|
|
|
518
540
|
stopEverything = Rails.stopEverything;
|
|
519
541
|
|
|
542
|
+
isContentEditable = Rails.isContentEditable;
|
|
543
|
+
|
|
520
544
|
Rails.handleMethod = function(e) {
|
|
521
545
|
var csrfParam, csrfToken, form, formContent, href, link, method;
|
|
522
546
|
link = this;
|
|
@@ -524,6 +548,9 @@ Released under the MIT license
|
|
|
524
548
|
if (!method) {
|
|
525
549
|
return;
|
|
526
550
|
}
|
|
551
|
+
if (isContentEditable(this)) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
527
554
|
href = Rails.href(link);
|
|
528
555
|
csrfToken = Rails.csrfToken();
|
|
529
556
|
csrfParam = Rails.csrfParam();
|
|
@@ -545,10 +572,10 @@ Released under the MIT license
|
|
|
545
572
|
|
|
546
573
|
}).call(this);
|
|
547
574
|
(function() {
|
|
548
|
-
var ajax, fire, getData, isCrossDomain, isRemote, matches, serializeElement, setData, stopEverything,
|
|
575
|
+
var ajax, fire, getData, isContentEditable, isCrossDomain, isRemote, matches, serializeElement, setData, stopEverything,
|
|
549
576
|
slice = [].slice;
|
|
550
577
|
|
|
551
|
-
matches = Rails.matches, getData = Rails.getData, setData = Rails.setData, fire = Rails.fire, stopEverything = Rails.stopEverything, ajax = Rails.ajax, isCrossDomain = Rails.isCrossDomain, serializeElement = Rails.serializeElement;
|
|
578
|
+
matches = Rails.matches, getData = Rails.getData, setData = Rails.setData, fire = Rails.fire, stopEverything = Rails.stopEverything, ajax = Rails.ajax, isCrossDomain = Rails.isCrossDomain, serializeElement = Rails.serializeElement, isContentEditable = Rails.isContentEditable;
|
|
552
579
|
|
|
553
580
|
isRemote = function(element) {
|
|
554
581
|
var value;
|
|
@@ -566,6 +593,10 @@ Released under the MIT license
|
|
|
566
593
|
fire(element, 'ajax:stopped');
|
|
567
594
|
return false;
|
|
568
595
|
}
|
|
596
|
+
if (isContentEditable(element)) {
|
|
597
|
+
fire(element, 'ajax:stopped');
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
569
600
|
withCredentials = element.getAttribute('data-with-credentials');
|
|
570
601
|
dataType = element.getAttribute('data-type') || 'script';
|
|
571
602
|
if (matches(element, Rails.formSubmitSelector)) {
|