-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathgeode.thor
More file actions
605 lines (510 loc) · 27 KB
/
geode.thor
File metadata and controls
605 lines (510 loc) · 27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
# Required gems across the entire framework
require 'bundler/setup'
# Required gems and files across the CLI
require 'thor'
require 'irb'
require 'sequel'
require 'dry-configurable'
require_relative 'geode/config'
require_relative 'geode/generator'
Sequel.extension :inflector, :migration, :schema_dumper
# Geode's main CLI; contains tasks related to Geode functionality
class Geode < Thor
# Throw exit code 1 on errors
def self.exit_on_failure?
true
end
# Throw an error if an unknown flag is provided
check_unknown_options!
map %w(-r -s) => :start
desc 'start [-d], [-a], [--load-only=one two three], [--add-slash] [--remove-slash=1234567890|global]', 'Load crystals and start the bot'
long_desc <<~LONG_DESC.strip
Loads crystals and starts the bot. With no options, this loads only the crystals in main.
Note: If two crystals with the same name are found by --load-only, an error will be thrown as crystals must
have unique names.
LONG_DESC
option :dev, type: :boolean,
aliases: '-d',
desc: 'Load dev crystals instead of main'
option :all, type: :boolean,
aliases: '-a',
desc: 'Load all crystals (main and dev)'
option :load_only, type: :array,
desc: 'Load only the given crystals (searching both main and dev)'
option :add_slash, type: :boolean,
desc: 'Add the slash commands defined in app/slash'
option :remove_slash, type: :string,
desc: 'Remove the slash commands in the server with the given ID, or global for global commands, except the ones defined in app/slash'
def start
# Validate that only one of the first three options is given
if options.reject{ |k, _v| k == 'add_slash' || k == 'remove_slash' }.count { |_k, v| v } > 1
raise Error, 'ERROR: Only one of -d, -a and --load-only can be given'
end
# Validate that if remove-slash is given it is a number or 'global'
if options[:remove_slash] && !(options[:remove_slash] =~ /\A(global|\d+)\z/)
raise Error, "ERROR: --remove-slash input must be a number or 'global'"
end
# Select the crystals to load, throwing an error if a crystal given in load_only is not found
if options[:dev]
Config.config.crystals_to_load = Dir['app/dev/**/*.rb']
elsif options[:all]
Config.config.crystals_to_load = (Dir['app/main/**/*.rb'] + Dir['app/dev/*.rb'])
elsif options[:load_only]
all_crystal_paths = Dir['app/main/**/*.rb'] + Dir['app/dev/**/*.rb']
Config.config.crystals_to_load = options[:load_only].map do |arg|
path = all_crystal_paths.find { |p| p.split('/')[2..-1].join('/') == (arg.underscore + '.rb') }
raise Error, "ERROR: Crystal #{arg} not found" unless path
path
end
else
Config.config.crystals_to_load = Dir['app/main/**/*.rb']
end
# Set add_slash config setting to true if option is given
Config.config.add_slash = options[:add_slash]
# Set remove_slash config to server ID or global if given
Config.config.remove_slash = options[:remove_slash]
# Load the bot script
load File.expand_path('app/bot.rb')
end
desc 'generate {crystal|slash|model|migration} ARGS', 'Generate a Geode crystal, model or migration'
long_desc <<~LONG_DESC.strip
Generate a Geode crystal, model or migration.
When generating a crystal, the format is
'generate crystal [-m], [--main], [--without-models] names...'
When generating a slash command, the format is 'generate slash name [--desc="Description"] [--server-id=1234567890]'
\x5When generating a model, the format is 'generate model name [--singleton] [fields...]'
\x5When generating a migration, the format is 'generate migration [--with-up-down] name'
If a model is being generated, the model's fields should be included in the format 'name:type'
(i.e. generate model name:string number:integer), similar to Rails.
\x5The allowed field types are: #{Generators::ModelGenerator::VALID_FIELD_TYPES.join(', ')}
\x5The 'id' field is special; if given, it must be of type 'primary_key'. If no primary key is
given, a primary key field named 'id' will automatically be created; it is skipped if a primary
key with a different name exists.
\x5The --singleton option allows you to generate a singleton model class, which will create a
table with only a single entry that can be retrieved using 'ModelClassName.instance'.
LONG_DESC
option :main, type: :boolean,
aliases: '-m',
desc: 'Generate a crystal in the main folder instead of dev (crystal generation only)'
option :without_models, type: :boolean,
desc: 'Generate a crystal without database model classes (crystal generation only)'
option :desc, type: :string,
desc: 'Generate a slash command with the given description (slash command generation only)'
option :server_id, type: :numeric,
desc: 'Generate a slash command with the given server ID (slash command generation only)'
option :singleton, type: :boolean,
desc: 'Generate a singleton model class instead of the standard (model generation only)'
option :with_up_down, type: :boolean,
desc: 'Generate a migration with up/down blocks instead of a change block (migration generation only)'
def generate(type, *args)
# Case generation type
case type
when 'crystal'
# Validate that no invalid options are given when a crystal is being generated
raise Error, 'ERROR: Option --desc should not be given when generating a crystal' if options[:desc]
raise Error, 'ERROR: Option --server-id should not be given when generating a crystal' if options[:server_id]
raise Error, 'ERROR: Option --singleton should not be given when generating a crystal' if options[:singleton]
raise Error, 'ERROR: Option --with-up-down should not be given when generating a crystal' if options[:with_up_down]
# Iterate through the given names and generate crystals for each
args.each do |crystal_name|
generator = Generators::CrystalGenerator.new(
crystal_name,
without_models: options[:without_models]
)
generator.generate_in(options[:main] ? 'app/main' : 'app/dev')
end
when 'slash'
# Validate that no invalid options are given when a slash command is being generated
raise Error, 'ERROR: Option -m, --main should not be given when generating a slash command' if options[:main]
raise Error, 'ERROR: Option --without-models should not be given when generating a slash command' if options[:without_models]
raise Error, 'ERROR: Option --singleton should not be given when generating a slash command' if options[:singleton]
raise Error, 'ERROR: Option --with-up-down should not be given when generating a slash command' if options[:with_up_down]
# Generate slash command
generator = Generators::SlashGenerator.new(
args[0],
description: options[:desc] || 'Enter description here',
server_id: options[:server_id]
)
generator.generate_in('app/slash')
when 'model'
# Validate that no invalid option is given when generating a model
raise Error, 'ERROR: Option --desc should not be given when generating a model' if options[:desc]
raise Error, 'ERROR: Option --server-id should not be given when generating a model' if options[:server_id]
raise Error, 'ERROR: Option -m, --main should not be given when generating a model' if options[:main]
raise Error, 'ERROR: Option --without-models should not be given when generating a model' if options[:without_models]
raise Error, 'ERROR: Option --with-up-down should not be given when generating a model' if options[:with_up_down]
name = args[0]
fields = args[1..-1]
# Validate that a name is given
raise Error, 'ERROR: Model name must be given' unless name
# If fields were given, validate that they have the correct format, the type is valid and if an id field is
# given, it is the primary key; if so, map the array to the correct format for the generator
if fields
fields.map! do |field_str|
unless (field_name, field_type = field_str.split(':')).size == 2
raise Error, "ERROR: #{field_str} is not in the correct format of name:type"
end
unless Generators::ModelGenerator::VALID_FIELD_TYPES.include?(field_type)
raise Error, "ERROR: #{field_str} has an invalid type"
end
if field_name == 'id' && field_type != 'primary_key'
raise Error, 'ERROR: Field id can only be primary key'
end
[field_name, field_type]
end
# If fields were not given, set fields equal to an empty array
else
fields = []
end
# Generate model (either standard or singleton)
generator = Generators::ModelGenerator.new(name, fields, singleton: options[:singleton])
generator.generate_in 'app/models', 'db/migrations'
when 'migration'
# Validate that no invalid option is given when generating a migration
raise Error, 'ERROR: Option -m, --main should not be given when generating a migration' if options[:main]
raise Error, 'ERROR: Option --without-models should not be given when generating a migration' if options[:without_models]
raise Error, 'ERROR: Option --desc should not be given when generating a migration' if options[:desc]
raise Error, 'ERROR: Option --server-id should not be given when generating a migration' if options[:server_id]
raise Error, 'ERROR: Option --singleton should not be given when generating a migration' if options[:singleton]
# Validate that exactly one argument (the migration name) is given
raise Error, 'ERROR: Migration name must be given' if args.size < 1
raise Error, 'ERROR: Only one migration name can be given' if args.size > 1
# Generate migration
generator = Generators::MigrationGenerator.new(args[0], with_up_down: options[:with_up_down])
generator.generate_in 'db/migrations'
else raise Error, 'ERROR: Type must be crystal, model or migration'
end
end
desc 'rename {crystal|model|migration} OLD_NAME NEW_NAME', 'Rename a Geode crystal, model or migration'
long_desc <<~LONG_DESC.strip
Rename a Geode crystal, model or migration.
When renaming a model, a new migration will be generated that renames the model's table.
\x5When renaming a migration, provide either the migration's name or version number for the old name.
Note: Renaming a model does not update any references to the model within crystals or lib scripts!
LONG_DESC
def rename(type, old_name, new_name)
# Case rename type
case type
when 'crystal'
# Validate that crystal with given name exists
unless (old_path = (Dir['app/dev/*.rb'] + Dir['app/main/*.rb']).find { |p| File.basename(p, '.*').camelize == old_name })
raise Error, "ERROR: Crystal #{old_name} not found"
end
new_path = "#{File.dirname(old_path)}/#{new_name.underscore}.rb"
# Write content of old crystal file to new, replacing all instances of old name with new
File.open(new_path, 'w') do |file|
file.write(File.read(old_path).gsub(old_name, new_name.camelize))
end
# Delete old file
File.delete(old_path)
puts "= Renamed crystal #{old_name} to #{new_name} at #{new_path}"
when 'model'
old_path = if File.exist? "app/models/#{old_name.underscore}.rb"
singleton = false
"app/models/#{old_name.underscore}.rb"
elsif File.exist? "app/models/#{old_name.underscore}_singleton.rb"
singleton = true
"app/models/#{old_name.underscore}_singleton.rb"
end
# Validate that model with given name exists
unless old_path
raise Error, "ERROR: Model #{old_name} not found"
end
new_path = singleton ? "app/models/#{new_name.underscore}_singleton.rb" : "app/models/#{new_name.underscore}.rb"
# Write content of old model file to new, replacing all instances of old name (and table if singleton) with new
File.open(new_path, 'w') do |file|
new_content = File.read(old_path).gsub(old_name.camelize, new_name.camelize)
new_content = new_content.gsub(":#{old_name.underscore}", ":#{new_name.underscore}") if singleton
file.write new_content
end
# Delete old file
File.delete(old_path)
puts "= Renamed model #{old_name.camelize} to #{new_name.camelize} at #{new_path}"
# Generate migration renaming old model's table to new
generator = Generators::ModelRenameMigrationGenerator.new(old_name, new_name, singleton)
generator.generate_in('db/migrations')
when 'migration'
# Validate that migration with given name or version number exists
old_path = Dir['db/migrations/*.rb'].find do |path|
filename = File.basename(path)
filename.to_i == old_name.to_i || filename[15..-4].camelize == old_name.camelize
end
raise Error, "ERROR: Migration #{old_name.camelize} not found" unless old_path
old_migration_name = File.basename(old_path)[15..-4].camelize
migration_version = File.basename(old_path).to_i
new_path = "db/migrations/#{migration_version}_#{new_name.underscore}.rb"
# Write content of old migration file to new, replacing all instances of old name with new
File.open(new_path, 'w') do |file|
file.write(File.read(old_path).gsub(old_migration_name, new_name.camelize))
end
# Delete old file
File.delete(old_path)
puts "= Renamed migration version #{migration_version} (#{old_migration_name}) to #{new_name.camelize} at #{new_path}"
else raise Error, 'ERROR: Generation type must be crystal, model or migration'
end
end
desc 'destroy {model|migration} NAME(S)', 'Destroy Geode models or migrations'
long_desc <<~LONG_DESC.strip
Destroy a Geode model or migration.
Destruction of models must be done one at a time; however multiple migrations may be deleted at a time.
When destroying a model, the migration that created its table and every migration afterward will be deleted
provided the model's table does not already exist in the database; otherwise, a new migration will be created
that drops the model's table.
\x5When destroying migrations, provide either the version number or name.
Note: Destroying migrations is unsafe; avoid doing it unless you are sure of what you are doing.
LONG_DESC
def destroy(type, *args)
# Validate that arguments have been given
raise Error, 'ERROR: At least one name must be given' if type && args.empty?
# Case destruction type
case type
when 'model'
# Validate that only one model name is given
raise Error, 'ERROR: Only one model can be deleted at a time' unless args.size == 1
model_name = args[0]
model_path = if File.exist? "app/models/#{model_name.underscore}.rb"
singleton = false
"app/models/#{model_name.underscore}.rb"
elsif File.exist? "app/models/#{model_name.underscore}_singleton.rb"
singleton = true
"app/models/#{model_name.underscore}_singleton.rb"
end
# Validate that model exists
raise Error, "ERROR: Model #{model_name} not found" unless model_path
table_name = singleton ? model_name.underscore : model_name.tableize
# Delete model, printing deletion to console
File.delete model_path
puts "- Deleted model file for model #{model_name}"
# Load the database
Sequel.sqlite(Config.config.db_path) do |db|
# If model's table exists in the database, generate new migration dropping the model's table
if db.table_exists?(table_name.to_sym)
generator = Generators::ModelDestroyMigrationGenerator.new(model_name, db, singleton)
generator.generate_in('db/migrations')
# Otherwise, delete the migration adding the model's table and every migration that follows
else
initial_migration_index = Dir['db/migrations/*.rb'].sort.index do |path|
path.include? "add_#{table_name}_table_to_database"
end
Dir['db/migrations/*.rb'].sort[initial_migration_index..-1].each do |migration_path|
migration_name = File.basename(migration_path)[15..-4].camelize
migration_version = File.basename(migration_path).to_i
File.delete(migration_path)
puts "- Deleted migration version #{migration_version} (#{migration_name})"
end
end
end
when 'migration'
all_migration_paths = Dir['db/migrations/*.rb']
# Validate that migrations with the given names or versions all exist and get their names, versions
# and file paths
migrations_to_delete = args.map do |migration_key|
migration_path = all_migration_paths.find do |path|
filename = File.basename(path)
filename.to_i == migration_key.to_i || filename[15..-4].camelize == migration_key
end
if (migration_path)
migration_name = File.basename(migration_path)[15..-4].camelize
migration_version = File.basename(migration_path).to_i
[migration_name, migration_version, migration_path]
else raise Error, "ERROR: Migration #{migration_key} not found"
end
end
# Delete all given migrations, printing deletions to console
migrations_to_delete.each do |migration_name, migration_version, migration_path|
File.delete(migration_path)
puts "- Deleted migration version #{migration_version} (#{migration_name})"
end
else raise Error, 'ERROR: Generation type must be model or migration'
end
end
end
# Geode's database management; contains tasks related to modifying the database
class Database < Thor
namespace :db
# Throw exit code 1 on errors
def self.exit_on_failure?
true
end
# Throw an error if an unknown flag is provided
check_unknown_options!
desc 'migrate [--version=N], [-s]', "Migrate this Geode's database or display migration status"
long_desc <<~LONG_DESC.strip
Migrate this Geode's database, or displays migration status. With no options, the database is migrated to the latest.
When --version is specified, the number given should be the timestamp of the migration.
When displaying migration status with -s, the current migration will be displayed along with how many
migrations behind the latest the database is currently on.
LONG_DESC
option :version, type: :numeric,
desc: 'Migrate the database to the given version'
option :status, type: :boolean,
aliases: '-s',
desc: 'Check the current status of migrations'
def migrate
# Load the database
Sequel.sqlite(Config.config.db_path) do |db|
# Validate that both version and status are not given at the same time
raise Error, 'ERROR: Only one of --version, -s can be given at a time' if options[:version] && options[:status]
# If version is given:
if options[:version]
# Validate that the given version exists
unless (file_path = Dir['db/migrations/*.rb'].find { |f| File.basename(f).to_i == options[:version] })
raise Error, "ERROR: Migration version #{options[:version]} not found"
end
filename = File.basename(file_path)
# Migrate the database to the given version
Sequel::Migrator.run(db, 'db/migrations', target: options[:version])
# Regenerate schema
generator = Generators::SchemaGenerator.new(db)
generator.generate_in('db')
puts "+ Database migrated to version #{options[:version]} (#{filename[15..-4].camelize})"
# If status is given, respond with migration status:
elsif options[:status]
filename = db[:schema_migrations].order(:filename).last[:filename]
migration_name = filename[15..-4].camelize
version_number = filename.to_i
puts "Database on migration #{migration_name} (version #{version_number})"
if Sequel::Migrator.is_current?(db, 'db/migrations')
puts 'Database is on latest migration'
else
all_migration_files = Dir['db/migrations/*.rb'].map { |p| File.basename(p) }
unmigrated_count = (all_migration_files - db[:schema_migrations].map(:filename)).count
puts "Database #{unmigrated_count} migration#{unmigrated_count == 1 ? nil : 's'} behind latest"
end
# If no options are given, migrate to latest and regenerate schema:
else
if Sequel::Migrator.is_current?(db, 'db/migrations')
puts 'Database is on latest migration'
else
Sequel::Migrator.run(db, 'db/migrations', options)
filename = db[:schema_migrations].order(:filename).last[:filename]
migration_name = filename[15..-4].camelize
version_number = filename.to_i
generator = Generators::SchemaGenerator.new(db)
generator.generate_in('db')
puts "+ Database migrated to latest version #{version_number} (#{migration_name})"
end
end
end
end
desc 'rollback [--step=N]', 'Revert migrations from the database'
long_desc <<~LONG_DESC.strip
Revert a number of migrations from the database. With no options, only one migration is rolled back.
--step will throw an error if the number of migrations to be rolled back is greater than the number of
migrations already run.
LONG_DESC
option :step, type: :numeric,
desc: 'Revert the given number of migrations'
def rollback
# Load the database
Sequel.sqlite(Config.config.db_path) do |db|
# Validate that the steps to rollback is not greater than the completed migrations
if options[:step]
migration_count = db[:schema_migrations].count
if options[:step] > migration_count
raise Error, "ERROR: Number of migrations to rollback less than #{options[:step] || 1}"
end
end
filename = db[:schema_migrations].order(:filename).map(:filename)[options[:step] ? -options[:step] - 1 : -2]
migration_name = filename[15..-4].camelize
version_number = filename.to_i
# Roll back the database to the given version
Sequel::Migrator.run(db, 'db/migrations', target: version_number)
# Regenerate schema
generator = Generators::SchemaGenerator.new(db)
generator.generate_in('db')
puts "+ Database rolled back to version #{version_number} (#{migration_name})"
end
end
desc 'console [--load-only=one two three]', 'Load an IRB console that allows database interaction'
long_desc <<~LONG_DESC.strip
Load an IRB console that allows interaction with the Geode's database and model classes.
\x5The Bot::Models module is included in the IRB shell; no need to call the full class name
to work with a model class.
When --load-only is given, only the given model classes will be loaded.
\x5When --without-models is given, no models will be loaded.
LONG_DESC
option :load_only, type: :array,
desc: 'Load only the given model classes'
option :without_models, type: :boolean,
desc: 'Load only the database, without model classes'
def console
# Validate that only one option is given at a time
if options[:load_only] && options[:without_models]
raise Error, 'ERROR: Only one of --load-only and --without-models can be given at a time'
end
# Validate that all given models exist if load_only is given and add their paths to config if so
if options[:load_only]
options[:load_only].each do |model_name|
model_paths = Array.new
if File.exist?(path = "app/models/#{model_name.underscore}.rb")
model_paths.push(path)
elsif File.exist?(path = "app/models/#{model_name.underscore}_singleton.rb")
model_paths.push(path)
else
raise Error, "ERROR: Model #{model_name} not found"
end
Config.config.models_to_load = model_paths
end
elsif !options[:without_models]
Config.config.models_to_load = Dir['app/models/*.rb']
end
# Load IRB console script
load 'geode/console.rb'
end
desc 'reset', 'Wipe the database and regenerate it using the current schema'
long_desc <<~LONG_DESC.strip
Wipe the database and regenerate it using the current schema. Does not affect the schema_migrations table.
Do not run this command unless you are sure of what you're doing.
If the option --tables=one two three is given, only the given tables will be reset, provided any tables that
are dependent on them are given as option arguments to be reset as well.
LONG_DESC
option :tables, type: :array,
desc: 'Reset only the given tables'
def reset
# Load the database
Sequel.sqlite(Config.config.db_path) do |db|
# Validate that if tables option is given, all given tables exist, none of them are schema_migrations, and
# either have no dependent tables or all dependent tables are included in the arguments
if options[:tables]
options[:tables].each do |table_name|
raise Error, 'ERROR: Table schema_migrations cannot be reset' if table_name == 'schema_migrations'
if db.table_exists?(table_name.to_sym)
dependent_tables = db.tables.select do |key|
db.foreign_key_list(key).any? { |fk| fk[:table] == table_name.to_sym }
end
unless dependent_tables.all? { |k| options[:tables].include? k.to_s }
raise Error, "ERROR: Table #{table_name} has dependencies"
end
else
raise Error, "ERROR: Table #{table_name} not found"
end
end
end
tables_to_reset = options[:tables] ? options[:tables].map(&:to_sym) : db.tables - [:schema_migrations]
# Verify that user wants to reset database
puts 'WARNING: THIS COMMAND WILL RESULT IN LOSS OF DATA!'
print 'Are you sure you want to reset? [y/n] '
response = STDIN.gets.chomp
until %w(y n).include? response.downcase
print 'Please enter a valid response. '
response = STDIN.gets.chomp
end
# If user has confirmed:
if response == 'y'
dependent_tables = tables_to_reset.select { |k| db.foreign_key_list(k).any? }
remaining_tables = tables_to_reset - dependent_tables
# Drop tables, beginning with dependent tables
db.drop_table(*dependent_tables)
db.drop_table(*remaining_tables)
# Load schema
load 'db/schema.rb'
if options[:tables]
puts '- Given tables regenerated from scratch using current schema db/schema.rb'
else
puts '- Database regenerated from scratch using current schema db/schema.rb'
end
end
end
end
end