declare_schema 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -1
  3. data/Gemfile.lock +1 -1
  4. data/lib/declare_schema/model/foreign_key_definition.rb +4 -8
  5. data/lib/declare_schema/model/index_definition.rb +0 -19
  6. data/lib/declare_schema/schema_change/all.rb +22 -0
  7. data/lib/declare_schema/schema_change/base.rb +45 -0
  8. data/lib/declare_schema/schema_change/column_add.rb +27 -0
  9. data/lib/declare_schema/schema_change/column_change.rb +32 -0
  10. data/lib/declare_schema/schema_change/column_remove.rb +20 -0
  11. data/lib/declare_schema/schema_change/column_rename.rb +23 -0
  12. data/lib/declare_schema/schema_change/foreign_key_add.rb +25 -0
  13. data/lib/declare_schema/schema_change/foreign_key_remove.rb +20 -0
  14. data/lib/declare_schema/schema_change/index_add.rb +33 -0
  15. data/lib/declare_schema/schema_change/index_remove.rb +20 -0
  16. data/lib/declare_schema/schema_change/primary_key_change.rb +33 -0
  17. data/lib/declare_schema/schema_change/table_add.rb +37 -0
  18. data/lib/declare_schema/schema_change/table_change.rb +36 -0
  19. data/lib/declare_schema/schema_change/table_remove.rb +22 -0
  20. data/lib/declare_schema/schema_change/table_rename.rb +22 -0
  21. data/lib/declare_schema/version.rb +1 -1
  22. data/lib/generators/declare_schema/migration/migrator.rb +172 -174
  23. data/spec/lib/declare_schema/interactive_primary_key_spec.rb +52 -14
  24. data/spec/lib/declare_schema/migration_generator_spec.rb +175 -303
  25. data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +0 -12
  26. data/spec/lib/declare_schema/schema_change/base_spec.rb +75 -0
  27. data/spec/lib/declare_schema/schema_change/column_add_spec.rb +30 -0
  28. data/spec/lib/declare_schema/schema_change/column_change_spec.rb +33 -0
  29. data/spec/lib/declare_schema/schema_change/column_remove_spec.rb +30 -0
  30. data/spec/lib/declare_schema/schema_change/column_rename_spec.rb +28 -0
  31. data/spec/lib/declare_schema/schema_change/foreign_key_add_spec.rb +29 -0
  32. data/spec/lib/declare_schema/schema_change/foreign_key_remove_spec.rb +29 -0
  33. data/spec/lib/declare_schema/schema_change/index_add_spec.rb +56 -0
  34. data/spec/lib/declare_schema/schema_change/index_remove_spec.rb +29 -0
  35. data/spec/lib/declare_schema/schema_change/primary_key_change_spec.rb +69 -0
  36. data/spec/lib/declare_schema/schema_change/table_add_spec.rb +50 -0
  37. data/spec/lib/declare_schema/schema_change/table_change_spec.rb +30 -0
  38. data/spec/lib/declare_schema/schema_change/table_remove_spec.rb +27 -0
  39. data/spec/lib/declare_schema/schema_change/table_rename_spec.rb +27 -0
  40. data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +59 -11
  41. data/spec/support/acceptance_spec_helpers.rb +2 -2
  42. metadata +34 -5
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module DeclareSchema
6
+ module SchemaChange
7
+ class TableChange < Base
8
+ def initialize(table_name, old_options, new_options)
9
+ @table_name = table_name
10
+ @old_options = old_options
11
+ @new_options = new_options
12
+ end
13
+
14
+ def up_command
15
+ alter_table(@table_name, @new_options)
16
+ end
17
+
18
+ def down_command
19
+ alter_table(@table_name, @old_options)
20
+ end
21
+
22
+ private
23
+
24
+ TABLE_OPTIONS_TO_SQL_MAPPINGS = {
25
+ charset: 'CHARACTER SET',
26
+ collation: 'COLLATE'
27
+ }.freeze
28
+
29
+ def alter_table(table_name, options)
30
+ sql_options = options.map { |key, value| [TABLE_OPTIONS_TO_SQL_MAPPINGS[key], value] }
31
+ statement = "ALTER TABLE #{ActiveRecord::Base.connection.quote_table_name(table_name)} #{sql_options.join(' ')}"
32
+ "execute #{statement.inspect}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module DeclareSchema
6
+ module SchemaChange
7
+ class TableRemove < Base
8
+ def initialize(table_name, add_table_back)
9
+ @table_name = table_name
10
+ @add_table_back = add_table_back
11
+ end
12
+
13
+ def up_command
14
+ "drop_table #{@table_name.to_sym.inspect}"
15
+ end
16
+
17
+ def down_command
18
+ @add_table_back
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module DeclareSchema
6
+ module SchemaChange
7
+ class TableRename < Base
8
+ def initialize(old_name, new_name)
9
+ @old_name = old_name
10
+ @new_name = new_name
11
+ end
12
+
13
+ def up_command
14
+ "rename_table #{@old_name.to_sym.inspect}, #{@new_name.to_sym.inspect}"
15
+ end
16
+
17
+ def down_command
18
+ "rename_table #{@new_name.to_sym.inspect}, #{@old_name.to_sym.inspect}"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclareSchema
4
- VERSION = "0.10.0"
4
+ VERSION = "0.10.1"
5
5
  end
@@ -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 = {})
50
+ def initialize(ambiguity_resolver = {}, renames: nil)
52
51
  @ambiguity_resolver = ambiguity_resolver
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)
@@ -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