migsupo 0.2.0 → 0.3.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/README.md +49 -0
- data/lib/migsupo/squash.rb +44 -0
- data/lib/migsupo/tasks/migsupo.rake +78 -0
- data/lib/migsupo/version.rb +1 -1
- data/lib/migsupo.rb +9 -8
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 46769b47cf0ccd1fddb7f3391c43cdec1cbe20df56cb3d18efdafc22c9427c09
|
|
4
|
+
data.tar.gz: a8edc433d3472182ddcfd5be264704c6d21ad58d70e491934c3f92cbf53cd3ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 03a340761dfb39ff21b8d80d98f1901b2e553c6b57b8178e0927fdb78db6f0247034c98d2e159379b754700d8ecd7e46ac45d24966794344a920dd93d1a49aae
|
|
7
|
+
data.tar.gz: cbeba7b4ec1da9528edec1ef4efd7c52d25101d3e6f8f071f4bddde68d23a30a059740b902a99475b620cea0f39aa12aa17a67bdc752fd6813045c6262b6b9cf
|
data/README.md
CHANGED
|
@@ -126,6 +126,10 @@ rails db:generate_migration:check
|
|
|
126
126
|
|
|
127
127
|
Adopt a hand-made database change as the truth. See [coherent](#coherent--when-the-db-was-changed-by-hand).
|
|
128
128
|
|
|
129
|
+
### `rails db:squash` / `rails db:squash:apply`
|
|
130
|
+
|
|
131
|
+
Collapse the whole migration history into a single `Init` migration. See [squash](#squash--collapse-the-history-into-one-init-migration).
|
|
132
|
+
|
|
129
133
|
## `coherent` — when the DB was changed by hand
|
|
130
134
|
|
|
131
135
|
Sometimes someone edits the database directly — an emergency `ALTER TABLE` in production, say — and that state is the one you want to keep. The normal flow cannot express this: the Schemafile is behind, and running a migration to catch up would touch columns that are already correct.
|
|
@@ -180,6 +184,51 @@ Nothing special. `db/migrate/*.rb` is a normal migration, so staging and product
|
|
|
180
184
|
- Only what migsupo models is tracked: tables, columns, indexes. Foreign keys, extensions and check constraints are neither diffed nor written to the Schemafile.
|
|
181
185
|
- Primary key options (`id: :uuid`, `primary_key: "uid"`) are not read back from the database, so the regenerated Schemafile drops them. Diffs are unaffected — table options are not compared — but re-add them by hand if you ever recreate the table from the Schemafile.
|
|
182
186
|
|
|
187
|
+
## `squash` — collapse the history into one Init migration
|
|
188
|
+
|
|
189
|
+
When `db/migrate` has grown long, `squash` replaces every migration file with a single `<timestamp>_init.rb` that creates the whole schema as declared in the Schemafile, and then rewrites `schema_migrations` to hold only that version. No DDL is executed.
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
db/migrate/*.rb (N files)
|
|
193
|
+
├─ db:squash → db/migrate/<ts>_init.rb (all other files deleted)
|
|
194
|
+
└─ db:squash:apply → schema_migrations = [<ts>] ← tables untouched
|
|
195
|
+
db/schema.rb re-dumped
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 1. Generate
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
rails db:squash
|
|
202
|
+
rails db:squash DRY_RUN=true # print the Init migration, change nothing
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
It aborts unless both hold:
|
|
206
|
+
|
|
207
|
+
- every migration file has been applied (pending ones may carry data migrations the Schemafile cannot express)
|
|
208
|
+
- the DB matches the Schemafile (the Init migration must reproduce this exact DB)
|
|
209
|
+
|
|
210
|
+
### 2. Review, then rewrite history
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
rails db:squash:apply VERSION=20261103120000
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`VERSION` can be omitted when `db/migrate` holds exactly one file (right after `db:squash`); that one is used.
|
|
217
|
+
|
|
218
|
+
### On other environments
|
|
219
|
+
|
|
220
|
+
Unlike `coherent`, this is **not** picked up by plain `rails db:migrate` — the Init migration would try to create tables that already exist. On every existing environment:
|
|
221
|
+
|
|
222
|
+
1. Deploy the pre-squash code and run `rails db:migrate` so the DB matches the Schemafile.
|
|
223
|
+
2. Deploy the squashed code and run `rails db:squash:apply VERSION=<ts>` **before** `rails db:migrate`.
|
|
224
|
+
|
|
225
|
+
New environments just run `rails db:migrate` (or `db:schema:load`).
|
|
226
|
+
|
|
227
|
+
### Caveats
|
|
228
|
+
|
|
229
|
+
- Data migrations and raw SQL in the old files are gone. Keep them elsewhere if you still need them.
|
|
230
|
+
- Only what migsupo models is emitted: tables, columns, indexes. Foreign keys, extensions and check constraints are not in the Init migration.
|
|
231
|
+
|
|
183
232
|
## Environment Variables
|
|
184
233
|
|
|
185
234
|
| Variable | Default | Description |
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require_relative "parser/schemafile_parser"
|
|
2
|
+
require_relative "generator/migration_builder"
|
|
3
|
+
require_relative "differ/operations/create_table"
|
|
4
|
+
require_relative "coherent"
|
|
5
|
+
|
|
6
|
+
module Migsupo
|
|
7
|
+
# Collapses the whole migration history into a single Init migration built
|
|
8
|
+
# from the Schemafile, then rewrites schema_migrations to match - no DDL.
|
|
9
|
+
module Squash
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# Built from the Schemafile, not the DB: the DB loader drops primary key
|
|
13
|
+
# options (id: :uuid etc.), the Schemafile keeps them.
|
|
14
|
+
def build(schemafile_path: nil, rails_version: nil)
|
|
15
|
+
schemafile_path ||= Migsupo.configuration.schemafile_path
|
|
16
|
+
schema = Parser::SchemafileParser.parse(schemafile_path)
|
|
17
|
+
ops = schema.tables.values.map { |table| Differ::Operations::CreateTable.new(table) }
|
|
18
|
+
|
|
19
|
+
Generator::MigrationBuilder.new(rails_version: rails_version).build(ops, class_name: "Init")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Deletes every migration file and writes <timestamp>_init.rb. Returns its path.
|
|
23
|
+
def write(content, migrations_dir: nil)
|
|
24
|
+
migrations_dir ||= Migsupo.configuration.migrations_dir
|
|
25
|
+
Dir.glob(File.join(migrations_dir, "*.rb")).each { |path| File.delete(path) }
|
|
26
|
+
|
|
27
|
+
path = File.join(migrations_dir, "#{Time.now.strftime('%Y%m%d%H%M%S')}_init.rb")
|
|
28
|
+
File.write(path, content)
|
|
29
|
+
path
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Replaces all history with the single version. Atomic so a failure
|
|
33
|
+
# cannot leave schema_migrations empty.
|
|
34
|
+
def rewrite_history(version)
|
|
35
|
+
connection = Coherent.connection
|
|
36
|
+
table = connection.quote_table_name("schema_migrations")
|
|
37
|
+
|
|
38
|
+
connection.transaction do
|
|
39
|
+
connection.execute("DELETE FROM #{table}")
|
|
40
|
+
connection.execute("INSERT INTO #{table} (version) VALUES (#{connection.quote(version.to_s)})")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -133,3 +133,81 @@ namespace :db do
|
|
|
133
133
|
end
|
|
134
134
|
end
|
|
135
135
|
end
|
|
136
|
+
|
|
137
|
+
namespace :db do
|
|
138
|
+
desc "Collapse every migration into a single Init migration built from the Schemafile"
|
|
139
|
+
task squash: :environment do
|
|
140
|
+
schemafile_path = ENV.fetch("SCHEMAFILE", Migsupo.configuration.schemafile_path)
|
|
141
|
+
output_dir = ENV.fetch("OUTPUT_DIR", Migsupo.configuration.migrations_dir)
|
|
142
|
+
dry_run = ENV["DRY_RUN"] == "true"
|
|
143
|
+
|
|
144
|
+
# Pending files may hold data migrations that the Schemafile cannot express.
|
|
145
|
+
pending = Migsupo::Coherent.pending(output_dir)
|
|
146
|
+
unless pending.empty?
|
|
147
|
+
puts "Run the pending migrations before squashing:"
|
|
148
|
+
pending.each { |version, name| puts " #{version} #{name}" }
|
|
149
|
+
exit 1
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Init replaces the history only if it reproduces this exact DB.
|
|
153
|
+
diff = Migsupo.calculate_diff(schemafile_path: schemafile_path, loader: :active_record)
|
|
154
|
+
unless diff.empty?
|
|
155
|
+
puts "The database and the Schemafile do not match. Bring them in sync before squashing:"
|
|
156
|
+
puts diff.to_s
|
|
157
|
+
exit 1
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
content = Migsupo::Squash.build(schemafile_path: schemafile_path, rails_version: Migsupo.detect_rails_version)
|
|
161
|
+
if dry_run
|
|
162
|
+
puts content
|
|
163
|
+
next
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
removed = Migsupo::Coherent.migration_files(output_dir).size
|
|
167
|
+
path = Migsupo::Squash.write(content, migrations_dir: output_dir)
|
|
168
|
+
version = File.basename(path)[/\A\d+/]
|
|
169
|
+
|
|
170
|
+
puts "Removed #{removed} migration file(s) and generated:"
|
|
171
|
+
puts " #{path}"
|
|
172
|
+
puts
|
|
173
|
+
puts "Review it, then replace the history on every environment (no DDL is executed):"
|
|
174
|
+
puts " rails db:squash:apply VERSION=#{version}"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
namespace :squash do
|
|
178
|
+
desc "Replace schema_migrations with the single Init version (VERSION=ts)"
|
|
179
|
+
task apply: :environment do
|
|
180
|
+
schemafile_path = ENV.fetch("SCHEMAFILE", Migsupo.configuration.schemafile_path)
|
|
181
|
+
output_dir = ENV.fetch("OUTPUT_DIR", Migsupo.configuration.migrations_dir)
|
|
182
|
+
version = ENV["VERSION"].to_s.strip
|
|
183
|
+
|
|
184
|
+
if version.empty?
|
|
185
|
+
files = Migsupo::Coherent.migration_files(output_dir)
|
|
186
|
+
unless files.size == 1
|
|
187
|
+
puts "VERSION is required when #{output_dir} does not hold exactly one migration (found #{files.size})."
|
|
188
|
+
exit 1
|
|
189
|
+
end
|
|
190
|
+
version = files.first.first
|
|
191
|
+
puts "Using #{files.first.last}"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
unless Migsupo::Coherent.migration_files(output_dir).to_h[version]
|
|
195
|
+
puts "No migration found in #{output_dir} for: #{version}"
|
|
196
|
+
exit 1
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
diff = Migsupo.calculate_diff(schemafile_path: schemafile_path, loader: :active_record)
|
|
200
|
+
unless diff.empty?
|
|
201
|
+
puts "The database and the Schemafile do not match. Run rails db:migrate on the pre-squash code first:"
|
|
202
|
+
puts diff.to_s
|
|
203
|
+
exit 1
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
Migsupo::Squash.rewrite_history(version)
|
|
207
|
+
puts "schema_migrations now holds only: #{version}"
|
|
208
|
+
|
|
209
|
+
Rake::Task["db:schema:dump"].invoke
|
|
210
|
+
puts "Updated db/schema.rb."
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
data/lib/migsupo/version.rb
CHANGED
data/lib/migsupo.rb
CHANGED
|
@@ -10,6 +10,7 @@ require_relative "migsupo/loader/schema_rb_loader"
|
|
|
10
10
|
require_relative "migsupo/differ/diff_calculator"
|
|
11
11
|
require_relative "migsupo/generator/migration_generator"
|
|
12
12
|
require_relative "migsupo/coherent"
|
|
13
|
+
require_relative "migsupo/squash"
|
|
13
14
|
|
|
14
15
|
module Migsupo
|
|
15
16
|
class << self
|
|
@@ -39,6 +40,14 @@ module Migsupo
|
|
|
39
40
|
.generate(diff, output_dir: output_dir, dry_run: dry_run)
|
|
40
41
|
end
|
|
41
42
|
|
|
43
|
+
def detect_rails_version
|
|
44
|
+
return configuration.migration_version if configuration.migration_version
|
|
45
|
+
return nil unless defined?(Rails)
|
|
46
|
+
|
|
47
|
+
major_minor = Rails::VERSION::STRING.split(".").first(2).join(".")
|
|
48
|
+
major_minor
|
|
49
|
+
end
|
|
50
|
+
|
|
42
51
|
private
|
|
43
52
|
|
|
44
53
|
def build_loader(loader)
|
|
@@ -62,14 +71,6 @@ module Migsupo
|
|
|
62
71
|
"db/schema.rb"
|
|
63
72
|
end
|
|
64
73
|
end
|
|
65
|
-
|
|
66
|
-
def detect_rails_version
|
|
67
|
-
return configuration.migration_version if configuration.migration_version
|
|
68
|
-
return nil unless defined?(Rails)
|
|
69
|
-
|
|
70
|
-
major_minor = Rails::VERSION::STRING.split(".").first(2).join(".")
|
|
71
|
-
major_minor
|
|
72
|
-
end
|
|
73
74
|
end
|
|
74
75
|
end
|
|
75
76
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: migsupo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- masak1yu
|
|
@@ -129,6 +129,7 @@ files:
|
|
|
129
129
|
- lib/migsupo/schema/index_definition.rb
|
|
130
130
|
- lib/migsupo/schema/schema_definition.rb
|
|
131
131
|
- lib/migsupo/schema/table_definition.rb
|
|
132
|
+
- lib/migsupo/squash.rb
|
|
132
133
|
- lib/migsupo/tasks/migsupo.rake
|
|
133
134
|
- lib/migsupo/version.rb
|
|
134
135
|
- migsupo.gemspec
|