declare_schema 0.10.0 → 0.12.0.pre.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -1
- data/Gemfile.lock +1 -1
- data/README.md +16 -6
- data/lib/declare_schema.rb +12 -1
- data/lib/declare_schema/extensions/active_record/fields_declaration.rb +4 -2
- data/lib/declare_schema/model.rb +59 -15
- data/lib/declare_schema/model/column.rb +2 -2
- data/lib/declare_schema/model/field_spec.rb +4 -4
- data/lib/declare_schema/model/foreign_key_definition.rb +6 -11
- data/lib/declare_schema/model/habtm_model_shim.rb +2 -2
- data/lib/declare_schema/model/index_definition.rb +8 -25
- 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/USAGE +14 -24
- data/lib/generators/declare_schema/migration/migration_generator.rb +40 -38
- data/lib/generators/declare_schema/migration/migrator.rb +190 -187
- data/lib/generators/declare_schema/migration/templates/migration.rb.erb +3 -3
- data/spec/lib/declare_schema/api_spec.rb +3 -1
- data/spec/lib/declare_schema/field_spec_spec.rb +3 -3
- data/spec/lib/declare_schema/generator_spec.rb +2 -2
- data/spec/lib/declare_schema/interactive_primary_key_spec.rb +60 -25
- data/spec/lib/declare_schema/migration_generator_spec.rb +471 -377
- data/spec/lib/declare_schema/model/column_spec.rb +2 -6
- data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +28 -16
- data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +4 -6
- data/spec/lib/declare_schema/model/index_definition_spec.rb +4 -4
- 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/generators/declare_schema/migration/migrator_spec.rb +59 -11
- data/spec/spec_helper.rb +1 -1
- data/spec/support/acceptance_spec_helpers.rb +2 -2
- metadata +34 -5
@@ -1,47 +1,37 @@
|
|
1
1
|
Description:
|
2
2
|
|
3
3
|
This generator compares your existing schema against the
|
4
|
-
schema declared inside your
|
5
|
-
models.
|
4
|
+
schema declared inside your declare_schema do ... end
|
5
|
+
declarations in your models.
|
6
6
|
|
7
7
|
If the generator finds differences, it will display the
|
8
|
-
migration it has created, and
|
9
|
-
[g]enerate migration, generate and [m]igrate now or [c]ancel?
|
10
|
-
Enter "g" to just generate the migration but do not run it.
|
11
|
-
Enter "m" to generate the migration and run it, or press "c"
|
12
|
-
to do nothing.
|
13
|
-
|
14
|
-
The generator will then prompt you for the migration name,
|
8
|
+
migration it has created, and prompt you for the migration name,
|
15
9
|
supplying a numbered default name.
|
16
10
|
|
17
11
|
The generator is conservative and will prompt you to resolve
|
18
12
|
any ambiguities.
|
19
13
|
|
20
|
-
|
14
|
+
Example:
|
21
15
|
|
22
|
-
$ rails generate declare_schema:migration
|
16
|
+
$ bundle exec rails generate declare_schema:migration
|
23
17
|
|
24
18
|
---------- Up Migration ----------
|
25
|
-
create_table :
|
19
|
+
create_table :users do |t|
|
20
|
+
t.string :first_name, limit: 50
|
21
|
+
t.string :last_name, limit: 50
|
26
22
|
t.datetime :created_at
|
27
23
|
t.datetime :updated_at
|
28
24
|
end
|
29
25
|
----------------------------------
|
30
26
|
|
31
27
|
---------- Down Migration --------
|
32
|
-
drop_table :
|
28
|
+
drop_table :users
|
33
29
|
----------------------------------
|
34
|
-
|
35
|
-
|
36
|
-
Migration filename:
|
37
|
-
(you can type spaces instead of '_' -- every little helps)
|
38
|
-
Filename [declare_schema_migration_2]: create_foo
|
30
|
+
Migration filename: (spaces will be converted to _) [declare_schema_migration_2]: create users
|
39
31
|
exists db/migrate
|
40
|
-
create db/migrate/
|
41
|
-
|
42
|
-
|
43
|
-
-- create_table(:yos)
|
44
|
-
-> 0.0856s
|
45
|
-
== CreateFoo: migrated (0.0858s) =============================================
|
32
|
+
create db/migrate/20091023183838_create_users.rb
|
33
|
+
|
34
|
+
Not running migration since --migrate not given. When you are ready, run:
|
46
35
|
|
36
|
+
bundle exec rails db:migrate
|
47
37
|
|
@@ -38,20 +38,18 @@ module DeclareSchema
|
|
38
38
|
type: :boolean,
|
39
39
|
desc: "Don't prompt for a migration name - just pick one"
|
40
40
|
|
41
|
-
class_option :generate,
|
42
|
-
aliases: '-g',
|
43
|
-
type: :boolean,
|
44
|
-
desc: "Don't prompt for action - generate the migration"
|
45
|
-
|
46
41
|
class_option :migrate,
|
47
42
|
aliases: '-m',
|
48
43
|
type: :boolean,
|
49
|
-
desc: "
|
44
|
+
desc: "After generating migration, run it"
|
50
45
|
|
51
46
|
def migrate
|
52
47
|
return if migrations_pending?
|
53
48
|
|
54
|
-
generator = Generators::DeclareSchema::Migration::Migrator.new
|
49
|
+
generator = Generators::DeclareSchema::Migration::Migrator.new do |to_create, to_drop, kind_str, name_prefix|
|
50
|
+
extract_renames!(to_create, to_drop, kind_str, name_prefix)
|
51
|
+
end
|
52
|
+
|
55
53
|
up, down = generator.generate
|
56
54
|
|
57
55
|
if up.blank?
|
@@ -67,34 +65,31 @@ module DeclareSchema
|
|
67
65
|
say down
|
68
66
|
say "----------------------------------"
|
69
67
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
when 4
|
93
|
-
rake('db:migrate')
|
94
|
-
else
|
95
|
-
rails_command('db:migrate')
|
96
|
-
end
|
68
|
+
final_migration_name =
|
69
|
+
name.presence ||
|
70
|
+
if !options[:default_name]
|
71
|
+
choose("\nMigration filename (spaces will be converted to _) [#{default_migration_name}]:", /^[a-z0-9_ ]*$/,
|
72
|
+
default_migration_name).strip.gsub(' ', '_').presence
|
73
|
+
end ||
|
74
|
+
default_migration_name
|
75
|
+
|
76
|
+
@up = indent(up.strip, 4)
|
77
|
+
@down = indent(down.strip, 4)
|
78
|
+
@migration_class_name = final_migration_name.camelize
|
79
|
+
|
80
|
+
migration_template('migration.rb.erb', "db/migrate/#{final_migration_name.underscore}.rb")
|
81
|
+
|
82
|
+
db_migrate_command = ::DeclareSchema.db_migrate_command
|
83
|
+
if options[:migrate]
|
84
|
+
say db_migrate_command
|
85
|
+
bare_rails_command = db_migrate_command.sub(/\Abundle exec +/, '').sub(/\Arake +|rails +/, '')
|
86
|
+
if ActiveSupport::VERSION::MAJOR < 5
|
87
|
+
rake(bare_rails_command)
|
88
|
+
else
|
89
|
+
rails_command(bare_rails_command)
|
97
90
|
end
|
91
|
+
else
|
92
|
+
say "\nNot running migration since --migrate not given. When you are ready, run:\n\n #{db_migrate_command}\n\n"
|
98
93
|
end
|
99
94
|
rescue ::DeclareSchema::UnknownTypeError => ex
|
100
95
|
say "Invalid field type: #{ex}"
|
@@ -102,8 +97,15 @@ module DeclareSchema
|
|
102
97
|
|
103
98
|
private
|
104
99
|
|
100
|
+
if ActiveSupport::VERSION::MAJOR < 5
|
101
|
+
def indent(string, columns)
|
102
|
+
whitespace = ' ' * columns
|
103
|
+
string.gsub("\n", "\n#{whitespace}").gsub(/ +\n/, "\n")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
105
107
|
def migrations_pending?
|
106
|
-
migrations = case
|
108
|
+
migrations = case ActiveSupport::VERSION::MAJOR
|
107
109
|
when 4
|
108
110
|
ActiveRecord::Migrator.migrations('db/migrate')
|
109
111
|
when 5
|
@@ -111,7 +113,7 @@ module DeclareSchema
|
|
111
113
|
else
|
112
114
|
ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths, ActiveRecord::SchemaMigration).migrations
|
113
115
|
end
|
114
|
-
pending_migrations = case
|
116
|
+
pending_migrations = case ActiveSupport::VERSION::MAJOR
|
115
117
|
when 4, 5
|
116
118
|
ActiveRecord::Migrator.new(:up, migrations).pending_migrations
|
117
119
|
else
|
@@ -176,8 +178,8 @@ module DeclareSchema
|
|
176
178
|
to_rename
|
177
179
|
end
|
178
180
|
|
179
|
-
def
|
180
|
-
|
181
|
+
def default_migration_name
|
182
|
+
Generators::DeclareSchema::Migration::Migrator.default_migration_name
|
181
183
|
end
|
182
184
|
end
|
183
185
|
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
|
-
|
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(
|
52
|
-
@ambiguity_resolver =
|
50
|
+
def initialize(renames: nil, &block)
|
51
|
+
@ambiguity_resolver = block
|
53
52
|
@drops = []
|
54
|
-
@renames =
|
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.
|
117
|
-
|
118
|
-
|
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
|
140
|
-
# A hash of
|
141
|
-
|
142
|
-
column_renames.
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
@@ -181,6 +187,12 @@ module Generators
|
|
181
187
|
models, db_tables = models_and_tables
|
182
188
|
models_by_table_name = {}
|
183
189
|
models.each do |m|
|
190
|
+
m.try(:field_specs)&.each do |_name, field_spec|
|
191
|
+
if (pre_migration = field_spec.options.delete(:pre_migration))
|
192
|
+
pre_migration.call(field_spec)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
184
196
|
if !models_by_table_name.has_key?(m.table_name)
|
185
197
|
models_by_table_name[m.table_name] = m
|
186
198
|
elsif m.superclass == models_by_table_name[m.table_name].superclass.superclass
|
@@ -197,90 +209,108 @@ module Generators
|
|
197
209
|
|
198
210
|
to_create = model_table_names - db_tables
|
199
211
|
to_drop = db_tables - model_table_names - self.class.always_ignore_tables
|
200
|
-
to_change = model_table_names
|
201
212
|
to_rename = extract_table_renames!(to_create, to_drop)
|
213
|
+
to_change = model_table_names
|
202
214
|
|
203
215
|
renames = to_rename.map do |old_name, new_name|
|
204
|
-
|
205
|
-
end
|
206
|
-
undo_renames = to_rename.map do |old_name, new_name|
|
207
|
-
"rename_table :#{new_name}, :#{old_name}"
|
208
|
-
end * "\n"
|
216
|
+
::DeclareSchema::SchemaChange::TableRename.new(old_name, new_name)
|
217
|
+
end
|
209
218
|
|
210
219
|
drops = to_drop.map do |t|
|
211
|
-
|
212
|
-
end
|
213
|
-
undo_drops = to_drop.map do |t|
|
214
|
-
add_table_back(t)
|
215
|
-
end * "\n\n"
|
220
|
+
::DeclareSchema::SchemaChange::TableRemove.new(t, add_table_back(t))
|
221
|
+
end
|
216
222
|
|
217
223
|
creates = to_create.map do |t|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
224
|
+
model = models_by_table_name[t]
|
225
|
+
disable_auto_increment = model.try(:disable_auto_increment)
|
226
|
+
|
227
|
+
primary_key_definition =
|
228
|
+
if disable_auto_increment
|
229
|
+
[:integer, :id, limit: 8, auto_increment: false, primary_key: true]
|
230
|
+
end
|
231
|
+
|
232
|
+
field_definitions = model.field_specs.values.sort_by(&:position).map do |f|
|
233
|
+
[f.type, f.name, f.sql_options]
|
234
|
+
end
|
235
|
+
|
236
|
+
table_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, table_options_for_model(model))
|
237
|
+
table_options = create_table_options(model, disable_auto_increment)
|
238
|
+
|
239
|
+
table_add = ::DeclareSchema::SchemaChange::TableAdd.new(t,
|
240
|
+
Array(primary_key_definition) + field_definitions,
|
241
|
+
table_options,
|
242
|
+
sql_options: table_options_definition.settings)
|
243
|
+
[
|
244
|
+
table_add,
|
245
|
+
*Array((create_indexes(model) if ::DeclareSchema.default_generate_indexing)),
|
246
|
+
*Array((create_constraints(model) if ::DeclareSchema.default_generate_foreign_keys))
|
247
|
+
]
|
248
|
+
end
|
223
249
|
|
224
250
|
changes = []
|
225
|
-
undo_changes = []
|
226
251
|
index_changes = []
|
227
|
-
undo_index_changes = []
|
228
252
|
fk_changes = []
|
229
|
-
undo_fk_changes = []
|
230
253
|
table_options_changes = []
|
231
|
-
undo_table_options_changes = []
|
232
254
|
|
233
255
|
to_change.each do |t|
|
234
256
|
model = models_by_table_name[t]
|
235
257
|
table = to_rename.key(t) || model.table_name
|
236
258
|
if table.in?(db_tables)
|
237
|
-
change,
|
259
|
+
change, index_change, fk_change, table_options_change = change_table(model, table)
|
238
260
|
changes << change
|
239
|
-
undo_changes << undo
|
240
261
|
index_changes << index_change
|
241
|
-
undo_index_changes << undo_index
|
242
262
|
fk_changes << fk_change
|
243
|
-
undo_fk_changes << undo_fk
|
244
263
|
table_options_changes << table_options_change
|
245
|
-
undo_table_options_changes << undo_table_options_change
|
246
264
|
end
|
247
265
|
end
|
248
266
|
|
249
|
-
|
250
|
-
|
267
|
+
migration_commands = [renames, drops, creates, changes, index_changes, fk_changes, table_options_changes].flatten
|
268
|
+
|
269
|
+
ordered_migration_commands = order_migrations(migration_commands)
|
251
270
|
|
252
|
-
|
271
|
+
up_and_down_migrations(ordered_migration_commands)
|
253
272
|
end
|
254
273
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
274
|
+
MIGRATION_ORDER = %w[ TableRename
|
275
|
+
TableAdd
|
276
|
+
TableChange
|
277
|
+
ColumnAdd
|
278
|
+
ColumnRename
|
279
|
+
ColumnChange
|
280
|
+
PrimaryKeyChange
|
281
|
+
IndexAdd
|
282
|
+
ForeignKeyAdd
|
283
|
+
ForeignKeyRemove
|
284
|
+
IndexRemove
|
285
|
+
ColumnRemove
|
286
|
+
TableRemove ]
|
263
287
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
288
|
+
def order_migrations(migration_commands)
|
289
|
+
migration_commands.each_with_index.sort_by do |command, index|
|
290
|
+
command_type = command.class.name.gsub(/.*::/, '')
|
291
|
+
priority = MIGRATION_ORDER.index(command_type) or raise "#{command_type.inspect} not found in #{MIGRATION_ORDER.inspect}"
|
292
|
+
[priority, index] # index keeps the sort stable in case of a tie
|
293
|
+
end.map(&:first) # remove the index
|
294
|
+
end
|
295
|
+
|
296
|
+
private
|
268
297
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
298
|
+
def up_and_down_migrations(migration_commands)
|
299
|
+
up = migration_commands.map(&:up ).select(&:present?)
|
300
|
+
down = migration_commands.map(&:down).select(&:present?).reverse
|
301
|
+
|
302
|
+
[up * "\n\n", down * "\n\n"]
|
273
303
|
end
|
274
304
|
|
275
305
|
def create_table_options(model, disable_auto_increment)
|
276
|
-
primary_key = model.
|
306
|
+
primary_key = model._declared_primary_key
|
277
307
|
if primary_key.blank? || disable_auto_increment
|
278
|
-
|
308
|
+
{ id: false }
|
279
309
|
elsif primary_key == "id"
|
280
|
-
|
310
|
+
{ id: :bigint }
|
281
311
|
else
|
282
|
-
|
283
|
-
end
|
312
|
+
{ primary_key: primary_key.to_sym }
|
313
|
+
end.merge(model._table_options)
|
284
314
|
end
|
285
315
|
|
286
316
|
def table_options_for_model(model)
|
@@ -288,42 +318,41 @@ module Generators
|
|
288
318
|
{}
|
289
319
|
else
|
290
320
|
{
|
291
|
-
charset: model.
|
292
|
-
collation: model.
|
321
|
+
charset: model._table_options&.[](:charset) || ::DeclareSchema.default_charset,
|
322
|
+
collation: model._table_options&.[](:collation) || ::DeclareSchema.default_collation
|
293
323
|
}
|
294
324
|
end
|
295
325
|
end
|
296
326
|
|
327
|
+
# TODO: TECH-5338: optimize that index doesn't need to be dropped on undo since entire table will be dropped
|
297
328
|
def create_indexes(model)
|
298
|
-
model.index_definitions.map
|
329
|
+
model.index_definitions.map do |i|
|
330
|
+
::DeclareSchema::SchemaChange::IndexAdd.new(model.table_name, i.columns, unique: i.unique, where: i.where, name: i.name)
|
331
|
+
end
|
299
332
|
end
|
300
333
|
|
301
334
|
def create_constraints(model)
|
302
|
-
model.constraint_specs.map
|
303
|
-
|
304
|
-
|
305
|
-
|
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(', '))
|
335
|
+
model.constraint_specs.map do |fk|
|
336
|
+
::DeclareSchema::SchemaChange::ForeignKeyAdd.new(fk.child_table_name, fk.parent_table_name,
|
337
|
+
column_name: fk.foreign_key_name, name: fk.constraint_name)
|
338
|
+
end
|
309
339
|
end
|
310
340
|
|
311
341
|
def change_table(model, current_table_name)
|
312
342
|
new_table_name = model.table_name
|
313
343
|
|
314
344
|
db_columns = model.connection.columns(current_table_name).index_by(&:name)
|
315
|
-
|
316
|
-
|
317
|
-
db_columns.delete(model._defined_primary_key)
|
345
|
+
if (pk = model._declared_primary_key.presence)
|
346
|
+
key_was_in_db_columns = db_columns.delete(pk)
|
318
347
|
end
|
319
348
|
|
320
349
|
model_column_names = model.field_specs.keys.map(&:to_s)
|
321
350
|
db_column_names = db_columns.keys.map(&:to_s)
|
322
351
|
|
323
352
|
to_add = model_column_names - db_column_names
|
324
|
-
to_add
|
353
|
+
to_add << pk if pk && !key_was_in_db_columns
|
325
354
|
to_remove = db_column_names - model_column_names
|
326
|
-
to_remove -= [
|
355
|
+
to_remove -= [pk.to_sym] if pk # TODO: The .to_sym here means this code is always a no-op, right? -Colin
|
327
356
|
|
328
357
|
to_rename = extract_column_renames!(to_add, to_remove, new_table_name)
|
329
358
|
|
@@ -332,37 +361,28 @@ module Generators
|
|
332
361
|
to_change = db_column_names & model_column_names
|
333
362
|
|
334
363
|
renames = to_rename.map do |old_name, new_name|
|
335
|
-
|
336
|
-
end
|
337
|
-
undo_renames = to_rename.map do |old_name, new_name|
|
338
|
-
"rename_column :#{new_table_name}, :#{new_name}, :#{old_name}"
|
364
|
+
::DeclareSchema::SchemaChange::ColumnRename.new(new_table_name, old_name, new_name)
|
339
365
|
end
|
340
366
|
|
341
|
-
to_add
|
367
|
+
to_add.sort_by! { |c| model.field_specs[c]&.position || 0 }
|
368
|
+
|
342
369
|
adds = to_add.map do |c|
|
343
|
-
|
370
|
+
type, options =
|
344
371
|
if (spec = model.field_specs[c])
|
345
|
-
|
346
|
-
[":#{spec.type}", *format_options(options.compact)]
|
372
|
+
[spec.type, spec.sql_options.merge(fk_field_options(model, c)).compact]
|
347
373
|
else
|
348
|
-
[
|
374
|
+
[:integer, {}]
|
349
375
|
end
|
350
|
-
|
351
|
-
end
|
352
|
-
undo_adds = to_add.map do |c|
|
353
|
-
"remove_column :#{new_table_name}, :#{c}"
|
376
|
+
::DeclareSchema::SchemaChange::ColumnAdd.new(new_table_name, c, type, options)
|
354
377
|
end
|
355
378
|
|
356
379
|
removes = to_remove.map do |c|
|
357
|
-
|
358
|
-
|
359
|
-
undo_removes = to_remove.map do |c|
|
360
|
-
add_column_back(model, current_table_name, c)
|
380
|
+
old_type, old_options = add_column_back(model, current_table_name, c)
|
381
|
+
::DeclareSchema::SchemaChange::ColumnRemove.new(new_table_name, c, old_type, old_options)
|
361
382
|
end
|
362
383
|
|
363
384
|
old_names = to_rename.invert
|
364
385
|
changes = []
|
365
|
-
undo_changes = []
|
366
386
|
to_change.each do |col_name_to_change|
|
367
387
|
orig_col_name = old_names[col_name_to_change] || col_name_to_change
|
368
388
|
column = db_columns[orig_col_name] or raise "failed to find column info for #{orig_col_name.inspect}"
|
@@ -374,36 +394,33 @@ module Generators
|
|
374
394
|
|
375
395
|
if !::DeclareSchema::Model::Column.equivalent_schema_attributes?(normalized_schema_attrs, col_attrs)
|
376
396
|
type = normalized_schema_attrs.delete(:type) or raise "no :type found in #{normalized_schema_attrs.inspect}"
|
377
|
-
|
378
|
-
|
379
|
-
|
397
|
+
old_type, old_options = change_column_back(model, current_table_name, orig_col_name)
|
398
|
+
changes << ::DeclareSchema::SchemaChange::ColumnChange.new(new_table_name, col_name_to_change,
|
399
|
+
new_type: type, new_options: normalized_schema_attrs,
|
400
|
+
old_type: old_type, old_options: old_options)
|
380
401
|
end
|
381
402
|
end
|
382
403
|
|
383
|
-
index_changes
|
384
|
-
fk_changes
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
table_options_changes
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
[(renames + adds + removes + changes)
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
fk_changes * "\n",
|
400
|
-
undo_fk_changes * "\n",
|
401
|
-
table_options_changes * "\n",
|
402
|
-
undo_table_options_changes * "\n"]
|
404
|
+
index_changes = change_indexes(model, current_table_name, to_rename)
|
405
|
+
fk_changes = if ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
|
406
|
+
[]
|
407
|
+
else
|
408
|
+
change_foreign_key_constraints(model, current_table_name)
|
409
|
+
end
|
410
|
+
table_options_changes = if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
|
411
|
+
change_table_options(model, current_table_name)
|
412
|
+
else
|
413
|
+
[]
|
414
|
+
end
|
415
|
+
|
416
|
+
[(renames + adds + removes + changes),
|
417
|
+
index_changes,
|
418
|
+
fk_changes,
|
419
|
+
table_options_changes]
|
403
420
|
end
|
404
421
|
|
405
|
-
def change_indexes(model, old_table_name,
|
406
|
-
::DeclareSchema.default_generate_indexing or return [
|
422
|
+
def change_indexes(model, old_table_name, to_rename)
|
423
|
+
::DeclareSchema.default_generate_indexing or return []
|
407
424
|
|
408
425
|
new_table_name = model.table_name
|
409
426
|
existing_indexes = ::DeclareSchema::Model::IndexDefinition.for_model(model, old_table_name)
|
@@ -415,69 +432,54 @@ module Generators
|
|
415
432
|
end
|
416
433
|
end || i
|
417
434
|
end
|
418
|
-
|
419
|
-
|
420
|
-
|
435
|
+
existing_primary_keys, existing_indexes_without_primary_key = existing_indexes.partition { |i| i.primary_key? }
|
436
|
+
defined_primary_keys, model_indexes_without_primary_key = model_indexes.partition { |i| i.primary_key? }
|
437
|
+
existing_primary_keys.size <= 1 or raise "too many existing primary keys! #{existing_primary_keys.inspect}"
|
438
|
+
defined_primary_keys.size <= 1 or raise "too many defined primary keys! #{defined_primary_keys.inspect}"
|
439
|
+
existing_primary_key = existing_primary_keys.first
|
440
|
+
defined_primary_key = defined_primary_keys.first
|
441
|
+
|
442
|
+
existing_primary_key_columns = (existing_primary_key&.columns || []).map { |col_name| to_rename[col_name] || col_name }
|
443
|
+
|
444
|
+
if !ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/)
|
445
|
+
change_primary_key =
|
446
|
+
if (existing_primary_key || defined_primary_key) &&
|
447
|
+
existing_primary_key_columns != defined_primary_key&.columns
|
448
|
+
::DeclareSchema::SchemaChange::PrimaryKeyChange.new(new_table_name, existing_primary_key_columns, defined_primary_key&.columns)
|
449
|
+
end
|
421
450
|
end
|
422
|
-
model_has_primary_key = model_indexes.any? { |i| i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME }
|
423
451
|
|
424
|
-
|
425
|
-
|
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)
|
452
|
+
drop_indexes = (existing_indexes_without_primary_key - model_indexes_without_primary_key).map do |i|
|
453
|
+
::DeclareSchema::SchemaChange::IndexRemove.new(new_table_name, i.columns, unique: i.unique, where: i.where, name: i.name)
|
428
454
|
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
455
|
|
435
|
-
|
436
|
-
|
437
|
-
|
456
|
+
add_indexes = (model_indexes_without_primary_key - existing_indexes_without_primary_key).map do |i|
|
457
|
+
::DeclareSchema::SchemaChange::IndexAdd.new(new_table_name, i.columns, unique: i.unique, where: i.where, name: i.name)
|
458
|
+
end
|
438
459
|
|
439
|
-
|
440
|
-
|
441
|
-
# for why the rescue exists
|
442
|
-
"remove_index :#{table}, name: :#{name} rescue ActiveRecord::StatementInvalid"
|
460
|
+
# the order is important here - adding a :unique, for instance needs to remove then add
|
461
|
+
[Array(change_primary_key) + drop_indexes + add_indexes]
|
443
462
|
end
|
444
463
|
|
445
464
|
def change_foreign_key_constraints(model, old_table_name)
|
446
465
|
ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/) and raise ArgumentError, 'SQLite does not support foreign keys'
|
447
|
-
::DeclareSchema.default_generate_foreign_keys or return [
|
466
|
+
::DeclareSchema.default_generate_foreign_keys or return []
|
448
467
|
|
449
|
-
new_table_name = model.table_name
|
450
468
|
existing_fks = ::DeclareSchema::Model::ForeignKeyDefinition.for_model(model, old_table_name)
|
451
469
|
model_fks = model.constraint_specs
|
452
470
|
|
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
471
|
drop_fks = (existing_fks - model_fks).map do |fk|
|
462
|
-
|
463
|
-
|
472
|
+
::DeclareSchema::SchemaChange::ForeignKeyRemove.new(fk.child_table_name, fk.parent_table_name,
|
473
|
+
column_name: fk.foreign_key_name, name: fk.constraint_name)
|
464
474
|
end
|
465
475
|
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
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
|
476
|
+
add_fks = (model_fks - existing_fks).map do |fk|
|
477
|
+
# next if fk.parent.constantize.abstract_class || fk.parent == fk.model.class_name
|
478
|
+
::DeclareSchema::SchemaChange::ForeignKeyAdd.new(fk.child_table_name, fk.parent_table_name,
|
479
|
+
column_name: fk.foreign_key_name, name: fk.constraint_name)
|
480
480
|
end
|
481
|
+
|
482
|
+
[drop_fks + add_fks]
|
481
483
|
end
|
482
484
|
|
483
485
|
def fk_field_options(model, field_name)
|
@@ -486,7 +488,7 @@ module Generators
|
|
486
488
|
parent_columns = connection.columns(parent_table) rescue []
|
487
489
|
pk_limit =
|
488
490
|
if (pk_column = parent_columns.find { |column| column.name.to_s == "id" }) # right now foreign keys assume id is the target
|
489
|
-
if
|
491
|
+
if ActiveSupport::VERSION::MAJOR < 5
|
490
492
|
pk_column.cast_type.limit
|
491
493
|
else
|
492
494
|
pk_column.limit
|
@@ -506,11 +508,12 @@ module Generators
|
|
506
508
|
new_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, table_options_for_model(model))
|
507
509
|
|
508
510
|
if old_options_definition.equivalent?(new_options_definition)
|
509
|
-
[
|
511
|
+
[]
|
510
512
|
else
|
511
513
|
[
|
512
|
-
|
513
|
-
|
514
|
+
::DeclareSchema::SchemaChange::TableChange.new(current_table_name,
|
515
|
+
old_options_definition.settings,
|
516
|
+
new_options_definition.settings)
|
514
517
|
]
|
515
518
|
end
|
516
519
|
end
|
@@ -528,7 +531,7 @@ module Generators
|
|
528
531
|
col_spec = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
|
529
532
|
schema_attributes = col_spec.schema_attributes
|
530
533
|
type = schema_attributes.delete(:type) or raise "no :type found in #{schema_attributes.inspect}"
|
531
|
-
[
|
534
|
+
[type, schema_attributes]
|
532
535
|
end
|
533
536
|
end
|
534
537
|
|
@@ -538,7 +541,7 @@ module Generators
|
|
538
541
|
col_spec = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
|
539
542
|
schema_attributes = col_spec.schema_attributes
|
540
543
|
type = schema_attributes.delete(:type) or raise "no :type found in #{schema_attributes.inspect}"
|
541
|
-
[
|
544
|
+
[type, schema_attributes]
|
542
545
|
end
|
543
546
|
end
|
544
547
|
|
@@ -551,7 +554,7 @@ module Generators
|
|
551
554
|
end
|
552
555
|
end
|
553
556
|
|
554
|
-
SchemaDumper = case
|
557
|
+
SchemaDumper = case ActiveSupport::VERSION::MAJOR
|
555
558
|
when 4
|
556
559
|
ActiveRecord::SchemaDumper
|
557
560
|
else
|