actionview 5.2.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +142 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +38 -0
  5. data/lib/action_view.rb +97 -0
  6. data/lib/action_view/base.rb +215 -0
  7. data/lib/action_view/buffers.rb +52 -0
  8. data/lib/action_view/context.rb +36 -0
  9. data/lib/action_view/dependency_tracker.rb +175 -0
  10. data/lib/action_view/digestor.rb +134 -0
  11. data/lib/action_view/flows.rb +76 -0
  12. data/lib/action_view/gem_version.rb +17 -0
  13. data/lib/action_view/helpers.rb +68 -0
  14. data/lib/action_view/helpers/active_model_helper.rb +55 -0
  15. data/lib/action_view/helpers/asset_tag_helper.rb +511 -0
  16. data/lib/action_view/helpers/asset_url_helper.rb +469 -0
  17. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  18. data/lib/action_view/helpers/cache_helper.rb +263 -0
  19. data/lib/action_view/helpers/capture_helper.rb +212 -0
  20. data/lib/action_view/helpers/controller_helper.rb +36 -0
  21. data/lib/action_view/helpers/csp_helper.rb +24 -0
  22. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  23. data/lib/action_view/helpers/date_helper.rb +1156 -0
  24. data/lib/action_view/helpers/debug_helper.rb +36 -0
  25. data/lib/action_view/helpers/form_helper.rb +2337 -0
  26. data/lib/action_view/helpers/form_options_helper.rb +887 -0
  27. data/lib/action_view/helpers/form_tag_helper.rb +917 -0
  28. data/lib/action_view/helpers/javascript_helper.rb +94 -0
  29. data/lib/action_view/helpers/number_helper.rb +451 -0
  30. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  31. data/lib/action_view/helpers/record_tag_helper.rb +23 -0
  32. data/lib/action_view/helpers/rendering_helper.rb +99 -0
  33. data/lib/action_view/helpers/sanitize_helper.rb +177 -0
  34. data/lib/action_view/helpers/tag_helper.rb +313 -0
  35. data/lib/action_view/helpers/tags.rb +44 -0
  36. data/lib/action_view/helpers/tags/base.rb +192 -0
  37. data/lib/action_view/helpers/tags/check_box.rb +66 -0
  38. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  39. data/lib/action_view/helpers/tags/collection_check_boxes.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_helpers.rb +119 -0
  41. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  42. data/lib/action_view/helpers/tags/collection_select.rb +30 -0
  43. data/lib/action_view/helpers/tags/color_field.rb +27 -0
  44. data/lib/action_view/helpers/tags/date_field.rb +15 -0
  45. data/lib/action_view/helpers/tags/date_select.rb +74 -0
  46. data/lib/action_view/helpers/tags/datetime_field.rb +32 -0
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +21 -0
  48. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  49. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  50. data/lib/action_view/helpers/tags/file_field.rb +10 -0
  51. data/lib/action_view/helpers/tags/grouped_collection_select.rb +31 -0
  52. data/lib/action_view/helpers/tags/hidden_field.rb +10 -0
  53. data/lib/action_view/helpers/tags/label.rb +81 -0
  54. data/lib/action_view/helpers/tags/month_field.rb +15 -0
  55. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  56. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  58. data/lib/action_view/helpers/tags/radio_button.rb +33 -0
  59. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  60. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  61. data/lib/action_view/helpers/tags/select.rb +43 -0
  62. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  63. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  64. data/lib/action_view/helpers/tags/text_field.rb +34 -0
  65. data/lib/action_view/helpers/tags/time_field.rb +15 -0
  66. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  67. data/lib/action_view/helpers/tags/time_zone_select.rb +22 -0
  68. data/lib/action_view/helpers/tags/translator.rb +44 -0
  69. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  70. data/lib/action_view/helpers/tags/week_field.rb +15 -0
  71. data/lib/action_view/helpers/text_helper.rb +486 -0
  72. data/lib/action_view/helpers/translation_helper.rb +141 -0
  73. data/lib/action_view/helpers/url_helper.rb +676 -0
  74. data/lib/action_view/layouts.rb +433 -0
  75. data/lib/action_view/locale/en.yml +56 -0
  76. data/lib/action_view/log_subscriber.rb +96 -0
  77. data/lib/action_view/lookup_context.rb +274 -0
  78. data/lib/action_view/model_naming.rb +14 -0
  79. data/lib/action_view/path_set.rb +100 -0
  80. data/lib/action_view/railtie.rb +82 -0
  81. data/lib/action_view/record_identifier.rb +112 -0
  82. data/lib/action_view/renderer/abstract_renderer.rb +55 -0
  83. data/lib/action_view/renderer/partial_renderer.rb +552 -0
  84. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +57 -0
  85. data/lib/action_view/renderer/renderer.rb +56 -0
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
  87. data/lib/action_view/renderer/template_renderer.rb +102 -0
  88. data/lib/action_view/rendering.rb +151 -0
  89. data/lib/action_view/routing_url_for.rb +145 -0
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template.rb +361 -0
  92. data/lib/action_view/template/error.rb +141 -0
  93. data/lib/action_view/template/handlers.rb +66 -0
  94. data/lib/action_view/template/handlers/builder.rb +25 -0
  95. data/lib/action_view/template/handlers/erb.rb +74 -0
  96. data/lib/action_view/template/handlers/erb/erubi.rb +83 -0
  97. data/lib/action_view/template/handlers/html.rb +11 -0
  98. data/lib/action_view/template/handlers/raw.rb +11 -0
  99. data/lib/action_view/template/html.rb +34 -0
  100. data/lib/action_view/template/resolver.rb +391 -0
  101. data/lib/action_view/template/text.rb +33 -0
  102. data/lib/action_view/template/types.rb +57 -0
  103. data/lib/action_view/test_case.rb +300 -0
  104. data/lib/action_view/testing/resolvers.rb +54 -0
  105. data/lib/action_view/version.rb +10 -0
  106. data/lib/action_view/view_paths.rb +105 -0
  107. data/lib/assets/compiled/rails-ujs.js +720 -0
  108. metadata +255 -0
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module CollectionCaching # :nodoc:
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # Fallback cache store if Action View is used without Rails.
9
+ # Otherwise overridden in Railtie to use Rails.cache.
10
+ mattr_accessor :collection_cache, default: ActiveSupport::Cache::MemoryStore.new
11
+ end
12
+
13
+ private
14
+ def cache_collection_render(instrumentation_payload)
15
+ return yield unless @options[:cached]
16
+
17
+ keyed_collection = collection_by_cache_keys
18
+ cached_partials = collection_cache.read_multi(*keyed_collection.keys)
19
+ instrumentation_payload[:cache_hits] = cached_partials.size
20
+
21
+ @collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
22
+ rendered_partials = @collection.empty? ? [] : yield
23
+
24
+ index = 0
25
+ fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do
26
+ rendered_partials[index].tap { index += 1 }
27
+ end
28
+ end
29
+
30
+ def callable_cache_key?
31
+ @options[:cached].respond_to?(:call)
32
+ end
33
+
34
+ def collection_by_cache_keys
35
+ seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
36
+
37
+ @collection.each_with_object({}) do |item, hash|
38
+ hash[expanded_cache_key(seed.call(item))] = item
39
+ end
40
+ end
41
+
42
+ def expanded_cache_key(key)
43
+ key = @view.combined_fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path))
44
+ key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
45
+ end
46
+
47
+ def fetch_or_cache_partial(cached_partials, order_by:)
48
+ order_by.map do |cache_key|
49
+ cached_partials.fetch(cache_key) do
50
+ yield.tap do |rendered_partial|
51
+ collection_cache.write(cache_key, rendered_partial)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ # This is the main entry point for rendering. It basically delegates
5
+ # to other objects like TemplateRenderer and PartialRenderer which
6
+ # actually renders the template.
7
+ #
8
+ # The Renderer will parse the options from the +render+ or +render_body+
9
+ # method and render a partial or a template based on the options. The
10
+ # +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all
11
+ # the setup and logic necessary to render a view and a new object is created
12
+ # each time +render+ is called.
13
+ class Renderer
14
+ attr_accessor :lookup_context
15
+
16
+ def initialize(lookup_context)
17
+ @lookup_context = lookup_context
18
+ end
19
+
20
+ # Main render entry point shared by Action View and Action Controller.
21
+ def render(context, options)
22
+ if options.key?(:partial)
23
+ render_partial(context, options)
24
+ else
25
+ render_template(context, options)
26
+ end
27
+ end
28
+
29
+ # Render but returns a valid Rack body. If fibers are defined, we return
30
+ # a streaming body that renders the template piece by piece.
31
+ #
32
+ # Note that partials are not supported to be rendered with streaming,
33
+ # so in such cases, we just wrap them in an array.
34
+ def render_body(context, options)
35
+ if options.key?(:partial)
36
+ [render_partial(context, options)]
37
+ else
38
+ StreamingTemplateRenderer.new(@lookup_context).render(context, options)
39
+ end
40
+ end
41
+
42
+ # Direct access to template rendering.
43
+ def render_template(context, options) #:nodoc:
44
+ TemplateRenderer.new(@lookup_context).render(context, options)
45
+ end
46
+
47
+ # Direct access to partial rendering.
48
+ def render_partial(context, options, &block) #:nodoc:
49
+ PartialRenderer.new(@lookup_context).render(context, options, block)
50
+ end
51
+
52
+ def cache_hits # :nodoc:
53
+ @cache_hits ||= {}
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fiber"
4
+
5
+ module ActionView
6
+ # == TODO
7
+ #
8
+ # * Support streaming from child templates, partials and so on.
9
+ # * Rack::Cache needs to support streaming bodies
10
+ class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
11
+ # A valid Rack::Body (i.e. it responds to each).
12
+ # It is initialized with a block that, when called, starts
13
+ # rendering the template.
14
+ class Body #:nodoc:
15
+ def initialize(&start)
16
+ @start = start
17
+ end
18
+
19
+ def each(&block)
20
+ begin
21
+ @start.call(block)
22
+ rescue Exception => exception
23
+ log_error(exception)
24
+ block.call ActionView::Base.streaming_completion_on_exception
25
+ end
26
+ self
27
+ end
28
+
29
+ private
30
+
31
+ # This is the same logging logic as in ShowExceptions middleware.
32
+ def log_error(exception)
33
+ logger = ActionView::Base.logger
34
+ return unless logger
35
+
36
+ message = "\n#{exception.class} (#{exception.message}):\n".dup
37
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
38
+ message << " " << exception.backtrace.join("\n ")
39
+ logger.fatal("#{message}\n\n")
40
+ end
41
+ end
42
+
43
+ # For streaming, instead of rendering a given a template, we return a Body
44
+ # object that responds to each. This object is initialized with a block
45
+ # that knows how to render the template.
46
+ def render_template(template, layout_name = nil, locals = {}) #:nodoc:
47
+ return [super] unless layout_name && template.supports_streaming?
48
+
49
+ locals ||= {}
50
+ layout = layout_name && find_layout(layout_name, locals.keys, [formats.first])
51
+
52
+ Body.new do |buffer|
53
+ delayed_render(buffer, template, layout, @view, locals)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def delayed_render(buffer, template, layout, view, locals)
60
+ # Wrap the given buffer in the StreamingBuffer and pass it to the
61
+ # underlying template handler. Now, every time something is concatenated
62
+ # to the buffer, it is not appended to an array, but streamed straight
63
+ # to the client.
64
+ output = ActionView::StreamingBuffer.new(buffer)
65
+ yielder = lambda { |*name| view._layout_for(*name) }
66
+
67
+ instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
68
+ outer_config = I18n.config
69
+ fiber = Fiber.new do
70
+ I18n.config = outer_config
71
+ if layout
72
+ layout.render(view, locals, output, &yielder)
73
+ else
74
+ # If you don't have a layout, just render the thing
75
+ # and concatenate the final result. This is the same
76
+ # as a layout with just <%= yield %>
77
+ output.safe_concat view._layout_for
78
+ end
79
+ end
80
+
81
+ # Set the view flow to support streaming. It will be aware
82
+ # when to stop rendering the layout because it needs to search
83
+ # something in the template and vice-versa.
84
+ view.view_flow = StreamingFlow.new(view, fiber)
85
+
86
+ # Yo! Start the fiber!
87
+ fiber.resume
88
+
89
+ # If the fiber is still alive, it means we need something
90
+ # from the template, so start rendering it. If not, it means
91
+ # the layout exited without requiring anything from the template.
92
+ if fiber.alive?
93
+ content = template.render(view, locals, &yielder)
94
+
95
+ # Once rendering the template is done, sets its content in the :layout key.
96
+ view.view_flow.set(:layout, content)
97
+
98
+ # In case the layout continues yielding, we need to resume
99
+ # the fiber until all yields are handled.
100
+ fiber.resume while fiber.alive?
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/try"
4
+
5
+ module ActionView
6
+ class TemplateRenderer < AbstractRenderer #:nodoc:
7
+ def render(context, options)
8
+ @view = context
9
+ @details = extract_details(options)
10
+ template = determine_template(options)
11
+
12
+ prepend_formats(template.formats)
13
+
14
+ @lookup_context.rendered_format ||= (template.formats.first || formats.first)
15
+
16
+ render_template(template, options[:layout], options[:locals])
17
+ end
18
+
19
+ private
20
+
21
+ # Determine the template to be rendered using the given options.
22
+ def determine_template(options)
23
+ keys = options.has_key?(:locals) ? options[:locals].keys : []
24
+
25
+ if options.key?(:body)
26
+ Template::Text.new(options[:body])
27
+ elsif options.key?(:plain)
28
+ Template::Text.new(options[:plain])
29
+ elsif options.key?(:html)
30
+ Template::HTML.new(options[:html], formats.first)
31
+ elsif options.key?(:file)
32
+ with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
33
+ elsif options.key?(:inline)
34
+ handler = Template.handler_for_extension(options[:type] || "erb")
35
+ Template.new(options[:inline], "inline template", handler, locals: keys)
36
+ elsif options.key?(:template)
37
+ if options[:template].respond_to?(:render)
38
+ options[:template]
39
+ else
40
+ find_template(options[:template], options[:prefixes], false, keys, @details)
41
+ end
42
+ else
43
+ raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
44
+ end
45
+ end
46
+
47
+ # Renders the given template. A string representing the layout can be
48
+ # supplied as well.
49
+ def render_template(template, layout_name = nil, locals = nil)
50
+ view, locals = @view, locals || {}
51
+
52
+ render_with_layout(layout_name, locals) do |layout|
53
+ instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
54
+ template.render(view, locals) { |*name| view._layout_for(*name) }
55
+ end
56
+ end
57
+ end
58
+
59
+ def render_with_layout(path, locals)
60
+ layout = path && find_layout(path, locals.keys, [formats.first])
61
+ content = yield(layout)
62
+
63
+ if layout
64
+ view = @view
65
+ view.view_flow.set(:layout, content)
66
+ layout.render(view, locals) { |*name| view._layout_for(*name) }
67
+ else
68
+ content
69
+ end
70
+ end
71
+
72
+ # This is the method which actually finds the layout using details in the lookup
73
+ # context object. If no layout is found, it checks if at least a layout with
74
+ # the given name exists across all details before raising the error.
75
+ def find_layout(layout, keys, formats)
76
+ resolve_layout(layout, keys, formats)
77
+ end
78
+
79
+ def resolve_layout(layout, keys, formats)
80
+ details = @details.dup
81
+ details[:formats] = formats
82
+
83
+ case layout
84
+ when String
85
+ begin
86
+ if layout.start_with?("/")
87
+ with_fallbacks { find_template(layout, nil, false, [], details) }
88
+ else
89
+ find_template(layout, nil, false, [], details)
90
+ end
91
+ rescue ActionView::MissingTemplate
92
+ all_details = @details.merge(formats: @lookup_context.default_formats)
93
+ raise unless template_exists?(layout, nil, false, [], all_details)
94
+ end
95
+ when Proc
96
+ resolve_layout(layout.call(formats), keys, formats)
97
+ else
98
+ layout
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,151 @@
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, @lookup_context = original_config, lookup_context
14
+ end
15
+
16
+ def locale
17
+ @original_config.locale
18
+ end
19
+
20
+ def locale=(value)
21
+ @lookup_context.locale = value
22
+ end
23
+ end
24
+
25
+ module Rendering
26
+ extend ActiveSupport::Concern
27
+ include ActionView::ViewPaths
28
+
29
+ # Overwrite process to setup I18n proxy.
30
+ def process(*) #:nodoc:
31
+ old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
32
+ super
33
+ ensure
34
+ I18n.config = old_config
35
+ end
36
+
37
+ module ClassMethods
38
+ def view_context_class
39
+ @view_context_class ||= begin
40
+ supports_path = supports_path?
41
+ routes = respond_to?(:_routes) && _routes
42
+ helpers = respond_to?(:_helpers) && _helpers
43
+
44
+ Class.new(ActionView::Base) do
45
+ if routes
46
+ include routes.url_helpers(supports_path)
47
+ include routes.mounted_helpers
48
+ end
49
+
50
+ if helpers
51
+ include helpers
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ attr_internal_writer :view_context_class
59
+
60
+ def view_context_class
61
+ @_view_context_class ||= self.class.view_context_class
62
+ end
63
+
64
+ # An instance of a view class. The default view class is ActionView::Base.
65
+ #
66
+ # The view class must have the following methods:
67
+ # View.new[lookup_context, assigns, controller]
68
+ # Create a new ActionView instance for a controller and we can also pass the arguments.
69
+ # View#render(option)
70
+ # Returns String with the rendered template
71
+ #
72
+ # Override this method in a module to change the default behavior.
73
+ def view_context
74
+ view_context_class.new(view_renderer, view_assigns, self)
75
+ end
76
+
77
+ # Returns an object that is able to render templates.
78
+ def view_renderer # :nodoc:
79
+ @_view_renderer ||= ActionView::Renderer.new(lookup_context)
80
+ end
81
+
82
+ def render_to_body(options = {})
83
+ _process_options(options)
84
+ _render_template(options)
85
+ end
86
+
87
+ def rendered_format
88
+ Template::Types[lookup_context.rendered_format]
89
+ end
90
+
91
+ private
92
+
93
+ # Find and render a template based on the options given.
94
+ def _render_template(options)
95
+ variant = options.delete(:variant)
96
+ assigns = options.delete(:assigns)
97
+ context = view_context
98
+
99
+ context.assign assigns if assigns
100
+ lookup_context.rendered_format = nil if options[:formats]
101
+ lookup_context.variants = variant if variant
102
+
103
+ view_renderer.render(context, options)
104
+ end
105
+
106
+ # Assign the rendered format to look up context.
107
+ def _process_format(format)
108
+ super
109
+ lookup_context.formats = [format.to_sym]
110
+ lookup_context.rendered_format = lookup_context.formats.first
111
+ end
112
+
113
+ # Normalize args by converting render "foo" to render :action => "foo" and
114
+ # render "foo/bar" to render :template => "foo/bar".
115
+ def _normalize_args(action = nil, options = {})
116
+ options = super(action, options)
117
+ case action
118
+ when NilClass
119
+ when Hash
120
+ options = action
121
+ when String, Symbol
122
+ action = action.to_s
123
+ key = action.include?(?/) ? :template : :action
124
+ options[key] = action
125
+ else
126
+ if action.respond_to?(:permitted?) && action.permitted?
127
+ options = action
128
+ else
129
+ options[:partial] = action
130
+ end
131
+ end
132
+
133
+ options
134
+ end
135
+
136
+ # Normalize options.
137
+ def _normalize_options(options)
138
+ options = super(options)
139
+ if options[:partial] == true
140
+ options[:partial] = action_name
141
+ end
142
+
143
+ if (options.keys & [:partial, :file, :template]).empty?
144
+ options[:prefixes] ||= _prefixes
145
+ end
146
+
147
+ options[:template] ||= (options[:action] || action_name).to_s
148
+ options
149
+ end
150
+ end
151
+ end