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