actionview 5.2.8.1 → 6.0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +280 -94
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -3
- data/lib/action_view/base.rb +108 -11
- data/lib/action_view/buffers.rb +15 -0
- data/lib/action_view/cache_expiry.rb +53 -0
- data/lib/action_view/context.rb +5 -9
- data/lib/action_view/digestor.rb +12 -20
- data/lib/action_view/flows.rb +0 -1
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/active_model_helper.rb +0 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +8 -31
- data/lib/action_view/helpers/asset_url_helper.rb +4 -3
- data/lib/action_view/helpers/cache_helper.rb +19 -12
- data/lib/action_view/helpers/capture_helper.rb +4 -0
- data/lib/action_view/helpers/csp_helper.rb +4 -2
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +70 -27
- data/lib/action_view/helpers/form_helper.rb +240 -8
- data/lib/action_view/helpers/form_options_helper.rb +27 -18
- data/lib/action_view/helpers/form_tag_helper.rb +17 -15
- data/lib/action_view/helpers/javascript_helper.rb +9 -8
- data/lib/action_view/helpers/number_helper.rb +8 -2
- data/lib/action_view/helpers/output_safety_helper.rb +1 -1
- data/lib/action_view/helpers/rendering_helper.rb +6 -4
- data/lib/action_view/helpers/sanitize_helper.rb +12 -18
- data/lib/action_view/helpers/tag_helper.rb +8 -7
- data/lib/action_view/helpers/tags/base.rb +9 -6
- data/lib/action_view/helpers/tags/check_box.rb +0 -1
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
- data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
- data/lib/action_view/helpers/tags/color_field.rb +1 -2
- data/lib/action_view/helpers/tags/date_field.rb +0 -1
- data/lib/action_view/helpers/tags/date_select.rb +0 -1
- data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
- data/lib/action_view/helpers/tags/datetime_local_field.rb +0 -1
- data/lib/action_view/helpers/tags/label.rb +0 -1
- data/lib/action_view/helpers/tags/month_field.rb +0 -1
- data/lib/action_view/helpers/tags/radio_button.rb +0 -1
- data/lib/action_view/helpers/tags/select.rb +0 -1
- data/lib/action_view/helpers/tags/text_field.rb +0 -1
- data/lib/action_view/helpers/tags/time_field.rb +0 -1
- data/lib/action_view/helpers/tags/translator.rb +1 -6
- data/lib/action_view/helpers/tags/week_field.rb +0 -1
- data/lib/action_view/helpers/text_helper.rb +3 -4
- data/lib/action_view/helpers/translation_helper.rb +19 -17
- data/lib/action_view/helpers/url_helper.rb +14 -14
- data/lib/action_view/helpers.rb +0 -2
- data/lib/action_view/layouts.rb +5 -8
- data/lib/action_view/log_subscriber.rb +6 -7
- data/lib/action_view/lookup_context.rb +75 -32
- data/lib/action_view/path_set.rb +5 -11
- data/lib/action_view/railtie.rb +24 -1
- data/lib/action_view/record_identifier.rb +2 -3
- data/lib/action_view/renderer/abstract_renderer.rb +56 -4
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +63 -17
- data/lib/action_view/renderer/partial_renderer.rb +67 -57
- data/lib/action_view/renderer/renderer.rb +16 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +5 -7
- data/lib/action_view/renderer/template_renderer.rb +25 -20
- data/lib/action_view/rendering.rb +51 -32
- data/lib/action_view/routing_url_for.rb +12 -11
- data/lib/action_view/template/error.rb +30 -15
- data/lib/action_view/template/handlers/builder.rb +2 -2
- data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
- data/lib/action_view/template/handlers/erb.rb +17 -8
- data/lib/action_view/template/handlers/html.rb +1 -1
- data/lib/action_view/template/handlers/raw.rb +2 -2
- data/lib/action_view/template/handlers.rb +27 -1
- data/lib/action_view/template/html.rb +14 -5
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +28 -0
- data/lib/action_view/template/resolver.rb +134 -135
- 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 +5 -3
- data/lib/action_view/template.rb +102 -71
- data/lib/action_view/test_case.rb +3 -4
- data/lib/action_view/testing/resolvers.rb +33 -21
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/view_paths.rb +25 -2
- data/lib/action_view.rb +4 -2
- data/lib/assets/compiled/rails-ujs.js +30 -4
- metadata +27 -18
- data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -16,7 +16,7 @@ module ActionView
|
|
16
16
|
alias_method :partial?, :partial
|
17
17
|
|
18
18
|
def self.build(name, prefix, partial)
|
19
|
-
virtual = ""
|
19
|
+
virtual = +""
|
20
20
|
virtual << "#{prefix}/" unless prefix.empty?
|
21
21
|
virtual << (partial ? "_#{name}" : name)
|
22
22
|
new name, prefix, partial, virtual
|
@@ -63,26 +63,11 @@ module ActionView
|
|
63
63
|
|
64
64
|
# Cache the templates returned by the block
|
65
65
|
def cache(key, name, prefix, partial, locals)
|
66
|
-
|
67
|
-
@data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
|
68
|
-
else
|
69
|
-
fresh_templates = yield
|
70
|
-
cached_templates = @data[key][name][prefix][partial][locals]
|
71
|
-
|
72
|
-
if templates_have_changed?(cached_templates, fresh_templates)
|
73
|
-
@data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates)
|
74
|
-
else
|
75
|
-
cached_templates || NO_TEMPLATES
|
76
|
-
end
|
77
|
-
end
|
66
|
+
@data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
|
78
67
|
end
|
79
68
|
|
80
69
|
def cache_query(query) # :nodoc:
|
81
|
-
|
82
|
-
@query_cache[query] ||= canonical_no_templates(yield)
|
83
|
-
else
|
84
|
-
yield
|
85
|
-
end
|
70
|
+
@query_cache[query] ||= canonical_no_templates(yield)
|
86
71
|
end
|
87
72
|
|
88
73
|
def clear
|
@@ -108,23 +93,9 @@ module ActionView
|
|
108
93
|
end
|
109
94
|
|
110
95
|
private
|
111
|
-
|
112
96
|
def canonical_no_templates(templates)
|
113
97
|
templates.empty? ? NO_TEMPLATES : templates
|
114
98
|
end
|
115
|
-
|
116
|
-
def templates_have_changed?(cached_templates, fresh_templates)
|
117
|
-
# if either the old or new template list is empty, we don't need to (and can't)
|
118
|
-
# compare modification times, and instead just check whether the lists are different
|
119
|
-
if cached_templates.blank? || fresh_templates.blank?
|
120
|
-
return fresh_templates.blank? != cached_templates.blank?
|
121
|
-
end
|
122
|
-
|
123
|
-
cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
|
124
|
-
|
125
|
-
# if a template has changed, it will be now be newer than all the cached templates
|
126
|
-
fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
|
127
|
-
end
|
128
99
|
end
|
129
100
|
|
130
101
|
cattr_accessor :caching, default: true
|
@@ -143,35 +114,32 @@ module ActionView
|
|
143
114
|
|
144
115
|
# Normalizes the arguments and passes it on to find_templates.
|
145
116
|
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
|
146
|
-
|
147
|
-
find_templates(name, prefix, partial, details)
|
148
|
-
end
|
149
|
-
end
|
117
|
+
locals = locals.map(&:to_s).sort!.freeze
|
150
118
|
|
151
|
-
def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = [])
|
152
119
|
cached(key, [name, prefix, partial], details, locals) do
|
153
|
-
|
120
|
+
_find_all(name, prefix, partial, details, key, locals)
|
154
121
|
end
|
155
122
|
end
|
156
123
|
|
124
|
+
alias :find_all_anywhere :find_all
|
125
|
+
deprecate :find_all_anywhere
|
126
|
+
|
157
127
|
def find_all_with_query(query) # :nodoc:
|
158
128
|
@cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
|
159
129
|
end
|
160
130
|
|
161
131
|
private
|
132
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
133
|
+
find_templates(name, prefix, partial, details, locals)
|
134
|
+
end
|
162
135
|
|
163
136
|
delegate :caching?, to: :class
|
164
137
|
|
165
138
|
# This is what child classes implement. No defaults are needed
|
166
139
|
# because Resolver guarantees that the arguments are present and
|
167
140
|
# normalized.
|
168
|
-
def find_templates(name, prefix, partial, details,
|
169
|
-
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details,
|
170
|
-
end
|
171
|
-
|
172
|
-
# Helpers that builds a path. Useful for building virtual paths.
|
173
|
-
def build_path(name, prefix, partial)
|
174
|
-
Path.build(name, prefix, partial)
|
141
|
+
def find_templates(name, prefix, partial, details, locals = [])
|
142
|
+
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
|
175
143
|
end
|
176
144
|
|
177
145
|
# Handles templates caching. If a key is given and caching is on
|
@@ -180,25 +148,13 @@ module ActionView
|
|
180
148
|
# resolver is fresher before returning it.
|
181
149
|
def cached(key, path_info, details, locals)
|
182
150
|
name, prefix, partial = path_info
|
183
|
-
locals = locals.map(&:to_s).sort!
|
184
151
|
|
185
152
|
if key
|
186
153
|
@cache.cache(key, name, prefix, partial, locals) do
|
187
|
-
|
154
|
+
yield
|
188
155
|
end
|
189
156
|
else
|
190
|
-
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
# Ensures all the resolver information is set in the template.
|
195
|
-
def decorate(templates, path_info, details, locals)
|
196
|
-
cached = nil
|
197
|
-
templates.each do |t|
|
198
|
-
t.locals = locals
|
199
|
-
t.formats = details[:formats] || [:html] if t.formats.empty?
|
200
|
-
t.variants = details[:variants] || [] if t.variants.empty?
|
201
|
-
t.virtual_path ||= (cached ||= build_path(*path_info))
|
157
|
+
yield
|
202
158
|
end
|
203
159
|
end
|
204
160
|
end
|
@@ -209,40 +165,68 @@ module ActionView
|
|
209
165
|
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
|
210
166
|
|
211
167
|
def initialize(pattern = nil)
|
212
|
-
|
168
|
+
if pattern
|
169
|
+
ActiveSupport::Deprecation.warn "Specifying a custom path for #{self.class} is deprecated. Implement a custom Resolver subclass instead."
|
170
|
+
@pattern = pattern
|
171
|
+
else
|
172
|
+
@pattern = DEFAULT_PATTERN
|
173
|
+
end
|
174
|
+
@unbound_templates = Concurrent::Map.new
|
213
175
|
super()
|
214
176
|
end
|
215
177
|
|
216
|
-
|
178
|
+
def clear_cache
|
179
|
+
@unbound_templates.clear
|
180
|
+
super()
|
181
|
+
end
|
217
182
|
|
218
|
-
|
183
|
+
private
|
184
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
219
185
|
path = Path.build(name, prefix, partial)
|
220
|
-
query(path, details, details[:formats],
|
186
|
+
query(path, details, details[:formats], locals, cache: !!key)
|
221
187
|
end
|
222
188
|
|
223
|
-
def query(path, details, formats,
|
224
|
-
|
225
|
-
|
226
|
-
template_paths = find_template_paths(query)
|
227
|
-
template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
|
189
|
+
def query(path, details, formats, locals, cache:)
|
190
|
+
template_paths = find_template_paths_from_details(path, details)
|
191
|
+
template_paths = reject_files_external_to_app(template_paths)
|
228
192
|
|
229
193
|
template_paths.map do |template|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
194
|
+
unbound_template =
|
195
|
+
if cache
|
196
|
+
@unbound_templates.compute_if_absent([template, path.virtual]) do
|
197
|
+
build_unbound_template(template, path.virtual)
|
198
|
+
end
|
199
|
+
else
|
200
|
+
build_unbound_template(template, path.virtual)
|
201
|
+
end
|
202
|
+
|
203
|
+
unbound_template.bind_locals(locals)
|
239
204
|
end
|
240
205
|
end
|
241
206
|
|
207
|
+
def build_unbound_template(template, virtual_path)
|
208
|
+
handler, format, variant = extract_handler_and_format_and_variant(template)
|
209
|
+
source = Template::Sources::File.new(template)
|
210
|
+
|
211
|
+
UnboundTemplate.new(
|
212
|
+
source,
|
213
|
+
template,
|
214
|
+
handler,
|
215
|
+
virtual_path: virtual_path,
|
216
|
+
format: format,
|
217
|
+
variant: variant,
|
218
|
+
)
|
219
|
+
end
|
220
|
+
|
242
221
|
def reject_files_external_to_app(files)
|
243
222
|
files.reject { |filename| !inside_path?(@path, filename) }
|
244
223
|
end
|
245
224
|
|
225
|
+
def find_template_paths_from_details(path, details)
|
226
|
+
query = build_query(path, details)
|
227
|
+
find_template_paths(query)
|
228
|
+
end
|
229
|
+
|
246
230
|
def find_template_paths(query)
|
247
231
|
Dir[query].uniq.reject do |filename|
|
248
232
|
File.directory?(filename) ||
|
@@ -279,70 +263,39 @@ module ActionView
|
|
279
263
|
end
|
280
264
|
|
281
265
|
def escape_entry(entry)
|
282
|
-
entry.gsub(/[*?{}\[\]]/, '\\\\\\&'
|
283
|
-
end
|
284
|
-
|
285
|
-
# Returns the file mtime from the filesystem.
|
286
|
-
def mtime(p)
|
287
|
-
File.mtime(p)
|
266
|
+
entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
|
288
267
|
end
|
289
268
|
|
290
269
|
# Extract handler, formats and variant from path. If a format cannot be found neither
|
291
270
|
# from the path, or the handler, we should return the array of formats given
|
292
271
|
# to the resolver.
|
293
272
|
def extract_handler_and_format_and_variant(path)
|
294
|
-
pieces = File.basename(path).split("."
|
273
|
+
pieces = File.basename(path).split(".")
|
295
274
|
pieces.shift
|
296
275
|
|
297
276
|
extension = pieces.pop
|
298
277
|
|
299
278
|
handler = Template.handler_for_extension(extension)
|
300
279
|
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
|
301
|
-
format
|
280
|
+
format = if format
|
281
|
+
Template::Types[format]&.ref
|
282
|
+
else
|
283
|
+
if handler.respond_to?(:default_format) # default_format can return nil
|
284
|
+
handler.default_format
|
285
|
+
else
|
286
|
+
nil
|
287
|
+
end
|
288
|
+
end
|
302
289
|
|
290
|
+
# Template::Types[format] and handler.default_format can return nil
|
303
291
|
[handler, format, variant]
|
304
292
|
end
|
305
293
|
end
|
306
294
|
|
307
|
-
# A resolver that loads files from the filesystem.
|
308
|
-
# resolving pattern. Such pattern can be a glob string supported by some variables.
|
309
|
-
#
|
310
|
-
# ==== Examples
|
311
|
-
#
|
312
|
-
# Default pattern, loads views the same way as previous versions of rails, eg. when you're
|
313
|
-
# looking for <tt>users/new</tt> it will produce query glob: <tt>users/new{.{en},}{.{html,js},}{.{erb,haml},}</tt>
|
314
|
-
#
|
315
|
-
# FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
|
316
|
-
#
|
317
|
-
# This one allows you to keep files with different formats in separate subdirectories,
|
318
|
-
# eg. <tt>users/new.html</tt> will be loaded from <tt>users/html/new.erb</tt> or <tt>users/new.html.erb</tt>,
|
319
|
-
# <tt>users/new.js</tt> from <tt>users/js/new.erb</tt> or <tt>users/new.js.erb</tt>, etc.
|
320
|
-
#
|
321
|
-
# FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
|
322
|
-
#
|
323
|
-
# If you don't specify a pattern then the default will be used.
|
324
|
-
#
|
325
|
-
# In order to use any of the customized resolvers above in a Rails application, you just need
|
326
|
-
# to configure ActionController::Base.view_paths in an initializer, for example:
|
327
|
-
#
|
328
|
-
# ActionController::Base.view_paths = FileSystemResolver.new(
|
329
|
-
# Rails.root.join("app/views"),
|
330
|
-
# ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}",
|
331
|
-
# )
|
332
|
-
#
|
333
|
-
# ==== Pattern format and variables
|
334
|
-
#
|
335
|
-
# Pattern has to be a valid glob string, and it allows you to use the
|
336
|
-
# following variables:
|
337
|
-
#
|
338
|
-
# * <tt>:prefix</tt> - usually the controller path
|
339
|
-
# * <tt>:action</tt> - name of the action
|
340
|
-
# * <tt>:locale</tt> - possible locale versions
|
341
|
-
# * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
|
342
|
-
# * <tt>:variants</tt> - possible request variants (for example phone, tablet...)
|
343
|
-
# * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
|
344
|
-
#
|
295
|
+
# A resolver that loads files from the filesystem.
|
345
296
|
class FileSystemResolver < PathResolver
|
297
|
+
attr_reader :path
|
298
|
+
|
346
299
|
def initialize(path, pattern = nil)
|
347
300
|
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
|
348
301
|
super(pattern)
|
@@ -362,30 +315,76 @@ module ActionView
|
|
362
315
|
|
363
316
|
# An Optimized resolver for Rails' most common case.
|
364
317
|
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
|
365
|
-
def
|
366
|
-
|
318
|
+
def initialize(path)
|
319
|
+
super(path)
|
320
|
+
end
|
367
321
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
322
|
+
private
|
323
|
+
def find_template_paths_from_details(path, details)
|
324
|
+
# Instead of checking for every possible path, as our other globs would
|
325
|
+
# do, scan the directory for files with the right prefix.
|
326
|
+
query = "#{escape_entry(File.join(@path, path))}*"
|
327
|
+
|
328
|
+
regex = build_regex(path, details)
|
329
|
+
|
330
|
+
Dir[query].uniq.reject do |filename|
|
331
|
+
# This regex match does double duty of finding only files which match
|
332
|
+
# details (instead of just matching the prefix) and also filtering for
|
333
|
+
# case-insensitive file systems.
|
334
|
+
!regex.match?(filename) ||
|
335
|
+
File.directory?(filename)
|
336
|
+
end.sort_by do |filename|
|
337
|
+
# Because we scanned the directory, instead of checking for files
|
338
|
+
# one-by-one, they will be returned in an arbitrary order.
|
339
|
+
# We can use the matches found by the regex and sort by their index in
|
340
|
+
# details.
|
341
|
+
match = filename.match(regex)
|
342
|
+
EXTENSIONS.keys.reverse.map do |ext|
|
343
|
+
if ext == :variants && details[ext] == :any
|
344
|
+
match[ext].nil? ? 0 : 1
|
345
|
+
elsif match[ext].nil?
|
346
|
+
# No match should be last
|
347
|
+
details[ext].length
|
348
|
+
else
|
349
|
+
found = match[ext].to_sym
|
350
|
+
details[ext].index(found)
|
351
|
+
end
|
352
|
+
end
|
373
353
|
end
|
374
|
-
end
|
354
|
+
end
|
375
355
|
|
376
|
-
|
377
|
-
|
356
|
+
def build_regex(path, details)
|
357
|
+
query = Regexp.escape(File.join(@path, path))
|
358
|
+
exts = EXTENSIONS.map do |ext, prefix|
|
359
|
+
match =
|
360
|
+
if ext == :variants && details[ext] == :any
|
361
|
+
".*?"
|
362
|
+
else
|
363
|
+
details[ext].compact.uniq.map { |e| Regexp.escape(e) }.join("|")
|
364
|
+
end
|
365
|
+
prefix = Regexp.escape(prefix)
|
366
|
+
"(#{prefix}(?<#{ext}>#{match}))?"
|
367
|
+
end.join
|
368
|
+
|
369
|
+
%r{\A#{query}#{exts}\z}
|
370
|
+
end
|
378
371
|
end
|
379
372
|
|
380
373
|
# The same as FileSystemResolver but does not allow templates to store
|
381
374
|
# a virtual path since it is invalid for such resolvers.
|
382
375
|
class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
|
376
|
+
private_class_method :new
|
377
|
+
|
383
378
|
def self.instances
|
384
379
|
[new(""), new("/")]
|
385
380
|
end
|
386
381
|
|
387
|
-
def
|
388
|
-
super
|
382
|
+
def build_unbound_template(template, _)
|
383
|
+
super(template, nil)
|
384
|
+
end
|
385
|
+
|
386
|
+
def reject_files_external_to_app(files)
|
387
|
+
files
|
389
388
|
end
|
390
389
|
end
|
391
390
|
end
|
@@ -8,7 +8,6 @@ module ActionView #:nodoc:
|
|
8
8
|
|
9
9
|
def initialize(string)
|
10
10
|
@string = string.to_s
|
11
|
-
@type = Types[:text]
|
12
11
|
end
|
13
12
|
|
14
13
|
def identifier
|
@@ -25,9 +24,12 @@ module ActionView #:nodoc:
|
|
25
24
|
to_str
|
26
25
|
end
|
27
26
|
|
28
|
-
def
|
29
|
-
|
27
|
+
def format
|
28
|
+
:text
|
30
29
|
end
|
30
|
+
|
31
|
+
def formats; Array(format); end
|
32
|
+
deprecate :formats
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|