actionview 6.0.0

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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +271 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/lib/action_view.rb +98 -0
  6. data/lib/action_view/base.rb +312 -0
  7. data/lib/action_view/buffers.rb +67 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +32 -0
  10. data/lib/action_view/dependency_tracker.rb +175 -0
  11. data/lib/action_view/digestor.rb +126 -0
  12. data/lib/action_view/flows.rb +76 -0
  13. data/lib/action_view/gem_version.rb +17 -0
  14. data/lib/action_view/helpers.rb +66 -0
  15. data/lib/action_view/helpers/active_model_helper.rb +55 -0
  16. data/lib/action_view/helpers/asset_tag_helper.rb +488 -0
  17. data/lib/action_view/helpers/asset_url_helper.rb +470 -0
  18. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  19. data/lib/action_view/helpers/cache_helper.rb +271 -0
  20. data/lib/action_view/helpers/capture_helper.rb +216 -0
  21. data/lib/action_view/helpers/controller_helper.rb +36 -0
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  24. data/lib/action_view/helpers/date_helper.rb +1200 -0
  25. data/lib/action_view/helpers/debug_helper.rb +36 -0
  26. data/lib/action_view/helpers/form_helper.rb +2569 -0
  27. data/lib/action_view/helpers/form_options_helper.rb +896 -0
  28. data/lib/action_view/helpers/form_tag_helper.rb +920 -0
  29. data/lib/action_view/helpers/javascript_helper.rb +95 -0
  30. data/lib/action_view/helpers/number_helper.rb +456 -0
  31. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  32. data/lib/action_view/helpers/rendering_helper.rb +101 -0
  33. data/lib/action_view/helpers/sanitize_helper.rb +171 -0
  34. data/lib/action_view/helpers/tag_helper.rb +314 -0
  35. data/lib/action_view/helpers/tags.rb +44 -0
  36. data/lib/action_view/helpers/tags/base.rb +196 -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 +39 -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 +145 -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 +316 -0
  78. data/lib/action_view/model_naming.rb +14 -0
  79. data/lib/action_view/path_set.rb +95 -0
  80. data/lib/action_view/railtie.rb +105 -0
  81. data/lib/action_view/record_identifier.rb +112 -0
  82. data/lib/action_view/renderer/abstract_renderer.rb +108 -0
  83. data/lib/action_view/renderer/partial_renderer.rb +563 -0
  84. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  85. data/lib/action_view/renderer/renderer.rb +68 -0
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
  87. data/lib/action_view/renderer/template_renderer.rb +108 -0
  88. data/lib/action_view/rendering.rb +171 -0
  89. data/lib/action_view/routing_url_for.rb +146 -0
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template.rb +393 -0
  92. data/lib/action_view/template/error.rb +161 -0
  93. data/lib/action_view/template/handlers.rb +92 -0
  94. data/lib/action_view/template/handlers/builder.rb +25 -0
  95. data/lib/action_view/template/handlers/erb.rb +84 -0
  96. data/lib/action_view/template/handlers/erb/erubi.rb +87 -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 +43 -0
  100. data/lib/action_view/template/inline.rb +22 -0
  101. data/lib/action_view/template/raw_file.rb +28 -0
  102. data/lib/action_view/template/resolver.rb +394 -0
  103. data/lib/action_view/template/sources.rb +13 -0
  104. data/lib/action_view/template/sources/file.rb +17 -0
  105. data/lib/action_view/template/text.rb +35 -0
  106. data/lib/action_view/template/types.rb +57 -0
  107. data/lib/action_view/test_case.rb +300 -0
  108. data/lib/action_view/testing/resolvers.rb +67 -0
  109. data/lib/action_view/unbound_template.rb +32 -0
  110. data/lib/action_view/version.rb +10 -0
  111. data/lib/action_view/view_paths.rb +129 -0
  112. data/lib/assets/compiled/rails-ujs.js +746 -0
  113. metadata +260 -0
@@ -0,0 +1,103 @@
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, view, template)
15
+ return yield unless @options[:cached]
16
+
17
+ # Result is a hash with the key represents the
18
+ # key used for cache lookup and the value is the item
19
+ # on which the partial is being rendered
20
+ keyed_collection, ordered_keys = collection_by_cache_keys(view, template)
21
+
22
+ # Pull all partials from cache
23
+ # Result is a hash, key matches the entry in
24
+ # `keyed_collection` where the cache was retrieved and the
25
+ # value is the value that was present in the cache
26
+ cached_partials = collection_cache.read_multi(*keyed_collection.keys)
27
+ instrumentation_payload[:cache_hits] = cached_partials.size
28
+
29
+ # Extract the items for the keys that are not found
30
+ # Set the uncached values to instance variable @collection
31
+ # which is used by the caller
32
+ @collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
33
+
34
+ # If all elements are already in cache then
35
+ # rendered partials will be an empty array
36
+ #
37
+ # If the cache is missing elements then
38
+ # the block will be called against the remaining items
39
+ # in the @collection.
40
+ rendered_partials = @collection.empty? ? [] : yield
41
+
42
+ index = 0
43
+ keyed_partials = fetch_or_cache_partial(cached_partials, template, order_by: keyed_collection.each_key) do
44
+ # This block is called once
45
+ # for every cache miss while preserving order.
46
+ rendered_partials[index].tap { index += 1 }
47
+ end
48
+
49
+ ordered_keys.map do |key|
50
+ keyed_partials[key]
51
+ end
52
+ end
53
+
54
+ def callable_cache_key?
55
+ @options[:cached].respond_to?(:call)
56
+ end
57
+
58
+ def collection_by_cache_keys(view, template)
59
+ seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
60
+
61
+ digest_path = view.digest_path_from_template(template)
62
+
63
+ @collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)|
64
+ key = expanded_cache_key(seed.call(item), view, template, digest_path)
65
+ ordered_keys << key
66
+ hash[key] = item
67
+ end
68
+ end
69
+
70
+ def expanded_cache_key(key, view, template, digest_path)
71
+ key = view.combined_fragment_cache_key(view.cache_fragment_name(key, virtual_path: template.virtual_path, digest_path: digest_path))
72
+ key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
73
+ end
74
+
75
+ # `order_by` is an enumerable object containing keys of the cache,
76
+ # all keys are passed in whether found already or not.
77
+ #
78
+ # `cached_partials` is a hash. If the value exists
79
+ # it represents the rendered partial from the cache
80
+ # otherwise `Hash#fetch` will take the value of its block.
81
+ #
82
+ # This method expects a block that will return the rendered
83
+ # partial. An example is to render all results
84
+ # for each element that was not found in the cache and store it as an array.
85
+ # Order it so that the first empty cache element in `cached_partials`
86
+ # corresponds to the first element in `rendered_partials`.
87
+ #
88
+ # If the partial is not already cached it will also be
89
+ # written back to the underlying cache store.
90
+ def fetch_or_cache_partial(cached_partials, template, order_by:)
91
+ order_by.each_with_object({}) do |cache_key, hash|
92
+ hash[cache_key] =
93
+ if content = cached_partials[cache_key]
94
+ build_rendered_template(content, template)
95
+ else
96
+ yield.tap do |rendered_partial|
97
+ collection_cache.write(cache_key, rendered_partial.body)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,68 @@
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
+ render_to_object(context, options).body
23
+ end
24
+
25
+ def render_to_object(context, options) # :nodoc:
26
+ if options.key?(:partial)
27
+ render_partial_to_object(context, options)
28
+ else
29
+ render_template_to_object(context, options)
30
+ end
31
+ end
32
+
33
+ # Render but returns a valid Rack body. If fibers are defined, we return
34
+ # a streaming body that renders the template piece by piece.
35
+ #
36
+ # Note that partials are not supported to be rendered with streaming,
37
+ # so in such cases, we just wrap them in an array.
38
+ def render_body(context, options)
39
+ if options.key?(:partial)
40
+ [render_partial(context, options)]
41
+ else
42
+ StreamingTemplateRenderer.new(@lookup_context).render(context, options)
43
+ end
44
+ end
45
+
46
+ # Direct access to template rendering.
47
+ def render_template(context, options) #:nodoc:
48
+ render_template_to_object(context, options).body
49
+ end
50
+
51
+ # Direct access to partial rendering.
52
+ def render_partial(context, options, &block) #:nodoc:
53
+ render_partial_to_object(context, options, &block).body
54
+ end
55
+
56
+ def cache_hits # :nodoc:
57
+ @cache_hits ||= {}
58
+ end
59
+
60
+ def render_template_to_object(context, options) #:nodoc:
61
+ TemplateRenderer.new(@lookup_context).render(context, options)
62
+ end
63
+
64
+ def render_partial_to_object(context, options, &block) #:nodoc:
65
+ PartialRenderer.new(@lookup_context).render(context, options, block)
66
+ end
67
+ end
68
+ 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"
37
+ message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_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(view, template, layout_name = nil, locals = {}) #:nodoc:
47
+ return [super.body] 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,108 @@
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
+ @details = extract_details(options)
9
+ template = determine_template(options)
10
+
11
+ prepend_formats(template.format)
12
+
13
+ render_template(context, template, options[:layout], options[:locals] || {})
14
+ end
15
+
16
+ private
17
+
18
+ # Determine the template to be rendered using the given options.
19
+ def determine_template(options)
20
+ keys = options.has_key?(:locals) ? options[:locals].keys : []
21
+
22
+ if options.key?(:body)
23
+ Template::Text.new(options[:body])
24
+ elsif options.key?(:plain)
25
+ Template::Text.new(options[:plain])
26
+ elsif options.key?(:html)
27
+ Template::HTML.new(options[:html], formats.first)
28
+ elsif options.key?(:file)
29
+ if File.exist?(options[:file])
30
+ Template::RawFile.new(options[:file])
31
+ else
32
+ ActiveSupport::Deprecation.warn "render file: should be given the absolute path to a file"
33
+ @lookup_context.with_fallbacks.find_template(options[:file], nil, false, keys, @details)
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?(:template)
44
+ if options[:template].respond_to?(:render)
45
+ options[:template]
46
+ else
47
+ @lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details)
48
+ end
49
+ else
50
+ raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
51
+ end
52
+ end
53
+
54
+ # Renders the given template. A string representing the layout can be
55
+ # supplied as well.
56
+ def render_template(view, template, layout_name, locals)
57
+ render_with_layout(view, template, layout_name, locals) do |layout|
58
+ instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
59
+ template.render(view, locals) { |*name| view._layout_for(*name) }
60
+ end
61
+ end
62
+ end
63
+
64
+ def render_with_layout(view, template, path, locals)
65
+ layout = path && find_layout(path, locals.keys, [formats.first])
66
+ content = yield(layout)
67
+
68
+ body = if layout
69
+ view.view_flow.set(:layout, content)
70
+ layout.render(view, locals) { |*name| view._layout_for(*name) }
71
+ else
72
+ content
73
+ end
74
+ build_rendered_template(body, template, layout)
75
+ end
76
+
77
+ # This is the method which actually finds the layout using details in the lookup
78
+ # context object. If no layout is found, it checks if at least a layout with
79
+ # the given name exists across all details before raising the error.
80
+ def find_layout(layout, keys, formats)
81
+ resolve_layout(layout, keys, formats)
82
+ end
83
+
84
+ def resolve_layout(layout, keys, formats)
85
+ details = @details.dup
86
+ details[:formats] = formats
87
+
88
+ case layout
89
+ when String
90
+ begin
91
+ if layout.start_with?("/")
92
+ ActiveSupport::Deprecation.warn "Rendering layouts from an absolute path is deprecated."
93
+ @lookup_context.with_fallbacks.find_template(layout, nil, false, [], details)
94
+ else
95
+ @lookup_context.find_template(layout, nil, false, [], details)
96
+ end
97
+ rescue ActionView::MissingTemplate
98
+ all_details = @details.merge(formats: @lookup_context.default_formats)
99
+ raise unless template_exists?(layout, nil, false, [], all_details)
100
+ end
101
+ when Proc
102
+ resolve_layout(layout.call(@lookup_context, formats), keys, formats)
103
+ else
104
+ layout
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,171 @@
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
+ attr_reader :rendered_format
30
+
31
+ def initialize
32
+ @rendered_format = nil
33
+ super
34
+ end
35
+
36
+ # Overwrite process to setup I18n proxy.
37
+ def process(*) #:nodoc:
38
+ old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
39
+ super
40
+ ensure
41
+ I18n.config = old_config
42
+ end
43
+
44
+ module ClassMethods
45
+ def _routes
46
+ end
47
+
48
+ def _helpers
49
+ end
50
+
51
+ def build_view_context_class(klass, supports_path, routes, helpers)
52
+ Class.new(klass) do
53
+ if routes
54
+ include routes.url_helpers(supports_path)
55
+ include routes.mounted_helpers
56
+ end
57
+
58
+ if helpers
59
+ include helpers
60
+ end
61
+ end
62
+ end
63
+
64
+ def view_context_class
65
+ klass = ActionView::LookupContext::DetailsKey.view_context_class(ActionView::Base)
66
+
67
+ @view_context_class ||= build_view_context_class(klass, supports_path?, _routes, _helpers)
68
+
69
+ if klass.changed?(@view_context_class)
70
+ @view_context_class = build_view_context_class(klass, supports_path?, _routes, _helpers)
71
+ end
72
+
73
+ @view_context_class
74
+ end
75
+ end
76
+
77
+ def view_context_class
78
+ self.class.view_context_class
79
+ end
80
+
81
+ # An instance of a view class. The default view class is ActionView::Base.
82
+ #
83
+ # The view class must have the following methods:
84
+ #
85
+ # * <tt>View.new(lookup_context, assigns, controller)</tt> — Create a new
86
+ # ActionView instance for a controller and we can also pass the arguments.
87
+ #
88
+ # * <tt>View#render(option)</tt> — Returns String with the rendered template.
89
+ #
90
+ # Override this method in a module to change the default behavior.
91
+ def view_context
92
+ view_context_class.new(lookup_context, view_assigns, self)
93
+ end
94
+
95
+ # Returns an object that is able to render templates.
96
+ def view_renderer # :nodoc:
97
+ # Lifespan: Per controller
98
+ @_view_renderer ||= ActionView::Renderer.new(lookup_context)
99
+ end
100
+
101
+ def render_to_body(options = {})
102
+ _process_options(options)
103
+ _render_template(options)
104
+ end
105
+
106
+ private
107
+
108
+ # Find and render a template based on the options given.
109
+ def _render_template(options)
110
+ variant = options.delete(:variant)
111
+ assigns = options.delete(:assigns)
112
+ context = view_context
113
+
114
+ context.assign assigns if assigns
115
+ lookup_context.variants = variant if variant
116
+
117
+ rendered_template = context.in_rendering_context(options) do |renderer|
118
+ renderer.render_to_object(context, options)
119
+ end
120
+
121
+ rendered_format = rendered_template.format || lookup_context.formats.first
122
+ @rendered_format = Template::Types[rendered_format]
123
+
124
+ rendered_template.body
125
+ end
126
+
127
+ # Assign the rendered format to look up context.
128
+ def _process_format(format)
129
+ super
130
+ lookup_context.formats = [format.to_sym] if format.to_sym
131
+ end
132
+
133
+ # Normalize args by converting render "foo" to render :action => "foo" and
134
+ # render "foo/bar" to render :template => "foo/bar".
135
+ def _normalize_args(action = nil, options = {})
136
+ options = super(action, options)
137
+ case action
138
+ when NilClass
139
+ when Hash
140
+ options = action
141
+ when String, Symbol
142
+ action = action.to_s
143
+ key = action.include?(?/) ? :template : :action
144
+ options[key] = action
145
+ else
146
+ if action.respond_to?(:permitted?) && action.permitted?
147
+ options = action
148
+ else
149
+ options[:partial] = action
150
+ end
151
+ end
152
+
153
+ options
154
+ end
155
+
156
+ # Normalize options.
157
+ def _normalize_options(options)
158
+ options = super(options)
159
+ if options[:partial] == true
160
+ options[:partial] = action_name
161
+ end
162
+
163
+ if (options.keys & [:partial, :file, :template]).empty?
164
+ options[:prefixes] ||= _prefixes
165
+ end
166
+
167
+ options[:template] ||= (options[:action] || action_name).to_s
168
+ options
169
+ end
170
+ end
171
+ end