bullet_train-super_scaffolding 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/config/bullet_train_super_scaffolding_manifest.js +0 -0
- data/config/routes.rb +2 -0
- data/lib/bullet_train/super_scaffolding/engine.rb +6 -0
- data/lib/bullet_train/super_scaffolding/version.rb +5 -0
- data/lib/bullet_train/super_scaffolding.rb +8 -0
- data/lib/scaffolding/class_names_transformer.rb +190 -0
- data/lib/scaffolding/oauth_providers.rb +107 -0
- data/lib/scaffolding/routes_file_manipulator.rb +384 -0
- data/lib/scaffolding/script.rb +543 -0
- data/lib/scaffolding/transformer.rb +1365 -0
- data/lib/scaffolding.rb +8 -0
- data/lib/tasks/bullet_train/super_scaffolding_tasks.rake +4 -0
- metadata +74 -0
@@ -0,0 +1,1365 @@
|
|
1
|
+
require "indefinite_article"
|
2
|
+
require "yaml"
|
3
|
+
|
4
|
+
class Scaffolding::Transformer
|
5
|
+
attr_accessor :child, :parent, :parents, :class_names_transformer, :cli_options, :additional_steps, :namespace, :suppress_could_not_find
|
6
|
+
|
7
|
+
def initialize(child, parents, cli_options = {})
|
8
|
+
self.child = child
|
9
|
+
self.parent = parents.first
|
10
|
+
self.parents = parents
|
11
|
+
self.namespace = cli_options["namespace"] || "account"
|
12
|
+
self.class_names_transformer = Scaffolding::ClassNamesTransformer.new(child, parent, namespace)
|
13
|
+
self.cli_options = cli_options
|
14
|
+
self.additional_steps = []
|
15
|
+
end
|
16
|
+
|
17
|
+
RUBY_NEW_FIELDS_PROCESSING_HOOK = "# 🚅 super scaffolding will insert processing for new fields above this line."
|
18
|
+
RUBY_NEW_ARRAYS_HOOK = "# 🚅 super scaffolding will insert new arrays above this line."
|
19
|
+
RUBY_NEW_FIELDS_HOOK = "# 🚅 super scaffolding will insert new fields above this line."
|
20
|
+
RUBY_ADDITIONAL_NEW_FIELDS_HOOK = "# 🚅 super scaffolding will also insert new fields above this line."
|
21
|
+
RUBY_EVEN_MORE_NEW_FIELDS_HOOK = "# 🚅 super scaffolding will additionally insert new fields above this line."
|
22
|
+
ENDPOINTS_HOOK = "# 🚅 super scaffolding will mount new endpoints above this line."
|
23
|
+
ERB_NEW_FIELDS_HOOK = "<%#{RUBY_NEW_FIELDS_HOOK} %>"
|
24
|
+
CONCERNS_HOOK = "# 🚅 add concerns above."
|
25
|
+
BELONGS_TO_HOOK = "# 🚅 add belongs_to associations above."
|
26
|
+
HAS_MANY_HOOK = "# 🚅 add has_many associations above."
|
27
|
+
OAUTH_PROVIDERS_HOOK = "# 🚅 add oauth providers above."
|
28
|
+
HAS_ONE_HOOK = "# 🚅 add has_one associations above."
|
29
|
+
SCOPES_HOOK = "# 🚅 add scopes above."
|
30
|
+
VALIDATIONS_HOOK = "# 🚅 add validations above."
|
31
|
+
CALLBACKS_HOOK = "# 🚅 add callbacks above."
|
32
|
+
DELEGATIONS_HOOK = "# 🚅 add delegations above."
|
33
|
+
METHODS_HOOK = "# 🚅 add methods above."
|
34
|
+
|
35
|
+
def encode_double_replacement_fix(string)
|
36
|
+
string.chars.join("~!@BT@!~")
|
37
|
+
end
|
38
|
+
|
39
|
+
def decode_double_replacement_fix(string)
|
40
|
+
string.gsub("~!@BT@!~", "")
|
41
|
+
end
|
42
|
+
|
43
|
+
def transform_string(string)
|
44
|
+
[
|
45
|
+
|
46
|
+
# full class name plural.
|
47
|
+
"Scaffolding::AbsolutelyAbstract::CreativeConcepts",
|
48
|
+
"Scaffolding::CompletelyConcrete::TangibleThings",
|
49
|
+
"scaffolding/absolutely_abstract/creative_concepts",
|
50
|
+
"scaffolding/completely_concrete/tangible_things",
|
51
|
+
"scaffolding/completely_concrete/_tangible_things",
|
52
|
+
"scaffolding_absolutely_abstract_creative_concepts",
|
53
|
+
"scaffolding_completely_concrete_tangible_things",
|
54
|
+
"scaffolding-absolutely-abstract-creative-concepts",
|
55
|
+
"scaffolding-completely-concrete-tangible-things",
|
56
|
+
|
57
|
+
# full class name singular.
|
58
|
+
"Scaffolding::AbsolutelyAbstract::CreativeConcept",
|
59
|
+
"Scaffolding::CompletelyConcrete::TangibleThing",
|
60
|
+
"scaffolding/absolutely_abstract/creative_concept",
|
61
|
+
"scaffolding/completely_concrete/tangible_thing",
|
62
|
+
"scaffolding_absolutely_abstract_creative_concept",
|
63
|
+
"scaffolding_completely_concrete_tangible_thing",
|
64
|
+
"scaffolding-absolutely-abstract-creative-concept",
|
65
|
+
"scaffolding-completely-concrete-tangible-thing",
|
66
|
+
|
67
|
+
# class name in context plural.
|
68
|
+
"absolutely_abstract_creative_concepts",
|
69
|
+
"completely_concrete_tangible_things",
|
70
|
+
"absolutely_abstract/creative_concepts",
|
71
|
+
"completely_concrete/tangible_things",
|
72
|
+
"absolutely-abstract-creative-concepts",
|
73
|
+
"completely-concrete-tangible-things",
|
74
|
+
|
75
|
+
# class name in context singular.
|
76
|
+
"absolutely_abstract_creative_concept",
|
77
|
+
"completely_concrete_tangible_thing",
|
78
|
+
"absolutely_abstract/creative_concept",
|
79
|
+
"completely_concrete/tangible_thing",
|
80
|
+
"absolutely-abstract-creative-concept",
|
81
|
+
"completely-concrete-tangible-thing",
|
82
|
+
|
83
|
+
# just class name singular.
|
84
|
+
"creative_concepts",
|
85
|
+
"tangible_things",
|
86
|
+
"creative-concepts",
|
87
|
+
"tangible-things",
|
88
|
+
"Creative Concepts",
|
89
|
+
"Tangible Things",
|
90
|
+
|
91
|
+
# just class name plural.
|
92
|
+
"creative_concept",
|
93
|
+
"tangible_thing",
|
94
|
+
"creative-concept",
|
95
|
+
"tangible-thing",
|
96
|
+
"Creative Concept",
|
97
|
+
"Tangible Thing",
|
98
|
+
|
99
|
+
# Account namespace vs. others.
|
100
|
+
":account",
|
101
|
+
"/account/"
|
102
|
+
|
103
|
+
].each do |needle|
|
104
|
+
string = string.gsub(needle, encode_double_replacement_fix(class_names_transformer.replacement_for(needle)))
|
105
|
+
end
|
106
|
+
decode_double_replacement_fix(string)
|
107
|
+
end
|
108
|
+
|
109
|
+
def get_transformed_file_content(file)
|
110
|
+
transformed_file_content = []
|
111
|
+
|
112
|
+
skipping = false
|
113
|
+
gathering_lines_to_repeat = false
|
114
|
+
|
115
|
+
parents_to_repeat_for = []
|
116
|
+
gathered_lines_for_repeating = nil
|
117
|
+
|
118
|
+
File.open(file).each_line do |line|
|
119
|
+
if line.include?("# 🚅 skip when scaffolding.")
|
120
|
+
next
|
121
|
+
end
|
122
|
+
|
123
|
+
if line.include?("# 🚅 skip this section if resource is nested directly under team.")
|
124
|
+
skipping = true if parent == "Team"
|
125
|
+
next
|
126
|
+
end
|
127
|
+
|
128
|
+
if line.include?("# 🚅 skip this section when scaffolding.")
|
129
|
+
skipping = true
|
130
|
+
next
|
131
|
+
end
|
132
|
+
|
133
|
+
if line.include?("# 🚅 stop any skipping we're doing now.")
|
134
|
+
skipping = false
|
135
|
+
next
|
136
|
+
end
|
137
|
+
|
138
|
+
if line.include?("# 🚅 for each child resource from team down to the resource we're scaffolding, repeat the following:")
|
139
|
+
gathering_lines_to_repeat = true
|
140
|
+
parents_to_repeat_for = ([child] + parents.dup).reverse
|
141
|
+
gathered_lines_for_repeating = []
|
142
|
+
next
|
143
|
+
end
|
144
|
+
|
145
|
+
if line.include?("# 🚅 stop repeating.")
|
146
|
+
gathering_lines_to_repeat = false
|
147
|
+
|
148
|
+
while parents_to_repeat_for.count > 1
|
149
|
+
current_parent = parents_to_repeat_for[0]
|
150
|
+
current_child = parents_to_repeat_for[1]
|
151
|
+
current_transformer = self.class.new(current_child, current_parent)
|
152
|
+
transformed_file_content << current_transformer.transform_string(gathered_lines_for_repeating.join)
|
153
|
+
parents_to_repeat_for.shift
|
154
|
+
end
|
155
|
+
|
156
|
+
next
|
157
|
+
end
|
158
|
+
|
159
|
+
if gathering_lines_to_repeat
|
160
|
+
gathered_lines_for_repeating << line
|
161
|
+
next
|
162
|
+
end
|
163
|
+
|
164
|
+
if skipping
|
165
|
+
next
|
166
|
+
end
|
167
|
+
|
168
|
+
# remove lines with 'remove in scaffolded files.'
|
169
|
+
unless line.include?("remove in scaffolded files.")
|
170
|
+
|
171
|
+
# only transform it if it doesn't have the lock emoji.
|
172
|
+
if line.include?("🔒")
|
173
|
+
# remove any comments that start with a lock.
|
174
|
+
line.gsub!(/\s+?#\s+🔒.*/, "")
|
175
|
+
else
|
176
|
+
line = transform_string(line)
|
177
|
+
end
|
178
|
+
|
179
|
+
transformed_file_content << line
|
180
|
+
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
transformed_file_content.join
|
185
|
+
end
|
186
|
+
|
187
|
+
def scaffold_file(file)
|
188
|
+
transformed_file_content = get_transformed_file_content(file)
|
189
|
+
transformed_file_name = transform_string(file)
|
190
|
+
|
191
|
+
transformed_directory_name = File.dirname(transformed_file_name)
|
192
|
+
unless File.directory?(transformed_directory_name)
|
193
|
+
FileUtils.mkdir_p(transformed_directory_name)
|
194
|
+
end
|
195
|
+
|
196
|
+
puts "Writing '#{transformed_file_name}'."
|
197
|
+
|
198
|
+
File.write(transformed_file_name, transformed_file_content.strip + "\n")
|
199
|
+
|
200
|
+
if transformed_file_name.split(".").last == "rb"
|
201
|
+
puts "Fixing Standard Ruby on '#{transformed_file_name}'."
|
202
|
+
# `standardrb --fix #{transformed_file_name} 2> /dev/null`
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def scaffold_directory(directory)
|
207
|
+
transformed_directory_name = transform_string(directory)
|
208
|
+
begin
|
209
|
+
Dir.mkdir(transformed_directory_name)
|
210
|
+
rescue Errno::EEXIST => _
|
211
|
+
puts "The directory #{transformed_directory_name} already exists, skipping generation.".yellow
|
212
|
+
rescue Errno::ENOENT => _
|
213
|
+
puts "Proceeding to generate '#{transformed_directory_name}'."
|
214
|
+
end
|
215
|
+
|
216
|
+
Dir.foreach(directory) do |file|
|
217
|
+
file = "#{directory}/#{file}"
|
218
|
+
unless File.directory?(file)
|
219
|
+
scaffold_file(file)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# pass in an array where this content should be inserted within the yml file. For example, to add content
|
225
|
+
# to admin.models pass in [:admin, :models]
|
226
|
+
def add_line_to_yml_file(file, content, location_array)
|
227
|
+
# First check that the given location array actually exists in the yml file:
|
228
|
+
yml = YAML.safe_load(File.read(file))
|
229
|
+
location_array.map!(&:to_s)
|
230
|
+
return nil if yml.dig(*location_array).nil? # Should we raise an error?
|
231
|
+
content += "\n" unless content[-1] == "\n"
|
232
|
+
# Find the location in the file where the location_array is
|
233
|
+
lines = File.readlines(file)
|
234
|
+
current_needle = location_array.shift.to_s
|
235
|
+
current_space = ""
|
236
|
+
insert_after = 1
|
237
|
+
lines.each_with_index do |line, index|
|
238
|
+
break if current_needle.nil?
|
239
|
+
if line.strip == current_needle + ":"
|
240
|
+
current_needle = location_array.shift.to_s
|
241
|
+
insert_after = index
|
242
|
+
current_space = line.match(/\s+/).to_s
|
243
|
+
end
|
244
|
+
end
|
245
|
+
new_lines = []
|
246
|
+
current_space += " "
|
247
|
+
lines.each_with_index do |line, index|
|
248
|
+
new_lines << line
|
249
|
+
new_lines << current_space + content if index == insert_after
|
250
|
+
end
|
251
|
+
File.write(file, new_lines.join)
|
252
|
+
end
|
253
|
+
|
254
|
+
def add_line_to_file(file, content, hook, options = {})
|
255
|
+
increase_indent = options[:increase_indent]
|
256
|
+
add_before = options[:add_before]
|
257
|
+
add_after = options[:add_after]
|
258
|
+
|
259
|
+
transformed_file_name = file
|
260
|
+
transformed_content = content
|
261
|
+
transform_hook = hook
|
262
|
+
|
263
|
+
begin
|
264
|
+
target_file_content = File.read(transformed_file_name)
|
265
|
+
rescue Errno::ENOENT => _
|
266
|
+
puts "Couldn't find '#{transformed_file_name}'".red unless suppress_could_not_find
|
267
|
+
return false
|
268
|
+
end
|
269
|
+
|
270
|
+
if target_file_content.include?(transformed_content)
|
271
|
+
puts "No need to update '#{transformed_file_name}'. It already has '#{transformed_content}'."
|
272
|
+
|
273
|
+
else
|
274
|
+
|
275
|
+
new_target_file_content = []
|
276
|
+
|
277
|
+
target_file_content.split("\n").each do |line|
|
278
|
+
if options[:exact_match] ? line == transform_hook : line.match(/#{Regexp.escape(transform_hook)}\s*$/)
|
279
|
+
|
280
|
+
if add_before
|
281
|
+
new_target_file_content << "#{line} #{add_before}"
|
282
|
+
else
|
283
|
+
unless options[:prepend]
|
284
|
+
new_target_file_content << line
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
line =~ /^(\s*).*#{Regexp.escape(transform_hook)}.*/
|
289
|
+
leading_whitespace = $1
|
290
|
+
|
291
|
+
incoming_leading_whitespace = nil
|
292
|
+
transformed_content.lines.each do |content_line|
|
293
|
+
content_line.rstrip
|
294
|
+
content_line =~ /^(\s*).*/
|
295
|
+
# this ignores empty lines.
|
296
|
+
# it accepts any amount of whitespace if we haven't seen any whitespace yet.
|
297
|
+
if content_line.present? && $1 && (incoming_leading_whitespace.nil? || $1.length < incoming_leading_whitespace.length)
|
298
|
+
incoming_leading_whitespace = $1
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
incoming_leading_whitespace ||= ""
|
303
|
+
|
304
|
+
transformed_content.lines.each do |content_line|
|
305
|
+
new_target_file_content << "#{leading_whitespace}#{" " if increase_indent}#{content_line.gsub(/^#{incoming_leading_whitespace}/, "").rstrip}".presence
|
306
|
+
end
|
307
|
+
|
308
|
+
new_target_file_content << "#{leading_whitespace}#{add_after}" if add_after
|
309
|
+
|
310
|
+
if options[:prepend]
|
311
|
+
new_target_file_content << line
|
312
|
+
end
|
313
|
+
|
314
|
+
else
|
315
|
+
|
316
|
+
new_target_file_content << line
|
317
|
+
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
puts "Updating '#{transformed_file_name}'."
|
322
|
+
|
323
|
+
File.write(transformed_file_name, new_target_file_content.join("\n").strip + "\n")
|
324
|
+
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def scaffold_add_line_to_file(file, content, hook, options = {})
|
329
|
+
file = transform_string(file)
|
330
|
+
content = transform_string(content)
|
331
|
+
hook = transform_string(hook)
|
332
|
+
add_line_to_file(file, content, hook, options)
|
333
|
+
end
|
334
|
+
|
335
|
+
def replace_line_in_file(file, content, in_place_of)
|
336
|
+
target_file_content = File.read(file)
|
337
|
+
|
338
|
+
if target_file_content.include?(content)
|
339
|
+
puts "No need to update '#{file}'. It already has '#{content}'."
|
340
|
+
else
|
341
|
+
puts "Updating '#{file}'."
|
342
|
+
target_file_content.gsub!(in_place_of, content)
|
343
|
+
File.write(file, target_file_content)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def scaffold_replace_line_in_file(file, content, in_place_of)
|
348
|
+
file = transform_string(file)
|
349
|
+
# we specifically don't transform the content, we assume a builder function created this content.
|
350
|
+
in_place_of = transform_string(in_place_of)
|
351
|
+
replace_line_in_file(file, content, in_place_of)
|
352
|
+
end
|
353
|
+
|
354
|
+
# if class_name isn't specified, we use `child`.
|
355
|
+
# if class_name is specified, then `child` is assumed to be a parent of `class_name`.
|
356
|
+
# returns an array with the ability line and a boolean indicating whether the ability line should be inserted among
|
357
|
+
# the abilities for admins only. (this happens when building an ability line for a resources that doesn't ultimately
|
358
|
+
# belong to a Team or a User.)
|
359
|
+
def build_ability_line(class_names = nil)
|
360
|
+
# e.g. ['Conversations::Message', 'Conversation']
|
361
|
+
if class_names
|
362
|
+
# e.g. 'Conversations::Message'
|
363
|
+
class_name = class_names.shift
|
364
|
+
# e.g. ['Conversation', 'Deliverable', 'Phase', 'Project', 'Team']
|
365
|
+
working_parents = class_names + [child] + parents
|
366
|
+
else
|
367
|
+
# e.g. 'Deliverable'
|
368
|
+
class_name = child
|
369
|
+
# e.g. ['Phase', 'Project', 'Team']
|
370
|
+
working_parents = parents.dup
|
371
|
+
end
|
372
|
+
|
373
|
+
case working_parents.last
|
374
|
+
when "User"
|
375
|
+
working_parents.pop
|
376
|
+
ability_line = "user_id: user.id"
|
377
|
+
when "Team"
|
378
|
+
working_parents.pop
|
379
|
+
ability_line = "team_id: user.team_ids"
|
380
|
+
else
|
381
|
+
# if a resources is specified that isn't ultimately owned by a team or a user, then only admins can manage it.
|
382
|
+
return ["can :manage, #{class_name}", true]
|
383
|
+
end
|
384
|
+
|
385
|
+
# e.g. ['Phase', 'Project']
|
386
|
+
while working_parents.any?
|
387
|
+
current_parent = working_parents.pop
|
388
|
+
current_transformer = Scaffolding::ClassNamesTransformer.new(working_parents.last || class_name, current_parent, namespace)
|
389
|
+
ability_line = "#{current_transformer.parent_variable_name_in_context}: {#{ability_line}}"
|
390
|
+
end
|
391
|
+
|
392
|
+
# e.g. "can :manage, Deliverable, phase: {project: {team_id: user.team_ids}}"
|
393
|
+
["can :manage, #{class_name}, #{ability_line}", false]
|
394
|
+
end
|
395
|
+
|
396
|
+
def build_conversation_ability_line
|
397
|
+
build_ability_line(["Conversations::Message", "Conversation"])
|
398
|
+
end
|
399
|
+
|
400
|
+
def add_scaffolding_hooks_to_model
|
401
|
+
before_scaffolding_hooks = <<~RUBY
|
402
|
+
#{CONCERNS_HOOK}
|
403
|
+
|
404
|
+
RUBY
|
405
|
+
|
406
|
+
after_scaffolding_hooks = <<-RUBY
|
407
|
+
#{BELONGS_TO_HOOK}
|
408
|
+
|
409
|
+
#{HAS_MANY_HOOK}
|
410
|
+
|
411
|
+
#{HAS_ONE_HOOK}
|
412
|
+
|
413
|
+
#{SCOPES_HOOK}
|
414
|
+
|
415
|
+
#{VALIDATIONS_HOOK}
|
416
|
+
|
417
|
+
#{CALLBACKS_HOOK}
|
418
|
+
|
419
|
+
#{DELEGATIONS_HOOK}
|
420
|
+
|
421
|
+
#{METHODS_HOOK}
|
422
|
+
RUBY
|
423
|
+
|
424
|
+
# add scaffolding hooks to the model.
|
425
|
+
unless File.readlines(transform_string("./app/models/scaffolding/completely_concrete/tangible_thing.rb")).join.include?(CONCERNS_HOOK)
|
426
|
+
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", before_scaffolding_hooks, "ApplicationRecord", increase_indent: true)
|
427
|
+
end
|
428
|
+
|
429
|
+
unless File.readlines(transform_string("./app/models/scaffolding/completely_concrete/tangible_thing.rb")).join.include?(BELONGS_TO_HOOK)
|
430
|
+
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", after_scaffolding_hooks, "end", prepend: true, increase_indent: true, exact_match: true)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
def add_ability_line_to_roles_yml(class_names = nil)
|
435
|
+
model_names = class_names || [child]
|
436
|
+
role_file = "./config/models/roles.yml"
|
437
|
+
model_names.each do |model_name|
|
438
|
+
add_line_to_yml_file(role_file, "#{model_name}: read", [:default, :models])
|
439
|
+
add_line_to_yml_file(role_file, "#{model_name}: manage", [:admin, :models])
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
def build_factory_setup
|
444
|
+
class_name = child
|
445
|
+
working_parents = parents.dup
|
446
|
+
current_parent = working_parents.pop
|
447
|
+
current_transformer = Scaffolding::Transformer.new(working_parents.last || class_name, [current_parent])
|
448
|
+
|
449
|
+
setup_lines = []
|
450
|
+
|
451
|
+
unless current_parent == "Team" || current_parent == "User"
|
452
|
+
setup_lines << current_transformer.transform_string("@absolutely_abstract_creative_concept = create(:scaffolding_absolutely_abstract_creative_concept)")
|
453
|
+
end
|
454
|
+
|
455
|
+
previous_assignment = current_transformer.transform_string("absolutely_abstract_creative_concept: @absolutely_abstract_creative_concept")
|
456
|
+
|
457
|
+
current_parent = working_parents.pop
|
458
|
+
|
459
|
+
while current_parent
|
460
|
+
current_transformer = Scaffolding::Transformer.new(working_parents.last || class_name, [current_parent])
|
461
|
+
setup_lines << current_transformer.transform_string("@absolutely_abstract_creative_concept = create(:scaffolding_absolutely_abstract_creative_concept, #{previous_assignment})")
|
462
|
+
previous_assignment = current_transformer.transform_string("absolutely_abstract_creative_concept: @absolutely_abstract_creative_concept")
|
463
|
+
|
464
|
+
current_parent = working_parents.pop
|
465
|
+
end
|
466
|
+
|
467
|
+
setup_lines << current_transformer.transform_string("@tangible_thing = create(:scaffolding_completely_concrete_tangible_thing, #{previous_assignment})")
|
468
|
+
|
469
|
+
setup_lines
|
470
|
+
end
|
471
|
+
|
472
|
+
def replace_in_file(file, before, after, target_regexp = nil)
|
473
|
+
puts "Replacing in '#{file}'."
|
474
|
+
if target_regexp.present?
|
475
|
+
target_file_content = ""
|
476
|
+
File.open(file).each_line do |l|
|
477
|
+
l.gsub!(before, after) if !!l.match(target_regexp)
|
478
|
+
target_file_content += l
|
479
|
+
end
|
480
|
+
else
|
481
|
+
target_file_content = File.read(file)
|
482
|
+
target_file_content.gsub!(before, after)
|
483
|
+
end
|
484
|
+
File.write(file, target_file_content)
|
485
|
+
end
|
486
|
+
|
487
|
+
def restart_server
|
488
|
+
# restart the server.
|
489
|
+
puts "Restarting the server so it picks up the new localization .yml file."
|
490
|
+
`./bin/rails restart`
|
491
|
+
end
|
492
|
+
|
493
|
+
def add_locale_helper_export_fix
|
494
|
+
namespaced_locale_export_hook = "# 🚅 super scaffolding will insert the export for the locale view helper here."
|
495
|
+
|
496
|
+
spacer = " "
|
497
|
+
indentation = spacer * 3
|
498
|
+
namespace_elements = child.underscore.pluralize.split("/")
|
499
|
+
last_element = namespace_elements.shift
|
500
|
+
lines_to_add = [last_element + ":"]
|
501
|
+
namespace_elements.map do |namespace_element|
|
502
|
+
lines_to_add << indentation + namespace_element + ":"
|
503
|
+
last_element = namespace_element
|
504
|
+
indentation += spacer
|
505
|
+
end
|
506
|
+
lines_to_add << lines_to_add.pop + " *#{last_element}"
|
507
|
+
|
508
|
+
scaffold_replace_line_in_file("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml", lines_to_add.join("\n"), namespaced_locale_export_hook)
|
509
|
+
end
|
510
|
+
|
511
|
+
def scaffold_new_breadcrumbs(child, parents)
|
512
|
+
scaffold_file("./app/views/account/scaffolding/completely_concrete/tangible_things/_breadcrumbs.html.erb")
|
513
|
+
puts
|
514
|
+
puts "Heads up! We're only able to generate the new breadcrumb views, so you'll have to edit `#{transform_string("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml")}` and add the label. You can look at `./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml` for an example of how to do this, but here's an example of what it should look like:".yellow
|
515
|
+
puts
|
516
|
+
puts transform_string("en:\n scaffolding/completely_concrete/tangible_things: &tangible_things\n label: &label Things\n breadcrumbs:\n label: *label").yellow
|
517
|
+
puts
|
518
|
+
end
|
519
|
+
|
520
|
+
def add_has_many_association
|
521
|
+
has_many_line = ["has_many :completely_concrete_tangible_things"]
|
522
|
+
|
523
|
+
# TODO I _think_ this is the right way to check for whether we need `class_name` to specify the name of the model.
|
524
|
+
unless transform_string("completely_concrete_tangible_things").classify == child
|
525
|
+
has_many_line << "class_name: \"Scaffolding::CompletelyConcrete::TangibleThing\""
|
526
|
+
end
|
527
|
+
|
528
|
+
has_many_line << "dependent: :destroy"
|
529
|
+
|
530
|
+
# TODO I _think_ this is the right way to check for whether we need `foreign_key` to specify the name of the model.
|
531
|
+
unless transform_string("absolutely_abstract_creative_concept_id") == "#{parent.underscore}_id"
|
532
|
+
has_many_line << "foreign_key: :absolutely_abstract_creative_concept_id"
|
533
|
+
|
534
|
+
# And if we need `foreign_key`, we should also specify `inverse_of`.
|
535
|
+
has_many_line << "inverse_of: :absolutely_abstract_creative_concept"
|
536
|
+
end
|
537
|
+
|
538
|
+
has_many_string = transform_string(has_many_line.join(", "))
|
539
|
+
add_line_to_file(transform_string("./app/models/scaffolding/absolutely_abstract/creative_concept.rb"), has_many_string, HAS_MANY_HOOK, prepend: true)
|
540
|
+
|
541
|
+
# Return the name of the has_many association.
|
542
|
+
has_many_string.split(",").first.split(":").last
|
543
|
+
end
|
544
|
+
|
545
|
+
def add_has_many_through_associations(has_many_through_transformer)
|
546
|
+
has_many_association = add_has_many_association
|
547
|
+
has_many_through_string = has_many_through_transformer.transform_string("has_many :completely_concrete_tangible_things, through: :$HAS_MANY_ASSOCIATION")
|
548
|
+
has_many_through_string.gsub!("$HAS_MANY_ASSOCIATION", has_many_association)
|
549
|
+
add_line_to_file(transform_string("./app/models/scaffolding/absolutely_abstract/creative_concept.rb"), has_many_through_string, HAS_MANY_HOOK, prepend: true)
|
550
|
+
end
|
551
|
+
|
552
|
+
def add_attributes_to_various_views(attributes, scaffolding_options = {})
|
553
|
+
sql_type_to_field_type_mapping = {
|
554
|
+
# 'binary' => '',
|
555
|
+
"boolean" => "buttons",
|
556
|
+
"date" => "date_field",
|
557
|
+
"datetime" => "date_and_time_field",
|
558
|
+
"decimal" => "text_field",
|
559
|
+
"float" => "text_field",
|
560
|
+
"integer" => "text_field",
|
561
|
+
"bigint" => "text_field",
|
562
|
+
# 'primary_key' => '',
|
563
|
+
# 'references' => '',
|
564
|
+
"string" => "text_field",
|
565
|
+
"text" => "text_area"
|
566
|
+
# 'time' => '',
|
567
|
+
# 'timestamp' => '',
|
568
|
+
}
|
569
|
+
|
570
|
+
# add attributes to various views.
|
571
|
+
attributes.each_with_index do |attribute, index|
|
572
|
+
first_table_cell = index == 0 && scaffolding_options[:type] == :crud
|
573
|
+
|
574
|
+
parts = attribute.split(":")
|
575
|
+
name = parts.shift
|
576
|
+
type = parts.join(":")
|
577
|
+
boolean_buttons = type == "boolean"
|
578
|
+
|
579
|
+
# extract any options they passed in with the field.
|
580
|
+
# will extract options declared with either [] or {}.
|
581
|
+
type, attribute_options = type.scan(/^(.*)[\[|{](.*)[\]|}]/).first || type
|
582
|
+
|
583
|
+
# create a hash of the options.
|
584
|
+
attribute_options = if attribute_options
|
585
|
+
attribute_options.split(",").map { |s|
|
586
|
+
option_name, option_value = s.split("=")
|
587
|
+
[option_name.to_sym, option_value || true]
|
588
|
+
}.to_h
|
589
|
+
else
|
590
|
+
{}
|
591
|
+
end
|
592
|
+
|
593
|
+
attribute_options[:label] ||= "label_string"
|
594
|
+
|
595
|
+
if sql_type_to_field_type_mapping[type]
|
596
|
+
type = sql_type_to_field_type_mapping[type]
|
597
|
+
end
|
598
|
+
|
599
|
+
is_id = name.match?(/_id$/)
|
600
|
+
is_ids = name.match?(/_ids$/)
|
601
|
+
# if this is the first attribute of a newly scaffolded model, that field is required.
|
602
|
+
is_required = attribute_options[:required] || (scaffolding_options[:type] == :crud && index == 0)
|
603
|
+
is_vanilla = attribute_options&.key?(:vanilla)
|
604
|
+
is_belongs_to = is_id && !is_vanilla
|
605
|
+
is_has_many = is_ids && !is_vanilla
|
606
|
+
is_multiple = attribute_options&.key?(:multiple) || is_has_many
|
607
|
+
is_association = is_belongs_to || is_has_many
|
608
|
+
|
609
|
+
# Sometimes we need all the magic of a `*_id` field, but without the scoping stuff.
|
610
|
+
# Possibly only ever used internally by `join-model`.
|
611
|
+
is_unscoped = attribute_options[:unscoped]
|
612
|
+
|
613
|
+
name_without_id = name.gsub(/_id$/, "")
|
614
|
+
name_without_ids = name.gsub(/_ids$/, "").pluralize
|
615
|
+
collection_name = is_ids ? name_without_ids : name_without_id.pluralize
|
616
|
+
|
617
|
+
# field on the show view.
|
618
|
+
attribute_partial ||= attribute_options[:attribute] || case type
|
619
|
+
when "trix_editor", "ckeditor"
|
620
|
+
"html"
|
621
|
+
when "buttons", "super_select", "options"
|
622
|
+
if boolean_buttons
|
623
|
+
"boolean"
|
624
|
+
elsif is_ids
|
625
|
+
"has_many"
|
626
|
+
elsif is_id
|
627
|
+
"belongs_to"
|
628
|
+
else
|
629
|
+
"option"
|
630
|
+
end
|
631
|
+
when "cloudinary_image"
|
632
|
+
attribute_options[:height] = 200
|
633
|
+
"image"
|
634
|
+
when "phone_field"
|
635
|
+
"phone_number"
|
636
|
+
when "date_field"
|
637
|
+
"date"
|
638
|
+
when "date_and_time_field"
|
639
|
+
"date_and_time"
|
640
|
+
when "email_field"
|
641
|
+
"email"
|
642
|
+
when "color_picker"
|
643
|
+
"code"
|
644
|
+
else
|
645
|
+
"text"
|
646
|
+
end
|
647
|
+
|
648
|
+
cell_attributes = if boolean_buttons
|
649
|
+
' class="text-center"'
|
650
|
+
end
|
651
|
+
|
652
|
+
# e.g. from `person_id` to `person` or `person_ids` to `people`.
|
653
|
+
attribute_name = if is_ids
|
654
|
+
name_without_ids
|
655
|
+
elsif is_id
|
656
|
+
name_without_id
|
657
|
+
else
|
658
|
+
name
|
659
|
+
end
|
660
|
+
|
661
|
+
title_case = if is_ids
|
662
|
+
# user_ids should be 'Users'
|
663
|
+
name_without_ids.humanize.titlecase
|
664
|
+
elsif is_id
|
665
|
+
name_without_id.humanize.titlecase
|
666
|
+
else
|
667
|
+
name.humanize.titlecase
|
668
|
+
end
|
669
|
+
|
670
|
+
attribute_assignment = case type
|
671
|
+
when "text_field", "password_field", "text_area"
|
672
|
+
"'Alternative String Value'"
|
673
|
+
when "email_field"
|
674
|
+
"'another.email@test.com'"
|
675
|
+
when "phone_field"
|
676
|
+
"'+19053871234'"
|
677
|
+
end
|
678
|
+
|
679
|
+
# don't do table columns for certain types of fields and attribute partials
|
680
|
+
if ["trix_editor", "ckeditor", "text_area"].include?(type) || ["html", "has_many"].include?(attribute_partial)
|
681
|
+
cli_options["skip-table"] = true
|
682
|
+
end
|
683
|
+
|
684
|
+
if type == "none"
|
685
|
+
cli_options["skip-form"] = true
|
686
|
+
end
|
687
|
+
|
688
|
+
if attribute_partial == "none"
|
689
|
+
cli_options["skip-show"] = true
|
690
|
+
cli_options["skip-table"] = true
|
691
|
+
end
|
692
|
+
|
693
|
+
#
|
694
|
+
# MODEL VALIDATIONS
|
695
|
+
#
|
696
|
+
|
697
|
+
unless cli_options["skip-form"] || is_unscoped
|
698
|
+
|
699
|
+
file_name = "./app/models/scaffolding/completely_concrete/tangible_thing.rb"
|
700
|
+
|
701
|
+
if is_association
|
702
|
+
field_content = if attribute_options[:source]
|
703
|
+
<<~RUBY
|
704
|
+
def valid_#{collection_name}
|
705
|
+
#{attribute_options[:source]}
|
706
|
+
end
|
707
|
+
|
708
|
+
RUBY
|
709
|
+
else
|
710
|
+
add_additional_step :yellow, transform_string("You'll need to implement the `valid_#{collection_name}` method of `Scaffolding::CompletelyConcrete::TangibleThing` in `./app/models/scaffolding/completely_concrete/tangible_thing.rb`. This is the method that will be used to populate the `#{type}` field and also validate that users aren't trying to exploit multitenancy.")
|
711
|
+
|
712
|
+
<<~RUBY
|
713
|
+
def valid_#{collection_name}
|
714
|
+
raise "please review and implement `valid_#{collection_name}` in `app/models/scaffolding/completely_concrete/tangible_thing.rb`."
|
715
|
+
# please specify what objects should be considered valid for assigning to `#{name_without_id}`.
|
716
|
+
# the resulting code should probably look something like `team.#{collection_name}`.
|
717
|
+
end
|
718
|
+
|
719
|
+
RUBY
|
720
|
+
end
|
721
|
+
|
722
|
+
scaffold_add_line_to_file(file_name, field_content, METHODS_HOOK, prepend: true)
|
723
|
+
|
724
|
+
if is_belongs_to
|
725
|
+
scaffold_add_line_to_file(file_name, "validates :#{name_without_id}, scope: true", VALIDATIONS_HOOK, prepend: true)
|
726
|
+
end
|
727
|
+
|
728
|
+
# TODO we need to add a multitenancy check for has many associations.
|
729
|
+
end
|
730
|
+
|
731
|
+
end
|
732
|
+
|
733
|
+
#
|
734
|
+
# FORM FIELD
|
735
|
+
#
|
736
|
+
|
737
|
+
unless cli_options["skip-form"]
|
738
|
+
|
739
|
+
# add `has_rich_text` for trix editor fields.
|
740
|
+
if type == "trix_editor"
|
741
|
+
file_name = "./app/models/scaffolding/completely_concrete/tangible_thing.rb"
|
742
|
+
scaffold_add_line_to_file(file_name, "has_rich_text :#{name}", HAS_ONE_HOOK, prepend: true)
|
743
|
+
end
|
744
|
+
|
745
|
+
# field on the form.
|
746
|
+
file_name = "./app/views/account/scaffolding/completely_concrete/tangible_things/_form.html.erb"
|
747
|
+
field_attributes = {method: ":#{name}"}
|
748
|
+
field_options = {}
|
749
|
+
|
750
|
+
if scaffolding_options[:type] == :crud && index == 0
|
751
|
+
field_options[:autofocus] = "true"
|
752
|
+
end
|
753
|
+
|
754
|
+
if is_id && type == "super_select"
|
755
|
+
field_options[:include_blank] = "t('.fields.#{name}.placeholder')"
|
756
|
+
# add_additional_step :yellow, transform_string("We've added a reference to a `placeholder` to the form for the select or super_select field, but unfortunately earlier versions of the scaffolded locales Yaml don't include a reference to `fields: *fields` under `form`. Please add it, otherwise your form won't be able to locate the appropriate placeholder label.")
|
757
|
+
end
|
758
|
+
|
759
|
+
if is_multiple
|
760
|
+
field_options[:multiple] = "true"
|
761
|
+
end
|
762
|
+
|
763
|
+
valid_values = if is_id
|
764
|
+
"valid_#{name_without_id.pluralize}"
|
765
|
+
elsif is_ids
|
766
|
+
"valid_#{collection_name}"
|
767
|
+
end
|
768
|
+
|
769
|
+
# https://stackoverflow.com/questions/21582464/is-there-a-ruby-hashto-s-equivalent-for-the-new-hash-syntax
|
770
|
+
if field_options.any?
|
771
|
+
field_options_key = if ["buttons", "super_select", "options"].include?(type)
|
772
|
+
:html_options
|
773
|
+
else
|
774
|
+
:options
|
775
|
+
end
|
776
|
+
field_attributes[field_options_key] = "{" + field_options.map { |key, value| "#{key}: #{value}" }.join(", ") + "}"
|
777
|
+
end
|
778
|
+
|
779
|
+
if is_association
|
780
|
+
short = attribute_options[:class_name].underscore.split("/").last
|
781
|
+
case type
|
782
|
+
when "buttons", "options"
|
783
|
+
field_attributes["\n options"] = "@tangible_thing.#{valid_values}.map { |#{short}| [#{short}.id, #{short}.#{attribute_options[:label]}] }"
|
784
|
+
when "super_select"
|
785
|
+
field_attributes["\n choices"] = "@tangible_thing.#{valid_values}.map { |#{short}| [#{short}.#{attribute_options[:label]}, #{short}.id] }"
|
786
|
+
end
|
787
|
+
end
|
788
|
+
|
789
|
+
field_content = "<%= render 'shared/fields/#{type}'#{", " if field_attributes.any?}#{field_attributes.map { |key, value| "#{key}: #{value}" }.join(", ")} %>"
|
790
|
+
scaffold_add_line_to_file(file_name, field_content, ERB_NEW_FIELDS_HOOK, prepend: true)
|
791
|
+
end
|
792
|
+
|
793
|
+
#
|
794
|
+
# SHOW VIEW
|
795
|
+
#
|
796
|
+
|
797
|
+
unless cli_options["skip-show"]
|
798
|
+
|
799
|
+
if is_id
|
800
|
+
<<~ERB
|
801
|
+
<% if @tangible_thing.#{name_without_id} %>
|
802
|
+
<div class="form-group">
|
803
|
+
<label class="col-form-label"><%= t('.fields.#{name}.heading') %></label>
|
804
|
+
<div>
|
805
|
+
<%= link_to @tangible_thing.#{name_without_id}.#{attribute_options[:label]}, [:account, @tangible_thing.#{name_without_id}] %>
|
806
|
+
</div>
|
807
|
+
</div>
|
808
|
+
<% end %>
|
809
|
+
ERB
|
810
|
+
elsif is_ids
|
811
|
+
<<~ERB
|
812
|
+
<% if @tangible_thing.#{collection_name}.any? %>
|
813
|
+
<div class="form-group">
|
814
|
+
<label class="col-form-label"><%= t('.fields.#{name}.heading') %></label>
|
815
|
+
<div>
|
816
|
+
<%= @tangible_thing.#{collection_name}.map { |#{name_without_ids}| link_to #{name_without_ids}.#{attribute_options[:label]}, [:account, #{name_without_ids}] }.to_sentence.html_safe %>
|
817
|
+
</div>
|
818
|
+
</div>
|
819
|
+
<% end %>
|
820
|
+
ERB
|
821
|
+
end
|
822
|
+
|
823
|
+
# this gets stripped and is one line, so indentation isn't a problem.
|
824
|
+
field_content = <<-ERB
|
825
|
+
<%= render 'shared/attributes/#{attribute_partial}', attribute: :#{attribute_name} %>
|
826
|
+
ERB
|
827
|
+
|
828
|
+
scaffold_add_line_to_file("./app/views/account/scaffolding/completely_concrete/tangible_things/show.html.erb", field_content.strip, ERB_NEW_FIELDS_HOOK, prepend: true)
|
829
|
+
|
830
|
+
end
|
831
|
+
|
832
|
+
#
|
833
|
+
# INDEX TABLE
|
834
|
+
#
|
835
|
+
|
836
|
+
unless cli_options["skip-table"]
|
837
|
+
|
838
|
+
# table header.
|
839
|
+
field_content = "<th#{cell_attributes.present? ? " " + cell_attributes : ""}><%= t('.fields.#{attribute_name}.heading') %></th>"
|
840
|
+
|
841
|
+
unless ["Team", "User"].include?(child)
|
842
|
+
scaffold_add_line_to_file("./app/views/account/scaffolding/completely_concrete/tangible_things/_index.html.erb", field_content, "<%# 🚅 super scaffolding will insert new field headers above this line. %>", prepend: true)
|
843
|
+
end
|
844
|
+
|
845
|
+
table_cell_options = []
|
846
|
+
|
847
|
+
if first_table_cell
|
848
|
+
table_cell_options << "url: [:account, tangible_thing]"
|
849
|
+
end
|
850
|
+
|
851
|
+
# this gets stripped and is one line, so indentation isn't a problem.
|
852
|
+
field_content = <<-ERB
|
853
|
+
<td#{cell_attributes}><%= render 'shared/attributes/#{attribute_partial}', attribute: :#{attribute_name}#{", #{table_cell_options.join(", ")}" if table_cell_options.any?} %></td>
|
854
|
+
ERB
|
855
|
+
|
856
|
+
unless ["Team", "User"].include?(child)
|
857
|
+
scaffold_add_line_to_file("./app/views/account/scaffolding/completely_concrete/tangible_things/_index.html.erb", field_content.strip, ERB_NEW_FIELDS_HOOK, prepend: true)
|
858
|
+
end
|
859
|
+
|
860
|
+
end
|
861
|
+
|
862
|
+
#
|
863
|
+
# LOCALIZATIONS
|
864
|
+
#
|
865
|
+
|
866
|
+
unless cli_options["skip-locales"]
|
867
|
+
|
868
|
+
yaml_template = <<~YAML
|
869
|
+
|
870
|
+
<%= name %>: <% if is_association %>&<%= attribute_name %><% end %>
|
871
|
+
_: &#{name} #{title_case}
|
872
|
+
label: *#{name}
|
873
|
+
heading: *#{name}
|
874
|
+
|
875
|
+
<% if type == "super_select" %>
|
876
|
+
<% if is_required %>
|
877
|
+
placeholder: Select <% title_case.with_indefinite_article %>
|
878
|
+
<% else %>
|
879
|
+
placeholder: None
|
880
|
+
<% end %>
|
881
|
+
<% end %>
|
882
|
+
|
883
|
+
<% if boolean_buttons %>
|
884
|
+
|
885
|
+
options:
|
886
|
+
yes: "Yes"
|
887
|
+
no: "No"
|
888
|
+
|
889
|
+
<% elsif ["buttons", "super_select", "options"].include?(type) && !is_association %>
|
890
|
+
|
891
|
+
options:
|
892
|
+
one: One
|
893
|
+
two: Two
|
894
|
+
three: Three
|
895
|
+
|
896
|
+
<% end %>
|
897
|
+
|
898
|
+
<% if is_association %>
|
899
|
+
<%= attribute_name %>: *<%= attribute_name %>
|
900
|
+
<% end %>
|
901
|
+
YAML
|
902
|
+
|
903
|
+
field_content = ERB.new(yaml_template).result(binding).lines.select(&:present?).join
|
904
|
+
|
905
|
+
scaffold_add_line_to_file("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml", field_content, RUBY_NEW_FIELDS_HOOK, prepend: true)
|
906
|
+
|
907
|
+
# active record's field label.
|
908
|
+
scaffold_add_line_to_file("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml", "#{name}: *#{name}", "# 🚅 super scaffolding will insert new activerecord attributes above this line.", prepend: true)
|
909
|
+
|
910
|
+
end
|
911
|
+
|
912
|
+
#
|
913
|
+
# STRONG PARAMETERS
|
914
|
+
#
|
915
|
+
|
916
|
+
unless cli_options["skip-form"]
|
917
|
+
|
918
|
+
# add attributes to strong params.
|
919
|
+
[
|
920
|
+
"./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb"
|
921
|
+
].each do |file|
|
922
|
+
if is_ids
|
923
|
+
scaffold_add_line_to_file(file, "#{name}: [],", RUBY_NEW_ARRAYS_HOOK, prepend: true)
|
924
|
+
else
|
925
|
+
scaffold_add_line_to_file(file, ":#{name},", RUBY_NEW_FIELDS_HOOK, prepend: true)
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
special_processing = case type
|
930
|
+
when "date_field"
|
931
|
+
"assign_date(strong_params, :#{name})"
|
932
|
+
when "date_and_time_field"
|
933
|
+
"assign_date_and_time(strong_params, :#{name})"
|
934
|
+
when "buttons"
|
935
|
+
if boolean_buttons
|
936
|
+
"assign_boolean(strong_params, :#{name})"
|
937
|
+
elsif is_multiple
|
938
|
+
"assign_checkboxes(strong_params, :#{name})"
|
939
|
+
end
|
940
|
+
when "super_select"
|
941
|
+
if boolean_buttons
|
942
|
+
"assign_boolean(strong_params, :#{name})"
|
943
|
+
elsif is_multiple
|
944
|
+
"assign_select_options(strong_params, :#{name})"
|
945
|
+
end
|
946
|
+
end
|
947
|
+
|
948
|
+
scaffold_add_line_to_file("./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb", special_processing, RUBY_NEW_FIELDS_PROCESSING_HOOK, prepend: true) if special_processing
|
949
|
+
end
|
950
|
+
|
951
|
+
#
|
952
|
+
# API ENDPOINT
|
953
|
+
#
|
954
|
+
|
955
|
+
unless cli_options["skip-api"]
|
956
|
+
|
957
|
+
# add attributes to endpoint.
|
958
|
+
if name.match?(/_ids$/)
|
959
|
+
scaffold_add_line_to_file("./app/controllers/api/v1/scaffolding/completely_concrete/tangible_things_endpoint.rb", "optional :#{name}, type: Array, desc: Api.heading(:#{name})", RUBY_NEW_ARRAYS_HOOK, prepend: true)
|
960
|
+
else
|
961
|
+
api_type = case type
|
962
|
+
when "date_field"
|
963
|
+
"Date"
|
964
|
+
when "date_and_time_field"
|
965
|
+
"DateTime"
|
966
|
+
when "buttons"
|
967
|
+
if boolean_buttons
|
968
|
+
"Boolean"
|
969
|
+
else
|
970
|
+
"String"
|
971
|
+
end
|
972
|
+
when "file_field"
|
973
|
+
"File"
|
974
|
+
else
|
975
|
+
"String"
|
976
|
+
end
|
977
|
+
|
978
|
+
scaffold_add_line_to_file("./app/controllers/api/v1/scaffolding/completely_concrete/tangible_things_endpoint.rb", "optional :#{name}, type: #{api_type}, desc: Api.heading(:#{name})", RUBY_NEW_FIELDS_HOOK, prepend: true)
|
979
|
+
end
|
980
|
+
|
981
|
+
end
|
982
|
+
|
983
|
+
#
|
984
|
+
# API SERIALIZER
|
985
|
+
#
|
986
|
+
|
987
|
+
unless cli_options["skip-api"]
|
988
|
+
|
989
|
+
# TODO The serializers can't handle these `has_rich_text` attributes.
|
990
|
+
unless type == "trix_editor"
|
991
|
+
[
|
992
|
+
"./app/views/account/scaffolding/completely_concrete/tangible_things/_tangible_thing.json.jbuilder",
|
993
|
+
"./app/serializers/api/v1/scaffolding/completely_concrete/tangible_thing_serializer.rb"
|
994
|
+
].each do |file|
|
995
|
+
scaffold_add_line_to_file(file, ":#{name},", RUBY_NEW_FIELDS_HOOK, prepend: true)
|
996
|
+
end
|
997
|
+
|
998
|
+
assertion = if type == "date_field"
|
999
|
+
"assert_equal Date.parse(tangible_thing_data['#{name}']), tangible_thing.#{name}"
|
1000
|
+
elsif type == "date_and_time_field"
|
1001
|
+
"assert_equal DateTime.parse(tangible_thing_data['#{name}']), tangible_thing.#{name}"
|
1002
|
+
else
|
1003
|
+
"assert_equal tangible_thing_data['#{name}'], tangible_thing.#{name}"
|
1004
|
+
end
|
1005
|
+
scaffold_add_line_to_file("./test/controllers/api/v1/scaffolding/completely_concrete/tangible_things_endpoint_test.rb", assertion, RUBY_NEW_FIELDS_HOOK, prepend: true)
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
# scaffold_add_line_to_file("./test/controllers/api/v1/scaffolding/completely_concrete/tangible_things_controller_test.rb", "assert_equal tangible_thing_attributes['#{name.gsub('_', '-')}'], tangible_thing.#{name}", RUBY_NEW_FIELDS_HOOK, prepend: true)
|
1009
|
+
|
1010
|
+
if attribute_assignment
|
1011
|
+
scaffold_add_line_to_file("./test/controllers/api/v1/scaffolding/completely_concrete/tangible_things_endpoint_test.rb", "#{name}: #{attribute_assignment},", RUBY_ADDITIONAL_NEW_FIELDS_HOOK, prepend: true)
|
1012
|
+
scaffold_add_line_to_file("./test/controllers/api/v1/scaffolding/completely_concrete/tangible_things_endpoint_test.rb", "assert_equal @tangible_thing.#{name}, #{attribute_assignment}", RUBY_EVEN_MORE_NEW_FIELDS_HOOK, prepend: true)
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
#
|
1017
|
+
# MODEL ASSOCATIONS
|
1018
|
+
#
|
1019
|
+
|
1020
|
+
unless cli_options["skip-model"]
|
1021
|
+
|
1022
|
+
if is_belongs_to
|
1023
|
+
unless attribute_options[:class_name]
|
1024
|
+
attribute_options[:class_name] = name_without_id.classify
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
file_name = "app/models/#{attribute_options[:class_name].underscore}.rb"
|
1028
|
+
unless File.exist?(file_name)
|
1029
|
+
raise "You'll need to specify a `class_name` option for `#{name}` because there is no `#{attribute_options[:class_name].classify}` model defined in `#{file_name}`. Try again with `#{name}:#{type}[class_name=SomeClassName]`."
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
modified_migration = false
|
1033
|
+
|
1034
|
+
# find the database migration that defines this relationship.
|
1035
|
+
expected_reference = "add_reference :#{class_names_transformer.table_name}, :#{name_without_id}"
|
1036
|
+
migration_file_name = `grep "#{expected_reference}" db/migrate/*`.split(":").first
|
1037
|
+
|
1038
|
+
# if that didn't work, see if we can find a creation of the reference when the table was created.
|
1039
|
+
unless migration_file_name
|
1040
|
+
confirmation_reference = "create_table :#{class_names_transformer.table_name}"
|
1041
|
+
confirmation_migration_file_name = `grep "#{confirmation_reference}" db/migrate/*`.split(":").first
|
1042
|
+
|
1043
|
+
fallback_reference = "t.references :#{name_without_id}"
|
1044
|
+
fallback_migration_file_name = `grep "#{fallback_reference}" db/migrate/* | grep #{confirmation_migration_file_name}`.split(":").first
|
1045
|
+
|
1046
|
+
if fallback_migration_file_name == confirmation_migration_file_name
|
1047
|
+
migration_file_name = fallback_migration_file_name
|
1048
|
+
end
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
unless is_required
|
1052
|
+
|
1053
|
+
if migration_file_name
|
1054
|
+
replace_in_file(migration_file_name, ":#{name_without_id}, null: false", ":#{name_without_id}, null: true")
|
1055
|
+
modified_migration = true
|
1056
|
+
else
|
1057
|
+
add_additional_step :yellow, "We would have expected there to be a migration that defined `#{expected_reference}`, but we didn't find one. Where was the reference added to this model? It's _probably_ the original creation of the table, but we couldn't find that either. Either way, you need to rollback, change 'null: false' to 'null: true' for this column, and re-run the migration (unless, of course, that attribute _is_ required, then you need to add a validation on the model)."
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
class_name_matches = name_without_id.tableize == attribute_options[:class_name].tableize.tr("/", "_")
|
1063
|
+
|
1064
|
+
# but also, if namespaces are involved, just don't...
|
1065
|
+
if attribute_options[:class_name].include?("::")
|
1066
|
+
class_name_matches = false
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
# unless the table name matches the association name.
|
1070
|
+
unless class_name_matches
|
1071
|
+
if migration_file_name
|
1072
|
+
# There are two forms this association creation can take.
|
1073
|
+
replace_in_file(migration_file_name, "foreign_key: true", "foreign_key: {to_table: \"#{attribute_options[:class_name].tableize.tr("/", "_")}\"}", /t\.references :#{name_without_id}/)
|
1074
|
+
replace_in_file(migration_file_name, "foreign_key: true", "foreign_key: {to_table: \"#{attribute_options[:class_name].tableize.tr("/", "_")}\"}", /add_reference :#{child.underscore.pluralize.tr("/", "_")}, :#{name_without_id}/)
|
1075
|
+
|
1076
|
+
# TODO also solve the 60 character long index limitation.
|
1077
|
+
modified_migration = true
|
1078
|
+
else
|
1079
|
+
add_additional_step :yellow, "We would have expected there to be a migration that defined `#{expected_reference}`, but we didn't find one. Where was the reference added to this model? It's _probably_ the original creation of the table. Either way, you need to rollback, change \"foreign_key: true\" to \"foreign_key: {to_table: '#{attribute_options[:class_name].tableize.tr("/", "_")}'}\" for this column, and re-run the migration."
|
1080
|
+
end
|
1081
|
+
end
|
1082
|
+
|
1083
|
+
optional_line = ", optional: true" unless is_required
|
1084
|
+
|
1085
|
+
# if the `belongs_to` is already there from `rails g model`..
|
1086
|
+
scaffold_replace_line_in_file(
|
1087
|
+
"./app/models/scaffolding/completely_concrete/tangible_thing.rb",
|
1088
|
+
class_name_matches ?
|
1089
|
+
"belongs_to :#{name_without_id}#{optional_line}" :
|
1090
|
+
"belongs_to :#{name_without_id}, class_name: \"#{attribute_options[:class_name]}\"#{optional_line}",
|
1091
|
+
"belongs_to :#{name_without_id}"
|
1092
|
+
)
|
1093
|
+
|
1094
|
+
# if it wasn't there, the replace will not have done anything, so we insert it entirely.
|
1095
|
+
# however, this won't do anything if the association is already there.
|
1096
|
+
scaffold_add_line_to_file(
|
1097
|
+
"./app/models/scaffolding/completely_concrete/tangible_thing.rb",
|
1098
|
+
class_name_matches ?
|
1099
|
+
"belongs_to :#{name_without_id}#{optional_line}" :
|
1100
|
+
"belongs_to :#{name_without_id}, class_name: \"#{attribute_options[:class_name]}\"#{optional_line}",
|
1101
|
+
BELONGS_TO_HOOK,
|
1102
|
+
prepend: true
|
1103
|
+
)
|
1104
|
+
|
1105
|
+
if modified_migration
|
1106
|
+
add_additional_step :yellow, "If you've already run the migration in `#{migration_file_name}`, you'll need to roll back and run it again."
|
1107
|
+
end
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
#
|
1113
|
+
# MODEL HOOKS
|
1114
|
+
#
|
1115
|
+
|
1116
|
+
unless cli_options["skip-model"]
|
1117
|
+
|
1118
|
+
if is_required && !is_belongs_to
|
1119
|
+
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "validates :#{name}, presence: true", VALIDATIONS_HOOK, prepend: true)
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
case type
|
1123
|
+
when "file_field"
|
1124
|
+
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "has_one_attached :#{name}", HAS_ONE_HOOK, prepend: true)
|
1125
|
+
when "trix_editor"
|
1126
|
+
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "has_rich_text :#{name}", HAS_ONE_HOOK, prepend: true)
|
1127
|
+
end
|
1128
|
+
|
1129
|
+
end
|
1130
|
+
end
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
def add_additional_step(color, message)
|
1134
|
+
additional_steps.push [color, message]
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
def scaffold_crud(attributes)
|
1138
|
+
if cli_options["only-index"]
|
1139
|
+
cli_options["skip-table"] = false
|
1140
|
+
cli_options["skip-views"] = true
|
1141
|
+
cli_options["skip-controller"] = true
|
1142
|
+
cli_options["skip-form"] = true
|
1143
|
+
cli_options["skip-show"] = true
|
1144
|
+
cli_options["skip-form"] = true
|
1145
|
+
cli_options["skip-api"] = true
|
1146
|
+
cli_options["skip-model"] = true
|
1147
|
+
cli_options["skip-parent"] = true
|
1148
|
+
cli_options["skip-locales"] = true
|
1149
|
+
cli_options["skip-routes"] = true
|
1150
|
+
end
|
1151
|
+
|
1152
|
+
if cli_options["namespace"]
|
1153
|
+
cli_options["skip-api"] = true
|
1154
|
+
cli_options["skip-model"] = true
|
1155
|
+
cli_options["skip-locales"] = true
|
1156
|
+
end
|
1157
|
+
|
1158
|
+
# TODO fix this. we can do this better.
|
1159
|
+
files = if cli_options["only-index"]
|
1160
|
+
[
|
1161
|
+
"./app/views/account/scaffolding/completely_concrete/tangible_things/_index.html.erb",
|
1162
|
+
"./app/views/account/scaffolding/completely_concrete/tangible_things/index.html.erb"
|
1163
|
+
]
|
1164
|
+
else
|
1165
|
+
# copy a ton of files over and do the appropriate string replace.
|
1166
|
+
[
|
1167
|
+
"./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb",
|
1168
|
+
"./app/views/account/scaffolding/completely_concrete/tangible_things",
|
1169
|
+
("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml" unless cli_options["skip-locales"]),
|
1170
|
+
("./app/controllers/api/v1/scaffolding/completely_concrete/tangible_things_endpoint.rb" unless cli_options["skip-api"]),
|
1171
|
+
("./test/controllers/api/v1/scaffolding/completely_concrete/tangible_things_endpoint_test.rb" unless cli_options["skip-api"]),
|
1172
|
+
("./app/serializers/api/v1/scaffolding/completely_concrete/tangible_thing_serializer.rb" unless cli_options["skip-api"])
|
1173
|
+
# "./app/filters/scaffolding/completely_concrete/tangible_things_filter.rb"
|
1174
|
+
].compact
|
1175
|
+
end
|
1176
|
+
|
1177
|
+
files.each do |name|
|
1178
|
+
if File.directory?(name)
|
1179
|
+
scaffold_directory(name)
|
1180
|
+
else
|
1181
|
+
scaffold_file(name)
|
1182
|
+
end
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
unless cli_options["skip-api"]
|
1186
|
+
|
1187
|
+
# add endpoint to the api.
|
1188
|
+
scaffold_add_line_to_file("./app/controllers/api/v1/root.rb", "mount Api::V1::Scaffolding::CompletelyConcrete::TangibleThingsEndpoint", ENDPOINTS_HOOK, prepend: true)
|
1189
|
+
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
unless cli_options["skip-model"]
|
1193
|
+
# find the database migration that defines this relationship.
|
1194
|
+
migration_file_name = `grep "create_table :#{class_names_transformer.table_name} do |t|" db/migrate/*`.split(":").first
|
1195
|
+
unless migration_file_name.present?
|
1196
|
+
raise "No migration file seems to exist for creating the table `#{class_names_transformer.table_name}`.\n" \
|
1197
|
+
"Please run the following command first and try Super Scaffolding again:\n" \
|
1198
|
+
"rails generate model #{child} #{parent.downcase!}:references #{attributes.join(" ")}"
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
# if needed, update the reference to the parent class name in the create_table migration
|
1202
|
+
current_transformer = Scaffolding::ClassNamesTransformer.new(child, parent, namespace)
|
1203
|
+
unless current_transformer.parent_variable_name_in_context.pluralize == current_transformer.parent_table_name
|
1204
|
+
replace_in_file(migration_file_name, "foreign_key: true", "foreign_key: {to_table: '#{current_transformer.parent_table_name}'}")
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
# update the factory generated by `rails g`.
|
1208
|
+
content = if transform_string(":absolutely_abstract_creative_concept") == transform_string(":scaffolding_absolutely_abstract_creative_concept")
|
1209
|
+
transform_string("association :absolutely_abstract_creative_concept")
|
1210
|
+
else
|
1211
|
+
transform_string("association :absolutely_abstract_creative_concept, factory: :scaffolding_absolutely_abstract_creative_concept")
|
1212
|
+
end
|
1213
|
+
scaffold_replace_line_in_file("./test/factories/scaffolding/completely_concrete/tangible_things.rb", content, "absolutely_abstract_creative_concept { nil }")
|
1214
|
+
|
1215
|
+
add_has_many_association
|
1216
|
+
|
1217
|
+
if class_names_transformer.belongs_to_needs_class_definition?
|
1218
|
+
scaffold_replace_line_in_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", transform_string("belongs_to :absolutely_abstract_creative_concept, class_name: \"Scaffolding::AbsolutelyAbstract::CreativeConcept\"\n"), transform_string("belongs_to :absolutely_abstract_creative_concept\n"))
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
# add user permissions.
|
1222
|
+
add_ability_line_to_roles_yml
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
unless cli_options["skip-api"]
|
1226
|
+
scaffold_replace_line_in_file("./test/controllers/api/v1/scaffolding/completely_concrete/tangible_things_endpoint_test.rb", build_factory_setup.join("\n"), "# 🚅 super scaffolding will insert factory setup in place of this line.")
|
1227
|
+
end
|
1228
|
+
|
1229
|
+
# add children to the show page of their parent.
|
1230
|
+
unless cli_options["skip-parent"] || parent == "None"
|
1231
|
+
scaffold_add_line_to_file("./app/views/account/scaffolding/absolutely_abstract/creative_concepts/show.html.erb", "<%= render 'account/scaffolding/completely_concrete/tangible_things/index', tangible_things: @creative_concept.completely_concrete_tangible_things, hide_back: true %>", "<%# 🚅 super scaffolding will insert new children above this line. %>", prepend: true)
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
unless cli_options["skip-model"]
|
1235
|
+
add_scaffolding_hooks_to_model
|
1236
|
+
end
|
1237
|
+
|
1238
|
+
#
|
1239
|
+
# DELEGATIONS
|
1240
|
+
#
|
1241
|
+
|
1242
|
+
unless cli_options["skip-model"]
|
1243
|
+
|
1244
|
+
if ["Team", "User"].include?(parents.last) && parent != parents.last
|
1245
|
+
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "has_one :#{parents.last.underscore}, through: :absolutely_abstract_creative_concept", HAS_ONE_HOOK, prepend: true)
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
end
|
1249
|
+
|
1250
|
+
add_attributes_to_various_views(attributes, type: :crud)
|
1251
|
+
|
1252
|
+
unless cli_options["skip-locales"]
|
1253
|
+
add_locale_helper_export_fix
|
1254
|
+
end
|
1255
|
+
|
1256
|
+
# add sortability.
|
1257
|
+
if cli_options["sortable"]
|
1258
|
+
unless cli_options["skip-model"]
|
1259
|
+
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "def collection\n absolutely_abstract_creative_concept.completely_concrete_tangible_things\nend\n\n", METHODS_HOOK, prepend: true)
|
1260
|
+
scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "include Sortable\n", CONCERNS_HOOK, prepend: true)
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
unless cli_options["skip-table"]
|
1264
|
+
scaffold_replace_line_in_file("./app/views/account/scaffolding/completely_concrete/tangible_things/_index.html.erb", transform_string("<tbody data-reorder=\"<%= url_for [:reorder, :account, context, collection] %>\">"), "<tbody>")
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
unless cli_options["skip-controller"]
|
1268
|
+
scaffold_add_line_to_file("./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb", "include SortableActions\n", "Account::ApplicationController", increase_indent: true)
|
1269
|
+
end
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
# titleize the localization file.
|
1273
|
+
unless cli_options["skip-locales"]
|
1274
|
+
replace_in_file(transform_string("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml"), child, child.underscore.humanize.titleize)
|
1275
|
+
end
|
1276
|
+
|
1277
|
+
# apply routes.
|
1278
|
+
unless cli_options["skip-routes"]
|
1279
|
+
routes_namespace = cli_options["namespace"] || "account"
|
1280
|
+
|
1281
|
+
begin
|
1282
|
+
routes_path = if routes_namespace == "account"
|
1283
|
+
"config/routes.rb"
|
1284
|
+
else
|
1285
|
+
"config/routes/#{routes_namespace}.rb"
|
1286
|
+
end
|
1287
|
+
routes_manipulator = Scaffolding::RoutesFileManipulator.new(routes_path, child, parent, cli_options)
|
1288
|
+
rescue Errno::ENOENT => _
|
1289
|
+
puts "Creating '#{routes_path}'.".green
|
1290
|
+
|
1291
|
+
unless File.directory?("config/routes")
|
1292
|
+
FileUtils.mkdir_p("config/routes")
|
1293
|
+
end
|
1294
|
+
|
1295
|
+
File.write(routes_path, <<~RUBY)
|
1296
|
+
collection_actions = [:index, :new, :create]
|
1297
|
+
|
1298
|
+
# 🚅 Don't remove this block, it will break Super Scaffolding.
|
1299
|
+
begin do
|
1300
|
+
namespace :#{routes_namespace} do
|
1301
|
+
shallow do
|
1302
|
+
resources :teams do
|
1303
|
+
end
|
1304
|
+
end
|
1305
|
+
end
|
1306
|
+
end
|
1307
|
+
RUBY
|
1308
|
+
|
1309
|
+
retry
|
1310
|
+
end
|
1311
|
+
|
1312
|
+
begin
|
1313
|
+
routes_manipulator.apply([routes_namespace])
|
1314
|
+
rescue
|
1315
|
+
add_additional_step :yellow, "We weren't able to automatically add your `#{routes_namespace}` routes for you. In theory this should be very rare, so if you could reach out on Slack, you could probably provide context that will help us fix whatever the problem was. In the meantime, to add the routes manually, we've got a guide at https://blog.bullettrain.co/nested-namespaced-rails-routing-examples/ ."
|
1316
|
+
end
|
1317
|
+
|
1318
|
+
routes_manipulator.write
|
1319
|
+
end
|
1320
|
+
|
1321
|
+
unless cli_options["skip-parent"]
|
1322
|
+
|
1323
|
+
if parent == "Team" || parent == "None"
|
1324
|
+
icon_name = nil
|
1325
|
+
if cli_options["sidebar"].present?
|
1326
|
+
icon_name = cli_options["sidebar"]
|
1327
|
+
else
|
1328
|
+
puts ""
|
1329
|
+
puts "Hey, models that are scoped directly off of a Team (or nothing) are eligible to be added to the sidebar. Do you want to add this resource to the sidebar menu? (y/N)"
|
1330
|
+
response = $stdin.gets.chomp
|
1331
|
+
if response.downcase[0] == "y"
|
1332
|
+
puts ""
|
1333
|
+
puts "OK, great! Let's do this! By default these menu items appear with a puzzle piece, but after you hit enter I'll open two different pages where you can view other icon options. When you find one you like, hover your mouse over it and then come back here and and enter the name of the icon you want to use. (Or hit enter to skip this step.)"
|
1334
|
+
$stdin.gets.chomp
|
1335
|
+
if `which open`.present?
|
1336
|
+
`open https://themify.me/themify-icons`
|
1337
|
+
`open https://fontawesome.com/icons?d=gallery&s=light`
|
1338
|
+
else
|
1339
|
+
puts "Sorry! We can't open these URLs automatically on your platform, but you can visit them manually:"
|
1340
|
+
puts ""
|
1341
|
+
puts " https://themify.me/themify-icons"
|
1342
|
+
puts " https://fontawesome.com/icons?d=gallery&s=light"
|
1343
|
+
puts ""
|
1344
|
+
end
|
1345
|
+
puts ""
|
1346
|
+
puts "Did you find an icon you wanted to use? Enter the full CSS class here (e.g. 'ti ti-globe' or 'fal fa-puzzle-piece') or hit enter to just use the puzzle piece:"
|
1347
|
+
icon_name = $stdin.gets.chomp
|
1348
|
+
puts ""
|
1349
|
+
unless icon_name.length > 0 || icon_name.downcase == "y"
|
1350
|
+
icon_name = "fal fa-puzzle-piece ti ti-gift"
|
1351
|
+
end
|
1352
|
+
end
|
1353
|
+
end
|
1354
|
+
if icon_name.present?
|
1355
|
+
replace_in_file(transform_string("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml"), "fal fa-puzzle-piece", icon_name)
|
1356
|
+
scaffold_add_line_to_file("./app/views/account/shared/_menu.html.erb", "<%= render 'account/scaffolding/completely_concrete/tangible_things/menu_item' %>", "<% # added by super scaffolding. %>")
|
1357
|
+
end
|
1358
|
+
end
|
1359
|
+
end
|
1360
|
+
|
1361
|
+
add_additional_step :yellow, transform_string("If you would like the table view you've just generated to reactively update when a Tangible Thing is updated on the server, please edit `app/models/scaffolding/absolutely_abstract/creative_concept.rb`, locate the `has_many :completely_concrete_tangible_things`, and add `enable_updates: true` to it.")
|
1362
|
+
|
1363
|
+
restart_server
|
1364
|
+
end
|
1365
|
+
end
|