declare_schema 0.8.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/declare_schema_build.yml +1 -1
- data/CHANGELOG.md +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
|