b4um_generators 0.1.0 → 0.2.7
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -2
- data/README.de.md +177 -5
- data/README.md +175 -5
- data/lib/b4um_generators/version.rb +1 -1
- data/lib/generators/b4um/attachment/attachment_generator.rb +514 -0
- data/lib/generators/b4um/authentication/authentication_generator.rb +414 -0
- data/lib/generators/b4um/authentication/templates/new.html.erb.tt +35 -0
- data/lib/generators/b4um/authentication/templates/sessions_controller.rb.tt +25 -0
- data/lib/generators/b4um/help/help_generator.rb +210 -5
- data/lib/generators/b4um/infinite_scroll/infinite_scroll_generator.rb +31 -0
- data/lib/generators/b4um/infinite_scroll/templates/infinite_scroll_controller.js +19 -9
- data/lib/generators/b4um/install/install_generator.rb +141 -2
- data/lib/generators/b4um/install/templates/b4um/forms.css +161 -1
- data/lib/generators/b4um/install/templates/b4um/navigation.css +13 -0
- data/lib/generators/b4um/install/templates/b4um/resources.css +18 -0
- data/lib/generators/b4um/install/templates/b4um.css +14 -0
- data/lib/generators/b4um/install/templates/navigation_helper.rb +13 -0
- data/lib/generators/b4um/readable/readable_generator.rb +77 -0
- data/lib/generators/b4um/search/search_generator.rb +58 -0
- metadata +7 -2
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/named_base"
|
|
5
|
+
|
|
6
|
+
module B4um
|
|
7
|
+
module Generators
|
|
8
|
+
class AttachmentGenerator < Rails::Generators::NamedBase
|
|
9
|
+
argument :attachment,
|
|
10
|
+
type: :string,
|
|
11
|
+
required: true,
|
|
12
|
+
banner: "ATTACHMENT"
|
|
13
|
+
|
|
14
|
+
class_option :multiple,
|
|
15
|
+
type: :boolean,
|
|
16
|
+
default: false,
|
|
17
|
+
desc: "Adds multiple attachments with has_many_attached."
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
def desc(_description = nil)
|
|
21
|
+
"Adds an attachment to an existing model."
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def validate_model
|
|
26
|
+
return if File.exist?(model_path)
|
|
27
|
+
|
|
28
|
+
raise Thor::Error,
|
|
29
|
+
"Model not found: app/models/#{file_name}.rb"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def add_attachment
|
|
33
|
+
model_content = File.read(model_path)
|
|
34
|
+
declaration = attachment_declaration
|
|
35
|
+
|
|
36
|
+
return if model_content.include?(declaration)
|
|
37
|
+
|
|
38
|
+
inject_into_class(
|
|
39
|
+
model_path,
|
|
40
|
+
class_name,
|
|
41
|
+
" #{declaration}\n"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
say_status(
|
|
45
|
+
:attachment,
|
|
46
|
+
"#{class_name} now has #{attachment}",
|
|
47
|
+
:green
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def update_form
|
|
52
|
+
return unless File.exist?(form_path)
|
|
53
|
+
|
|
54
|
+
content = File.read(form_path)
|
|
55
|
+
|
|
56
|
+
return if content.include?("form.file_field :#{attachment}")
|
|
57
|
+
|
|
58
|
+
marker = ' <div class="form-actions">'
|
|
59
|
+
|
|
60
|
+
return unless content.include?(marker)
|
|
61
|
+
|
|
62
|
+
insert_into_file(
|
|
63
|
+
form_path,
|
|
64
|
+
attachment_form,
|
|
65
|
+
before: marker
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def update_controller_params
|
|
70
|
+
return unless File.exist?(controller_path)
|
|
71
|
+
|
|
72
|
+
if options[:multiple]
|
|
73
|
+
update_multiple_attachment_params
|
|
74
|
+
else
|
|
75
|
+
update_single_attachment_params
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def update_resource
|
|
80
|
+
return unless File.exist?(resource_path)
|
|
81
|
+
|
|
82
|
+
content = File.read(resource_path)
|
|
83
|
+
|
|
84
|
+
return if content.include?(
|
|
85
|
+
%(<strong class="resource-label">#{attachment.humanize}:</strong>)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
closing_tag = "\n</div>\n"
|
|
89
|
+
|
|
90
|
+
return unless content.end_with?(closing_tag)
|
|
91
|
+
|
|
92
|
+
resource = attachment_resource
|
|
93
|
+
|
|
94
|
+
content = content.sub(
|
|
95
|
+
%r{</div>\n\z},
|
|
96
|
+
"#{resource}</div>\n"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
File.write(resource_path, content)
|
|
100
|
+
|
|
101
|
+
say_status(
|
|
102
|
+
:insert,
|
|
103
|
+
resource_path.sub("#{destination_root}/", ""),
|
|
104
|
+
:green
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def update_single_attachment_params
|
|
111
|
+
content = File.read(controller_path)
|
|
112
|
+
|
|
113
|
+
return if attachment_permitted?(content)
|
|
114
|
+
|
|
115
|
+
pattern = /
|
|
116
|
+
params\.expect\(
|
|
117
|
+
#{Regexp.escape(file_name)}:\s*
|
|
118
|
+
\[
|
|
119
|
+
(?<attributes>[^\]]*)
|
|
120
|
+
\]
|
|
121
|
+
\)
|
|
122
|
+
/mx
|
|
123
|
+
|
|
124
|
+
return unless content.match?(pattern)
|
|
125
|
+
|
|
126
|
+
gsub_file(controller_path, pattern) do |match|
|
|
127
|
+
match.sub(
|
|
128
|
+
/\]\)$/,
|
|
129
|
+
", :#{attachment}])"
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def update_multiple_attachment_params
|
|
135
|
+
content = File.read(controller_path)
|
|
136
|
+
|
|
137
|
+
update_multiple_permitted_params(content)
|
|
138
|
+
update_multiple_attachment_action
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def update_multiple_permitted_params(content)
|
|
142
|
+
return if multiple_attachment_permitted?(content)
|
|
143
|
+
|
|
144
|
+
pattern = /
|
|
145
|
+
params\.expect\(
|
|
146
|
+
#{Regexp.escape(file_name)}:\s*
|
|
147
|
+
\[
|
|
148
|
+
(?<attributes>[^\]]*)
|
|
149
|
+
\]
|
|
150
|
+
\)
|
|
151
|
+
/mx
|
|
152
|
+
|
|
153
|
+
return unless content.match?(pattern)
|
|
154
|
+
|
|
155
|
+
gsub_file(controller_path, pattern) do |match|
|
|
156
|
+
match.sub(
|
|
157
|
+
/\]\)$/,
|
|
158
|
+
", #{attachment}: [], remove_#{attachment}_ids: []])"
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def multiple_attachment_permitted?(content)
|
|
164
|
+
content.include?(
|
|
165
|
+
"#{attachment}: [], remove_#{attachment}_ids: []"
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def update_multiple_attachment_action
|
|
170
|
+
content = File.read(controller_path)
|
|
171
|
+
|
|
172
|
+
return if content.include?(
|
|
173
|
+
"new_#{attachment} = update_params.delete(\"#{attachment}\")"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
setup = [
|
|
177
|
+
" def update",
|
|
178
|
+
" update_params = #{file_name}_params",
|
|
179
|
+
" new_#{attachment} = update_params.delete(\"#{attachment}\")",
|
|
180
|
+
" remove_#{attachment}_ids = update_params.delete(\"remove_#{attachment}_ids\")",
|
|
181
|
+
"",
|
|
182
|
+
" respond_to do |format|",
|
|
183
|
+
""
|
|
184
|
+
].join("\n")
|
|
185
|
+
|
|
186
|
+
gsub_file(
|
|
187
|
+
controller_path,
|
|
188
|
+
" def update\n respond_to do |format|\n",
|
|
189
|
+
setup
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
attachment_handling = [
|
|
193
|
+
" if @#{file_name}.update(update_params)",
|
|
194
|
+
" @#{file_name}.#{attachment}.attach(new_#{attachment}) if new_#{attachment}.present?",
|
|
195
|
+
"",
|
|
196
|
+
" if remove_#{attachment}_ids.present?",
|
|
197
|
+
" @#{file_name}.#{attachment}.attachments",
|
|
198
|
+
" .where(id: remove_#{attachment}_ids)",
|
|
199
|
+
" .find_each(&:purge)",
|
|
200
|
+
" end",
|
|
201
|
+
""
|
|
202
|
+
].join("\n")
|
|
203
|
+
|
|
204
|
+
gsub_file(
|
|
205
|
+
controller_path,
|
|
206
|
+
" if @#{file_name}.update(#{file_name}_params)\n",
|
|
207
|
+
attachment_handling
|
|
208
|
+
)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def attachment_declaration
|
|
212
|
+
if options[:multiple]
|
|
213
|
+
"has_many_attached :#{attachment}"
|
|
214
|
+
else
|
|
215
|
+
"has_one_attached :#{attachment}"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def model_path
|
|
220
|
+
File.join(
|
|
221
|
+
destination_root,
|
|
222
|
+
"app/models",
|
|
223
|
+
"#{file_name}.rb"
|
|
224
|
+
)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def form_path
|
|
228
|
+
File.join(
|
|
229
|
+
destination_root,
|
|
230
|
+
"app/views",
|
|
231
|
+
plural_name,
|
|
232
|
+
"_form.html.erb"
|
|
233
|
+
)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def resource_path
|
|
237
|
+
File.join(
|
|
238
|
+
destination_root,
|
|
239
|
+
"app/views",
|
|
240
|
+
plural_name,
|
|
241
|
+
"_#{file_name}.html.erb"
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def attachment_resource
|
|
246
|
+
if options[:multiple]
|
|
247
|
+
multiple_attachment_resource
|
|
248
|
+
else
|
|
249
|
+
single_attachment_resource
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def single_attachment_resource
|
|
254
|
+
<<~ERB.indent(2)
|
|
255
|
+
<div class="resource-field">
|
|
256
|
+
<strong class="resource-label">#{attachment.humanize}:</strong>
|
|
257
|
+
|
|
258
|
+
<div class="resource-image"
|
|
259
|
+
data-controller="image-lightbox"
|
|
260
|
+
data-action="keydown->image-lightbox#keydown">
|
|
261
|
+
|
|
262
|
+
<% if #{file_name}.#{attachment}.attached? %>
|
|
263
|
+
<button type="button"
|
|
264
|
+
class="resource-image__button"
|
|
265
|
+
data-image-lightbox-target="item"
|
|
266
|
+
data-image-lightbox-url="<%= url_for(#{file_name}.#{attachment}) %>"
|
|
267
|
+
data-image-lightbox-alt="<%= #{file_name}.#{attachment}.filename.to_s %>"
|
|
268
|
+
data-action="click->image-lightbox#open">
|
|
269
|
+
|
|
270
|
+
<%= image_tag #{file_name}.#{attachment},
|
|
271
|
+
class: "resource-image__image",
|
|
272
|
+
alt: #{file_name}.#{attachment}.filename.to_s %>
|
|
273
|
+
</button>
|
|
274
|
+
|
|
275
|
+
<div class="image-lightbox"
|
|
276
|
+
data-image-lightbox-target="overlay"
|
|
277
|
+
data-action="click->image-lightbox#closeOnBackground"
|
|
278
|
+
tabindex="-1"
|
|
279
|
+
role="dialog"
|
|
280
|
+
aria-modal="true"
|
|
281
|
+
aria-label="Image preview"
|
|
282
|
+
hidden>
|
|
283
|
+
|
|
284
|
+
<div class="image-lightbox__counter"
|
|
285
|
+
data-image-lightbox-target="counter"></div>
|
|
286
|
+
|
|
287
|
+
<button type="button"
|
|
288
|
+
class="image-lightbox__close"
|
|
289
|
+
aria-label="Close image"
|
|
290
|
+
data-action="click->image-lightbox#close">
|
|
291
|
+
×
|
|
292
|
+
</button>
|
|
293
|
+
|
|
294
|
+
<div class="image-lightbox__stage"
|
|
295
|
+
data-image-lightbox-target="stage"
|
|
296
|
+
data-action="pointerdown->image-lightbox#pointerDown pointermove->image-lightbox#pointerMove pointerup->image-lightbox#pointerUp pointercancel->image-lightbox#pointerCancel">
|
|
297
|
+
<img class="image-lightbox__image"
|
|
298
|
+
data-image-lightbox-target="image"
|
|
299
|
+
alt="">
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<div class="image-lightbox__caption"
|
|
303
|
+
data-image-lightbox-target="caption"></div>
|
|
304
|
+
</div>
|
|
305
|
+
<% else %>
|
|
306
|
+
<span class="resource-value resource-value--empty">No image</span>
|
|
307
|
+
<% end %>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
ERB
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def multiple_attachment_resource
|
|
314
|
+
<<~ERB.indent(2)
|
|
315
|
+
<div class="resource-field">
|
|
316
|
+
<strong class="resource-label">#{attachment.humanize}:</strong>
|
|
317
|
+
|
|
318
|
+
<div class="resource-images"
|
|
319
|
+
data-controller="image-lightbox"
|
|
320
|
+
data-action="keydown->image-lightbox#keydown">
|
|
321
|
+
|
|
322
|
+
<% if #{file_name}.#{attachment}.attached? %>
|
|
323
|
+
<% #{file_name}.#{attachment}.each do |image| %>
|
|
324
|
+
<button type="button"
|
|
325
|
+
class="resource-images__button"
|
|
326
|
+
data-image-lightbox-target="item"
|
|
327
|
+
data-image-lightbox-url="<%= url_for(image) %>"
|
|
328
|
+
data-image-lightbox-alt="<%= image.filename.to_s %>"
|
|
329
|
+
data-action="click->image-lightbox#open">
|
|
330
|
+
|
|
331
|
+
<%= image_tag image,
|
|
332
|
+
class: "resource-images__image",
|
|
333
|
+
alt: image.filename.to_s %>
|
|
334
|
+
</button>
|
|
335
|
+
<% end %>
|
|
336
|
+
|
|
337
|
+
<div class="image-lightbox"
|
|
338
|
+
data-image-lightbox-target="overlay"
|
|
339
|
+
data-action="click->image-lightbox#closeOnBackground"
|
|
340
|
+
tabindex="-1"
|
|
341
|
+
role="dialog"
|
|
342
|
+
aria-modal="true"
|
|
343
|
+
aria-label="Image gallery"
|
|
344
|
+
hidden>
|
|
345
|
+
|
|
346
|
+
<div class="image-lightbox__counter"
|
|
347
|
+
data-image-lightbox-target="counter"></div>
|
|
348
|
+
|
|
349
|
+
<button type="button"
|
|
350
|
+
class="image-lightbox__close"
|
|
351
|
+
aria-label="Close image"
|
|
352
|
+
data-action="click->image-lightbox#close">
|
|
353
|
+
×
|
|
354
|
+
</button>
|
|
355
|
+
|
|
356
|
+
<button type="button"
|
|
357
|
+
class="image-lightbox__previous"
|
|
358
|
+
aria-label="Previous image"
|
|
359
|
+
data-action="click->image-lightbox#previous">
|
|
360
|
+
‹
|
|
361
|
+
</button>
|
|
362
|
+
|
|
363
|
+
<div class="image-lightbox__stage"
|
|
364
|
+
data-image-lightbox-target="stage"
|
|
365
|
+
data-action="pointerdown->image-lightbox#pointerDown pointermove->image-lightbox#pointerMove pointerup->image-lightbox#pointerUp pointercancel->image-lightbox#pointerCancel">
|
|
366
|
+
<img class="image-lightbox__image"
|
|
367
|
+
data-image-lightbox-target="image"
|
|
368
|
+
alt="">
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<button type="button"
|
|
372
|
+
class="image-lightbox__next"
|
|
373
|
+
aria-label="Next image"
|
|
374
|
+
data-action="click->image-lightbox#next">
|
|
375
|
+
›
|
|
376
|
+
</button>
|
|
377
|
+
|
|
378
|
+
<div class="image-lightbox__caption"
|
|
379
|
+
data-image-lightbox-target="caption"></div>
|
|
380
|
+
</div>
|
|
381
|
+
<% else %>
|
|
382
|
+
<span class="resource-value resource-value--empty">No images</span>
|
|
383
|
+
<% end %>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
ERB
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def controller_path
|
|
390
|
+
File.join(
|
|
391
|
+
destination_root,
|
|
392
|
+
"app/controllers",
|
|
393
|
+
"#{plural_name}_controller.rb"
|
|
394
|
+
)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def attachment_permitted?(content)
|
|
398
|
+
content.match?(
|
|
399
|
+
/params\.expect\(#{Regexp.escape(file_name)}:.*:#{Regexp.escape(attachment)}.*\)/m
|
|
400
|
+
)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def attachment_form
|
|
404
|
+
if options[:multiple]
|
|
405
|
+
multiple_attachment_form
|
|
406
|
+
else
|
|
407
|
+
single_attachment_form
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def single_attachment_form
|
|
412
|
+
<<~ERB.indent(2)
|
|
413
|
+
<div class="form-field form-field--file"
|
|
414
|
+
data-controller="image-preview">
|
|
415
|
+
|
|
416
|
+
<div class="form-file-control">
|
|
417
|
+
<%= form.label :#{attachment},
|
|
418
|
+
class: "form-file-label" %>
|
|
419
|
+
|
|
420
|
+
<%= form.file_field :#{attachment},
|
|
421
|
+
class: "form-file-input",
|
|
422
|
+
accept: "image/*",
|
|
423
|
+
data: {
|
|
424
|
+
image_preview_target: "input",
|
|
425
|
+
action: "change->image-preview#preview"
|
|
426
|
+
} %>
|
|
427
|
+
</div>
|
|
428
|
+
|
|
429
|
+
<div class="form-image-preview"
|
|
430
|
+
data-image-preview-target="container"
|
|
431
|
+
<%= "hidden" unless #{file_name}.#{attachment}.attached? %>>
|
|
432
|
+
|
|
433
|
+
<% if #{file_name}.#{attachment}.attached? %>
|
|
434
|
+
<%= image_tag #{file_name}.#{attachment},
|
|
435
|
+
class: "form-image-preview__image",
|
|
436
|
+
alt: #{file_name}.#{attachment}.filename.to_s,
|
|
437
|
+
data: {
|
|
438
|
+
image_preview_target: "image"
|
|
439
|
+
} %>
|
|
440
|
+
<% else %>
|
|
441
|
+
<img class="form-image-preview__image"
|
|
442
|
+
data-image-preview-target="image"
|
|
443
|
+
alt="Image preview">
|
|
444
|
+
<% end %>
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
|
|
448
|
+
ERB
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def multiple_attachment_form
|
|
452
|
+
<<~ERB.indent(2)
|
|
453
|
+
<div class="form-field form-field--file"
|
|
454
|
+
data-controller="image-preview">
|
|
455
|
+
|
|
456
|
+
<div class="form-file-control">
|
|
457
|
+
<%= form.label :#{attachment},
|
|
458
|
+
class: "form-file-label" %>
|
|
459
|
+
|
|
460
|
+
<% #{file_name}.#{attachment}.each do |image| %>
|
|
461
|
+
<%= form.hidden_field :#{attachment},
|
|
462
|
+
multiple: true,
|
|
463
|
+
value: image.signed_id %>
|
|
464
|
+
<% end %>
|
|
465
|
+
|
|
466
|
+
<%= form.file_field :#{attachment},
|
|
467
|
+
class: "form-file-input",
|
|
468
|
+
accept: "image/*",
|
|
469
|
+
multiple: true,
|
|
470
|
+
data: {
|
|
471
|
+
image_preview_target: "input",
|
|
472
|
+
action: "change->image-preview#previewMultiple"
|
|
473
|
+
} %>
|
|
474
|
+
</div>
|
|
475
|
+
|
|
476
|
+
<div class="form-image-preview form-image-preview--multiple"
|
|
477
|
+
data-image-preview-target="container"
|
|
478
|
+
<%= "hidden" unless #{file_name}.#{attachment}.attached? %>>
|
|
479
|
+
|
|
480
|
+
<% #{file_name}.#{attachment}.each do |image| %>
|
|
481
|
+
<div class="form-image-preview__item"
|
|
482
|
+
role="button"
|
|
483
|
+
tabindex="0"
|
|
484
|
+
aria-pressed="false"
|
|
485
|
+
data-image-preview-target="existingItem"
|
|
486
|
+
data-action="click->image-preview#toggleRemoval keydown->image-preview#toggleRemovalWithKeyboard">
|
|
487
|
+
|
|
488
|
+
<%= image_tag image,
|
|
489
|
+
class: "form-image-preview__image",
|
|
490
|
+
alt: image.filename.to_s %>
|
|
491
|
+
|
|
492
|
+
<%= check_box_tag(
|
|
493
|
+
"#{file_name}[remove_#{attachment}_ids][]",
|
|
494
|
+
image.id,
|
|
495
|
+
false,
|
|
496
|
+
class: "form-image-preview__remove-checkbox",
|
|
497
|
+
data: {
|
|
498
|
+
image_preview_target: "removeCheckbox"
|
|
499
|
+
}
|
|
500
|
+
) %>
|
|
501
|
+
|
|
502
|
+
<div class="form-image-preview__remove-status">
|
|
503
|
+
Will be removed
|
|
504
|
+
</div>
|
|
505
|
+
</div>
|
|
506
|
+
<% end %>
|
|
507
|
+
</div>
|
|
508
|
+
</div>
|
|
509
|
+
|
|
510
|
+
ERB
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
end
|