aboutsummaryrefslogtreecommitdiffstats
path: root/actionview/lib/action_view/rendering.rb
blob: 7247598ac2c8373a20cfbcad1075582f998e1987 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# frozen_string_literal: true

require "action_view/view_paths"

module ActionView
  # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
  # it will trigger the lookup_context and consequently expire the cache.
  class I18nProxy < ::I18n::Config #:nodoc:
    attr_reader :original_config, :lookup_context

    def initialize(original_config, lookup_context)
      original_config = original_config.original_config if original_config.respond_to?(:original_config)
      @original_config, @lookup_context = original_config, lookup_context
    end

    def locale
      @original_config.locale
    end

    def locale=(value)
      @lookup_context.locale = value
    end
  end

  module Rendering
    extend ActiveSupport::Concern
    include ActionView::ViewPaths

    attr_reader :rendered_format

    def initialize
      @rendered_format = nil
      super
    end

    # Overwrite process to setup I18n proxy.
    def process(*) #:nodoc:
      old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
      super
    ensure
      I18n.config = old_config
    end

    module ClassMethods
      def _routes
      end

      def _helpers
      end

      def build_view_context_class(klass, supports_path, routes, helpers)
        Class.new(klass) do
          if routes
            include routes.url_helpers(supports_path)
            include routes.mounted_helpers
          end

          if helpers
            include helpers
          end
        end
      end

      def view_context_class
        klass = ActionView::LookupContext::DetailsKey.view_context_class(ActionView::Base)

        @view_context_class ||= build_view_context_class(klass, supports_path?, _routes, _helpers)

        if klass.changed?(@view_context_class)
          @view_context_class = build_view_context_class(klass, supports_path?, _routes, _helpers)
        end

        @view_context_class
      end
    end

    def view_context_class
      self.class.view_context_class
    end

    # An instance of a view class. The default view class is ActionView::Base.
    #
    # The view class must have the following methods:
    #
    # * <tt>View.new(lookup_context, assigns, controller)</tt> — Create a new
    #   ActionView instance for a controller and we can also pass the arguments.
    #
    # * <tt>View#render(option)</tt> — Returns String with the rendered template.
    #
    # Override this method in a module to change the default behavior.
    def view_context
      view_context_class.new(lookup_context, view_assigns, self)
    end

    # Returns an object that is able to render templates.
    def view_renderer # :nodoc:
      # Lifespan: Per controller
      @_view_renderer ||= ActionView::Renderer.new(lookup_context)
    end

    def render_to_body(options = {})
      _process_options(options)
      _render_template(options)
    end

    private
      # Find and render a template based on the options given.
      def _render_template(options)
        variant = options.delete(:variant)
        assigns = options.delete(:assigns)
        context = view_context

        context.assign assigns if assigns
        lookup_context.variants = variant if variant

        rendered_template = context.in_rendering_context(options) do |renderer|
          renderer.render_to_object(context, options)
        end

        rendered_format = rendered_template.format || lookup_context.formats.first
        @rendered_format = Template::Types[rendered_format]

        rendered_template.body
      end

      # Assign the rendered format to look up context.
      def _process_format(format)
        super
        lookup_context.formats = [format.to_sym] if format.to_sym
      end

      # Normalize args by converting render "foo" to render :action => "foo" and
      # render "foo/bar" to render :template => "foo/bar".
      def _normalize_args(action = nil, options = {})
        options = super(action, options)
        case action
        when NilClass
        when Hash
          options = action
        when String, Symbol
          action = action.to_s
          key = action.include?(?/) ? :template : :action
          options[key] = action
        else
          if action.respond_to?(:permitted?) && action.permitted?
            options = action
          else
            options[:partial] = action
          end
        end

        options
      end

      # Normalize options.
      def _normalize_options(options)
        options = super(options)
        if options[:partial] == true
          options[:partial] = action_name
        end

        if (options.keys & [:partial, :file, :template]).empty?
          options[:prefixes] ||= _prefixes
        end

        options[:template] ||= (options[:action] || action_name).to_s
        options
      end
  end
end