/
rendering.rb
260 lines (235 loc) · 8.02 KB
/
rendering.rb
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# frozen_string_literal: true
# :markup: markdown
module ActionController
module Rendering
extend ActiveSupport::Concern
RENDER_FORMATS_IN_PRIORITY = [:body, :plain, :html]
module ClassMethods
# Documentation at ActionController::Renderer#render
delegate :render, to: :renderer
# Returns a renderer instance (inherited from ActionController::Renderer) for
# the controller.
attr_reader :renderer
def setup_renderer! # :nodoc:
@renderer = Renderer.for(self)
end
def inherited(klass)
klass.setup_renderer!
super
end
end
# Renders a template and assigns the result to `self.response_body`.
#
# If no rendering mode option is specified, the template will be derived from
# the first argument.
#
# render "posts/show"
# # => renders app/views/posts/show.html.erb
#
# # In a PostsController action...
# render :show
# # => renders app/views/posts/show.html.erb
#
# If the first argument responds to `render_in`, the template will be rendered
# by calling `render_in` with the current view context.
#
# class Greeting
# def render_in(view_context)
# view_context.render html: "<h1>Hello, World</h1>"
# end
#
# def format
# :html
# end
# end
#
# render(Greeting.new)
# # => "<h1>Hello, World</h1>"
#
# render(renderable: Greeting.new)
# # => "<h1>Hello, World</h1>"
#
# #### Rendering Mode
#
# `:partial`
# : See ActionView::PartialRenderer for details.
#
# render partial: "posts/form", locals: { post: Post.new }
# # => renders app/views/posts/_form.html.erb
#
# `:file`
# : Renders the contents of a file. This option should **not** be used with
# unsanitized user input.
#
# render file: "/path/to/some/file"
# # => renders /path/to/some/file
#
# `:inline`
# : Renders an ERB template string.
#
# @name = "World"
# render inline: "<h1>Hello, <%= @name %>!</h1>"
# # => renders "<h1>Hello, World!</h1>"
#
# `:body`
# : Renders the provided text, and sets the content type as `text/plain`.
#
# render body: "Hello, World!"
# # => renders "Hello, World!"
#
# `:plain`
# : Renders the provided text, and sets the content type as `text/plain`.
#
# render plain: "Hello, World!"
# # => renders "Hello, World!"
#
# `:html`
# : Renders the provided HTML string, and sets the content type as
# `text/html`. If the string is not `html_safe?`, performs HTML escaping on
# the string before rendering.
#
# render html: "<h1>Hello, World!</h1>".html_safe
# # => renders "<h1>Hello, World!</h1>"
#
# render html: "<h1>Hello, World!</h1>"
# # => renders "<h1>Hello, World!</h1>"
#
# `:json`
# : Renders the provided object as JSON, and sets the content type as
# `application/json`. If the object is not a string, it will be converted to
# JSON by calling `to_json`.
#
# render json: { hello: "world" }
# # => renders "{\"hello\":\"world\"}"
#
# `:renderable`
# : Renders the provided object by calling `render_in` with the current view
# context. The response format is determined by calling `format` on the
# renderable if it responds to `format`, falling back to `text/html` by
# default.
#
# render renderable: Greeting.new
# # => renders "<h1>Hello, World</h1>"
#
#
# By default, when a rendering mode is specified, no layout template is
# rendered.
#
# #### Options
#
# `:assigns`
# : Hash of instance variable assignments for the template.
#
# render inline: "<h1>Hello, <%= @name %>!</h1>", assigns: { name: "World" }
# # => renders "<h1>Hello, World!</h1>"
#
# `:locals`
# : Hash of local variable assignments for the template.
#
# render inline: "<h1>Hello, <%= name %>!</h1>", locals: { name: "World" }
# # => renders "<h1>Hello, World!</h1>"
#
# `:layout`
# : The layout template to render. Can also be `false` or `true` to disable or
# (re)enable the default layout template.
#
# render "posts/show", layout: "holiday"
# # => renders app/views/posts/show.html.erb with the app/views/layouts/holiday.html.erb layout
#
# render "posts/show", layout: false
# # => renders app/views/posts/show.html.erb with no layout
#
# render inline: "<h1>Hello, World!</h1>", layout: true
# # => renders "<h1>Hello, World!</h1>" with the default layout
#
# `:status`
# : The HTTP status code to send with the response. Can be specified as a
# number or as the status name in Symbol form. Defaults to 200.
#
# render "posts/new", status: 422
# # => renders app/views/posts/new.html.erb with HTTP status code 422
#
# render "posts/new", status: :unprocessable_entity
# # => renders app/views/posts/new.html.erb with HTTP status code 422
#
#--
# Check for double render errors and set the content_type after rendering.
def render(*args)
raise ::AbstractController::DoubleRenderError if response_body
super
end
# Similar to #render, but only returns the rendered template as a string,
# instead of setting `self.response_body`.
#--
# Override render_to_string because body can now be set to a Rack body.
def render_to_string(*)
result = super
if result.respond_to?(:each)
string = +""
result.each { |r| string << r }
string
else
result
end
end
def render_to_body(options = {}) # :nodoc:
super || _render_in_priorities(options) || " "
end
private
# Before processing, set the request formats in current controller formats.
def process_action(*) # :nodoc:
self.formats = request.formats.filter_map(&:ref)
super
end
def _process_variant(options)
if defined?(request) && !request.nil? && request.variant.present?
options[:variant] = request.variant
end
end
def _render_in_priorities(options)
RENDER_FORMATS_IN_PRIORITY.each do |format|
return options[format] if options.key?(format)
end
nil
end
def _set_html_content_type
self.content_type = Mime[:html].to_s
end
def _set_rendered_content_type(format)
if format && !response.media_type
self.content_type = format.to_s
end
end
def _set_vary_header
if response.headers["Vary"].blank? && request.should_apply_vary_header?
response.headers["Vary"] = "Accept"
end
end
# Normalize both text and status options.
def _normalize_options(options)
_normalize_text(options)
if options[:html]
options[:html] = ERB::Util.html_escape(options[:html])
end
if options[:status]
options[:status] = Rack::Utils.status_code(options[:status])
end
super
end
def _normalize_text(options)
RENDER_FORMATS_IN_PRIORITY.each do |format|
if options.key?(format) && options[format].respond_to?(:to_text)
options[format] = options[format].to_text
end
end
end
# Process controller specific options, as status, content-type and location.
def _process_options(options)
status, content_type, location = options.values_at(:status, :content_type, :location)
self.status = status if status
self.content_type = content_type if content_type
headers["Location"] = url_for(location) if location
super
end
end
end