actual_db_schema 0.4.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +14 -1
- data/Appraisals +8 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +3 -3
- data/Gemfile.lock +200 -24
- data/README.md +37 -21
- data/Rakefile +2 -2
- data/actual_db_schema.gemspec +8 -2
- data/gemfiles/rails.6.0.gemfile +13 -0
- data/gemfiles/rails.6.1.gemfile +13 -0
- data/gemfiles/rails.7.0.gemfile +13 -0
- data/gemfiles/rails.7.1.gemfile +13 -0
- data/lib/actual_db_schema/commands/base.rb +33 -0
- data/lib/actual_db_schema/commands/list.rb +37 -0
- data/lib/actual_db_schema/commands/rollback.rb +22 -0
- data/lib/actual_db_schema/patches/migration_context.rb +42 -0
- data/lib/actual_db_schema/patches/migration_proxy.rb +13 -0
- data/lib/actual_db_schema/patches/migrator.rb +13 -0
- data/lib/actual_db_schema/version.rb +1 -1
- data/lib/actual_db_schema.rb +30 -1
- data/lib/railtie.rb +0 -1
- data/lib/tasks/db.rake +5 -87
- metadata +86 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 116a62e0bbc1d2f43ad5e7654f732807bb6807520f26f33bfde84889601bb895
|
4
|
+
data.tar.gz: 63c36388ed0efa1d19b9daec936206b6580e22b1b04b25a0db3f058047c34b56
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef27b62051c6183b68a35018b0ed7f402f25f8de2f3d3da8f842b914c13516a49b00ab57687a513bca04b664fc682b40c93c75929e414dfea514c2e0065a68b9
|
7
|
+
data.tar.gz: 7a7f0ce3ef0ce09a5a0ecf90ff0aec292ae6f9ab58050ce946dd849123b36162398592a9249ea9ec4acecbe9cfe2031737cbe16c5c0b5ef89989ebb48237fee9
|
data/.rubocop.yml
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
AllCops:
|
2
|
-
TargetRubyVersion: 2.
|
2
|
+
TargetRubyVersion: 2.7
|
3
|
+
Exclude:
|
4
|
+
- gemfiles/*
|
5
|
+
- test/dummy_app/**/*
|
6
|
+
- vendor/**/*
|
3
7
|
|
4
8
|
Style/StringLiterals:
|
5
9
|
Enabled: true
|
@@ -11,3 +15,12 @@ Style/StringLiteralsInInterpolation:
|
|
11
15
|
|
12
16
|
Layout/LineLength:
|
13
17
|
Max: 120
|
18
|
+
|
19
|
+
Metrics/BlockLength:
|
20
|
+
Exclude:
|
21
|
+
- test/**/*
|
22
|
+
- actual_db_schema.gemspec
|
23
|
+
|
24
|
+
Metrics/MethodLength:
|
25
|
+
Exclude:
|
26
|
+
- test/**/*
|
data/Appraisals
ADDED
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
@@ -5,8 +5,8 @@ source "https://rubygems.org"
|
|
5
5
|
# Specify your gem's dependencies in actual_db_schema.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
gem "
|
9
|
-
|
8
|
+
gem "activerecord", "~> 7.1.0"
|
9
|
+
gem "activesupport", "~> 7.1.0"
|
10
10
|
gem "minitest", "~> 5.0"
|
11
|
-
|
11
|
+
gem "rake"
|
12
12
|
gem "rubocop", "~> 1.21"
|
data/Gemfile.lock
CHANGED
@@ -1,61 +1,237 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
actual_db_schema (0.
|
5
|
-
activerecord
|
4
|
+
actual_db_schema (0.6.0)
|
5
|
+
activerecord (>= 6.0.0)
|
6
|
+
activesupport (>= 6.0.0)
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: https://rubygems.org/
|
9
10
|
specs:
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
actioncable (7.1.1)
|
12
|
+
actionpack (= 7.1.1)
|
13
|
+
activesupport (= 7.1.1)
|
14
|
+
nio4r (~> 2.0)
|
15
|
+
websocket-driver (>= 0.6.1)
|
16
|
+
zeitwerk (~> 2.6)
|
17
|
+
actionmailbox (7.1.1)
|
18
|
+
actionpack (= 7.1.1)
|
19
|
+
activejob (= 7.1.1)
|
20
|
+
activerecord (= 7.1.1)
|
21
|
+
activestorage (= 7.1.1)
|
22
|
+
activesupport (= 7.1.1)
|
23
|
+
mail (>= 2.7.1)
|
24
|
+
net-imap
|
25
|
+
net-pop
|
26
|
+
net-smtp
|
27
|
+
actionmailer (7.1.1)
|
28
|
+
actionpack (= 7.1.1)
|
29
|
+
actionview (= 7.1.1)
|
30
|
+
activejob (= 7.1.1)
|
31
|
+
activesupport (= 7.1.1)
|
32
|
+
mail (~> 2.5, >= 2.5.4)
|
33
|
+
net-imap
|
34
|
+
net-pop
|
35
|
+
net-smtp
|
36
|
+
rails-dom-testing (~> 2.2)
|
37
|
+
actionpack (7.1.1)
|
38
|
+
actionview (= 7.1.1)
|
39
|
+
activesupport (= 7.1.1)
|
40
|
+
nokogiri (>= 1.8.5)
|
41
|
+
rack (>= 2.2.4)
|
42
|
+
rack-session (>= 1.0.1)
|
43
|
+
rack-test (>= 0.6.3)
|
44
|
+
rails-dom-testing (~> 2.2)
|
45
|
+
rails-html-sanitizer (~> 1.6)
|
46
|
+
actiontext (7.1.1)
|
47
|
+
actionpack (= 7.1.1)
|
48
|
+
activerecord (= 7.1.1)
|
49
|
+
activestorage (= 7.1.1)
|
50
|
+
activesupport (= 7.1.1)
|
51
|
+
globalid (>= 0.6.0)
|
52
|
+
nokogiri (>= 1.8.5)
|
53
|
+
actionview (7.1.1)
|
54
|
+
activesupport (= 7.1.1)
|
55
|
+
builder (~> 3.1)
|
56
|
+
erubi (~> 1.11)
|
57
|
+
rails-dom-testing (~> 2.2)
|
58
|
+
rails-html-sanitizer (~> 1.6)
|
59
|
+
activejob (7.1.1)
|
60
|
+
activesupport (= 7.1.1)
|
61
|
+
globalid (>= 0.3.6)
|
62
|
+
activemodel (7.1.1)
|
63
|
+
activesupport (= 7.1.1)
|
64
|
+
activerecord (7.1.1)
|
65
|
+
activemodel (= 7.1.1)
|
66
|
+
activesupport (= 7.1.1)
|
67
|
+
timeout (>= 0.4.0)
|
68
|
+
activestorage (7.1.1)
|
69
|
+
actionpack (= 7.1.1)
|
70
|
+
activejob (= 7.1.1)
|
71
|
+
activerecord (= 7.1.1)
|
72
|
+
activesupport (= 7.1.1)
|
73
|
+
marcel (~> 1.0)
|
74
|
+
activesupport (7.1.1)
|
75
|
+
base64
|
76
|
+
bigdecimal
|
16
77
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
78
|
+
connection_pool (>= 2.2.5)
|
79
|
+
drb
|
17
80
|
i18n (>= 1.6, < 2)
|
18
81
|
minitest (>= 5.1)
|
82
|
+
mutex_m
|
19
83
|
tzinfo (~> 2.0)
|
84
|
+
appraisal (2.5.0)
|
85
|
+
bundler
|
86
|
+
rake
|
87
|
+
thor (>= 0.14.0)
|
20
88
|
ast (2.4.2)
|
89
|
+
base64 (0.1.1)
|
90
|
+
bigdecimal (3.1.4)
|
91
|
+
builder (3.2.4)
|
21
92
|
concurrent-ruby (1.2.2)
|
93
|
+
connection_pool (2.4.1)
|
94
|
+
crass (1.0.6)
|
95
|
+
date (3.3.3)
|
96
|
+
debug (1.8.0)
|
97
|
+
irb (>= 1.5.0)
|
98
|
+
reline (>= 0.3.1)
|
99
|
+
drb (2.1.1)
|
100
|
+
ruby2_keywords
|
101
|
+
erubi (1.12.0)
|
102
|
+
globalid (1.2.1)
|
103
|
+
activesupport (>= 6.1)
|
22
104
|
i18n (1.14.1)
|
23
105
|
concurrent-ruby (~> 1.0)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
106
|
+
io-console (0.6.0)
|
107
|
+
irb (1.8.3)
|
108
|
+
rdoc
|
109
|
+
reline (>= 0.3.8)
|
110
|
+
json (2.6.3)
|
111
|
+
language_server-protocol (3.17.0.3)
|
112
|
+
loofah (2.21.4)
|
113
|
+
crass (~> 1.0.2)
|
114
|
+
nokogiri (>= 1.12.0)
|
115
|
+
mail (2.8.1)
|
116
|
+
mini_mime (>= 0.1.1)
|
117
|
+
net-imap
|
118
|
+
net-pop
|
119
|
+
net-smtp
|
120
|
+
marcel (1.0.2)
|
121
|
+
mini_mime (1.1.5)
|
122
|
+
minitest (5.20.0)
|
123
|
+
mutex_m (0.1.2)
|
124
|
+
net-imap (0.4.4)
|
125
|
+
date
|
126
|
+
net-protocol
|
127
|
+
net-pop (0.1.2)
|
128
|
+
net-protocol
|
129
|
+
net-protocol (0.2.1)
|
130
|
+
timeout
|
131
|
+
net-smtp (0.4.0)
|
132
|
+
net-protocol
|
133
|
+
nio4r (2.5.9)
|
134
|
+
nokogiri (1.15.4-x86_64-darwin)
|
135
|
+
racc (~> 1.4)
|
136
|
+
nokogiri (1.15.4-x86_64-linux)
|
137
|
+
racc (~> 1.4)
|
138
|
+
parallel (1.23.0)
|
139
|
+
parser (3.2.2.4)
|
28
140
|
ast (~> 2.4.1)
|
141
|
+
racc
|
142
|
+
psych (5.1.1.1)
|
143
|
+
stringio
|
144
|
+
racc (1.7.3)
|
145
|
+
rack (3.0.8)
|
146
|
+
rack-session (2.0.0)
|
147
|
+
rack (>= 3.0.0)
|
148
|
+
rack-test (2.1.0)
|
149
|
+
rack (>= 1.3)
|
150
|
+
rackup (2.1.0)
|
151
|
+
rack (>= 3)
|
152
|
+
webrick (~> 1.8)
|
153
|
+
rails (7.1.1)
|
154
|
+
actioncable (= 7.1.1)
|
155
|
+
actionmailbox (= 7.1.1)
|
156
|
+
actionmailer (= 7.1.1)
|
157
|
+
actionpack (= 7.1.1)
|
158
|
+
actiontext (= 7.1.1)
|
159
|
+
actionview (= 7.1.1)
|
160
|
+
activejob (= 7.1.1)
|
161
|
+
activemodel (= 7.1.1)
|
162
|
+
activerecord (= 7.1.1)
|
163
|
+
activestorage (= 7.1.1)
|
164
|
+
activesupport (= 7.1.1)
|
165
|
+
bundler (>= 1.15.0)
|
166
|
+
railties (= 7.1.1)
|
167
|
+
rails-dom-testing (2.2.0)
|
168
|
+
activesupport (>= 5.0.0)
|
169
|
+
minitest
|
170
|
+
nokogiri (>= 1.6)
|
171
|
+
rails-html-sanitizer (1.6.0)
|
172
|
+
loofah (~> 2.21)
|
173
|
+
nokogiri (~> 1.14)
|
174
|
+
railties (7.1.1)
|
175
|
+
actionpack (= 7.1.1)
|
176
|
+
activesupport (= 7.1.1)
|
177
|
+
irb
|
178
|
+
rackup (>= 1.0.0)
|
179
|
+
rake (>= 12.2)
|
180
|
+
thor (~> 1.0, >= 1.2.2)
|
181
|
+
zeitwerk (~> 2.6)
|
29
182
|
rainbow (3.1.1)
|
30
|
-
rake (13.0
|
31
|
-
|
32
|
-
|
33
|
-
|
183
|
+
rake (13.1.0)
|
184
|
+
rdoc (6.5.0)
|
185
|
+
psych (>= 4.0.0)
|
186
|
+
regexp_parser (2.8.2)
|
187
|
+
reline (0.3.9)
|
188
|
+
io-console (~> 0.5)
|
189
|
+
rexml (3.2.6)
|
190
|
+
rubocop (1.57.2)
|
34
191
|
json (~> 2.3)
|
192
|
+
language_server-protocol (>= 3.17.0)
|
35
193
|
parallel (~> 1.10)
|
36
|
-
parser (>= 3.
|
194
|
+
parser (>= 3.2.2.4)
|
37
195
|
rainbow (>= 2.2.2, < 4.0)
|
38
196
|
regexp_parser (>= 1.8, < 3.0)
|
39
197
|
rexml (>= 3.2.5, < 4.0)
|
40
|
-
rubocop-ast (>= 1.
|
198
|
+
rubocop-ast (>= 1.28.1, < 2.0)
|
41
199
|
ruby-progressbar (~> 1.7)
|
42
|
-
unicode-display_width (>=
|
43
|
-
rubocop-ast (1.
|
44
|
-
parser (>= 3.
|
45
|
-
ruby-progressbar (1.
|
200
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
201
|
+
rubocop-ast (1.30.0)
|
202
|
+
parser (>= 3.2.1.0)
|
203
|
+
ruby-progressbar (1.13.0)
|
204
|
+
ruby2_keywords (0.0.5)
|
205
|
+
sqlite3 (1.6.8-x86_64-darwin)
|
206
|
+
sqlite3 (1.6.8-x86_64-linux)
|
207
|
+
stringio (3.0.8)
|
208
|
+
thor (1.3.0)
|
209
|
+
timeout (0.4.0)
|
46
210
|
tzinfo (2.0.6)
|
47
211
|
concurrent-ruby (~> 1.0)
|
48
|
-
unicode-display_width (2.
|
212
|
+
unicode-display_width (2.5.0)
|
213
|
+
webrick (1.8.1)
|
214
|
+
websocket-driver (0.7.6)
|
215
|
+
websocket-extensions (>= 0.1.0)
|
216
|
+
websocket-extensions (0.1.5)
|
217
|
+
zeitwerk (2.6.12)
|
49
218
|
|
50
219
|
PLATFORMS
|
51
220
|
x86_64-darwin-20
|
221
|
+
x86_64-darwin-22
|
52
222
|
x86_64-linux
|
53
223
|
|
54
224
|
DEPENDENCIES
|
225
|
+
activerecord (~> 7.1.0)
|
226
|
+
activesupport (~> 7.1.0)
|
55
227
|
actual_db_schema!
|
228
|
+
appraisal
|
229
|
+
debug
|
56
230
|
minitest (~> 5.0)
|
57
|
-
|
231
|
+
rails
|
232
|
+
rake
|
58
233
|
rubocop (~> 1.21)
|
234
|
+
sqlite3
|
59
235
|
|
60
236
|
BUNDLED WITH
|
61
237
|
2.3.7
|
data/README.md
CHANGED
@@ -2,7 +2,37 @@
|
|
2
2
|
|
3
3
|
# ActualDbSchema
|
4
4
|
|
5
|
-
|
5
|
+
Does switching between branches in your Rails app mess up the DB schema?
|
6
|
+
|
7
|
+
Keep the DB schema actual across branches in your Rails project. Just install `actual_db_schema` gem and run `db:migrate` in branches as usual. It automatically rolls back the *phantom migrations* (non-relevant to the current branch). No additional steps are needed.
|
8
|
+
|
9
|
+
## Why ActualDbSchema
|
10
|
+
|
11
|
+
Still not clear why it's needed? To grasp the purpose of this gem and the issue it addresses, review the problem definition outlined below.
|
12
|
+
|
13
|
+
### The problem definition
|
14
|
+
|
15
|
+
Imagine you're working on **branch A**. You add a not-null column to a database table with a migration. You run the migration. Then you switch to **branch B**. The code in **branch B** isn't aware of this newly added field. When it tries to write data to the table, it fails with an error `null value provided for non-null field`. Why? The existing code is writing a null value into the column with a not-null constraint.
|
16
|
+
|
17
|
+
Here's an example of this error:
|
18
|
+
|
19
|
+
ActiveRecord::NotNullViolation:
|
20
|
+
PG::NotNullViolation: ERROR: null value in column "log" of relation "check_results" violates not-null constraint
|
21
|
+
DETAIL: Failing row contains (8, 46, success, 2022-10-16 21:47:21.07212, 2022-10-16 21:47:21.07212, null).
|
22
|
+
|
23
|
+
Furthermore, the `db:migrate` task on **branch B** generates an irrelevant diff on the `schema.rb` file, reflecting the new column added in **branch A**.
|
24
|
+
|
25
|
+
To fix this, you need to switch back to **branch A**, find the migration that added the problematic field, and roll it back. We'll call it a *phantom migration*. It's a pain, especially if you have a lot of branches in your project because you have to remember which branch the *phantom migration* is in and then manually roll it back.
|
26
|
+
|
27
|
+
With `actual_db_schema` gem you don't need to care about that anymore. It saves you time by handling all this dirty work behind the scenes automatically.
|
28
|
+
|
29
|
+
### How it solves the issue
|
30
|
+
|
31
|
+
This gem stores all run migrations with their code in the `tmp/migrations` folder. Whenever you perform a schema dump, it rolls back the *phantom migrations*.
|
32
|
+
|
33
|
+
The *phantom migrations* list is the difference between the migrations you've executed (in the `tmp/migrations` folder) and the current ones (in the `db/migrate` folder).
|
34
|
+
|
35
|
+
Therefore, all you do is run rails `db:migrate` in your current branch. `actual_db_schema` will ensure the DB schema is up-to-date. You'll never have an inaccurate `schema.rb` file again.
|
6
36
|
|
7
37
|
## Installation
|
8
38
|
|
@@ -20,28 +50,14 @@ And then execute:
|
|
20
50
|
|
21
51
|
## Usage
|
22
52
|
|
23
|
-
|
24
|
-
|
25
|
-
In **branch A** I add a mandatory (not null) field into DB via migration and run it.
|
26
|
-
Then I switch to another **branch B**. This branch's code is not aware of that field.
|
27
|
-
As the result, the code is failing with an error "null value provided for non-null field".
|
28
|
-
Moreover, a DB rake task generates a diff on `schema.rb` that's not relevant to this branch.
|
29
|
-
I can switch to **branch A** and roll back the migration, but I need to remember that branch or waste time on it.
|
30
|
-
|
31
|
-
Some example of this error:
|
32
|
-
|
33
|
-
ActiveRecord::NotNullViolation:
|
34
|
-
PG::NotNullViolation: ERROR: null value in column "log" of relation "check_results" violates not-null constraint
|
35
|
-
DETAIL: Failing row contains (8, 46, success, 2022-10-16 21:47:21.07212, 2022-10-16 21:47:21.07212, null).
|
36
|
-
|
37
|
-
This gem saves all run migrations inside `tmp/migrations` folder.
|
38
|
-
Every run of schema dump (that's a dependency of `db:migrate` task as well) rolls back the "unknown" for the current branch migrations
|
39
|
-
looking into the `tmp/migrations` folder.
|
53
|
+
Just run `rails db:migrate` inside the current branch.
|
40
54
|
|
41
|
-
|
55
|
+
> [!WARNING]
|
56
|
+
> This solution implies that all migrations are reversible. The irreversible migrations should be solved manually. At the moment, the gem ignores them. You will see warnings in the terminal for each irreversible migrations.
|
42
57
|
|
43
|
-
|
44
|
-
|
58
|
+
The gem offers the following rake tasks that can be manually run according to your preferences:
|
59
|
+
- `rails db:rollback_branches` - run it to manually rolls back phantom migrations.
|
60
|
+
- `rails db:phantom_migrations` - displays a list of phantom migrations.
|
45
61
|
|
46
62
|
## Development
|
47
63
|
|
data/Rakefile
CHANGED
@@ -6,11 +6,11 @@ require "rake/testtask"
|
|
6
6
|
Rake::TestTask.new(:test) do |t|
|
7
7
|
t.libs << "test"
|
8
8
|
t.libs << "lib"
|
9
|
-
t.test_files = FileList["test/**/test_*.rb"]
|
9
|
+
t.test_files = FileList["test/**/test_*.rb", "test/**/*_test.rb"]
|
10
10
|
end
|
11
11
|
|
12
12
|
require "rubocop/rake_task"
|
13
13
|
|
14
14
|
RuboCop::RakeTask.new
|
15
15
|
|
16
|
-
task default: %i[test
|
16
|
+
task default: %i[test]
|
data/actual_db_schema.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
DESC
|
17
17
|
spec.homepage = "https://github.com/widefix/actual_db_schema"
|
18
18
|
spec.license = "MIT"
|
19
|
-
spec.required_ruby_version = ">= 2.
|
19
|
+
spec.required_ruby_version = ">= 2.7.0"
|
20
20
|
|
21
21
|
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
22
22
|
|
@@ -36,7 +36,13 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.require_paths = ["lib"]
|
37
37
|
|
38
38
|
# Uncomment to register a new dependency of your gem
|
39
|
-
spec.
|
39
|
+
spec.add_runtime_dependency "activerecord", ">= 6.0.0"
|
40
|
+
spec.add_runtime_dependency "activesupport", ">= 6.0.0"
|
41
|
+
|
42
|
+
spec.add_development_dependency "appraisal"
|
43
|
+
spec.add_development_dependency "debug"
|
44
|
+
spec.add_development_dependency "rails"
|
45
|
+
spec.add_development_dependency "sqlite3"
|
40
46
|
|
41
47
|
# For more information and examples about making a new gem, check out our
|
42
48
|
# guide at: https://bundler.io/guides/creating_gem.html
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file was generated by Appraisal
|
4
|
+
|
5
|
+
source "https://rubygems.org"
|
6
|
+
|
7
|
+
gem "activerecord", "~> 6.0.0"
|
8
|
+
gem "activesupport", "~> 6.0.0"
|
9
|
+
gem "minitest", "~> 5.0"
|
10
|
+
gem "rake"
|
11
|
+
gem "rubocop", "~> 1.21"
|
12
|
+
|
13
|
+
gemspec path: "../"
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file was generated by Appraisal
|
4
|
+
|
5
|
+
source "https://rubygems.org"
|
6
|
+
|
7
|
+
gem "activerecord", "~> 6.1.0"
|
8
|
+
gem "activesupport", "~> 6.1.0"
|
9
|
+
gem "minitest", "~> 5.0"
|
10
|
+
gem "rake"
|
11
|
+
gem "rubocop", "~> 1.21"
|
12
|
+
|
13
|
+
gemspec path: "../"
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file was generated by Appraisal
|
4
|
+
|
5
|
+
source "https://rubygems.org"
|
6
|
+
|
7
|
+
gem "activerecord", "~> 7.0.0"
|
8
|
+
gem "activesupport", "~> 7.0.0"
|
9
|
+
gem "minitest", "~> 5.0"
|
10
|
+
gem "rake"
|
11
|
+
gem "rubocop", "~> 1.21"
|
12
|
+
|
13
|
+
gemspec path: "../"
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file was generated by Appraisal
|
4
|
+
|
5
|
+
source "https://rubygems.org"
|
6
|
+
|
7
|
+
gem "activerecord", "~> 7.1.0"
|
8
|
+
gem "activesupport", "~> 7.1.0"
|
9
|
+
gem "minitest", "~> 5.0"
|
10
|
+
gem "rake"
|
11
|
+
gem "rubocop", "~> 1.21"
|
12
|
+
|
13
|
+
gemspec path: "../"
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActualDbSchema
|
4
|
+
module Commands
|
5
|
+
# Base class for all commands
|
6
|
+
class Base
|
7
|
+
def call
|
8
|
+
unless ActualDbSchema.config.fetch(:enabled, true)
|
9
|
+
raise "ActualDbSchema is disabled. Set ActualDbSchema.config[:enabled] = true to enable it."
|
10
|
+
end
|
11
|
+
|
12
|
+
if ActiveRecord::Migration.current_version >= 6
|
13
|
+
ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:rollback_branches")
|
14
|
+
end
|
15
|
+
|
16
|
+
call_impl
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def call_impl
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
def context
|
26
|
+
@context ||=
|
27
|
+
ActiveRecord::Base.connection.migration_context.tap do |c|
|
28
|
+
c.extend(ActualDbSchema::Patches::MigrationContext)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActualDbSchema
|
4
|
+
module Commands
|
5
|
+
# Shows the list of phantom migrations
|
6
|
+
class List < Base
|
7
|
+
private
|
8
|
+
|
9
|
+
def call_impl
|
10
|
+
preambule
|
11
|
+
table
|
12
|
+
end
|
13
|
+
|
14
|
+
def indexed_phantom_migrations
|
15
|
+
@indexed_phantom_migrations ||= context.migrations.index_by { |m| m.version.to_s }
|
16
|
+
end
|
17
|
+
|
18
|
+
def preambule
|
19
|
+
puts "\nPhantom migrations\n\n"
|
20
|
+
puts "Below is a list of irrelevant migrations executed in unmerged branches."
|
21
|
+
puts "To bring your database schema up to date, the migrations marked as \"up\" should be rolled back."
|
22
|
+
puts "\ndatabase: #{ActiveRecord::Base.connection_db_config.database}\n\n"
|
23
|
+
puts %(#{"Status".center(8)} #{"Migration ID".ljust(14)} Migration File)
|
24
|
+
puts "-" * 50
|
25
|
+
end
|
26
|
+
|
27
|
+
def table
|
28
|
+
context.migrations_status.each do |status, version|
|
29
|
+
migration = indexed_phantom_migrations[version]
|
30
|
+
next unless migration
|
31
|
+
|
32
|
+
puts %(#{status.center(8)} #{version.to_s.ljust(14)} #{migration.filename.gsub("#{Rails.root}/", "")})
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActualDbSchema
|
4
|
+
module Commands
|
5
|
+
# Rolls back all phantom migrations
|
6
|
+
class Rollback < Base
|
7
|
+
private
|
8
|
+
|
9
|
+
def call_impl
|
10
|
+
context.rollback_branches
|
11
|
+
|
12
|
+
return if ActualDbSchema.failed.empty?
|
13
|
+
|
14
|
+
puts ""
|
15
|
+
puts "[ActualDbSchema] Irreversible migrations were found from other branches. Roll them back or fix manually:"
|
16
|
+
puts ""
|
17
|
+
puts ActualDbSchema.failed.map { |migration| "- #{migration.filename}" }.join("\n")
|
18
|
+
puts ""
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActualDbSchema
|
4
|
+
module Patches
|
5
|
+
# Add new command to roll back the phantom migrations
|
6
|
+
module MigrationContext
|
7
|
+
def rollback_branches
|
8
|
+
ActualDbSchema.failed = []
|
9
|
+
migrations.reverse_each do |migration|
|
10
|
+
migrator = down_migrator_for(migration)
|
11
|
+
migrator.extend(ActualDbSchema::Patches::Migrator)
|
12
|
+
migrator.migrate
|
13
|
+
rescue StandardError => e
|
14
|
+
raise unless e.message.include?("ActiveRecord::IrreversibleMigration")
|
15
|
+
|
16
|
+
ActualDbSchema.failed << migration
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def down_migrator_for(migration)
|
23
|
+
if ActiveRecord::Migration.current_version < 6
|
24
|
+
ActiveRecord::Migrator.new(:down, [migration], migration.version)
|
25
|
+
elsif ActiveRecord::Migration.current_version < 7.1
|
26
|
+
ActiveRecord::Migrator.new(:down, [migration], schema_migration, migration.version)
|
27
|
+
else
|
28
|
+
ActiveRecord::Migrator.new(:down, [migration], schema_migration, internal_metadata, migration.version)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def migration_files
|
33
|
+
paths = Array(migrations_paths)
|
34
|
+
current_branch_files = Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
|
35
|
+
other_branches_files = Dir["#{ActualDbSchema.migrated_folder}/**/[0-9]*_*.rb"]
|
36
|
+
|
37
|
+
current_branch_file_names = current_branch_files.map { |f| ActualDbSchema.migration_filename(f) }
|
38
|
+
other_branches_files.reject { |f| ActualDbSchema.migration_filename(f).in?(current_branch_file_names) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActualDbSchema
|
4
|
+
module Patches
|
5
|
+
# Records the migration file into the tmp folder after it's been migrated
|
6
|
+
module MigrationProxy
|
7
|
+
def migrate(direction)
|
8
|
+
super(direction)
|
9
|
+
FileUtils.copy(filename, ActualDbSchema.migrated_folder.join(basename)) if direction == :up
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActualDbSchema
|
4
|
+
module Patches
|
5
|
+
# Run only one migration that's being rolled back
|
6
|
+
module Migrator
|
7
|
+
def runnable
|
8
|
+
migration = migrations.first # there is only one migration, because we pass only one here
|
9
|
+
ran?(migration) ? [migration] : []
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/actual_db_schema.rb
CHANGED
@@ -1,8 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_record/migration"
|
3
4
|
require_relative "actual_db_schema/version"
|
5
|
+
require_relative "actual_db_schema/patches/migration_proxy"
|
6
|
+
require_relative "actual_db_schema/patches/migrator"
|
7
|
+
require_relative "actual_db_schema/patches/migration_context"
|
8
|
+
|
9
|
+
require_relative "actual_db_schema/commands/base"
|
10
|
+
require_relative "actual_db_schema/commands/rollback"
|
11
|
+
require_relative "actual_db_schema/commands/list"
|
4
12
|
|
5
13
|
# The main module definition
|
6
14
|
module ActualDbSchema
|
7
|
-
|
15
|
+
raise NotImplementedError, "ActualDbSchema is only supported in Rails" unless defined?(Rails)
|
16
|
+
|
17
|
+
require "railtie"
|
18
|
+
|
19
|
+
class << self
|
20
|
+
attr_accessor :config, :failed
|
21
|
+
end
|
22
|
+
|
23
|
+
self.failed = []
|
24
|
+
self.config = {
|
25
|
+
enabled: Rails.env.development?
|
26
|
+
}
|
27
|
+
|
28
|
+
def self.migrated_folder
|
29
|
+
Rails.root.join("tmp", "migrated").tap { |folder| FileUtils.mkdir_p(folder) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.migration_filename(fullpath)
|
33
|
+
fullpath.split("/").last
|
34
|
+
end
|
8
35
|
end
|
36
|
+
|
37
|
+
ActiveRecord::MigrationProxy.prepend(ActualDbSchema::Patches::MigrationProxy)
|
data/lib/railtie.rb
CHANGED
data/lib/tasks/db.rake
CHANGED
@@ -1,96 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
return unless Rails.env.development?
|
4
|
-
|
5
|
-
require "active_record/migration"
|
6
|
-
|
7
|
-
def migrated_folder
|
8
|
-
Rails.root.join("tmp", "migrated").tap { |folder| FileUtils.mkdir_p(folder) }
|
9
|
-
end
|
10
|
-
|
11
|
-
def migration_filename(fullpath)
|
12
|
-
fullpath.split("/").last
|
13
|
-
end
|
14
|
-
|
15
|
-
# All patches are namespaced into this module
|
16
|
-
module ActualDbSchema
|
17
|
-
class << self
|
18
|
-
attr_accessor :failed
|
19
|
-
end
|
20
|
-
|
21
|
-
self.failed = []
|
22
|
-
|
23
|
-
# Track migrated migrations inside the tmp folder
|
24
|
-
module MigrationProxyPatch
|
25
|
-
def migrate(direction)
|
26
|
-
super(direction)
|
27
|
-
FileUtils.copy(filename, migrated_folder.join(basename)) if direction == :up
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# Run only one migration that's being rolled back
|
32
|
-
module MigratorPatch
|
33
|
-
def runnable
|
34
|
-
migration = migrations.first # there is only one migration, because we pass only one here
|
35
|
-
ran?(migration) ? [migration] : []
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# Add new command to roll back the phantom migrations
|
40
|
-
module MigrationContextPatch
|
41
|
-
def rollback_branches
|
42
|
-
migrations.reverse_each do |migration|
|
43
|
-
migrator = down_migrator_for(migration)
|
44
|
-
migrator.extend(ActualDbSchema::MigratorPatch)
|
45
|
-
migrator.migrate
|
46
|
-
rescue StandardError => e
|
47
|
-
raise unless e.message.include?("ActiveRecord::IrreversibleMigration")
|
48
|
-
|
49
|
-
ActualDbSchema.failed << migration
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def down_migrator_for(migration)
|
56
|
-
if ActiveRecord::Migration.current_version < 6
|
57
|
-
ActiveRecord::Migrator.new(:down, [migration], migration.version)
|
58
|
-
else
|
59
|
-
ActiveRecord::Migrator.new(:down, [migration], schema_migration, migration.version)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def migration_files
|
64
|
-
paths = Array(migrations_paths)
|
65
|
-
current_branch_files = Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
|
66
|
-
other_branches_files = Dir["#{migrated_folder}/**/[0-9]*_*.rb"]
|
67
|
-
|
68
|
-
current_branch_file_names = current_branch_files.map { |f| migration_filename(f) }
|
69
|
-
other_branches_files.reject { |f| migration_filename(f).in?(current_branch_file_names) }
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
ActiveRecord::MigrationProxy.prepend(ActualDbSchema::MigrationProxyPatch)
|
75
|
-
|
76
3
|
namespace :db do
|
77
4
|
desc "Rollback migrations that were run inside not a merged branch."
|
78
5
|
task rollback_branches: :load_config do
|
79
|
-
|
80
|
-
|
81
|
-
end
|
6
|
+
ActualDbSchema::Commands::Rollback.new.call
|
7
|
+
end
|
82
8
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
if ActualDbSchema.failed.any?
|
87
|
-
puts ""
|
88
|
-
puts "[ActualDbSchema] Irreversible migrations were found from other branches. Roll them back or fix manually:"
|
89
|
-
puts ""
|
90
|
-
puts ActualDbSchema.failed.map { |migration| "- #{migration.filename}" }.join("\n")
|
91
|
-
puts ""
|
92
|
-
end
|
93
|
-
# raise ActualDbSchema.failed.inspect
|
9
|
+
desc "List all phantom migrations - non-relevant migrations that were run inside not a merged branch."
|
10
|
+
task phantom_migrations: :load_config do
|
11
|
+
ActualDbSchema::Commands::List.new.call
|
94
12
|
end
|
95
13
|
|
96
14
|
task _dump: :rollback_branches
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actual_db_schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrei Kaleshka
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,9 +16,79 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 6.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 6.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 6.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 6.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: appraisal
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: debug
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sqlite3
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
22
92
|
version_requirements: !ruby/object:Gem::Requirement
|
23
93
|
requirements:
|
24
94
|
- - ">="
|
@@ -35,6 +105,7 @@ extensions: []
|
|
35
105
|
extra_rdoc_files: []
|
36
106
|
files:
|
37
107
|
- ".rubocop.yml"
|
108
|
+
- Appraisals
|
38
109
|
- CHANGELOG.md
|
39
110
|
- CODE_OF_CONDUCT.md
|
40
111
|
- Gemfile
|
@@ -43,7 +114,17 @@ files:
|
|
43
114
|
- README.md
|
44
115
|
- Rakefile
|
45
116
|
- actual_db_schema.gemspec
|
117
|
+
- gemfiles/rails.6.0.gemfile
|
118
|
+
- gemfiles/rails.6.1.gemfile
|
119
|
+
- gemfiles/rails.7.0.gemfile
|
120
|
+
- gemfiles/rails.7.1.gemfile
|
46
121
|
- lib/actual_db_schema.rb
|
122
|
+
- lib/actual_db_schema/commands/base.rb
|
123
|
+
- lib/actual_db_schema/commands/list.rb
|
124
|
+
- lib/actual_db_schema/commands/rollback.rb
|
125
|
+
- lib/actual_db_schema/patches/migration_context.rb
|
126
|
+
- lib/actual_db_schema/patches/migration_proxy.rb
|
127
|
+
- lib/actual_db_schema/patches/migrator.rb
|
47
128
|
- lib/actual_db_schema/version.rb
|
48
129
|
- lib/railtie.rb
|
49
130
|
- lib/tasks/db.rake
|
@@ -63,14 +144,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
63
144
|
requirements:
|
64
145
|
- - ">="
|
65
146
|
- !ruby/object:Gem::Version
|
66
|
-
version: 2.
|
147
|
+
version: 2.7.0
|
67
148
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
149
|
requirements:
|
69
150
|
- - ">="
|
70
151
|
- !ruby/object:Gem::Version
|
71
152
|
version: '0'
|
72
153
|
requirements: []
|
73
|
-
rubygems_version: 3.
|
154
|
+
rubygems_version: 3.4.21
|
74
155
|
signing_key:
|
75
156
|
specification_version: 4
|
76
157
|
summary: Keep your DB clean and consistent between branches.
|