declare_schema 0.10.0 → 0.12.0.pre.2
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/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
|