omg-actionview 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/app/assets/javascripts/rails-ujs.esm.js +686 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +316 -0
  8. data/lib/action_view/buffers.rb +165 -0
  9. data/lib/action_view/cache_expiry.rb +69 -0
  10. data/lib/action_view/context.rb +32 -0
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
  12. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  13. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  14. data/lib/action_view/dependency_tracker.rb +41 -0
  15. data/lib/action_view/deprecator.rb +7 -0
  16. data/lib/action_view/digestor.rb +130 -0
  17. data/lib/action_view/flows.rb +75 -0
  18. data/lib/action_view/gem_version.rb +17 -0
  19. data/lib/action_view/helpers/active_model_helper.rb +54 -0
  20. data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
  21. data/lib/action_view/helpers/asset_url_helper.rb +473 -0
  22. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  23. data/lib/action_view/helpers/cache_helper.rb +315 -0
  24. data/lib/action_view/helpers/capture_helper.rb +236 -0
  25. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  26. data/lib/action_view/helpers/controller_helper.rb +42 -0
  27. data/lib/action_view/helpers/csp_helper.rb +26 -0
  28. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  29. data/lib/action_view/helpers/date_helper.rb +1266 -0
  30. data/lib/action_view/helpers/debug_helper.rb +38 -0
  31. data/lib/action_view/helpers/form_helper.rb +2765 -0
  32. data/lib/action_view/helpers/form_options_helper.rb +927 -0
  33. data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
  34. data/lib/action_view/helpers/javascript_helper.rb +96 -0
  35. data/lib/action_view/helpers/number_helper.rb +165 -0
  36. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  37. data/lib/action_view/helpers/rendering_helper.rb +218 -0
  38. data/lib/action_view/helpers/sanitize_helper.rb +201 -0
  39. data/lib/action_view/helpers/tag_helper.rb +621 -0
  40. data/lib/action_view/helpers/tags/base.rb +138 -0
  41. data/lib/action_view/helpers/tags/check_box.rb +65 -0
  42. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  43. data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
  44. data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
  45. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  46. data/lib/action_view/helpers/tags/collection_select.rb +33 -0
  47. data/lib/action_view/helpers/tags/color_field.rb +26 -0
  48. data/lib/action_view/helpers/tags/date_field.rb +14 -0
  49. data/lib/action_view/helpers/tags/date_select.rb +75 -0
  50. data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
  51. data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
  52. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  53. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  54. data/lib/action_view/helpers/tags/file_field.rb +26 -0
  55. data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
  56. data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/label.rb +84 -0
  58. data/lib/action_view/helpers/tags/month_field.rb +14 -0
  59. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  60. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  61. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  62. data/lib/action_view/helpers/tags/radio_button.rb +32 -0
  63. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  64. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  65. data/lib/action_view/helpers/tags/select.rb +45 -0
  66. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  67. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  68. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  69. data/lib/action_view/helpers/tags/text_field.rb +33 -0
  70. data/lib/action_view/helpers/tags/time_field.rb +23 -0
  71. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  72. data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
  73. data/lib/action_view/helpers/tags/translator.rb +39 -0
  74. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  75. data/lib/action_view/helpers/tags/week_field.rb +14 -0
  76. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  77. data/lib/action_view/helpers/tags.rb +47 -0
  78. data/lib/action_view/helpers/text_helper.rb +568 -0
  79. data/lib/action_view/helpers/translation_helper.rb +161 -0
  80. data/lib/action_view/helpers/url_helper.rb +812 -0
  81. data/lib/action_view/helpers.rb +68 -0
  82. data/lib/action_view/layouts.rb +434 -0
  83. data/lib/action_view/locale/en.yml +56 -0
  84. data/lib/action_view/log_subscriber.rb +132 -0
  85. data/lib/action_view/lookup_context.rb +299 -0
  86. data/lib/action_view/model_naming.rb +14 -0
  87. data/lib/action_view/path_registry.rb +57 -0
  88. data/lib/action_view/path_set.rb +84 -0
  89. data/lib/action_view/railtie.rb +132 -0
  90. data/lib/action_view/record_identifier.rb +118 -0
  91. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  92. data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
  93. data/lib/action_view/render_parser.rb +40 -0
  94. data/lib/action_view/renderer/abstract_renderer.rb +186 -0
  95. data/lib/action_view/renderer/collection_renderer.rb +204 -0
  96. data/lib/action_view/renderer/object_renderer.rb +34 -0
  97. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
  98. data/lib/action_view/renderer/partial_renderer.rb +267 -0
  99. data/lib/action_view/renderer/renderer.rb +107 -0
  100. data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
  101. data/lib/action_view/renderer/template_renderer.rb +115 -0
  102. data/lib/action_view/rendering.rb +190 -0
  103. data/lib/action_view/routing_url_for.rb +149 -0
  104. data/lib/action_view/tasks/cache_digests.rake +25 -0
  105. data/lib/action_view/template/error.rb +264 -0
  106. data/lib/action_view/template/handlers/builder.rb +25 -0
  107. data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
  108. data/lib/action_view/template/handlers/erb.rb +157 -0
  109. data/lib/action_view/template/handlers/html.rb +11 -0
  110. data/lib/action_view/template/handlers/raw.rb +11 -0
  111. data/lib/action_view/template/handlers.rb +66 -0
  112. data/lib/action_view/template/html.rb +33 -0
  113. data/lib/action_view/template/inline.rb +22 -0
  114. data/lib/action_view/template/raw_file.rb +25 -0
  115. data/lib/action_view/template/renderable.rb +30 -0
  116. data/lib/action_view/template/resolver.rb +212 -0
  117. data/lib/action_view/template/sources/file.rb +17 -0
  118. data/lib/action_view/template/sources.rb +13 -0
  119. data/lib/action_view/template/text.rb +32 -0
  120. data/lib/action_view/template/types.rb +50 -0
  121. data/lib/action_view/template.rb +580 -0
  122. data/lib/action_view/template_details.rb +66 -0
  123. data/lib/action_view/template_path.rb +66 -0
  124. data/lib/action_view/test_case.rb +449 -0
  125. data/lib/action_view/testing/resolvers.rb +44 -0
  126. data/lib/action_view/unbound_template.rb +67 -0
  127. data/lib/action_view/version.rb +10 -0
  128. data/lib/action_view/view_paths.rb +117 -0
  129. data/lib/action_view.rb +104 -0
  130. metadata +275 -0
@@ -0,0 +1,449 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/redefine_method"
4
+ require "action_controller"
5
+ require "action_controller/test_case"
6
+ require "action_view"
7
+
8
+ require "rails-dom-testing"
9
+
10
+ module ActionView
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.
15
+ class TestCase < ActiveSupport::TestCase
16
+ class TestController < ActionController::Base
17
+ include ActionDispatch::TestProcess
18
+
19
+ attr_accessor :request, :response, :params
20
+
21
+ class << self
22
+ # Overrides AbstractController::Base#controller_path
23
+ attr_accessor :controller_path
24
+ end
25
+
26
+ def controller_path=(path)
27
+ self.class.controller_path = path
28
+ end
29
+
30
+ def self.controller_name
31
+ "test"
32
+ end
33
+
34
+ def initialize
35
+ super
36
+ self.class.controller_path = ""
37
+ @request = ActionController::TestRequest.create(self.class)
38
+ @response = ActionDispatch::TestResponse.new
39
+
40
+ @request.env.delete("PATH_INFO")
41
+ @params = ActionController::Parameters.new
42
+ end
43
+ end
44
+
45
+ module Behavior
46
+ extend ActiveSupport::Concern
47
+
48
+ include ActionDispatch::Assertions, ActionDispatch::TestProcess
49
+ include Rails::Dom::Testing::Assertions
50
+ include ActionController::TemplateAssertions
51
+ include ActionView::Context
52
+
53
+ include ActionDispatch::Routing::PolymorphicRoutes
54
+
55
+ include AbstractController::Helpers
56
+ include ActionView::Helpers
57
+ include ActionView::RecordIdentifier
58
+ include ActionView::RoutingUrlFor
59
+
60
+ include ActiveSupport::Testing::ConstantLookup
61
+
62
+ delegate :lookup_context, to: :controller
63
+ attr_accessor :controller, :request, :output_buffer, :rendered
64
+
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
+
155
+ def tests(helper_class)
156
+ case helper_class
157
+ when String, Symbol
158
+ self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize
159
+ when Module
160
+ self.helper_class = helper_class
161
+ end
162
+ end
163
+
164
+ def determine_default_helper_class(name)
165
+ determine_constant_from_test_name(name) do |constant|
166
+ Module === constant && !(Class === constant)
167
+ end
168
+ end
169
+
170
+ def helper_method(*methods)
171
+ # Almost a duplicate from ActionController::Helpers
172
+ methods.flatten.each do |method|
173
+ _helpers_for_modification.module_eval <<~end_eval, __FILE__, __LINE__ + 1
174
+ def #{method}(...) # def current_user(...)
175
+ _test_case.send(:'#{method}', ...) # _test_case.send(:'current_user', ...)
176
+ end # end
177
+ end_eval
178
+ end
179
+ end
180
+
181
+ attr_writer :helper_class
182
+
183
+ def helper_class
184
+ @helper_class ||= determine_default_helper_class(name)
185
+ end
186
+
187
+ def new(*)
188
+ include_helper_modules!
189
+ super
190
+ end
191
+
192
+ private
193
+ def include_helper_modules!
194
+ helper(helper_class) if helper_class
195
+ include _helpers
196
+ end
197
+ end
198
+
199
+ included do
200
+ class_attribute :content_class, instance_accessor: false, default: RenderedViewContent
201
+
202
+ setup :setup_with_controller
203
+
204
+ register_parser :html, -> rendered { Rails::Dom::Testing.html_document_fragment.parse(rendered) }
205
+ register_parser :json, -> rendered { JSON.parse(rendered, object_class: ActiveSupport::HashWithIndifferentAccess) }
206
+
207
+ ActiveSupport.run_load_hooks(:action_view_test_case, self)
208
+
209
+ helper do
210
+ def protect_against_forgery?
211
+ false
212
+ end
213
+
214
+ def _test_case
215
+ controller._test_case
216
+ end
217
+ end
218
+ end
219
+
220
+ def setup_with_controller
221
+ controller_class = Class.new(ActionView::TestCase::TestController)
222
+ @controller = controller_class.new
223
+ @request = @controller.request
224
+ @view_flow = ActionView::OutputFlow.new
225
+ @output_buffer = ActionView::OutputBuffer.new
226
+ @rendered = self.class.content_class.new(+"")
227
+
228
+ test_case_instance = self
229
+ controller_class.define_method(:_test_case) { test_case_instance }
230
+ end
231
+
232
+ def config
233
+ @controller.config if @controller.respond_to?(:config)
234
+ end
235
+
236
+ def render(options = {}, local_assigns = {}, &block)
237
+ view.assign(view_assigns)
238
+ @rendered << output = view.render(options, local_assigns, &block)
239
+ output
240
+ end
241
+
242
+ def rendered_views
243
+ @_rendered_views ||= RenderedViewsCollection.new
244
+ end
245
+
246
+ ##
247
+ # :method: rendered
248
+ #
249
+ # Returns the content rendered by the last +render+ call.
250
+ #
251
+ # The returned object behaves like a string but also exposes a number of methods
252
+ # that allows you to parse the content string in formats registered using
253
+ # <tt>.register_parser</tt>.
254
+ #
255
+ # By default includes the following parsers:
256
+ #
257
+ # +.html+
258
+ #
259
+ # Parse the <tt>rendered</tt> content String into HTML. By default, this means
260
+ # a <tt>Nokogiri::XML::Node</tt>.
261
+ #
262
+ # test "renders HTML" do
263
+ # article = Article.create!(title: "Hello, world")
264
+ #
265
+ # render partial: "articles/article", locals: { article: article }
266
+ #
267
+ # assert_pattern { rendered.html.at("main h1") => { content: "Hello, world" } }
268
+ # end
269
+ #
270
+ # To parse the rendered content into a <tt>Capybara::Simple::Node</tt>,
271
+ # re-register an <tt>:html</tt> parser with a call to
272
+ # <tt>Capybara.string</tt>:
273
+ #
274
+ # register_parser :html, -> rendered { Capybara.string(rendered) }
275
+ #
276
+ # test "renders HTML" do
277
+ # article = Article.create!(title: "Hello, world")
278
+ #
279
+ # render partial: article
280
+ #
281
+ # rendered.html.assert_css "h1", text: "Hello, world"
282
+ # end
283
+ #
284
+ # +.json+
285
+ #
286
+ # Parse the <tt>rendered</tt> content String into JSON. By default, this means
287
+ # a <tt>ActiveSupport::HashWithIndifferentAccess</tt>.
288
+ #
289
+ # test "renders JSON" do
290
+ # article = Article.create!(title: "Hello, world")
291
+ #
292
+ # render formats: :json, partial: "articles/article", locals: { article: article }
293
+ #
294
+ # assert_pattern { rendered.json => { title: "Hello, world" } }
295
+ # end
296
+
297
+ def _routes
298
+ @controller._routes if @controller.respond_to?(:_routes)
299
+ end
300
+
301
+ class RenderedViewContent < String # :nodoc:
302
+ end
303
+
304
+ # Need to experiment if this priority is the best one: rendered => output_buffer
305
+ class RenderedViewsCollection
306
+ def initialize
307
+ @rendered_views ||= Hash.new { |hash, key| hash[key] = [] }
308
+ end
309
+
310
+ def add(view, locals)
311
+ @rendered_views[view] ||= []
312
+ @rendered_views[view] << locals
313
+ end
314
+
315
+ def locals_for(view)
316
+ @rendered_views[view]
317
+ end
318
+
319
+ def rendered_views
320
+ @rendered_views.keys
321
+ end
322
+
323
+ def view_rendered?(view, expected_locals)
324
+ locals_for(view).any? do |actual_locals|
325
+ expected_locals.all? { |key, value| value == actual_locals[key] }
326
+ end
327
+ end
328
+ end
329
+
330
+ private
331
+ # Need to experiment if this priority is the best one: rendered => output_buffer
332
+ def document_root_element
333
+ Rails::Dom::Testing.html_document.parse(@rendered.blank? ? @output_buffer.to_str : @rendered).root
334
+ end
335
+
336
+ module Locals
337
+ attr_accessor :rendered_views
338
+
339
+ def render(options = {}, local_assigns = {})
340
+ case options
341
+ when Hash
342
+ if block_given?
343
+ rendered_views.add options[:layout], options[:locals]
344
+ elsif options.key?(:partial)
345
+ rendered_views.add options[:partial], options[:locals]
346
+ end
347
+ else
348
+ rendered_views.add options, local_assigns
349
+ end
350
+
351
+ super
352
+ end
353
+ end
354
+
355
+ # The instance of ActionView::Base that is used by +render+.
356
+ def view
357
+ @view ||= begin
358
+ view = @controller.view_context
359
+ view.singleton_class.include(_helpers)
360
+ view.extend(Locals)
361
+ view.rendered_views = rendered_views
362
+ view.output_buffer = output_buffer
363
+ view
364
+ end
365
+ end
366
+
367
+ alias_method :_view, :view
368
+
369
+ INTERNAL_IVARS = [
370
+ :@NAME,
371
+ :@failures,
372
+ :@assertions,
373
+ :@__io__,
374
+ :@_assertion_wrapped,
375
+ :@_assertions,
376
+ :@_result,
377
+ :@_routes,
378
+ :@controller,
379
+ :@_controller,
380
+ :@_request,
381
+ :@_config,
382
+ :@_default_form_builder,
383
+ :@_layouts,
384
+ :@_files,
385
+ :@_rendered_views,
386
+ :@method_name,
387
+ :@output_buffer,
388
+ :@_partials,
389
+ :@passed,
390
+ :@rendered,
391
+ :@request,
392
+ :@routes,
393
+ :@tagged_logger,
394
+ :@_templates,
395
+ :@options,
396
+ :@test_passed,
397
+ :@view,
398
+ :@view_context_class,
399
+ :@view_flow,
400
+ :@_subscribers,
401
+ :@html_document,
402
+ ]
403
+
404
+ def _user_defined_ivars
405
+ instance_variables - INTERNAL_IVARS
406
+ end
407
+
408
+ # Returns a Hash of instance variables and their values, as defined by
409
+ # the user in the test case, which are then assigned to the view being
410
+ # rendered. This is generally intended for internal use and extension
411
+ # frameworks.
412
+ def view_assigns
413
+ Hash[_user_defined_ivars.map do |ivar|
414
+ [ivar[1..-1].to_sym, instance_variable_get(ivar)]
415
+ end]
416
+ end
417
+
418
+ def method_missing(selector, ...)
419
+ begin
420
+ routes = @controller.respond_to?(:_routes) && @controller._routes
421
+ rescue
422
+ # Don't call routes, if there is an error on _routes call
423
+ end
424
+
425
+ if routes &&
426
+ (routes.named_routes.route_defined?(selector) ||
427
+ routes.mounted_helpers.method_defined?(selector))
428
+ @controller.__send__(selector, ...)
429
+ else
430
+ super
431
+ end
432
+ end
433
+
434
+ def respond_to_missing?(name, include_private = false)
435
+ begin
436
+ routes = @controller.respond_to?(:_routes) && @controller._routes
437
+ rescue
438
+ # Don't call routes, if there is an error on _routes call
439
+ end
440
+
441
+ routes &&
442
+ (routes.named_routes.route_defined?(name) ||
443
+ routes.mounted_helpers.method_defined?(name))
444
+ end
445
+ end
446
+
447
+ include Behavior
448
+ end
449
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/template/resolver"
4
+
5
+ module ActionView # :nodoc:
6
+ # Use FixtureResolver in your tests to simulate the presence of files on the
7
+ # file system. This is used internally by Rails' own test suite, and is
8
+ # useful for testing extensions that have no way of knowing what the file
9
+ # system will look like at runtime.
10
+ class FixtureResolver < FileSystemResolver
11
+ def initialize(hash = {})
12
+ super("")
13
+ @hash = hash
14
+ @path = ""
15
+ end
16
+
17
+ def data
18
+ @hash
19
+ end
20
+
21
+ def to_s
22
+ @hash.keys.join(", ")
23
+ end
24
+
25
+ private
26
+ def template_glob(glob)
27
+ @hash.keys.filter_map do |path|
28
+ "/#{path}" if File.fnmatch(glob, path)
29
+ end
30
+ end
31
+
32
+ def source_for_template(template)
33
+ @hash[template.from(1)]
34
+ end
35
+ end
36
+
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)]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/map"
4
+
5
+ module ActionView
6
+ class UnboundTemplate
7
+ attr_reader :virtual_path, :details
8
+ delegate :locale, :format, :variant, :handler, to: :@details
9
+
10
+ def initialize(source, identifier, details:, virtual_path:)
11
+ @source = source
12
+ @identifier = identifier
13
+ @details = details
14
+ @virtual_path = virtual_path
15
+
16
+ @templates = Concurrent::Map.new(initial_capacity: 2)
17
+ @write_lock = Mutex.new
18
+ end
19
+
20
+ def bind_locals(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
+ if template.strict_locals?
30
+ # Under strict locals, we only need one template.
31
+ # This replaces the @templates Concurrent::Map with a hash which
32
+ # returns this template for every key.
33
+ @templates = Hash.new(template).freeze
34
+ else
35
+ # This may have already been assigned, but we've already de-dup'd so
36
+ # reassignment is fine.
37
+ @templates[locals.dup] = template
38
+ end
39
+ end
40
+ end
41
+ template
42
+ end
43
+
44
+ def built_templates # :nodoc:
45
+ @templates.values
46
+ end
47
+
48
+ private
49
+ def build_template(locals)
50
+ Template.new(
51
+ @source,
52
+ @identifier,
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)
60
+ )
61
+ end
62
+
63
+ def normalize_locals(locals)
64
+ locals.map(&:to_sym).sort!.freeze
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gem_version"
4
+
5
+ module ActionView
6
+ # Returns the currently loaded version of Action View as a +Gem::Version+.
7
+ def self.version
8
+ gem_version
9
+ end
10
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module ViewPaths
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ ActionView::PathRegistry.set_view_paths(self, ActionView::PathSet.new.freeze)
9
+ end
10
+
11
+ delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=,
12
+ :locale, :locale=, to: :lookup_context
13
+
14
+ module ClassMethods
15
+ def _view_paths
16
+ ActionView::PathRegistry.get_view_paths(self)
17
+ end
18
+
19
+ def _view_paths=(paths)
20
+ ActionView::PathRegistry.set_view_paths(self, paths)
21
+ end
22
+
23
+ def _prefixes # :nodoc:
24
+ @_prefixes ||= begin
25
+ return local_prefixes if superclass.abstract?
26
+
27
+ local_prefixes + superclass._prefixes
28
+ end
29
+ end
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
+
38
+ # Append a path to the list of view paths for this controller.
39
+ #
40
+ # ==== Parameters
41
+ # * <tt>path</tt> - If a String is provided, it gets converted into
42
+ # the default view path. You may also provide a custom view path
43
+ # (see ActionView::PathSet for more information)
44
+ def append_view_path(path)
45
+ self._view_paths = view_paths + _build_view_paths(path)
46
+ end
47
+
48
+ # Prepend a path to the list of view paths for this controller.
49
+ #
50
+ # ==== Parameters
51
+ # * <tt>path</tt> - If a String is provided, it gets converted into
52
+ # the default view path. You may also provide a custom view path
53
+ # (see ActionView::PathSet for more information)
54
+ def prepend_view_path(path)
55
+ self._view_paths = _build_view_paths(path) + view_paths
56
+ end
57
+
58
+ # A list of all of the default view paths for this controller.
59
+ def view_paths
60
+ _view_paths
61
+ end
62
+
63
+ # Set the view paths.
64
+ #
65
+ # ==== Parameters
66
+ # * <tt>paths</tt> - If a PathSet is provided, use that;
67
+ # otherwise, process the parameter into a PathSet.
68
+ def view_paths=(paths)
69
+ self._view_paths = _build_view_paths(paths)
70
+ end
71
+
72
+ private
73
+ # Override this method in your controller if you want to change paths prefixes for finding views.
74
+ # Prefixes defined here will still be added to parents' <tt>._prefixes</tt>.
75
+ def local_prefixes
76
+ [controller_path]
77
+ end
78
+ end
79
+
80
+ # The prefixes used in render "foo" shortcuts.
81
+ def _prefixes # :nodoc:
82
+ self.class._prefixes
83
+ end
84
+
85
+ # LookupContext is the object responsible for holding all
86
+ # information required for looking up templates, i.e. view paths and
87
+ # details. Check ActionView::LookupContext for more information.
88
+ def lookup_context
89
+ @_lookup_context ||=
90
+ ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
91
+ end
92
+
93
+ def details_for_lookup
94
+ {}
95
+ end
96
+
97
+ # Append a path to the list of view paths for the current LookupContext.
98
+ #
99
+ # ==== Parameters
100
+ # * <tt>path</tt> - If a String is provided, it gets converted into
101
+ # the default view path. You may also provide a custom view path
102
+ # (see ActionView::PathSet for more information)
103
+ def append_view_path(path)
104
+ lookup_context.append_view_paths(self.class._build_view_paths(path))
105
+ end
106
+
107
+ # Prepend a path to the list of view paths for the current LookupContext.
108
+ #
109
+ # ==== Parameters
110
+ # * <tt>path</tt> - If a String is provided, it gets converted into
111
+ # the default view path. You may also provide a custom view path
112
+ # (see ActionView::PathSet for more information)
113
+ def prepend_view_path(path)
114
+ lookup_context.prepend_view_paths(self.class._build_view_paths(path))
115
+ end
116
+ end
117
+ end