declare_schema 0.9.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/declare_schema_build.yml +1 -1
  3. data/CHANGELOG.md +32 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +12 -2
  6. data/lib/declare_schema.rb +12 -1
  7. data/lib/declare_schema/dsl.rb +39 -0
  8. data/lib/declare_schema/extensions/active_record/fields_declaration.rb +22 -3
  9. data/lib/declare_schema/model.rb +4 -6
  10. data/lib/declare_schema/model/column.rb +1 -1
  11. data/lib/declare_schema/model/foreign_key_definition.rb +6 -11
  12. data/lib/declare_schema/model/index_definition.rb +1 -20
  13. data/lib/declare_schema/schema_change/all.rb +22 -0
  14. data/lib/declare_schema/schema_change/base.rb +45 -0
  15. data/lib/declare_schema/schema_change/column_add.rb +27 -0
  16. data/lib/declare_schema/schema_change/column_change.rb +32 -0
  17. data/lib/declare_schema/schema_change/column_remove.rb +20 -0
  18. data/lib/declare_schema/schema_change/column_rename.rb +23 -0
  19. data/lib/declare_schema/schema_change/foreign_key_add.rb +25 -0
  20. data/lib/declare_schema/schema_change/foreign_key_remove.rb +20 -0
  21. data/lib/declare_schema/schema_change/index_add.rb +33 -0
  22. data/lib/declare_schema/schema_change/index_remove.rb +20 -0
  23. data/lib/declare_schema/schema_change/primary_key_change.rb +33 -0
  24. data/lib/declare_schema/schema_change/table_add.rb +37 -0
  25. data/lib/declare_schema/schema_change/table_change.rb +36 -0
  26. data/lib/declare_schema/schema_change/table_remove.rb +22 -0
  27. data/lib/declare_schema/schema_change/table_rename.rb +22 -0
  28. data/lib/declare_schema/version.rb +1 -1
  29. data/lib/generators/declare_schema/migration/USAGE +14 -24
  30. data/lib/generators/declare_schema/migration/migration_generator.rb +40 -38
  31. data/lib/generators/declare_schema/migration/migrator.rb +175 -177
  32. data/lib/generators/declare_schema/migration/templates/migration.rb.erb +3 -3
  33. data/lib/generators/declare_schema/support/model.rb +4 -4
  34. data/spec/lib/declare_schema/api_spec.rb +8 -8
  35. data/spec/lib/declare_schema/field_declaration_dsl_spec.rb +41 -15
  36. data/spec/lib/declare_schema/field_spec_spec.rb +2 -2
  37. data/spec/lib/declare_schema/generator_spec.rb +5 -5
  38. data/spec/lib/declare_schema/interactive_primary_key_spec.rb +117 -28
  39. data/spec/lib/declare_schema/migration_generator_spec.rb +1990 -843
  40. data/spec/lib/declare_schema/model/column_spec.rb +49 -23
  41. data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +158 -57
  42. data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +0 -2
  43. data/spec/lib/declare_schema/model/index_definition_spec.rb +189 -78
  44. data/spec/lib/declare_schema/model/table_options_definition_spec.rb +75 -11
  45. data/spec/lib/declare_schema/schema_change/base_spec.rb +75 -0
  46. data/spec/lib/declare_schema/schema_change/column_add_spec.rb +30 -0
  47. data/spec/lib/declare_schema/schema_change/column_change_spec.rb +33 -0
  48. data/spec/lib/declare_schema/schema_change/column_remove_spec.rb +30 -0
  49. data/spec/lib/declare_schema/schema_change/column_rename_spec.rb +28 -0
  50. data/spec/lib/declare_schema/schema_change/foreign_key_add_spec.rb +29 -0
  51. data/spec/lib/declare_schema/schema_change/foreign_key_remove_spec.rb +29 -0
  52. data/spec/lib/declare_schema/schema_change/index_add_spec.rb +56 -0
  53. data/spec/lib/declare_schema/schema_change/index_remove_spec.rb +29 -0
  54. data/spec/lib/declare_schema/schema_change/primary_key_change_spec.rb +69 -0
  55. data/spec/lib/declare_schema/schema_change/table_add_spec.rb +50 -0
  56. data/spec/lib/declare_schema/schema_change/table_change_spec.rb +30 -0
  57. data/spec/lib/declare_schema/schema_change/table_remove_spec.rb +27 -0
  58. data/spec/lib/declare_schema/schema_change/table_rename_spec.rb +27 -0
  59. data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +59 -11
  60. data/spec/spec_helper.rb +1 -1
  61. data/spec/support/acceptance_spec_helpers.rb +2 -2
  62. metadata +35 -6
  63. data/test_responses.txt +0 -2
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'active_record'
4
4
  require 'active_record/connection_adapters/abstract_adapter'
5
+ require 'declare_schema/schema_change/all'
5
6
 
6
7
  module Generators
7
8
  module DeclareSchema
@@ -24,9 +25,7 @@ module Generators
24
25
  end
25
26
 
26
27
  def run(renames = {})
27
- g = Migrator.new
28
- g.renames = renames
29
- g.generate
28
+ Migrator.new(renames: renames).generate
30
29
  end
31
30
 
32
31
  def default_migration_name
@@ -48,14 +47,12 @@ module Generators
48
47
  deprecate :default_charset=, :default_collation=, :default_charset, :default_collation, deprecator: ActiveSupport::Deprecation.new('1.0', 'declare_schema')
49
48
  end
50
49
 
51
- def initialize(ambiguity_resolver = {})
52
- @ambiguity_resolver = ambiguity_resolver
50
+ def initialize(renames: nil, &block)
51
+ @ambiguity_resolver = block
53
52
  @drops = []
54
- @renames = nil
53
+ @renames = renames
55
54
  end
56
55
 
57
- attr_accessor :renames
58
-
59
56
  def load_rails_models
60
57
  ActiveRecord::Migration.verbose = false
61
58
 
@@ -109,19 +106,24 @@ module Generators
109
106
  # return a hash of table renames and modifies the passed arrays so
110
107
  # that renamed tables are no longer listed as to_create or to_drop
111
108
  def extract_table_renames!(to_create, to_drop)
112
- if renames
109
+ if @renames
113
110
  # A hash of table renames has been provided
114
111
 
115
112
  to_rename = {}
116
- renames.each_pair do |old_name, new_name|
117
- new_name = new_name[:table_name] if new_name.is_a?(Hash)
118
- next unless new_name
119
-
120
- if to_create.delete(new_name.to_s) && to_drop.delete(old_name.to_s)
121
- to_rename[old_name.to_s] = new_name.to_s
122
- else
123
- raise Error, "Invalid table rename specified: #{old_name} => #{new_name}"
113
+ @renames.each do |old_name, new_name|
114
+ if new_name.is_a?(Hash)
115
+ new_name = new_name[:table_name]
124
116
  end
117
+ new_name or next
118
+
119
+ old_name = old_name.to_s
120
+ new_name = new_name.to_s
121
+
122
+ to_create.delete(new_name) or raise Error,
123
+ "Rename specified new name: #{new_name.inspect} but it was not in the `to_create` list"
124
+ to_drop.delete(old_name) or raise Error,
125
+ "Rename specified old name: #{old_name.inspect} but it was not in the `to_drop` list"
126
+ to_rename[old_name] = new_name
125
127
  end
126
128
  to_rename
127
129
 
@@ -133,18 +135,22 @@ module Generators
133
135
  end
134
136
  end
135
137
 
138
+ # return a hash of column renames and modifies the passed arrays so
139
+ # that renamed columns are no longer listed as to_create or to_drop
136
140
  def extract_column_renames!(to_add, to_remove, table_name)
137
- if renames
141
+ if @renames
138
142
  to_rename = {}
139
- if (column_renames = renames&.[](table_name.to_sym))
140
- # A hash of table renames has been provided
141
-
142
- column_renames.each_pair do |old_name, new_name|
143
- if to_add.delete(new_name.to_s) && to_remove.delete(old_name.to_s)
144
- to_rename[old_name.to_s] = new_name.to_s
145
- else
146
- raise Error, "Invalid rename specified: #{old_name} => #{new_name}"
147
- end
143
+ if (column_renames = @renames[table_name.to_sym])
144
+ # A hash of column renames has been provided
145
+
146
+ column_renames.each do |old_name, new_name|
147
+ old_name = old_name.to_s
148
+ new_name = new_name.to_s
149
+ to_add.delete(new_name) or raise Error,
150
+ "Rename specified new name: #{new_name.inspect} but it was not in the `to_add` list for table #{table_name}"
151
+ to_remove.delete(old_name) or raise Error,
152
+ "Rename specified old name: #{old_name.inspect} but it was not in the `to_remove` list for table #{table_name}"
153
+ to_rename[old_name] = new_name
148
154
  end
149
155
  end
150
156
  to_rename
@@ -201,85 +207,103 @@ module Generators
201
207
  to_rename = extract_table_renames!(to_create, to_drop)
202
208
 
203
209
  renames = to_rename.map do |old_name, new_name|
204
- "rename_table :#{old_name}, :#{new_name}"
205
- end * "\n"
206
- undo_renames = to_rename.map do |old_name, new_name|
207
- "rename_table :#{new_name}, :#{old_name}"
208
- end * "\n"
210
+ ::DeclareSchema::SchemaChange::TableRename.new(old_name, new_name)
211
+ end
209
212
 
210
213
  drops = to_drop.map do |t|
211
- "drop_table :#{t}"
212
- end * "\n"
213
- undo_drops = to_drop.map do |t|
214
- add_table_back(t)
215
- end * "\n\n"
214
+ ::DeclareSchema::SchemaChange::TableRemove.new(t, add_table_back(t))
215
+ end
216
216
 
217
217
  creates = to_create.map do |t|
218
- create_table(models_by_table_name[t])
219
- end * "\n\n"
220
- undo_creates = to_create.map do |t|
221
- "drop_table :#{t}"
222
- end * "\n"
218
+ model = models_by_table_name[t]
219
+ disable_auto_increment = model.try(:disable_auto_increment)
220
+
221
+ primary_key_definition =
222
+ if disable_auto_increment
223
+ [:integer, :id, limit: 8, auto_increment: false, primary_key: true]
224
+ end
225
+
226
+ field_definitions = model.field_specs.values.sort_by(&:position).map do |f|
227
+ [f.type, f.name, f.sql_options]
228
+ end
229
+
230
+ table_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, table_options_for_model(model))
231
+ table_options = create_table_options(model, disable_auto_increment)
232
+
233
+ table_add = ::DeclareSchema::SchemaChange::TableAdd.new(t,
234
+ Array(primary_key_definition) + field_definitions,
235
+ table_options,
236
+ sql_options: table_options_definition.settings)
237
+ [
238
+ table_add,
239
+ *Array((create_indexes(model) if ::DeclareSchema.default_generate_indexing)),
240
+ *Array((create_constraints(model) if ::DeclareSchema.default_generate_foreign_keys))
241
+ ]
242
+ end
223
243
 
224
244
  changes = []
225
- undo_changes = []
226
245
  index_changes = []
227
- undo_index_changes = []
228
246
  fk_changes = []
229
- undo_fk_changes = []
230
247
  table_options_changes = []
231
- undo_table_options_changes = []
232
248
 
233
249
  to_change.each do |t|
234
250
  model = models_by_table_name[t]
235
251
  table = to_rename.key(t) || model.table_name
236
252
  if table.in?(db_tables)
237
- change, undo, index_change, undo_index, fk_change, undo_fk, table_options_change, undo_table_options_change = change_table(model, table)
253
+ change, index_change, fk_change, table_options_change = change_table(model, table)
238
254
  changes << change
239
- undo_changes << undo
240
255
  index_changes << index_change
241
- undo_index_changes << undo_index
242
256
  fk_changes << fk_change
243
- undo_fk_changes << undo_fk
244
257
  table_options_changes << table_options_change
245
- undo_table_options_changes << undo_table_options_change
246
258
  end
247
259
  end
248
260
 
249
- up = [renames, drops, creates, changes, index_changes, fk_changes, table_options_changes].flatten.reject(&:blank?) * "\n\n"
250
- down = [undo_changes, undo_renames, undo_drops, undo_creates, undo_index_changes, undo_fk_changes, undo_table_options_changes].flatten.reject(&:blank?) * "\n\n"
261
+ migration_commands = [renames, drops, creates, changes, index_changes, fk_changes, table_options_changes].flatten
262
+
263
+ ordered_migration_commands = order_migrations(migration_commands)
251
264
 
252
- [up, down]
265
+ up_and_down_migrations(ordered_migration_commands)
253
266
  end
254
267
 
255
- def create_table(model)
256
- longest_field_name = model.field_specs.values.map { |f| f.type.to_s.length }.max
257
- disable_auto_increment = model.respond_to?(:disable_auto_increment) && model.disable_auto_increment
258
- table_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, table_options_for_model(model))
259
- field_definitions = [
260
- ("t.integer :id, limit: 8, auto_increment: false, primary_key: true" if disable_auto_increment),
261
- *(model.field_specs.values.sort_by(&:position).map { |f| create_field(f, longest_field_name) })
262
- ].compact
268
+ MIGRATION_ORDER = %w[ TableRename
269
+ TableAdd
270
+ TableChange
271
+ ColumnAdd
272
+ ColumnRename
273
+ ColumnChange
274
+ PrimaryKeyChange
275
+ IndexAdd
276
+ ForeignKeyAdd
277
+ ForeignKeyRemove
278
+ IndexRemove
279
+ ColumnRemove
280
+ TableRemove ]
263
281
 
264
- <<~EOS.strip
265
- create_table :#{model.table_name}, #{create_table_options(model, disable_auto_increment)} do |t|
266
- #{field_definitions.join("\n")}
267
- end
282
+ def order_migrations(migration_commands)
283
+ migration_commands.each_with_index.sort_by do |command, index|
284
+ command_type = command.class.name.gsub(/.*::/, '')
285
+ priority = MIGRATION_ORDER.index(command_type) or raise "#{command_type.inspect} not found in #{MIGRATION_ORDER.inspect}"
286
+ [priority, index] # index keeps the sort stable in case of a tie
287
+ end.map(&:first) # remove the index
288
+ end
289
+
290
+ private
291
+
292
+ def up_and_down_migrations(migration_commands)
293
+ up = migration_commands.map(&:up ).select(&:present?)
294
+ down = migration_commands.map(&:down).select(&:present?).reverse
268
295
 
269
- #{table_options_definition.alter_table_statement unless ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)}
270
- #{create_indexes(model).join("\n") if ::DeclareSchema.default_generate_indexing}
271
- #{create_constraints(model).join("\n") if ::DeclareSchema.default_generate_foreign_keys}
272
- EOS
296
+ [up * "\n\n", down * "\n\n"]
273
297
  end
274
298
 
275
299
  def create_table_options(model, disable_auto_increment)
276
300
  primary_key = model._defined_primary_key
277
301
  if primary_key.blank? || disable_auto_increment
278
- "id: false"
302
+ { id: false }
279
303
  elsif primary_key == "id"
280
- "id: :bigint"
304
+ { id: :bigint }
281
305
  else
282
- "primary_key: :#{primary_key}"
306
+ { primary_key: primary_key.to_sym }
283
307
  end
284
308
  end
285
309
 
@@ -294,18 +318,18 @@ module Generators
294
318
  end
295
319
  end
296
320
 
321
+ # TODO: TECH-5338: optimize that index doesn't need to be dropped on undo since entire table will be dropped
297
322
  def create_indexes(model)
298
- model.index_definitions.map { |i| i.to_add_statement(model.table_name) }
323
+ model.index_definitions.map do |i|
324
+ ::DeclareSchema::SchemaChange::IndexAdd.new(model.table_name, i.columns, unique: i.unique, where: i.where, name: i.name)
325
+ end
299
326
  end
300
327
 
301
328
  def create_constraints(model)
302
- model.constraint_specs.map { |fk| fk.to_add_statement }
303
- end
304
-
305
- def create_field(field_spec, field_name_width)
306
- options = field_spec.sql_options.merge(fk_field_options(field_spec.model, field_spec.name))
307
- args = [field_spec.name.inspect] + format_options(options.compact)
308
- format("t.%-*s %s", field_name_width, field_spec.type, args.join(', '))
329
+ model.constraint_specs.map do |fk|
330
+ ::DeclareSchema::SchemaChange::ForeignKeyAdd.new(fk.child_table_name, fk.parent_table_name,
331
+ column_name: fk.foreign_key_name, name: fk.constraint_name)
332
+ end
309
333
  end
310
334
 
311
335
  def change_table(model, current_table_name)
@@ -332,37 +356,28 @@ module Generators
332
356
  to_change = db_column_names & model_column_names
333
357
 
334
358
  renames = to_rename.map do |old_name, new_name|
335
- "rename_column :#{new_table_name}, :#{old_name}, :#{new_name}"
336
- end
337
- undo_renames = to_rename.map do |old_name, new_name|
338
- "rename_column :#{new_table_name}, :#{new_name}, :#{old_name}"
359
+ ::DeclareSchema::SchemaChange::ColumnRename.new(new_table_name, old_name, new_name)
339
360
  end
340
361
 
341
- to_add = to_add.sort_by { |c| model.field_specs[c]&.position || 0 }
362
+ to_add.sort_by! { |c| model.field_specs[c]&.position || 0 }
363
+
342
364
  adds = to_add.map do |c|
343
- args =
365
+ type, options =
344
366
  if (spec = model.field_specs[c])
345
- options = spec.sql_options.merge(fk_field_options(model, c))
346
- [":#{spec.type}", *format_options(options.compact)]
367
+ [spec.type, spec.sql_options.merge(fk_field_options(model, c)).compact]
347
368
  else
348
- [":integer"]
369
+ [:integer, {}]
349
370
  end
350
- ["add_column :#{new_table_name}, :#{c}", *args].join(', ')
351
- end
352
- undo_adds = to_add.map do |c|
353
- "remove_column :#{new_table_name}, :#{c}"
371
+ ::DeclareSchema::SchemaChange::ColumnAdd.new(new_table_name, c, type, options)
354
372
  end
355
373
 
356
374
  removes = to_remove.map do |c|
357
- "remove_column :#{new_table_name}, :#{c}"
358
- end
359
- undo_removes = to_remove.map do |c|
360
- add_column_back(model, current_table_name, c)
375
+ old_type, old_options = add_column_back(model, current_table_name, c)
376
+ ::DeclareSchema::SchemaChange::ColumnRemove.new(new_table_name, c, old_type, old_options)
361
377
  end
362
378
 
363
379
  old_names = to_rename.invert
364
380
  changes = []
365
- undo_changes = []
366
381
  to_change.each do |col_name_to_change|
367
382
  orig_col_name = old_names[col_name_to_change] || col_name_to_change
368
383
  column = db_columns[orig_col_name] or raise "failed to find column info for #{orig_col_name.inspect}"
@@ -374,36 +389,33 @@ module Generators
374
389
 
375
390
  if !::DeclareSchema::Model::Column.equivalent_schema_attributes?(normalized_schema_attrs, col_attrs)
376
391
  type = normalized_schema_attrs.delete(:type) or raise "no :type found in #{normalized_schema_attrs.inspect}"
377
- changes << ["change_column #{new_table_name.to_sym.inspect}", col_name_to_change.to_sym.inspect,
378
- type.to_sym.inspect, *format_options(normalized_schema_attrs)].join(", ")
379
- undo_changes << change_column_back(model, current_table_name, orig_col_name)
392
+ old_type, old_options = change_column_back(model, current_table_name, orig_col_name)
393
+ changes << ::DeclareSchema::SchemaChange::ColumnChange.new(new_table_name, col_name_to_change,
394
+ new_type: type, new_options: normalized_schema_attrs,
395
+ old_type: old_type, old_options: old_options)
380
396
  end
381
397
  end
382
398
 
383
- index_changes, undo_index_changes = change_indexes(model, current_table_name, to_remove)
384
- fk_changes, undo_fk_changes = if ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
385
- [[], []]
386
- else
387
- change_foreign_key_constraints(model, current_table_name)
388
- end
389
- table_options_changes, undo_table_options_changes = if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
390
- change_table_options(model, current_table_name)
391
- else
392
- [[], []]
393
- end
394
-
395
- [(renames + adds + removes + changes) * "\n",
396
- (undo_renames + undo_adds + undo_removes + undo_changes) * "\n",
397
- index_changes * "\n",
398
- undo_index_changes * "\n",
399
- fk_changes * "\n",
400
- undo_fk_changes * "\n",
401
- table_options_changes * "\n",
402
- undo_table_options_changes * "\n"]
399
+ index_changes = change_indexes(model, current_table_name, to_rename)
400
+ fk_changes = if ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
401
+ []
402
+ else
403
+ change_foreign_key_constraints(model, current_table_name)
404
+ end
405
+ table_options_changes = if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
406
+ change_table_options(model, current_table_name)
407
+ else
408
+ []
409
+ end
410
+
411
+ [(renames + adds + removes + changes),
412
+ index_changes,
413
+ fk_changes,
414
+ table_options_changes]
403
415
  end
404
416
 
405
- def change_indexes(model, old_table_name, to_remove)
406
- ::DeclareSchema.default_generate_indexing or return [[], []]
417
+ def change_indexes(model, old_table_name, to_rename)
418
+ ::DeclareSchema.default_generate_indexing or return []
407
419
 
408
420
  new_table_name = model.table_name
409
421
  existing_indexes = ::DeclareSchema::Model::IndexDefinition.for_model(model, old_table_name)
@@ -415,69 +427,54 @@ module Generators
415
427
  end
416
428
  end || i
417
429
  end
418
- existing_has_primary_key = existing_indexes.any? do |i|
419
- i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME &&
420
- !i.fields.all? { |f| to_remove.include?(f) } # if we're removing the primary key column(s), the primary key index will be removed too
430
+ existing_primary_keys, existing_indexes_without_primary_key = existing_indexes.partition { |i| i.primary_key? }
431
+ defined_primary_keys, model_indexes_without_primary_key = model_indexes.partition { |i| i.primary_key? }
432
+ existing_primary_keys.size <= 1 or raise "too many existing primary keys! #{existing_primary_keys.inspect}"
433
+ defined_primary_keys.size <= 1 or raise "too many defined primary keys! #{defined_primary_keys.inspect}"
434
+ existing_primary_key = existing_primary_keys.first
435
+ defined_primary_key = defined_primary_keys.first
436
+
437
+ existing_primary_key_columns = (existing_primary_key&.columns || []).map { |col_name| to_rename[col_name] || col_name }
438
+
439
+ if !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
440
+ change_primary_key =
441
+ if (existing_primary_key || defined_primary_key) &&
442
+ existing_primary_key_columns != defined_primary_key&.columns
443
+ ::DeclareSchema::SchemaChange::PrimaryKeyChange.new(new_table_name, existing_primary_key_columns, defined_primary_key&.columns)
444
+ end
421
445
  end
422
- model_has_primary_key = model_indexes.any? { |i| i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME }
423
446
 
424
- undo_add_indexes = []
425
- add_indexes = (model_indexes - existing_indexes).map do |i|
426
- undo_add_indexes << drop_index(old_table_name, i.name) unless i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME
427
- i.to_add_statement(new_table_name, existing_has_primary_key)
447
+ drop_indexes = (existing_indexes_without_primary_key - model_indexes_without_primary_key).map do |i|
448
+ ::DeclareSchema::SchemaChange::IndexRemove.new(new_table_name, i.columns, unique: i.unique, where: i.where, name: i.name)
428
449
  end
429
- undo_drop_indexes = []
430
- drop_indexes = (existing_indexes - model_indexes).map do |i|
431
- undo_drop_indexes << i.to_add_statement(old_table_name, model_has_primary_key)
432
- drop_index(new_table_name, i.name) unless i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME
433
- end.compact
434
450
 
435
- # the order is important here - adding a :unique, for instance needs to remove then add
436
- [drop_indexes + add_indexes, undo_add_indexes + undo_drop_indexes]
437
- end
451
+ add_indexes = (model_indexes_without_primary_key - existing_indexes_without_primary_key).map do |i|
452
+ ::DeclareSchema::SchemaChange::IndexAdd.new(new_table_name, i.columns, unique: i.unique, where: i.where, name: i.name)
453
+ end
438
454
 
439
- def drop_index(table, name)
440
- # see https://hobo.lighthouseapp.com/projects/8324/tickets/566
441
- # for why the rescue exists
442
- "remove_index :#{table}, name: :#{name} rescue ActiveRecord::StatementInvalid"
455
+ # the order is important here - adding a :unique, for instance needs to remove then add
456
+ [Array(change_primary_key) + drop_indexes + add_indexes]
443
457
  end
444
458
 
445
459
  def change_foreign_key_constraints(model, old_table_name)
446
460
  ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/) and raise ArgumentError, 'SQLite does not support foreign keys'
447
- ::DeclareSchema.default_generate_foreign_keys or return [[], []]
461
+ ::DeclareSchema.default_generate_foreign_keys or return []
448
462
 
449
- new_table_name = model.table_name
450
463
  existing_fks = ::DeclareSchema::Model::ForeignKeyDefinition.for_model(model, old_table_name)
451
464
  model_fks = model.constraint_specs
452
465
 
453
- undo_add_fks = []
454
- add_fks = (model_fks - existing_fks).map do |fk|
455
- # next if fk.parent.constantize.abstract_class || fk.parent == fk.model.class_name
456
- undo_add_fks << remove_foreign_key(old_table_name, fk.constraint_name)
457
- fk.to_add_statement
458
- end
459
-
460
- undo_drop_fks = []
461
466
  drop_fks = (existing_fks - model_fks).map do |fk|
462
- undo_drop_fks << fk.to_add_statement
463
- remove_foreign_key(new_table_name, fk.constraint_name)
467
+ ::DeclareSchema::SchemaChange::ForeignKeyRemove.new(fk.child_table_name, fk.parent_table_name,
468
+ column_name: fk.foreign_key_name, name: fk.constraint_name)
464
469
  end
465
470
 
466
- [drop_fks + add_fks, undo_add_fks + undo_drop_fks]
467
- end
468
-
469
- def remove_foreign_key(old_table_name, fk_name)
470
- "remove_foreign_key(#{old_table_name.inspect}, name: #{fk_name.to_s.inspect})"
471
- end
472
-
473
- def format_options(options)
474
- options.map do |k, v|
475
- if k.is_a?(Symbol)
476
- "#{k}: #{v.inspect}"
477
- else
478
- "#{k.inspect} => #{v.inspect}"
479
- end
471
+ add_fks = (model_fks - existing_fks).map do |fk|
472
+ # next if fk.parent.constantize.abstract_class || fk.parent == fk.model.class_name
473
+ ::DeclareSchema::SchemaChange::ForeignKeyAdd.new(fk.child_table_name, fk.parent_table_name,
474
+ column_name: fk.foreign_key_name, name: fk.constraint_name)
480
475
  end
476
+
477
+ [drop_fks + add_fks]
481
478
  end
482
479
 
483
480
  def fk_field_options(model, field_name)
@@ -486,7 +483,7 @@ module Generators
486
483
  parent_columns = connection.columns(parent_table) rescue []
487
484
  pk_limit =
488
485
  if (pk_column = parent_columns.find { |column| column.name.to_s == "id" }) # right now foreign keys assume id is the target
489
- if Rails::VERSION::MAJOR < 5
486
+ if ActiveSupport::VERSION::MAJOR < 5
490
487
  pk_column.cast_type.limit
491
488
  else
492
489
  pk_column.limit
@@ -506,11 +503,12 @@ module Generators
506
503
  new_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, table_options_for_model(model))
507
504
 
508
505
  if old_options_definition.equivalent?(new_options_definition)
509
- [[], []]
506
+ []
510
507
  else
511
508
  [
512
- [new_options_definition.alter_table_statement],
513
- [old_options_definition.alter_table_statement]
509
+ ::DeclareSchema::SchemaChange::TableChange.new(current_table_name,
510
+ old_options_definition.settings,
511
+ new_options_definition.settings)
514
512
  ]
515
513
  end
516
514
  end
@@ -528,7 +526,7 @@ module Generators
528
526
  col_spec = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
529
527
  schema_attributes = col_spec.schema_attributes
530
528
  type = schema_attributes.delete(:type) or raise "no :type found in #{schema_attributes.inspect}"
531
- ["add_column :#{current_table_name}, :#{col_name}, #{type.inspect}", *format_options(schema_attributes)].join(', ')
529
+ [type, schema_attributes]
532
530
  end
533
531
  end
534
532
 
@@ -538,7 +536,7 @@ module Generators
538
536
  col_spec = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
539
537
  schema_attributes = col_spec.schema_attributes
540
538
  type = schema_attributes.delete(:type) or raise "no :type found in #{schema_attributes.inspect}"
541
- ["change_column #{current_table_name.to_sym.inspect}", col_name.to_sym.inspect, type.to_sym.inspect, *format_options(schema_attributes)].join(', ')
539
+ [type, schema_attributes]
542
540
  end
543
541
  end
544
542
 
@@ -551,7 +549,7 @@ module Generators
551
549
  end
552
550
  end
553
551
 
554
- SchemaDumper = case Rails::VERSION::MAJOR
552
+ SchemaDumper = case ActiveSupport::VERSION::MAJOR
555
553
  when 4
556
554
  ActiveRecord::SchemaDumper
557
555
  else