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,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