declare_schema 0.8.0.pre.6 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/declare_schema_build.yml +1 -1
- data/CHANGELOG.md +28 -1
- data/Gemfile.lock +1 -1
- data/README.md +91 -13
- data/lib/declare_schema.rb +46 -0
- data/lib/declare_schema/dsl.rb +39 -0
- data/lib/declare_schema/extensions/active_record/fields_declaration.rb +23 -4
- data/lib/declare_schema/model.rb +51 -59
- data/lib/declare_schema/model/field_spec.rb +11 -8
- data/lib/declare_schema/model/foreign_key_definition.rb +4 -8
- data/lib/declare_schema/model/habtm_model_shim.rb +1 -1
- data/lib/declare_schema/model/index_definition.rb +0 -19
- data/lib/declare_schema/schema_change/all.rb +22 -0
- data/lib/declare_schema/schema_change/base.rb +45 -0
- data/lib/declare_schema/schema_change/column_add.rb +27 -0
- data/lib/declare_schema/schema_change/column_change.rb +32 -0
- data/lib/declare_schema/schema_change/column_remove.rb +20 -0
- data/lib/declare_schema/schema_change/column_rename.rb +23 -0
- data/lib/declare_schema/schema_change/foreign_key_add.rb +25 -0
- data/lib/declare_schema/schema_change/foreign_key_remove.rb +20 -0
- data/lib/declare_schema/schema_change/index_add.rb +33 -0
- data/lib/declare_schema/schema_change/index_remove.rb +20 -0
- data/lib/declare_schema/schema_change/primary_key_change.rb +33 -0
- data/lib/declare_schema/schema_change/table_add.rb +37 -0
- data/lib/declare_schema/schema_change/table_change.rb +36 -0
- data/lib/declare_schema/schema_change/table_remove.rb +22 -0
- data/lib/declare_schema/schema_change/table_rename.rb +22 -0
- data/lib/declare_schema/version.rb +1 -1
- data/lib/generators/declare_schema/migration/migrator.rb +189 -202
- data/lib/generators/declare_schema/support/model.rb +4 -4
- data/spec/lib/declare_schema/api_spec.rb +7 -7
- data/spec/lib/declare_schema/field_declaration_dsl_spec.rb +41 -15
- data/spec/lib/declare_schema/field_spec_spec.rb +50 -6
- data/spec/lib/declare_schema/generator_spec.rb +3 -3
- data/spec/lib/declare_schema/interactive_primary_key_spec.rb +116 -26
- data/spec/lib/declare_schema/migration_generator_spec.rb +1891 -845
- data/spec/lib/declare_schema/model/column_spec.rb +47 -17
- data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +134 -57
- data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +3 -3
- data/spec/lib/declare_schema/model/index_definition_spec.rb +188 -77
- data/spec/lib/declare_schema/model/table_options_definition_spec.rb +75 -11
- data/spec/lib/declare_schema/schema_change/base_spec.rb +75 -0
- data/spec/lib/declare_schema/schema_change/column_add_spec.rb +30 -0
- data/spec/lib/declare_schema/schema_change/column_change_spec.rb +33 -0
- data/spec/lib/declare_schema/schema_change/column_remove_spec.rb +30 -0
- data/spec/lib/declare_schema/schema_change/column_rename_spec.rb +28 -0
- data/spec/lib/declare_schema/schema_change/foreign_key_add_spec.rb +29 -0
- data/spec/lib/declare_schema/schema_change/foreign_key_remove_spec.rb +29 -0
- data/spec/lib/declare_schema/schema_change/index_add_spec.rb +56 -0
- data/spec/lib/declare_schema/schema_change/index_remove_spec.rb +29 -0
- data/spec/lib/declare_schema/schema_change/primary_key_change_spec.rb +69 -0
- data/spec/lib/declare_schema/schema_change/table_add_spec.rb +50 -0
- data/spec/lib/declare_schema/schema_change/table_change_spec.rb +30 -0
- data/spec/lib/declare_schema/schema_change/table_remove_spec.rb +27 -0
- data/spec/lib/declare_schema/schema_change/table_rename_spec.rb +27 -0
- data/spec/lib/declare_schema_spec.rb +101 -0
- data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +71 -13
- data/spec/support/acceptance_spec_helpers.rb +2 -2
- metadata +33 -3
- data/test_responses.txt +0 -2
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module DeclareSchema
|
6
|
+
module SchemaChange
|
7
|
+
class TableAdd < Base
|
8
|
+
def initialize(table_name, fields, create_table_options, sql_options: nil)
|
9
|
+
@table_name = table_name
|
10
|
+
fields.all? do |type, name, options|
|
11
|
+
type.is_a?(Symbol) && name.is_a?(Symbol) && options.is_a?(Hash)
|
12
|
+
end or raise ArgumentError, "fields must be Array(Array(Symbol, Symbol, Hash)); got #{fields.inspect}"
|
13
|
+
@fields = fields
|
14
|
+
@create_table_options = create_table_options
|
15
|
+
@create_table_options = @create_table_options.merge(options: sql_options) if sql_options.present?
|
16
|
+
end
|
17
|
+
|
18
|
+
def up_command
|
19
|
+
longest_field_type_length = @fields.map { |type, _name, _option| type.to_s.length }.max
|
20
|
+
|
21
|
+
<<~EOS.strip
|
22
|
+
create_table #{[@table_name.to_sym.inspect, *self.class.format_options(@create_table_options)].join(', ')} do |t|
|
23
|
+
#{@fields.map do |type, name, options|
|
24
|
+
padded_type = format("%-*s", longest_field_type_length, type)
|
25
|
+
args = [name.inspect, *self.class.format_options(options)].join(', ')
|
26
|
+
" t.#{padded_type} #{args}"
|
27
|
+
end.join("\n")}
|
28
|
+
end
|
29
|
+
EOS
|
30
|
+
end
|
31
|
+
|
32
|
+
def down_command
|
33
|
+
"drop_table #{@table_name.to_sym.inspect}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -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
|
@@ -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
|
@@ -9,29 +10,14 @@ module Generators
|
|
9
10
|
class Migrator
|
10
11
|
class Error < RuntimeError; end
|
11
12
|
|
12
|
-
DEFAULT_CHARSET = "utf8mb4"
|
13
|
-
DEFAULT_COLLATION = "utf8mb4_bin"
|
14
|
-
|
15
13
|
@ignore_models = []
|
16
14
|
@ignore_tables = []
|
17
15
|
@before_generating_migration_callback = nil
|
18
16
|
@active_record_class = ActiveRecord::Base
|
19
|
-
@default_charset = DEFAULT_CHARSET
|
20
|
-
@default_collation = DEFAULT_COLLATION
|
21
17
|
|
22
18
|
class << self
|
23
|
-
attr_accessor :ignore_models, :ignore_tables
|
24
|
-
attr_reader :active_record_class, :
|
25
|
-
|
26
|
-
def default_charset=(charset)
|
27
|
-
charset.is_a?(String) or raise ArgumentError, "charset must be a string (got #{charset.inspect})"
|
28
|
-
@default_charset = charset
|
29
|
-
end
|
30
|
-
|
31
|
-
def default_collation=(collation)
|
32
|
-
collation.is_a?(String) or raise ArgumentError, "collation must be a string (got #{collation.inspect})"
|
33
|
-
@default_collation = collation
|
34
|
-
end
|
19
|
+
attr_accessor :ignore_models, :ignore_tables
|
20
|
+
attr_reader :active_record_class, :before_generating_migration_callback
|
35
21
|
|
36
22
|
def active_record_class
|
37
23
|
@active_record_class.is_a?(Class) or @active_record_class = @active_record_class.to_s.constantize
|
@@ -39,9 +25,7 @@ module Generators
|
|
39
25
|
end
|
40
26
|
|
41
27
|
def run(renames = {})
|
42
|
-
|
43
|
-
g.renames = renames
|
44
|
-
g.generate
|
28
|
+
Migrator.new(renames: renames).generate
|
45
29
|
end
|
46
30
|
|
47
31
|
def default_migration_name
|
@@ -58,16 +42,17 @@ module Generators
|
|
58
42
|
block or raise ArgumentError, 'A block is required when setting the before_generating_migration callback'
|
59
43
|
@before_generating_migration_callback = block
|
60
44
|
end
|
45
|
+
|
46
|
+
delegate :default_charset=, :default_collation=, :default_charset, :default_collation, to: ::DeclareSchema
|
47
|
+
deprecate :default_charset=, :default_collation=, :default_charset, :default_collation, deprecator: ActiveSupport::Deprecation.new('1.0', 'declare_schema')
|
61
48
|
end
|
62
49
|
|
63
|
-
def initialize(ambiguity_resolver = {})
|
50
|
+
def initialize(ambiguity_resolver = {}, renames: nil)
|
64
51
|
@ambiguity_resolver = ambiguity_resolver
|
65
52
|
@drops = []
|
66
|
-
@renames =
|
53
|
+
@renames = renames
|
67
54
|
end
|
68
55
|
|
69
|
-
attr_accessor :renames
|
70
|
-
|
71
56
|
def load_rails_models
|
72
57
|
ActiveRecord::Migration.verbose = false
|
73
58
|
|
@@ -121,19 +106,24 @@ module Generators
|
|
121
106
|
# return a hash of table renames and modifies the passed arrays so
|
122
107
|
# that renamed tables are no longer listed as to_create or to_drop
|
123
108
|
def extract_table_renames!(to_create, to_drop)
|
124
|
-
if renames
|
109
|
+
if @renames
|
125
110
|
# A hash of table renames has been provided
|
126
111
|
|
127
112
|
to_rename = {}
|
128
|
-
renames.
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
if to_create.delete(new_name.to_s) && to_drop.delete(old_name.to_s)
|
133
|
-
to_rename[old_name.to_s] = new_name.to_s
|
134
|
-
else
|
135
|
-
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]
|
136
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
|
137
127
|
end
|
138
128
|
to_rename
|
139
129
|
|
@@ -145,18 +135,22 @@ module Generators
|
|
145
135
|
end
|
146
136
|
end
|
147
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
|
148
140
|
def extract_column_renames!(to_add, to_remove, table_name)
|
149
|
-
if renames
|
141
|
+
if @renames
|
150
142
|
to_rename = {}
|
151
|
-
if (column_renames = renames
|
152
|
-
# A hash of
|
153
|
-
|
154
|
-
column_renames.
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
160
154
|
end
|
161
155
|
end
|
162
156
|
to_rename
|
@@ -213,84 +207,103 @@ module Generators
|
|
213
207
|
to_rename = extract_table_renames!(to_create, to_drop)
|
214
208
|
|
215
209
|
renames = to_rename.map do |old_name, new_name|
|
216
|
-
|
217
|
-
end
|
218
|
-
undo_renames = to_rename.map do |old_name, new_name|
|
219
|
-
"rename_table :#{new_name}, :#{old_name}"
|
220
|
-
end * "\n"
|
210
|
+
::DeclareSchema::SchemaChange::TableRename.new(old_name, new_name)
|
211
|
+
end
|
221
212
|
|
222
213
|
drops = to_drop.map do |t|
|
223
|
-
|
224
|
-
end
|
225
|
-
undo_drops = to_drop.map do |t|
|
226
|
-
add_table_back(t)
|
227
|
-
end * "\n\n"
|
214
|
+
::DeclareSchema::SchemaChange::TableRemove.new(t, add_table_back(t))
|
215
|
+
end
|
228
216
|
|
229
217
|
creates = to_create.map do |t|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
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
|
235
243
|
|
236
244
|
changes = []
|
237
|
-
undo_changes = []
|
238
245
|
index_changes = []
|
239
|
-
undo_index_changes = []
|
240
246
|
fk_changes = []
|
241
|
-
undo_fk_changes = []
|
242
247
|
table_options_changes = []
|
243
|
-
undo_table_options_changes = []
|
244
248
|
|
245
249
|
to_change.each do |t|
|
246
250
|
model = models_by_table_name[t]
|
247
251
|
table = to_rename.key(t) || model.table_name
|
248
252
|
if table.in?(db_tables)
|
249
|
-
change,
|
253
|
+
change, index_change, fk_change, table_options_change = change_table(model, table)
|
250
254
|
changes << change
|
251
|
-
undo_changes << undo
|
252
255
|
index_changes << index_change
|
253
|
-
undo_index_changes << undo_index
|
254
256
|
fk_changes << fk_change
|
255
|
-
undo_fk_changes << undo_fk
|
256
257
|
table_options_changes << table_options_change
|
257
|
-
undo_table_options_changes << undo_table_options_change
|
258
258
|
end
|
259
259
|
end
|
260
260
|
|
261
|
-
|
262
|
-
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
|
263
262
|
|
264
|
-
|
263
|
+
ordered_migration_commands = order_migrations(migration_commands)
|
264
|
+
|
265
|
+
up_and_down_migrations(ordered_migration_commands)
|
265
266
|
end
|
266
267
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
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 ]
|
275
281
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
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
|
280
295
|
|
281
|
-
|
282
|
-
#{create_indexes(model).join("\n") unless Migrator.disable_indexing}
|
283
|
-
#{create_constraints(model).join("\n") unless Migrator.disable_indexing}
|
284
|
-
EOS
|
296
|
+
[up * "\n\n", down * "\n\n"]
|
285
297
|
end
|
286
298
|
|
287
299
|
def create_table_options(model, disable_auto_increment)
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
300
|
+
primary_key = model._defined_primary_key
|
301
|
+
if primary_key.blank? || disable_auto_increment
|
302
|
+
{ id: false }
|
303
|
+
elsif primary_key == "id"
|
304
|
+
{ id: :bigint }
|
292
305
|
else
|
293
|
-
|
306
|
+
{ primary_key: primary_key.to_sym }
|
294
307
|
end
|
295
308
|
end
|
296
309
|
|
@@ -299,42 +312,42 @@ module Generators
|
|
299
312
|
{}
|
300
313
|
else
|
301
314
|
{
|
302
|
-
charset: model.table_options[:charset] ||
|
303
|
-
collation: model.table_options[:collation] ||
|
315
|
+
charset: model.table_options[:charset] || ::DeclareSchema.default_charset,
|
316
|
+
collation: model.table_options[:collation] || ::DeclareSchema.default_collation
|
304
317
|
}
|
305
318
|
end
|
306
319
|
end
|
307
320
|
|
321
|
+
# TODO: TECH-5338: optimize that index doesn't need to be dropped on undo since entire table will be dropped
|
308
322
|
def create_indexes(model)
|
309
|
-
model.index_definitions.map
|
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
|
310
326
|
end
|
311
327
|
|
312
328
|
def create_constraints(model)
|
313
|
-
model.constraint_specs.map
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
options = field_spec.sql_options.merge(fk_field_options(field_spec.model, field_spec.name))
|
318
|
-
args = [field_spec.name.inspect] + format_options(options.compact)
|
319
|
-
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
|
320
333
|
end
|
321
334
|
|
322
335
|
def change_table(model, current_table_name)
|
323
336
|
new_table_name = model.table_name
|
324
337
|
|
325
338
|
db_columns = model.connection.columns(current_table_name).index_by(&:name)
|
326
|
-
key_missing = db_columns[model.
|
327
|
-
if model.
|
328
|
-
db_columns.delete(model.
|
339
|
+
key_missing = db_columns[model._defined_primary_key].nil? && model._defined_primary_key.present?
|
340
|
+
if model._defined_primary_key.present?
|
341
|
+
db_columns.delete(model._defined_primary_key)
|
329
342
|
end
|
330
343
|
|
331
344
|
model_column_names = model.field_specs.keys.map(&:to_s)
|
332
345
|
db_column_names = db_columns.keys.map(&:to_s)
|
333
346
|
|
334
347
|
to_add = model_column_names - db_column_names
|
335
|
-
to_add += [model.
|
348
|
+
to_add += [model._defined_primary_key] if key_missing && model._defined_primary_key.present?
|
336
349
|
to_remove = db_column_names - model_column_names
|
337
|
-
to_remove -= [model.
|
350
|
+
to_remove -= [model._defined_primary_key.to_sym] if model._defined_primary_key.present?
|
338
351
|
|
339
352
|
to_rename = extract_column_renames!(to_add, to_remove, new_table_name)
|
340
353
|
|
@@ -343,37 +356,28 @@ module Generators
|
|
343
356
|
to_change = db_column_names & model_column_names
|
344
357
|
|
345
358
|
renames = to_rename.map do |old_name, new_name|
|
346
|
-
|
347
|
-
end
|
348
|
-
undo_renames = to_rename.map do |old_name, new_name|
|
349
|
-
"rename_column :#{new_table_name}, :#{new_name}, :#{old_name}"
|
359
|
+
::DeclareSchema::SchemaChange::ColumnRename.new(new_table_name, old_name, new_name)
|
350
360
|
end
|
351
361
|
|
352
|
-
to_add
|
362
|
+
to_add.sort_by! { |c| model.field_specs[c]&.position || 0 }
|
363
|
+
|
353
364
|
adds = to_add.map do |c|
|
354
|
-
|
365
|
+
type, options =
|
355
366
|
if (spec = model.field_specs[c])
|
356
|
-
|
357
|
-
[":#{spec.type}", *format_options(options.compact)]
|
367
|
+
[spec.type, spec.sql_options.merge(fk_field_options(model, c)).compact]
|
358
368
|
else
|
359
|
-
[
|
369
|
+
[:integer, {}]
|
360
370
|
end
|
361
|
-
|
362
|
-
end
|
363
|
-
undo_adds = to_add.map do |c|
|
364
|
-
"remove_column :#{new_table_name}, :#{c}"
|
371
|
+
::DeclareSchema::SchemaChange::ColumnAdd.new(new_table_name, c, type, options)
|
365
372
|
end
|
366
373
|
|
367
374
|
removes = to_remove.map do |c|
|
368
|
-
|
369
|
-
|
370
|
-
undo_removes = to_remove.map do |c|
|
371
|
-
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)
|
372
377
|
end
|
373
378
|
|
374
379
|
old_names = to_rename.invert
|
375
380
|
changes = []
|
376
|
-
undo_changes = []
|
377
381
|
to_change.each do |col_name_to_change|
|
378
382
|
orig_col_name = old_names[col_name_to_change] || col_name_to_change
|
379
383
|
column = db_columns[orig_col_name] or raise "failed to find column info for #{orig_col_name.inspect}"
|
@@ -385,36 +389,33 @@ module Generators
|
|
385
389
|
|
386
390
|
if !::DeclareSchema::Model::Column.equivalent_schema_attributes?(normalized_schema_attrs, col_attrs)
|
387
391
|
type = normalized_schema_attrs.delete(:type) or raise "no :type found in #{normalized_schema_attrs.inspect}"
|
388
|
-
|
389
|
-
|
390
|
-
|
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)
|
391
396
|
end
|
392
397
|
end
|
393
398
|
|
394
|
-
index_changes
|
395
|
-
fk_changes
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
table_options_changes
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
[(renames + adds + removes + changes)
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
fk_changes * "\n",
|
411
|
-
undo_fk_changes * "\n",
|
412
|
-
table_options_changes * "\n",
|
413
|
-
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]
|
414
415
|
end
|
415
416
|
|
416
|
-
def change_indexes(model, old_table_name,
|
417
|
-
|
417
|
+
def change_indexes(model, old_table_name, to_rename)
|
418
|
+
::DeclareSchema.default_generate_indexing or return []
|
418
419
|
|
419
420
|
new_table_name = model.table_name
|
420
421
|
existing_indexes = ::DeclareSchema::Model::IndexDefinition.for_model(model, old_table_name)
|
@@ -426,69 +427,54 @@ module Generators
|
|
426
427
|
end
|
427
428
|
end || i
|
428
429
|
end
|
429
|
-
|
430
|
-
|
431
|
-
|
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
|
432
445
|
end
|
433
|
-
model_has_primary_key = model_indexes.any? { |i| i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME }
|
434
446
|
|
435
|
-
|
436
|
-
|
437
|
-
undo_add_indexes << drop_index(old_table_name, i.name) unless i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME
|
438
|
-
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)
|
439
449
|
end
|
440
|
-
undo_drop_indexes = []
|
441
|
-
drop_indexes = (existing_indexes - model_indexes).map do |i|
|
442
|
-
undo_drop_indexes << i.to_add_statement(old_table_name, model_has_primary_key)
|
443
|
-
drop_index(new_table_name, i.name) unless i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME
|
444
|
-
end.compact
|
445
450
|
|
446
|
-
|
447
|
-
|
448
|
-
|
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
|
449
454
|
|
450
|
-
|
451
|
-
|
452
|
-
# for why the rescue exists
|
453
|
-
"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]
|
454
457
|
end
|
455
458
|
|
456
459
|
def change_foreign_key_constraints(model, old_table_name)
|
457
460
|
ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/) and raise ArgumentError, 'SQLite does not support foreign keys'
|
458
|
-
|
461
|
+
::DeclareSchema.default_generate_foreign_keys or return []
|
459
462
|
|
460
|
-
new_table_name = model.table_name
|
461
463
|
existing_fks = ::DeclareSchema::Model::ForeignKeyDefinition.for_model(model, old_table_name)
|
462
464
|
model_fks = model.constraint_specs
|
463
465
|
|
464
|
-
undo_add_fks = []
|
465
|
-
add_fks = (model_fks - existing_fks).map do |fk|
|
466
|
-
# next if fk.parent.constantize.abstract_class || fk.parent == fk.model.class_name
|
467
|
-
undo_add_fks << remove_foreign_key(old_table_name, fk.constraint_name)
|
468
|
-
fk.to_add_statement
|
469
|
-
end
|
470
|
-
|
471
|
-
undo_drop_fks = []
|
472
466
|
drop_fks = (existing_fks - model_fks).map do |fk|
|
473
|
-
|
474
|
-
|
467
|
+
::DeclareSchema::SchemaChange::ForeignKeyRemove.new(fk.child_table_name, fk.parent_table_name,
|
468
|
+
column_name: fk.foreign_key_name, name: fk.constraint_name)
|
475
469
|
end
|
476
470
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
"remove_foreign_key(#{old_table_name.inspect}, name: #{fk_name.to_s.inspect})"
|
482
|
-
end
|
483
|
-
|
484
|
-
def format_options(options)
|
485
|
-
options.map do |k, v|
|
486
|
-
if k.is_a?(Symbol)
|
487
|
-
"#{k}: #{v.inspect}"
|
488
|
-
else
|
489
|
-
"#{k.inspect} => #{v.inspect}"
|
490
|
-
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)
|
491
475
|
end
|
476
|
+
|
477
|
+
[drop_fks + add_fks]
|
492
478
|
end
|
493
479
|
|
494
480
|
def fk_field_options(model, field_name)
|
@@ -517,11 +503,12 @@ module Generators
|
|
517
503
|
new_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, table_options_for_model(model))
|
518
504
|
|
519
505
|
if old_options_definition.equivalent?(new_options_definition)
|
520
|
-
[
|
506
|
+
[]
|
521
507
|
else
|
522
508
|
[
|
523
|
-
|
524
|
-
|
509
|
+
::DeclareSchema::SchemaChange::TableChange.new(current_table_name,
|
510
|
+
old_options_definition.settings,
|
511
|
+
new_options_definition.settings)
|
525
512
|
]
|
526
513
|
end
|
527
514
|
end
|
@@ -539,7 +526,7 @@ module Generators
|
|
539
526
|
col_spec = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
|
540
527
|
schema_attributes = col_spec.schema_attributes
|
541
528
|
type = schema_attributes.delete(:type) or raise "no :type found in #{schema_attributes.inspect}"
|
542
|
-
[
|
529
|
+
[type, schema_attributes]
|
543
530
|
end
|
544
531
|
end
|
545
532
|
|
@@ -549,7 +536,7 @@ module Generators
|
|
549
536
|
col_spec = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
|
550
537
|
schema_attributes = col_spec.schema_attributes
|
551
538
|
type = schema_attributes.delete(:type) or raise "no :type found in #{schema_attributes.inspect}"
|
552
|
-
[
|
539
|
+
[type, schema_attributes]
|
553
540
|
end
|
554
541
|
end
|
555
542
|
|
@@ -584,8 +571,8 @@ module Generators
|
|
584
571
|
# TODO: rewrite this method to use charset and collation variables rather than manipulating strings. -Colin
|
585
572
|
def fix_mysql_charset_and_collation(dumped_schema)
|
586
573
|
if !dumped_schema['options: ']
|
587
|
-
dumped_schema.sub!('",', "\", options: \"DEFAULT CHARSET=#{
|
588
|
-
"COLLATE=#{
|
574
|
+
dumped_schema.sub!('",', "\", options: \"DEFAULT CHARSET=#{::DeclareSchema.default_charset} "+
|
575
|
+
"COLLATE=#{::DeclareSchema.default_collation}\",")
|
589
576
|
end
|
590
577
|
default_charset = dumped_schema[/CHARSET=(\w+)/, 1] or raise "unable to find charset in #{dumped_schema.inspect}"
|
591
578
|
default_collation = dumped_schema[/COLLATE=(\w+)/, 1] || default_collation_from_charset(default_charset) or
|