omg-actionview 8.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +25 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/app/assets/javascripts/rails-ujs.esm.js +686 -0
  6. data/app/assets/javascripts/rails-ujs.js +630 -0
  7. data/lib/action_view/base.rb +316 -0
  8. data/lib/action_view/buffers.rb +165 -0
  9. data/lib/action_view/cache_expiry.rb +69 -0
  10. data/lib/action_view/context.rb +32 -0
  11. data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
  12. data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
  13. data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
  14. data/lib/action_view/dependency_tracker.rb +41 -0
  15. data/lib/action_view/deprecator.rb +7 -0
  16. data/lib/action_view/digestor.rb +130 -0
  17. data/lib/action_view/flows.rb +75 -0
  18. data/lib/action_view/gem_version.rb +17 -0
  19. data/lib/action_view/helpers/active_model_helper.rb +54 -0
  20. data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
  21. data/lib/action_view/helpers/asset_url_helper.rb +473 -0
  22. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  23. data/lib/action_view/helpers/cache_helper.rb +315 -0
  24. data/lib/action_view/helpers/capture_helper.rb +236 -0
  25. data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
  26. data/lib/action_view/helpers/controller_helper.rb +42 -0
  27. data/lib/action_view/helpers/csp_helper.rb +26 -0
  28. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  29. data/lib/action_view/helpers/date_helper.rb +1266 -0
  30. data/lib/action_view/helpers/debug_helper.rb +38 -0
  31. data/lib/action_view/helpers/form_helper.rb +2765 -0
  32. data/lib/action_view/helpers/form_options_helper.rb +927 -0
  33. data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
  34. data/lib/action_view/helpers/javascript_helper.rb +96 -0
  35. data/lib/action_view/helpers/number_helper.rb +165 -0
  36. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  37. data/lib/action_view/helpers/rendering_helper.rb +218 -0
  38. data/lib/action_view/helpers/sanitize_helper.rb +201 -0
  39. data/lib/action_view/helpers/tag_helper.rb +621 -0
  40. data/lib/action_view/helpers/tags/base.rb +138 -0
  41. data/lib/action_view/helpers/tags/check_box.rb +65 -0
  42. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  43. data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
  44. data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
  45. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  46. data/lib/action_view/helpers/tags/collection_select.rb +33 -0
  47. data/lib/action_view/helpers/tags/color_field.rb +26 -0
  48. data/lib/action_view/helpers/tags/date_field.rb +14 -0
  49. data/lib/action_view/helpers/tags/date_select.rb +75 -0
  50. data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
  51. data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
  52. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  53. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  54. data/lib/action_view/helpers/tags/file_field.rb +26 -0
  55. data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
  56. data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/label.rb +84 -0
  58. data/lib/action_view/helpers/tags/month_field.rb +14 -0
  59. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  60. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  61. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  62. data/lib/action_view/helpers/tags/radio_button.rb +32 -0
  63. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  64. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  65. data/lib/action_view/helpers/tags/select.rb +45 -0
  66. data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
  67. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  68. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  69. data/lib/action_view/helpers/tags/text_field.rb +33 -0
  70. data/lib/action_view/helpers/tags/time_field.rb +23 -0
  71. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  72. data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
  73. data/lib/action_view/helpers/tags/translator.rb +39 -0
  74. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  75. data/lib/action_view/helpers/tags/week_field.rb +14 -0
  76. data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
  77. data/lib/action_view/helpers/tags.rb +47 -0
  78. data/lib/action_view/helpers/text_helper.rb +568 -0
  79. data/lib/action_view/helpers/translation_helper.rb +161 -0
  80. data/lib/action_view/helpers/url_helper.rb +812 -0
  81. data/lib/action_view/helpers.rb +68 -0
  82. data/lib/action_view/layouts.rb +434 -0
  83. data/lib/action_view/locale/en.yml +56 -0
  84. data/lib/action_view/log_subscriber.rb +132 -0
  85. data/lib/action_view/lookup_context.rb +299 -0
  86. data/lib/action_view/model_naming.rb +14 -0
  87. data/lib/action_view/path_registry.rb +57 -0
  88. data/lib/action_view/path_set.rb +84 -0
  89. data/lib/action_view/railtie.rb +132 -0
  90. data/lib/action_view/record_identifier.rb +118 -0
  91. data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
  92. data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
  93. data/lib/action_view/render_parser.rb +40 -0
  94. data/lib/action_view/renderer/abstract_renderer.rb +186 -0
  95. data/lib/action_view/renderer/collection_renderer.rb +204 -0
  96. data/lib/action_view/renderer/object_renderer.rb +34 -0
  97. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
  98. data/lib/action_view/renderer/partial_renderer.rb +267 -0
  99. data/lib/action_view/renderer/renderer.rb +107 -0
  100. data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
  101. data/lib/action_view/renderer/template_renderer.rb +115 -0
  102. data/lib/action_view/rendering.rb +190 -0
  103. data/lib/action_view/routing_url_for.rb +149 -0
  104. data/lib/action_view/tasks/cache_digests.rake +25 -0
  105. data/lib/action_view/template/error.rb +264 -0
  106. data/lib/action_view/template/handlers/builder.rb +25 -0
  107. data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
  108. data/lib/action_view/template/handlers/erb.rb +157 -0
  109. data/lib/action_view/template/handlers/html.rb +11 -0
  110. data/lib/action_view/template/handlers/raw.rb +11 -0
  111. data/lib/action_view/template/handlers.rb +66 -0
  112. data/lib/action_view/template/html.rb +33 -0
  113. data/lib/action_view/template/inline.rb +22 -0
  114. data/lib/action_view/template/raw_file.rb +25 -0
  115. data/lib/action_view/template/renderable.rb +30 -0
  116. data/lib/action_view/template/resolver.rb +212 -0
  117. data/lib/action_view/template/sources/file.rb +17 -0
  118. data/lib/action_view/template/sources.rb +13 -0
  119. data/lib/action_view/template/text.rb +32 -0
  120. data/lib/action_view/template/types.rb +50 -0
  121. data/lib/action_view/template.rb +580 -0
  122. data/lib/action_view/template_details.rb +66 -0
  123. data/lib/action_view/template_path.rb +66 -0
  124. data/lib/action_view/test_case.rb +449 -0
  125. data/lib/action_view/testing/resolvers.rb +44 -0
  126. data/lib/action_view/unbound_template.rb +67 -0
  127. data/lib/action_view/version.rb +10 -0
  128. data/lib/action_view/view_paths.rb +117 -0
  129. data/lib/action_view.rb +104 -0
  130. metadata +275 -0
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ # = Action View \Renderer
5
+ #
6
+ # This is the main entry point for rendering. It basically delegates
7
+ # to other objects like TemplateRenderer and PartialRenderer which
8
+ # actually renders the template.
9
+ #
10
+ # The Renderer will parse the options from the +render+ or +render_body+
11
+ # method and render a partial or a template based on the options. The
12
+ # +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all
13
+ # the setup and logic necessary to render a view and a new object is created
14
+ # each time +render+ is called.
15
+ class Renderer
16
+ attr_accessor :lookup_context
17
+
18
+ def initialize(lookup_context)
19
+ @lookup_context = lookup_context
20
+ end
21
+
22
+ # Main render entry point shared by Action View and Action Controller.
23
+ def render(context, options)
24
+ render_to_object(context, options).body
25
+ end
26
+
27
+ def render_to_object(context, options) # :nodoc:
28
+ if options.key?(:partial)
29
+ render_partial_to_object(context, options)
30
+ else
31
+ render_template_to_object(context, options)
32
+ end
33
+ end
34
+
35
+ # Render but returns a valid Rack body. If fibers are defined, we return
36
+ # a streaming body that renders the template piece by piece.
37
+ #
38
+ # Note that partials are not supported to be rendered with streaming,
39
+ # so in such cases, we just wrap them in an array.
40
+ def render_body(context, options)
41
+ if options.key?(:partial)
42
+ [render_partial(context, options)]
43
+ else
44
+ StreamingTemplateRenderer.new(@lookup_context).render(context, options)
45
+ end
46
+ end
47
+
48
+ def render_partial(context, options, &block) # :nodoc:
49
+ render_partial_to_object(context, options, &block).body
50
+ end
51
+
52
+ def cache_hits # :nodoc:
53
+ @cache_hits ||= {}
54
+ end
55
+
56
+ private
57
+ def render_template_to_object(context, options)
58
+ TemplateRenderer.new(@lookup_context).render(context, options)
59
+ end
60
+
61
+ def render_partial_to_object(context, options, &block)
62
+ partial = options[:partial]
63
+ if String === partial
64
+ collection = collection_from_options(options)
65
+
66
+ if collection
67
+ # Collection + Partial
68
+ renderer = CollectionRenderer.new(@lookup_context, options)
69
+ renderer.render_collection_with_partial(collection, partial, context, block)
70
+ else
71
+ if options.key?(:object)
72
+ # Object + Partial
73
+ renderer = ObjectRenderer.new(@lookup_context, options)
74
+ renderer.render_object_with_partial(options[:object], partial, context, block)
75
+ else
76
+ # Partial
77
+ renderer = PartialRenderer.new(@lookup_context, options)
78
+ renderer.render(partial, context, block)
79
+ end
80
+ end
81
+ else
82
+ collection = collection_from_object(partial) || collection_from_options(options)
83
+
84
+ if collection
85
+ # Collection + Derived Partial
86
+ renderer = CollectionRenderer.new(@lookup_context, options)
87
+ renderer.render_collection_derive_partial(collection, context, block)
88
+ else
89
+ # Object + Derived Partial
90
+ renderer = ObjectRenderer.new(@lookup_context, options)
91
+ renderer.render_object_derive_partial(partial, context, block)
92
+ end
93
+ end
94
+ end
95
+
96
+ def collection_from_options(options)
97
+ if options.key?(:collection)
98
+ collection = options[:collection]
99
+ collection || []
100
+ end
101
+ end
102
+
103
+ def collection_from_object(object)
104
+ object if object.respond_to?(:to_ary)
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module ActionView
5
+ # == TODO
6
+ #
7
+ # * Support streaming from child templates, partials and so on.
8
+ # * Rack::Cache needs to support streaming bodies
9
+ class StreamingTemplateRenderer < TemplateRenderer # :nodoc:
10
+ # A valid Rack::Body (i.e. it responds to each).
11
+ # It is initialized with a block that, when called, starts
12
+ # rendering the template.
13
+ class Body # :nodoc:
14
+ def initialize(&start)
15
+ @start = start
16
+ end
17
+
18
+ def each(&block)
19
+ begin
20
+ @start.call(block)
21
+ rescue Exception => exception
22
+ log_error(exception)
23
+ block.call ActionView::Base.streaming_completion_on_exception
24
+ end
25
+ self
26
+ end
27
+
28
+ private
29
+ # This is the same logging logic as in ShowExceptions middleware.
30
+ def log_error(exception)
31
+ logger = ActionView::Base.logger
32
+ return unless logger
33
+
34
+ message = +"\n#{exception.class} (#{exception.message}):\n"
35
+ message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_source_code)
36
+ message << " " << exception.backtrace.join("\n ")
37
+ logger.fatal("#{message}\n\n")
38
+ end
39
+ end
40
+
41
+ # For streaming, instead of rendering a given a template, we return a Body
42
+ # object that responds to each. This object is initialized with a block
43
+ # that knows how to render the template.
44
+ def render_template(view, template, layout_name = nil, locals = {}) # :nodoc:
45
+ return [super.body] unless layout_name && template.supports_streaming?
46
+
47
+ locals ||= {}
48
+ layout = find_layout(layout_name, locals.keys, [formats.first])
49
+
50
+ Body.new do |buffer|
51
+ delayed_render(buffer, template, layout, view, locals)
52
+ end
53
+ end
54
+
55
+ private
56
+ def delayed_render(buffer, template, layout, view, locals)
57
+ # Wrap the given buffer in the StreamingBuffer and pass it to the
58
+ # underlying template handler. Now, every time something is concatenated
59
+ # to the buffer, it is not appended to an array, but streamed straight
60
+ # to the client.
61
+ output = ActionView::StreamingBuffer.new(buffer)
62
+ yielder = lambda { |*name| view._layout_for(*name) }
63
+
64
+ ActiveSupport::Notifications.instrument(
65
+ "render_template.action_view",
66
+ identifier: template.identifier,
67
+ layout: layout && layout.virtual_path,
68
+ locals: locals
69
+ ) do
70
+ outer_config = I18n.config
71
+ fiber = Fiber.new do
72
+ I18n.config = outer_config
73
+ if layout
74
+ layout.render(view, locals, output, &yielder)
75
+ else
76
+ # If you don't have a layout, just render the thing
77
+ # and concatenate the final result. This is the same
78
+ # as a layout with just <%= yield %>
79
+ output.safe_concat view._layout_for
80
+ end
81
+ end
82
+
83
+ # Set the view flow to support streaming. It will be aware
84
+ # when to stop rendering the layout because it needs to search
85
+ # something in the template and vice-versa.
86
+ view.view_flow = StreamingFlow.new(view, fiber)
87
+
88
+ # Yo! Start the fiber!
89
+ fiber.resume
90
+
91
+ # If the fiber is still alive, it means we need something
92
+ # from the template, so start rendering it. If not, it means
93
+ # the layout exited without requiring anything from the template.
94
+ if fiber.alive?
95
+ content = template.render(view, locals, &yielder)
96
+
97
+ # Once rendering the template is done, sets its content in the :layout key.
98
+ view.view_flow.set(:layout, content)
99
+
100
+ # In case the layout continues yielding, we need to resume
101
+ # the fiber until all yields are handled.
102
+ fiber.resume while fiber.alive?
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class TemplateRenderer < AbstractRenderer # :nodoc:
5
+ def render(context, options)
6
+ @details = extract_details(options)
7
+ template = determine_template(options)
8
+
9
+ prepend_formats(template.format)
10
+
11
+ render_template(context, template, options[:layout], options[:locals] || {})
12
+ end
13
+
14
+ private
15
+ # Determine the template to be rendered using the given options.
16
+ def determine_template(options)
17
+ keys = options.has_key?(:locals) ? options[:locals].keys : []
18
+
19
+ if options.key?(:body)
20
+ Template::Text.new(options[:body])
21
+ elsif options.key?(:plain)
22
+ Template::Text.new(options[:plain])
23
+ elsif options.key?(:html)
24
+ Template::HTML.new(options[:html], formats.first)
25
+ elsif options.key?(:file)
26
+ if File.exist?(options[:file])
27
+ Template::RawFile.new(options[:file])
28
+ else
29
+ if Pathname.new(options[:file]).absolute?
30
+ raise ArgumentError, "File #{options[:file]} does not exist"
31
+ else
32
+ raise ArgumentError, "`render file:` should be given the absolute path to a file. '#{options[:file]}' was given instead"
33
+ end
34
+ end
35
+ elsif options.key?(:inline)
36
+ handler = Template.handler_for_extension(options[:type] || "erb")
37
+ format = if handler.respond_to?(:default_format)
38
+ handler.default_format
39
+ else
40
+ @lookup_context.formats.first
41
+ end
42
+ Template::Inline.new(options[:inline], "inline template", handler, locals: keys, format: format)
43
+ elsif options.key?(:renderable)
44
+ Template::Renderable.new(options[:renderable])
45
+ elsif options.key?(:template)
46
+ if options[:template].respond_to?(:render)
47
+ options[:template]
48
+ else
49
+ @lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details)
50
+ end
51
+ else
52
+ raise ArgumentError, "You invoked render but did not give any of :body, :file, :html, :inline, :partial, :plain, :renderable, or :template option."
53
+ end
54
+ end
55
+
56
+ # Renders the given template. A string representing the layout can be
57
+ # supplied as well.
58
+ def render_template(view, template, layout_name, locals)
59
+ render_with_layout(view, template, layout_name, locals) do |layout|
60
+ ActiveSupport::Notifications.instrument(
61
+ "render_template.action_view",
62
+ identifier: template.identifier,
63
+ layout: layout && layout.virtual_path,
64
+ locals: locals
65
+ ) do
66
+ template.render(view, locals) { |*name| view._layout_for(*name) }
67
+ end
68
+ end
69
+ end
70
+
71
+ def render_with_layout(view, template, path, locals)
72
+ layout = path && find_layout(path, locals.keys, [formats.first])
73
+
74
+ body = if layout
75
+ ActiveSupport::Notifications.instrument("render_layout.action_view", identifier: layout.identifier) do
76
+ view.view_flow.set(:layout, yield(layout))
77
+ layout.render(view, locals) { |*name| view._layout_for(*name) }
78
+ end
79
+ else
80
+ yield
81
+ end
82
+ build_rendered_template(body, template)
83
+ end
84
+
85
+ # This is the method which actually finds the layout using details in the lookup
86
+ # context object. If no layout is found, it checks if at least a layout with
87
+ # the given name exists across all details before raising the error.
88
+ def find_layout(layout, keys, formats)
89
+ resolve_layout(layout, keys, formats)
90
+ end
91
+
92
+ def resolve_layout(layout, keys, formats)
93
+ details = @details.dup
94
+ details[:formats] = formats
95
+
96
+ case layout
97
+ when String
98
+ begin
99
+ if layout.start_with?("/")
100
+ raise ArgumentError, "Rendering layouts from an absolute path is not supported."
101
+ else
102
+ @lookup_context.find_template(layout, nil, false, [], details)
103
+ end
104
+ rescue ActionView::MissingTemplate
105
+ all_details = @details.merge(formats: @lookup_context.default_formats)
106
+ raise unless template_exists?(layout, nil, false, [], **all_details)
107
+ end
108
+ when Proc
109
+ resolve_layout(layout.call(@lookup_context, formats), keys, formats)
110
+ else
111
+ layout
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/view_paths"
4
+
5
+ module ActionView
6
+ # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
7
+ # it will trigger the lookup_context and consequently expire the cache.
8
+ class I18nProxy < ::I18n::Config # :nodoc:
9
+ attr_reader :original_config, :lookup_context
10
+
11
+ def initialize(original_config, lookup_context)
12
+ original_config = original_config.original_config if original_config.respond_to?(:original_config)
13
+ @original_config = original_config
14
+ @lookup_context = lookup_context
15
+ end
16
+
17
+ def locale
18
+ @original_config.locale
19
+ end
20
+
21
+ def locale=(value)
22
+ @lookup_context.locale = value
23
+ end
24
+ end
25
+
26
+ module Rendering
27
+ extend ActiveSupport::Concern
28
+ include ActionView::ViewPaths
29
+
30
+ attr_internal_reader :rendered_format
31
+
32
+ def initialize
33
+ @_rendered_format = nil
34
+ super
35
+ end
36
+
37
+ # Override process to set up I18n proxy.
38
+ def process(...) # :nodoc:
39
+ old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
40
+ super
41
+ ensure
42
+ I18n.config = old_config
43
+ end
44
+
45
+ module ClassMethods
46
+ def _routes
47
+ end
48
+
49
+ def _helpers
50
+ end
51
+
52
+ def inherit_view_context_class?
53
+ superclass.respond_to?(:view_context_class) &&
54
+ supports_path? == superclass.supports_path? &&
55
+ _routes.equal?(superclass._routes) &&
56
+ _helpers.equal?(superclass._helpers)
57
+ end
58
+
59
+ def build_view_context_class(klass, supports_path, routes, helpers)
60
+ if inherit_view_context_class?
61
+ return superclass.view_context_class
62
+ end
63
+
64
+ Class.new(klass) do
65
+ if routes
66
+ include routes.url_helpers(supports_path)
67
+ include routes.mounted_helpers
68
+ end
69
+
70
+ if helpers
71
+ include helpers
72
+ end
73
+ end
74
+ end
75
+
76
+ def eager_load!
77
+ super
78
+ view_context_class
79
+ nil
80
+ end
81
+
82
+ def view_context_class
83
+ klass = ActionView::LookupContext::DetailsKey.view_context_class
84
+
85
+ @view_context_class ||= build_view_context_class(klass, supports_path?, _routes, _helpers)
86
+
87
+ if klass.changed?(@view_context_class)
88
+ @view_context_class = build_view_context_class(klass, supports_path?, _routes, _helpers)
89
+ end
90
+
91
+ @view_context_class
92
+ end
93
+ end
94
+
95
+ def view_context_class
96
+ self.class.view_context_class
97
+ end
98
+
99
+ # An instance of a view class. The default view class is ActionView::Base.
100
+ #
101
+ # The view class must have the following methods:
102
+ #
103
+ # * <tt>View.new(lookup_context, assigns, controller)</tt> — Create a new
104
+ # ActionView instance for a controller and we can also pass the arguments.
105
+ #
106
+ # * <tt>View#render(option)</tt> — Returns String with the rendered template.
107
+ #
108
+ # Override this method in a module to change the default behavior.
109
+ def view_context
110
+ view_context_class.new(lookup_context, view_assigns, self)
111
+ end
112
+
113
+ # Returns an object that is able to render templates.
114
+ def view_renderer # :nodoc:
115
+ # Lifespan: Per controller
116
+ @_view_renderer ||= ActionView::Renderer.new(lookup_context)
117
+ end
118
+
119
+ def render_to_body(options = {})
120
+ _process_options(options)
121
+ _render_template(options)
122
+ end
123
+
124
+ private
125
+ # Find and render a template based on the options given.
126
+ def _render_template(options)
127
+ variant = options.delete(:variant)
128
+ assigns = options.delete(:assigns)
129
+ context = view_context
130
+
131
+ context.assign assigns if assigns
132
+ lookup_context.variants = variant if variant
133
+
134
+ rendered_template = context.in_rendering_context(options) do |renderer|
135
+ renderer.render_to_object(context, options)
136
+ end
137
+
138
+ rendered_format = rendered_template.format || lookup_context.formats.first
139
+ @_rendered_format = Template::Types[rendered_format]
140
+
141
+ rendered_template.body
142
+ end
143
+
144
+ # Assign the rendered format to look up context.
145
+ def _process_format(format)
146
+ super
147
+ lookup_context.formats = [format.to_sym] if format.to_sym
148
+ end
149
+
150
+ # Normalize args by converting render "foo" to render action: "foo" and
151
+ # render "foo/bar" to render template: "foo/bar".
152
+ def _normalize_args(action = nil, options = {})
153
+ options = super(action, options)
154
+ case action
155
+ when NilClass
156
+ when Hash
157
+ options = action
158
+ when String, Symbol
159
+ action = action.to_s
160
+ key = action.include?(?/) ? :template : :action
161
+ options[key] = action
162
+ else
163
+ if action.respond_to?(:permitted?) && action.permitted?
164
+ options = action
165
+ elsif action.respond_to?(:render_in)
166
+ options[:renderable] = action
167
+ else
168
+ options[:partial] = action
169
+ end
170
+ end
171
+
172
+ options
173
+ end
174
+
175
+ # Normalize options.
176
+ def _normalize_options(options)
177
+ options = super(options)
178
+ if options[:partial] == true
179
+ options[:partial] = action_name
180
+ end
181
+
182
+ if !options.keys.intersect?([:partial, :file, :template])
183
+ options[:prefixes] ||= _prefixes
184
+ end
185
+
186
+ options[:template] ||= (options[:action] || action_name).to_s
187
+ options
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/routing/polymorphic_routes"
4
+
5
+ module ActionView
6
+ module RoutingUrlFor
7
+ # Returns the URL for the set of +options+ provided. This takes the
8
+ # same options as +url_for+ in Action Controller (see the
9
+ # documentation for ActionDispatch::Routing::UrlFor#url_for). Note that by default
10
+ # <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative <tt>"/controller/action"</tt>
11
+ # instead of the fully qualified URL like <tt>"http://example.com/controller/action"</tt>.
12
+ #
13
+ # ==== Options
14
+ # * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path.
15
+ # * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified).
16
+ # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in <tt>"/archive/2005/"</tt>. Note that this
17
+ # is currently not recommended since it breaks caching.
18
+ # * <tt>:host</tt> - Overrides the default (current) host if provided.
19
+ # * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
20
+ # * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
21
+ # * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
22
+ #
23
+ # ==== Relying on named routes
24
+ #
25
+ # Passing a record (like an Active Record) instead of a hash as the options parameter will
26
+ # trigger the named route for that record. The lookup will happen on the name of the class. So passing a
27
+ # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as
28
+ # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route).
29
+ #
30
+ # ==== Implicit Controller Namespacing
31
+ #
32
+ # Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one.
33
+ #
34
+ # ==== Examples
35
+ # <%= url_for(action: 'index') %>
36
+ # # => /blogs/
37
+ #
38
+ # <%= url_for(action: 'find', controller: 'books') %>
39
+ # # => /books/find
40
+ #
41
+ # <%= url_for(action: 'login', controller: 'members', only_path: false, protocol: 'https') %>
42
+ # # => https://www.example.com/members/login/
43
+ #
44
+ # <%= url_for(action: 'play', anchor: 'player') %>
45
+ # # => /messages/play/#player
46
+ #
47
+ # <%= url_for(action: 'jump', anchor: 'tax&ship') %>
48
+ # # => /testing/jump/#tax&ship
49
+ #
50
+ # <%= url_for(Workshop) %>
51
+ # # => /workshops
52
+ #
53
+ # <%= url_for(Workshop.new) %>
54
+ # # relies on Workshop answering a persisted? call (and in this case returning false)
55
+ # # => /workshops
56
+ #
57
+ # <%= url_for(@workshop) %>
58
+ # # calls @workshop.to_param which by default returns the id
59
+ # # => /workshops/5
60
+ #
61
+ # # to_param can be re-defined in a model to provide different URL names:
62
+ # # => /workshops/1-workshop-name
63
+ #
64
+ # <%= url_for("http://www.example.com") %>
65
+ # # => http://www.example.com
66
+ #
67
+ # <%= url_for(:back) %>
68
+ # # if request.env["HTTP_REFERER"] is set to "http://www.example.com"
69
+ # # => http://www.example.com
70
+ #
71
+ # <%= url_for(:back) %>
72
+ # # if request.env["HTTP_REFERER"] is not set or is blank
73
+ # # => javascript:history.back()
74
+ #
75
+ # <%= url_for(action: 'index', controller: 'users') %>
76
+ # # Assuming an "admin" namespace
77
+ # # => /admin/users
78
+ #
79
+ # <%= url_for(action: 'index', controller: '/users') %>
80
+ # # Specify absolute path with beginning slash
81
+ # # => /users
82
+ def url_for(options = nil)
83
+ case options
84
+ when String
85
+ options
86
+ when nil
87
+ super(only_path: _generate_paths_by_default)
88
+ when Hash
89
+ options = options.symbolize_keys
90
+ ensure_only_path_option(options)
91
+
92
+ super(options)
93
+ when ActionController::Parameters
94
+ ensure_only_path_option(options)
95
+
96
+ super(options)
97
+ when :back
98
+ _back_url
99
+ when Array
100
+ components = options.dup
101
+ options = components.extract_options!
102
+ ensure_only_path_option(options)
103
+
104
+ if options[:only_path]
105
+ polymorphic_path(components, options)
106
+ else
107
+ polymorphic_url(components, options)
108
+ end
109
+ else
110
+ method = _generate_paths_by_default ? :path : :url
111
+ builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.public_send(method)
112
+
113
+ case options
114
+ when Symbol
115
+ builder.handle_string_call(self, options)
116
+ when Class
117
+ builder.handle_class_call(self, options)
118
+ else
119
+ builder.handle_model_call(self, options)
120
+ end
121
+ end
122
+ end
123
+
124
+ def url_options # :nodoc:
125
+ return super unless controller.respond_to?(:url_options)
126
+ controller.url_options
127
+ end
128
+
129
+ private
130
+ def _routes_context
131
+ controller
132
+ end
133
+
134
+ def optimize_routes_generation?
135
+ controller.respond_to?(:optimize_routes_generation?, true) ?
136
+ controller.optimize_routes_generation? : super
137
+ end
138
+
139
+ def _generate_paths_by_default
140
+ true
141
+ end
142
+
143
+ def ensure_only_path_option(options)
144
+ unless options.key?(:only_path)
145
+ options[:only_path] = _generate_paths_by_default unless options[:host]
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :cache_digests do
4
+ desc "Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)"
5
+ task nested_dependencies: :environment do
6
+ abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present?
7
+ puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:to_dep_map)
8
+ end
9
+
10
+ desc "Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)"
11
+ task dependencies: :environment do
12
+ abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present?
13
+ puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:name)
14
+ end
15
+
16
+ class CacheDigests
17
+ def self.template_name
18
+ ENV["TEMPLATE"].split(".", 2).first
19
+ end
20
+
21
+ def self.finder
22
+ ApplicationController.new.lookup_context
23
+ end
24
+ end
25
+ end