declare_schema 0.8.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/declare_schema_build.yml +1 -1
- data/CHANGELOG.md +37 -0
- data/Gemfile.lock +1 -1
- data/README.md +102 -14
- data/lib/declare_schema.rb +57 -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 +4 -6
- data/lib/declare_schema/model/column.rb +1 -1
- data/lib/declare_schema/model/field_spec.rb +9 -6
- data/lib/declare_schema/model/foreign_key_definition.rb +6 -11
- data/lib/declare_schema/model/index_definition.rb +1 -20
- 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 +184 -198
- data/lib/generators/declare_schema/migration/templates/migration.rb.erb +1 -1
- data/lib/generators/declare_schema/support/model.rb +4 -4
- data/spec/lib/declare_schema/api_spec.rb +8 -8
- data/spec/lib/declare_schema/field_declaration_dsl_spec.rb +41 -15
- data/spec/lib/declare_schema/field_spec_spec.rb +44 -2
- data/spec/lib/declare_schema/generator_spec.rb +5 -5
- data/spec/lib/declare_schema/interactive_primary_key_spec.rb +117 -28
- data/spec/lib/declare_schema/migration_generator_spec.rb +1990 -843
- data/spec/lib/declare_schema/model/column_spec.rb +49 -23
- data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +158 -57
- data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +0 -2
- data/spec/lib/declare_schema/model/index_definition_spec.rb +189 -78
- 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/spec_helper.rb +1 -1
- data/spec/support/acceptance_spec_helpers.rb +2 -2
- metadata +36 -6
- data/test_responses.txt +0 -2
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module DeclareSchema
|
6
|
+
module SchemaChange
|
7
|
+
class TableRemove < Base
|
8
|
+
def initialize(table_name, add_table_back)
|
9
|
+
@table_name = table_name
|
10
|
+
@add_table_back = add_table_back
|
11
|
+
end
|
12
|
+
|
13
|
+
def up_command
|
14
|
+
"drop_table #{@table_name.to_sym.inspect}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def down_command
|
18
|
+
@add_table_back
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module DeclareSchema
|
6
|
+
module SchemaChange
|
7
|
+
class TableRename < Base
|
8
|
+
def initialize(old_name, new_name)
|
9
|
+
@old_name = old_name
|
10
|
+
@new_name = new_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def up_command
|
14
|
+
"rename_table #{@old_name.to_sym.inspect}, #{@new_name.to_sym.inspect}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def down_command
|
18
|
+
"rename_table #{@new_name.to_sym.inspect}, #{@old_name.to_sym.inspect}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,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, 4)
|
77
|
+
@down = indent(down, 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
|
@@ -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(
|
64
|
-
@ambiguity_resolver =
|
50
|
+
def initialize(renames: nil, &block)
|
51
|
+
@ambiguity_resolver = block
|
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,85 +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
300
|
primary_key = model._defined_primary_key
|
289
301
|
if primary_key.blank? || disable_auto_increment
|
290
|
-
|
302
|
+
{ id: false }
|
291
303
|
elsif primary_key == "id"
|
292
|
-
|
304
|
+
{ id: :bigint }
|
293
305
|
else
|
294
|
-
|
306
|
+
{ primary_key: primary_key.to_sym }
|
295
307
|
end
|
296
308
|
end
|
297
309
|
|
@@ -300,24 +312,24 @@ module Generators
|
|
300
312
|
{}
|
301
313
|
else
|
302
314
|
{
|
303
|
-
charset: model.table_options[:charset] ||
|
304
|
-
collation: model.table_options[:collation] ||
|
315
|
+
charset: model.table_options[:charset] || ::DeclareSchema.default_charset,
|
316
|
+
collation: model.table_options[:collation] || ::DeclareSchema.default_collation
|
305
317
|
}
|
306
318
|
end
|
307
319
|
end
|
308
320
|
|
321
|
+
# TODO: TECH-5338: optimize that index doesn't need to be dropped on undo since entire table will be dropped
|
309
322
|
def create_indexes(model)
|
310
|
-
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
|
311
326
|
end
|
312
327
|
|
313
328
|
def create_constraints(model)
|
314
|
-
model.constraint_specs.map
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
options = field_spec.sql_options.merge(fk_field_options(field_spec.model, field_spec.name))
|
319
|
-
args = [field_spec.name.inspect] + format_options(options.compact)
|
320
|
-
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
|
321
333
|
end
|
322
334
|
|
323
335
|
def change_table(model, current_table_name)
|
@@ -344,37 +356,28 @@ module Generators
|
|
344
356
|
to_change = db_column_names & model_column_names
|
345
357
|
|
346
358
|
renames = to_rename.map do |old_name, new_name|
|
347
|
-
|
348
|
-
end
|
349
|
-
undo_renames = to_rename.map do |old_name, new_name|
|
350
|
-
"rename_column :#{new_table_name}, :#{new_name}, :#{old_name}"
|
359
|
+
::DeclareSchema::SchemaChange::ColumnRename.new(new_table_name, old_name, new_name)
|
351
360
|
end
|
352
361
|
|
353
|
-
to_add
|
362
|
+
to_add.sort_by! { |c| model.field_specs[c]&.position || 0 }
|
363
|
+
|
354
364
|
adds = to_add.map do |c|
|
355
|
-
|
365
|
+
type, options =
|
356
366
|
if (spec = model.field_specs[c])
|
357
|
-
|
358
|
-
[":#{spec.type}", *format_options(options.compact)]
|
367
|
+
[spec.type, spec.sql_options.merge(fk_field_options(model, c)).compact]
|
359
368
|
else
|
360
|
-
[
|
369
|
+
[:integer, {}]
|
361
370
|
end
|
362
|
-
|
363
|
-
end
|
364
|
-
undo_adds = to_add.map do |c|
|
365
|
-
"remove_column :#{new_table_name}, :#{c}"
|
371
|
+
::DeclareSchema::SchemaChange::ColumnAdd.new(new_table_name, c, type, options)
|
366
372
|
end
|
367
373
|
|
368
374
|
removes = to_remove.map do |c|
|
369
|
-
|
370
|
-
|
371
|
-
undo_removes = to_remove.map do |c|
|
372
|
-
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)
|
373
377
|
end
|
374
378
|
|
375
379
|
old_names = to_rename.invert
|
376
380
|
changes = []
|
377
|
-
undo_changes = []
|
378
381
|
to_change.each do |col_name_to_change|
|
379
382
|
orig_col_name = old_names[col_name_to_change] || col_name_to_change
|
380
383
|
column = db_columns[orig_col_name] or raise "failed to find column info for #{orig_col_name.inspect}"
|
@@ -386,36 +389,33 @@ module Generators
|
|
386
389
|
|
387
390
|
if !::DeclareSchema::Model::Column.equivalent_schema_attributes?(normalized_schema_attrs, col_attrs)
|
388
391
|
type = normalized_schema_attrs.delete(:type) or raise "no :type found in #{normalized_schema_attrs.inspect}"
|
389
|
-
|
390
|
-
|
391
|
-
|
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)
|
392
396
|
end
|
393
397
|
end
|
394
398
|
|
395
|
-
index_changes
|
396
|
-
fk_changes
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
table_options_changes
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
[(renames + adds + removes + changes)
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
fk_changes * "\n",
|
412
|
-
undo_fk_changes * "\n",
|
413
|
-
table_options_changes * "\n",
|
414
|
-
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]
|
415
415
|
end
|
416
416
|
|
417
|
-
def change_indexes(model, old_table_name,
|
418
|
-
|
417
|
+
def change_indexes(model, old_table_name, to_rename)
|
418
|
+
::DeclareSchema.default_generate_indexing or return []
|
419
419
|
|
420
420
|
new_table_name = model.table_name
|
421
421
|
existing_indexes = ::DeclareSchema::Model::IndexDefinition.for_model(model, old_table_name)
|
@@ -427,69 +427,54 @@ module Generators
|
|
427
427
|
end
|
428
428
|
end || i
|
429
429
|
end
|
430
|
-
|
431
|
-
|
432
|
-
|
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
|
433
445
|
end
|
434
|
-
model_has_primary_key = model_indexes.any? { |i| i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME }
|
435
446
|
|
436
|
-
|
437
|
-
|
438
|
-
undo_add_indexes << drop_index(old_table_name, i.name) unless i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME
|
439
|
-
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)
|
440
449
|
end
|
441
|
-
undo_drop_indexes = []
|
442
|
-
drop_indexes = (existing_indexes - model_indexes).map do |i|
|
443
|
-
undo_drop_indexes << i.to_add_statement(old_table_name, model_has_primary_key)
|
444
|
-
drop_index(new_table_name, i.name) unless i.name == ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME
|
445
|
-
end.compact
|
446
450
|
|
447
|
-
|
448
|
-
|
449
|
-
|
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
|
450
454
|
|
451
|
-
|
452
|
-
|
453
|
-
# for why the rescue exists
|
454
|
-
"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]
|
455
457
|
end
|
456
458
|
|
457
459
|
def change_foreign_key_constraints(model, old_table_name)
|
458
460
|
ActiveRecord::Base.connection.class.name.match?(/SQLite3Adapter/) and raise ArgumentError, 'SQLite does not support foreign keys'
|
459
|
-
|
461
|
+
::DeclareSchema.default_generate_foreign_keys or return []
|
460
462
|
|
461
|
-
new_table_name = model.table_name
|
462
463
|
existing_fks = ::DeclareSchema::Model::ForeignKeyDefinition.for_model(model, old_table_name)
|
463
464
|
model_fks = model.constraint_specs
|
464
465
|
|
465
|
-
undo_add_fks = []
|
466
|
-
add_fks = (model_fks - existing_fks).map do |fk|
|
467
|
-
# next if fk.parent.constantize.abstract_class || fk.parent == fk.model.class_name
|
468
|
-
undo_add_fks << remove_foreign_key(old_table_name, fk.constraint_name)
|
469
|
-
fk.to_add_statement
|
470
|
-
end
|
471
|
-
|
472
|
-
undo_drop_fks = []
|
473
466
|
drop_fks = (existing_fks - model_fks).map do |fk|
|
474
|
-
|
475
|
-
|
467
|
+
::DeclareSchema::SchemaChange::ForeignKeyRemove.new(fk.child_table_name, fk.parent_table_name,
|
468
|
+
column_name: fk.foreign_key_name, name: fk.constraint_name)
|
476
469
|
end
|
477
470
|
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
"remove_foreign_key(#{old_table_name.inspect}, name: #{fk_name.to_s.inspect})"
|
483
|
-
end
|
484
|
-
|
485
|
-
def format_options(options)
|
486
|
-
options.map do |k, v|
|
487
|
-
if k.is_a?(Symbol)
|
488
|
-
"#{k}: #{v.inspect}"
|
489
|
-
else
|
490
|
-
"#{k.inspect} => #{v.inspect}"
|
491
|
-
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)
|
492
475
|
end
|
476
|
+
|
477
|
+
[drop_fks + add_fks]
|
493
478
|
end
|
494
479
|
|
495
480
|
def fk_field_options(model, field_name)
|
@@ -498,7 +483,7 @@ module Generators
|
|
498
483
|
parent_columns = connection.columns(parent_table) rescue []
|
499
484
|
pk_limit =
|
500
485
|
if (pk_column = parent_columns.find { |column| column.name.to_s == "id" }) # right now foreign keys assume id is the target
|
501
|
-
if
|
486
|
+
if ActiveSupport::VERSION::MAJOR < 5
|
502
487
|
pk_column.cast_type.limit
|
503
488
|
else
|
504
489
|
pk_column.limit
|
@@ -518,11 +503,12 @@ module Generators
|
|
518
503
|
new_options_definition = ::DeclareSchema::Model::TableOptionsDefinition.new(model.table_name, table_options_for_model(model))
|
519
504
|
|
520
505
|
if old_options_definition.equivalent?(new_options_definition)
|
521
|
-
[
|
506
|
+
[]
|
522
507
|
else
|
523
508
|
[
|
524
|
-
|
525
|
-
|
509
|
+
::DeclareSchema::SchemaChange::TableChange.new(current_table_name,
|
510
|
+
old_options_definition.settings,
|
511
|
+
new_options_definition.settings)
|
526
512
|
]
|
527
513
|
end
|
528
514
|
end
|
@@ -540,7 +526,7 @@ module Generators
|
|
540
526
|
col_spec = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
|
541
527
|
schema_attributes = col_spec.schema_attributes
|
542
528
|
type = schema_attributes.delete(:type) or raise "no :type found in #{schema_attributes.inspect}"
|
543
|
-
[
|
529
|
+
[type, schema_attributes]
|
544
530
|
end
|
545
531
|
end
|
546
532
|
|
@@ -550,7 +536,7 @@ module Generators
|
|
550
536
|
col_spec = ::DeclareSchema::Model::Column.new(model, current_table_name, column)
|
551
537
|
schema_attributes = col_spec.schema_attributes
|
552
538
|
type = schema_attributes.delete(:type) or raise "no :type found in #{schema_attributes.inspect}"
|
553
|
-
[
|
539
|
+
[type, schema_attributes]
|
554
540
|
end
|
555
541
|
end
|
556
542
|
|
@@ -563,7 +549,7 @@ module Generators
|
|
563
549
|
end
|
564
550
|
end
|
565
551
|
|
566
|
-
SchemaDumper = case
|
552
|
+
SchemaDumper = case ActiveSupport::VERSION::MAJOR
|
567
553
|
when 4
|
568
554
|
ActiveRecord::SchemaDumper
|
569
555
|
else
|
@@ -585,8 +571,8 @@ module Generators
|
|
585
571
|
# TODO: rewrite this method to use charset and collation variables rather than manipulating strings. -Colin
|
586
572
|
def fix_mysql_charset_and_collation(dumped_schema)
|
587
573
|
if !dumped_schema['options: ']
|
588
|
-
dumped_schema.sub!('",', "\", options: \"DEFAULT CHARSET=#{
|
589
|
-
"COLLATE=#{
|
574
|
+
dumped_schema.sub!('",', "\", options: \"DEFAULT CHARSET=#{::DeclareSchema.default_charset} "+
|
575
|
+
"COLLATE=#{::DeclareSchema.default_collation}\",")
|
590
576
|
end
|
591
577
|
default_charset = dumped_schema[/CHARSET=(\w+)/, 1] or raise "unable to find charset in #{dumped_schema.inspect}"
|
592
578
|
default_collation = dumped_schema[/COLLATE=(\w+)/, 1] || default_collation_from_charset(default_charset) or
|