actual_db_schema 0.8.0 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +2 -1
- data/README.md +45 -3
- data/Rakefile +2 -0
- data/actual_db_schema.gemspec +5 -5
- data/docker/mysql-init/create_secondary_db.sql +1 -0
- data/docker/postgres-init/create_secondary_db.sql +1 -0
- data/docker-compose.yml +23 -0
- data/gemfiles/rails.6.0.gemfile +2 -0
- data/gemfiles/rails.6.1.gemfile +2 -0
- data/gemfiles/rails.7.0.gemfile +2 -0
- data/gemfiles/rails.7.1.gemfile +2 -0
- data/gemfiles/rails.edge.gemfile +2 -0
- data/lib/actual_db_schema/commands/rollback.rb +5 -6
- data/lib/actual_db_schema/configuration.rb +32 -0
- data/lib/actual_db_schema/failed_migration.rb +1 -1
- data/lib/actual_db_schema/git_hooks.rb +10 -0
- data/lib/actual_db_schema/multi_tenant.rb +63 -0
- data/lib/actual_db_schema/patches/migration_context.rb +72 -20
- data/lib/actual_db_schema/version.rb +1 -1
- data/lib/actual_db_schema.rb +7 -6
- data/lib/generators/actual_db_schema/templates/actual_db_schema.rb +23 -0
- data/lib/tasks/actual_db_schema.rake +29 -1
- data/lib/tasks/test.rake +69 -0
- metadata +14 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9197558d7b71582d339535933b2186c9ce3db7d0c4dd492759db5a226931ddf8
|
4
|
+
data.tar.gz: ccd05c4164478a54130c6bee32a234abbb2f193292438b8d35b6d7edd313802c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b12528875d4b7d5b12739b1a37877d979bccc6d66f7a0006454ae260b1215eb69bb5baa2aae332e68bc4f1fa29859e4b00a427591cf78acf2aebc57264889a46
|
7
|
+
data.tar.gz: ae2baeaae67451740cc6773abbc79e4d0896baf96b99e7c2baebe027c1b7b05bbbbb75da5717be47c43d240835810a1681408e79c3d44449f7f44c3d4e8830a6
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## [0.8.1] - 2025-01-15
|
2
|
+
|
3
|
+
- Support for multiple database schemas, ensuring compatibility with multi-tenant applications using the apartment gem or similar solutions
|
4
|
+
- DSL for configuring the gem, simplifying setup and customization
|
5
|
+
- Rake task added to initialize the gem
|
6
|
+
- Improved the post-checkout git hook to run only when switching branches, reducing unnecessary executions during file checkouts
|
7
|
+
- Fixed the changelog link in the gemspec, ensuring Rubygems points to the correct file and the link works
|
8
|
+
|
1
9
|
## [0.8.0] - 2024-12-30
|
2
10
|
- Enhanced Console Visibility: Automatically rolled-back phantom migrations now provide clearer and more visible logs in the console
|
3
11
|
- Git Hooks for Branch Management: Introduced hooks that automatically rollback phantom migrations after checking out a branch. Additionally, the schema migration rake task can now be executed automatically upon branch checkout
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
actual_db_schema (0.8.
|
4
|
+
actual_db_schema (0.8.1)
|
5
5
|
activerecord
|
6
6
|
activesupport
|
7
7
|
csv
|
@@ -222,6 +222,7 @@ GEM
|
|
222
222
|
zeitwerk (2.6.12)
|
223
223
|
|
224
224
|
PLATFORMS
|
225
|
+
arm64-darwin-22
|
225
226
|
arm64-darwin-23
|
226
227
|
x86_64-darwin-20
|
227
228
|
x86_64-darwin-22
|
data/README.md
CHANGED
@@ -50,6 +50,16 @@ And then execute:
|
|
50
50
|
|
51
51
|
If you cannot commit changes to the repo or Gemfile, consider the local Gemfile installation described in [this post](https://blog.widefix.com/personal-gemfile-for-development/).
|
52
52
|
|
53
|
+
Next, generate your ActualDbSchema initializer file by running:
|
54
|
+
|
55
|
+
```sh
|
56
|
+
rake actual_db_schema:install
|
57
|
+
```
|
58
|
+
|
59
|
+
This will create a `config/initializers/actual_db_schema.rb` file with all the available configuration options so you can adjust them as needed. It will also prompt you to install the post-checkout Git hook for automatic phantom migration rollback when switching branches.
|
60
|
+
|
61
|
+
For more details on the available configuration options, see the sections below.
|
62
|
+
|
53
63
|
## Usage
|
54
64
|
|
55
65
|
Just run `rails db:migrate` inside the current branch. It will roll back all phantom migrations for all configured databases in your `database.yml.`
|
@@ -86,7 +96,7 @@ export ACTUAL_DB_SCHEMA_UI_ENABLED=true
|
|
86
96
|
Add the following line to your initializer file (`config/initializers/actual_db_schema.rb`):
|
87
97
|
|
88
98
|
```ruby
|
89
|
-
|
99
|
+
config.ui_enabled = true
|
90
100
|
```
|
91
101
|
|
92
102
|
> With this option, the UI can be disabled for all environments or be enabled in specific ones.
|
@@ -107,7 +117,7 @@ export ACTUAL_DB_SCHEMA_AUTO_ROLLBACK_DISABLED=true
|
|
107
117
|
Add the following line to your initializer file (`config/initializers/actual_db_schema.rb`):
|
108
118
|
|
109
119
|
```ruby
|
110
|
-
|
120
|
+
config.auto_rollback_disabled = true
|
111
121
|
```
|
112
122
|
|
113
123
|
## Automatic Phantom Migration Rollback On Branch Switch
|
@@ -126,7 +136,7 @@ export ACTUAL_DB_SCHEMA_GIT_HOOKS_ENABLED=true
|
|
126
136
|
Add the following line to your initializer file (`config/initializers/actual_db_schema.rb`):
|
127
137
|
|
128
138
|
```ruby
|
129
|
-
|
139
|
+
config.git_hooks_enabled = true
|
130
140
|
```
|
131
141
|
|
132
142
|
### Installing the Post-Checkout Hook
|
@@ -144,6 +154,20 @@ This task will prompt you to choose one of the three options:
|
|
144
154
|
|
145
155
|
Based on your selection, a post-checkout hook will be installed or updated in your `.git/hooks` folder.
|
146
156
|
|
157
|
+
## Multi-Tenancy Support
|
158
|
+
|
159
|
+
If your application leverages multiple schemas for multi-tenancy — such as those implemented by the [apartment](https://github.com/influitive/apartment) gem or similar solutions — you can configure ActualDbSchema to handle migrations across all schemas. To do so, add the following configuration to your initializer file (`config/initializers/actual_db_schema.rb`):
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
config.multi_tenant_schemas = -> { # list of all active schemas }
|
163
|
+
```
|
164
|
+
|
165
|
+
### Example:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
config.multi_tenant_schemas = -> { ["public", "tenant1", "tenant2"] }
|
169
|
+
```
|
170
|
+
|
147
171
|
## Development
|
148
172
|
|
149
173
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -182,6 +206,24 @@ To run tests with a specific version of Rails using Appraisal:
|
|
182
206
|
bundle exec appraisal rails.6.0 rake test TEST=test/rake_task_test.rb TESTOPTS="--name=/db::db:rollback_branches#test_0003_keeps/"
|
183
207
|
```
|
184
208
|
|
209
|
+
By default, `rake test` runs tests using `SQLite3`. To explicitly run tests with `SQLite3`, `PostgreSQL`, or `MySQL`, you can use the following tasks:
|
210
|
+
- Run tests with `SQLite3`:
|
211
|
+
```sh
|
212
|
+
bundle exec rake test:sqlite3
|
213
|
+
```
|
214
|
+
- Run tests with `PostgreSQL` (requires Docker):
|
215
|
+
```sh
|
216
|
+
bundle exec rake test:postgresql
|
217
|
+
```
|
218
|
+
- Run tests with `MySQL` (requires Docker):
|
219
|
+
```sh
|
220
|
+
bundle exec rake test:mysql2
|
221
|
+
```
|
222
|
+
- Run tests for all supported adapters:
|
223
|
+
```sh
|
224
|
+
bundle exec rake test:all
|
225
|
+
```
|
226
|
+
|
185
227
|
## Contributing
|
186
228
|
|
187
229
|
Bug reports and pull requests are welcome on GitHub at https://github.com/widefix/actual_db_schema. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/widefix/actual_db_schema/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
data/actual_db_schema.gemspec
CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.metadata["homepage_uri"] = spec.homepage
|
23
23
|
spec.metadata["source_code_uri"] = "https://github.com/widefix/actual_db_schema"
|
24
|
-
spec.metadata["changelog_uri"] = "
|
24
|
+
spec.metadata["changelog_uri"] = "https://github.com/widefix/actual_db_schema/blob/main/CHANGELOG.md"
|
25
25
|
|
26
26
|
# Specify which files should be added to the gem when it is released.
|
27
27
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -47,10 +47,10 @@ Gem::Specification.new do |spec|
|
|
47
47
|
spec.post_install_message = <<~MSG
|
48
48
|
Thank you for installing ActualDbSchema!
|
49
49
|
|
50
|
-
|
51
|
-
1.
|
52
|
-
|
53
|
-
2.
|
50
|
+
Next steps:
|
51
|
+
1. Run `rake actual_db_schema:install` to generate the initializer file and install
|
52
|
+
the post-checkout Git hook for automatic phantom migration rollback when switching branches.
|
53
|
+
2. Or, if you prefer environment variables, skip this step.
|
54
54
|
|
55
55
|
For more information, see the README.
|
56
56
|
|
@@ -0,0 +1 @@
|
|
1
|
+
CREATE DATABASE actual_db_schema_test_secondary;
|
@@ -0,0 +1 @@
|
|
1
|
+
CREATE DATABASE actual_db_schema_test_secondary;
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
version: '3.8'
|
2
|
+
|
3
|
+
services:
|
4
|
+
postgres:
|
5
|
+
image: postgres:14
|
6
|
+
environment:
|
7
|
+
POSTGRES_USER: postgres
|
8
|
+
POSTGRES_PASSWORD: password
|
9
|
+
POSTGRES_DB: actual_db_schema_test
|
10
|
+
ports:
|
11
|
+
- "5432:5432"
|
12
|
+
volumes:
|
13
|
+
- ./docker/postgres-init:/docker-entrypoint-initdb.d
|
14
|
+
|
15
|
+
mysql:
|
16
|
+
image: mysql:8.0
|
17
|
+
environment:
|
18
|
+
MYSQL_ROOT_PASSWORD: password
|
19
|
+
MYSQL_DATABASE: actual_db_schema_test
|
20
|
+
ports:
|
21
|
+
- "3306:3306"
|
22
|
+
volumes:
|
23
|
+
- ./docker/mysql-init:/docker-entrypoint-initdb.d
|
data/gemfiles/rails.6.0.gemfile
CHANGED
data/gemfiles/rails.6.1.gemfile
CHANGED
data/gemfiles/rails.7.0.gemfile
CHANGED
data/gemfiles/rails.7.1.gemfile
CHANGED
data/gemfiles/rails.edge.gemfile
CHANGED
@@ -17,7 +17,7 @@ module ActualDbSchema
|
|
17
17
|
def call_impl
|
18
18
|
rolled_back = context.rollback_branches(manual_mode: @manual_mode)
|
19
19
|
|
20
|
-
return unless rolled_back
|
20
|
+
return unless rolled_back || ActualDbSchema.failed.any?
|
21
21
|
|
22
22
|
ActualDbSchema.failed.empty? ? print_success : print_error
|
23
23
|
end
|
@@ -43,11 +43,10 @@ module ActualDbSchema
|
|
43
43
|
|
44
44
|
def failed_migrations_list
|
45
45
|
ActualDbSchema.failed.map.with_index(1) do |failed, index|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
MIGRATION
|
46
|
+
migration_details = colorize("Migration ##{index}:\n", :yellow)
|
47
|
+
migration_details += " File: #{failed.short_filename}\n"
|
48
|
+
migration_details += " Schema: #{failed.schema}\n" if failed.schema
|
49
|
+
migration_details + " Branch: #{failed.branch}\n"
|
51
50
|
end.join("\n")
|
52
51
|
end
|
53
52
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActualDbSchema
|
4
|
+
# Manages the configuration settings for the gem.
|
5
|
+
class Configuration
|
6
|
+
attr_accessor :enabled, :auto_rollback_disabled, :ui_enabled, :git_hooks_enabled, :multi_tenant_schemas
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@enabled = Rails.env.development?
|
10
|
+
@auto_rollback_disabled = ENV["ACTUAL_DB_SCHEMA_AUTO_ROLLBACK_DISABLED"].present?
|
11
|
+
@ui_enabled = Rails.env.development? || ENV["ACTUAL_DB_SCHEMA_UI_ENABLED"].present?
|
12
|
+
@git_hooks_enabled = ENV["ACTUAL_DB_SCHEMA_GIT_HOOKS_ENABLED"].present?
|
13
|
+
@multi_tenant_schemas = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
public_send(key)
|
18
|
+
end
|
19
|
+
|
20
|
+
def []=(key, value)
|
21
|
+
public_send("#{key}=", value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch(key, default = nil)
|
25
|
+
if respond_to?(key)
|
26
|
+
public_send(key)
|
27
|
+
else
|
28
|
+
default
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActualDbSchema
|
4
|
-
FailedMigration = Struct.new(:migration, :exception, :branch, keyword_init: true) do
|
4
|
+
FailedMigration = Struct.new(:migration, :exception, :branch, :schema, keyword_init: true) do
|
5
5
|
def filename
|
6
6
|
migration.filename
|
7
7
|
end
|
@@ -15,6 +15,11 @@ module ActualDbSchema
|
|
15
15
|
# ActualDbSchema post-checkout hook (ROLLBACK)
|
16
16
|
# Runs db:rollback_branches on branch checkout.
|
17
17
|
|
18
|
+
# Check if this is a file checkout or creating a new branch
|
19
|
+
if [ "$3" == "0" ] || [ "$1" == "$2" ]; then
|
20
|
+
exit 0
|
21
|
+
fi
|
22
|
+
|
18
23
|
if [ -f ./bin/rails ]; then
|
19
24
|
if [ -n "$ACTUAL_DB_SCHEMA_GIT_HOOKS_ENABLED" ]; then
|
20
25
|
GIT_HOOKS_ENABLED="$ACTUAL_DB_SCHEMA_GIT_HOOKS_ENABLED"
|
@@ -34,6 +39,11 @@ module ActualDbSchema
|
|
34
39
|
# ActualDbSchema post-checkout hook (MIGRATE)
|
35
40
|
# Runs db:migrate on branch checkout.
|
36
41
|
|
42
|
+
# Check if this is a file checkout or creating a new branch
|
43
|
+
if [ "$3" == "0" ] || [ "$1" == "$2" ]; then
|
44
|
+
exit 0
|
45
|
+
fi
|
46
|
+
|
37
47
|
if [ -f ./bin/rails ]; then
|
38
48
|
if [ -n "$ACTUAL_DB_SCHEMA_GIT_HOOKS_ENABLED" ]; then
|
39
49
|
GIT_HOOKS_ENABLED="$ACTUAL_DB_SCHEMA_GIT_HOOKS_ENABLED"
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActualDbSchema
|
4
|
+
# Handles multi-tenancy support by switching schemas for supported databases
|
5
|
+
module MultiTenant
|
6
|
+
include ActualDbSchema::OutputFormatter
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def with_schema(schema_name)
|
10
|
+
context = switch_schema(schema_name)
|
11
|
+
yield
|
12
|
+
ensure
|
13
|
+
restore_context(context)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def adapter_name
|
19
|
+
ActiveRecord::Base.connection.adapter_name
|
20
|
+
end
|
21
|
+
|
22
|
+
def switch_schema(schema_name)
|
23
|
+
case adapter_name
|
24
|
+
when /postgresql/i
|
25
|
+
switch_postgresql_schema(schema_name)
|
26
|
+
when /mysql/i
|
27
|
+
switch_mysql_schema(schema_name)
|
28
|
+
else
|
29
|
+
message = "[ActualDbSchema] Multi-tenancy not supported for adapter: #{adapter_name}. " \
|
30
|
+
"Proceeding without schema switching."
|
31
|
+
puts colorize(message, :gray)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def switch_postgresql_schema(schema_name)
|
36
|
+
old_search_path = ActiveRecord::Base.connection.schema_search_path
|
37
|
+
ActiveRecord::Base.connection.schema_search_path = schema_name
|
38
|
+
{ type: :postgresql, old_context: old_search_path }
|
39
|
+
end
|
40
|
+
|
41
|
+
def switch_mysql_schema(schema_name)
|
42
|
+
old_db = ActiveRecord::Base.connection.current_database
|
43
|
+
ActiveRecord::Base.connection.execute("USE #{ActiveRecord::Base.connection.quote_table_name(schema_name)}")
|
44
|
+
{ type: :mysql, old_context: old_db }
|
45
|
+
end
|
46
|
+
|
47
|
+
def restore_context(context)
|
48
|
+
return unless context
|
49
|
+
|
50
|
+
case context[:type]
|
51
|
+
when :postgresql
|
52
|
+
ActiveRecord::Base.connection.schema_search_path = context[:old_context] if context[:old_context]
|
53
|
+
when :mysql
|
54
|
+
return unless context[:old_context]
|
55
|
+
|
56
|
+
ActiveRecord::Base.connection.execute(
|
57
|
+
"USE #{ActiveRecord::Base.connection.quote_table_name(context[:old_context])}"
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -3,23 +3,21 @@
|
|
3
3
|
module ActualDbSchema
|
4
4
|
module Patches
|
5
5
|
# Add new command to roll back the phantom migrations
|
6
|
-
module MigrationContext
|
6
|
+
module MigrationContext # rubocop:disable Metrics/ModuleLength
|
7
7
|
include ActualDbSchema::OutputFormatter
|
8
8
|
|
9
9
|
def rollback_branches(manual_mode: false)
|
10
|
-
|
10
|
+
schemas = multi_tenant_schemas&.call || []
|
11
|
+
schema_count = schemas.any? ? schemas.size : 1
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
rolled_back_migrations = if schemas.any?
|
14
|
+
rollback_multi_tenant(schemas, manual_mode: manual_mode)
|
15
|
+
else
|
16
|
+
rollback_branches_for_schema(manual_mode: manual_mode)
|
17
|
+
end
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
migrate(migration) if !manual_mode || user_wants_rollback?
|
18
|
-
rescue StandardError => e
|
19
|
-
handle_rollback_error(migration, e)
|
20
|
-
end
|
21
|
-
|
22
|
-
rolled_back
|
19
|
+
delete_migrations(rolled_back_migrations, schema_count)
|
20
|
+
rolled_back_migrations.any?
|
23
21
|
end
|
24
22
|
|
25
23
|
def phantom_migrations
|
@@ -34,6 +32,32 @@ module ActualDbSchema
|
|
34
32
|
|
35
33
|
private
|
36
34
|
|
35
|
+
def rollback_branches_for_schema(manual_mode: false, schema_name: nil, rolled_back_migrations: [])
|
36
|
+
phantom_migrations.reverse_each do |migration|
|
37
|
+
next unless status_up?(migration)
|
38
|
+
|
39
|
+
show_info_for(migration, schema_name) if manual_mode
|
40
|
+
migrate(migration, rolled_back_migrations, schema_name) if !manual_mode || user_wants_rollback?
|
41
|
+
rescue StandardError => e
|
42
|
+
handle_rollback_error(migration, e, schema_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
rolled_back_migrations
|
46
|
+
end
|
47
|
+
|
48
|
+
def rollback_multi_tenant(schemas, manual_mode: false)
|
49
|
+
all_rolled_back_migrations = []
|
50
|
+
|
51
|
+
schemas.each do |schema_name|
|
52
|
+
ActualDbSchema::MultiTenant.with_schema(schema_name) do
|
53
|
+
rollback_branches_for_schema(manual_mode: manual_mode, schema_name: schema_name,
|
54
|
+
rolled_back_migrations: all_rolled_back_migrations)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
all_rolled_back_migrations
|
59
|
+
end
|
60
|
+
|
37
61
|
def down_migrator_for(migration)
|
38
62
|
if ActiveRecord::Migration.current_version < 6
|
39
63
|
ActiveRecord::Migrator.new(:down, [migration], migration.version)
|
@@ -69,26 +93,29 @@ module ActualDbSchema
|
|
69
93
|
answer[0] == "y"
|
70
94
|
end
|
71
95
|
|
72
|
-
def show_info_for(migration)
|
96
|
+
def show_info_for(migration, schema_name = nil)
|
73
97
|
puts colorize("\n[ActualDbSchema] A phantom migration was found and is about to be rolled back.", :gray)
|
74
98
|
puts "Please make a decision from the options below to proceed.\n\n"
|
99
|
+
puts "Schema: #{schema_name}" if schema_name
|
75
100
|
puts "Branch: #{branch_for(migration.version.to_s)}"
|
76
101
|
puts "Database: #{ActualDbSchema.db_config[:database]}"
|
77
102
|
puts "Version: #{migration.version}\n\n"
|
78
103
|
puts File.read(migration.filename)
|
79
104
|
end
|
80
105
|
|
81
|
-
def migrate(migration)
|
106
|
+
def migrate(migration, rolled_back_migrations, schema_name = nil)
|
82
107
|
migration.name = extract_class_name(migration.filename)
|
83
108
|
|
84
|
-
message = "[ActualDbSchema]
|
85
|
-
|
109
|
+
message = "[ActualDbSchema]"
|
110
|
+
message += " #{schema_name}:" if schema_name
|
111
|
+
message += " Rolling back phantom migration #{migration.version} #{migration.name} " \
|
112
|
+
"(from branch: #{branch_for(migration.version.to_s)})"
|
86
113
|
puts colorize(message, :gray)
|
87
114
|
|
88
115
|
migrator = down_migrator_for(migration)
|
89
116
|
migrator.extend(ActualDbSchema::Patches::Migrator)
|
90
117
|
migrator.migrate
|
91
|
-
|
118
|
+
rolled_back_migrations << migration
|
92
119
|
end
|
93
120
|
|
94
121
|
def extract_class_name(filename)
|
@@ -104,20 +131,45 @@ module ActualDbSchema
|
|
104
131
|
@metadata ||= ActualDbSchema::Store.instance.read
|
105
132
|
end
|
106
133
|
|
107
|
-
def handle_rollback_error(migration, exception)
|
134
|
+
def handle_rollback_error(migration, exception, schema_name = nil)
|
108
135
|
error_message = <<~ERROR
|
109
136
|
Error encountered during rollback:
|
110
137
|
|
111
|
-
#{exception.message
|
138
|
+
#{cleaned_exception_message(exception.message)}
|
112
139
|
ERROR
|
113
140
|
|
114
141
|
puts colorize(error_message, :red)
|
115
142
|
ActualDbSchema.failed << FailedMigration.new(
|
116
143
|
migration: migration,
|
117
144
|
exception: exception,
|
118
|
-
branch: branch_for(migration.version.to_s)
|
145
|
+
branch: branch_for(migration.version.to_s),
|
146
|
+
schema: schema_name
|
119
147
|
)
|
120
148
|
end
|
149
|
+
|
150
|
+
def cleaned_exception_message(message)
|
151
|
+
patterns_to_remove = [
|
152
|
+
/^An error has occurred, all later migrations canceled:\s*/,
|
153
|
+
/^An error has occurred, this and all later migrations canceled:\s*/
|
154
|
+
]
|
155
|
+
|
156
|
+
patterns_to_remove.reduce(message.strip) { |msg, pattern| msg.gsub(pattern, "").strip }
|
157
|
+
end
|
158
|
+
|
159
|
+
def delete_migrations(migrations, schema_count)
|
160
|
+
migration_counts = migrations.each_with_object(Hash.new(0)) do |migration, hash|
|
161
|
+
hash[migration.filename] += 1
|
162
|
+
end
|
163
|
+
|
164
|
+
migrations.uniq.each do |migration|
|
165
|
+
count = migration_counts[migration.filename]
|
166
|
+
File.delete(migration.filename) if count == schema_count && File.exist?(migration.filename)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def multi_tenant_schemas
|
171
|
+
ActualDbSchema.config[:multi_tenant_schemas]
|
172
|
+
end
|
121
173
|
end
|
122
174
|
end
|
123
175
|
end
|
data/lib/actual_db_schema.rb
CHANGED
@@ -4,6 +4,7 @@ require "actual_db_schema/engine"
|
|
4
4
|
require "active_record/migration"
|
5
5
|
require "csv"
|
6
6
|
require_relative "actual_db_schema/git"
|
7
|
+
require_relative "actual_db_schema/configuration"
|
7
8
|
require_relative "actual_db_schema/store"
|
8
9
|
require_relative "actual_db_schema/version"
|
9
10
|
require_relative "actual_db_schema/migration"
|
@@ -14,6 +15,7 @@ require_relative "actual_db_schema/patches/migration_proxy"
|
|
14
15
|
require_relative "actual_db_schema/patches/migrator"
|
15
16
|
require_relative "actual_db_schema/patches/migration_context"
|
16
17
|
require_relative "actual_db_schema/git_hooks"
|
18
|
+
require_relative "actual_db_schema/multi_tenant"
|
17
19
|
|
18
20
|
require_relative "actual_db_schema/commands/base"
|
19
21
|
require_relative "actual_db_schema/commands/rollback"
|
@@ -28,12 +30,11 @@ module ActualDbSchema
|
|
28
30
|
end
|
29
31
|
|
30
32
|
self.failed = []
|
31
|
-
self.config =
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
}
|
33
|
+
self.config = Configuration.new
|
34
|
+
|
35
|
+
def self.configure
|
36
|
+
yield(config)
|
37
|
+
end
|
37
38
|
|
38
39
|
def self.migrated_folder
|
39
40
|
migrated_folders.first
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# ActualDbSchema initializer
|
4
|
+
# Adjust the configuration as needed.
|
5
|
+
|
6
|
+
ActualDbSchema.configure do |config|
|
7
|
+
# Enable the gem.
|
8
|
+
config.enabled = Rails.env.development?
|
9
|
+
|
10
|
+
# Disable automatic rollback of phantom migrations.
|
11
|
+
# config.auto_rollback_disabled = true
|
12
|
+
config.auto_rollback_disabled = ENV["ACTUAL_DB_SCHEMA_AUTO_ROLLBACK_DISABLED"].present?
|
13
|
+
|
14
|
+
# Enable the UI for managing migrations.
|
15
|
+
config.ui_enabled = Rails.env.development? || ENV["ACTUAL_DB_SCHEMA_UI_ENABLED"].present?
|
16
|
+
|
17
|
+
# Enable automatic phantom migration rollback on branch switch,
|
18
|
+
# config.git_hooks_enabled = true
|
19
|
+
config.git_hooks_enabled = ENV["ACTUAL_DB_SCHEMA_GIT_HOOKS_ENABLED"].present?
|
20
|
+
|
21
|
+
# If your application leverages multiple schemas for multi-tenancy, define the active schemas.
|
22
|
+
# config.multi_tenant_schemas = -> { ["public", "tenant1", "tenant2"] }
|
23
|
+
end
|
@@ -1,6 +1,34 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
namespace :actual_db_schema do
|
3
|
+
namespace :actual_db_schema do # rubocop:disable Metrics/BlockLength
|
4
|
+
desc "Install ActualDbSchema initializer and post-checkout git hook."
|
5
|
+
task :install do
|
6
|
+
extend ActualDbSchema::OutputFormatter
|
7
|
+
|
8
|
+
initializer_path = Rails.root.join("config", "initializers", "actual_db_schema.rb")
|
9
|
+
initializer_content = File.read(
|
10
|
+
File.expand_path("../../lib/generators/actual_db_schema/templates/actual_db_schema.rb", __dir__)
|
11
|
+
)
|
12
|
+
|
13
|
+
if File.exist?(initializer_path)
|
14
|
+
puts colorize("[ActualDbSchema] An initializer already exists at #{initializer_path}.", :gray)
|
15
|
+
puts "Overwrite the existing file at #{initializer_path}? [y,n] "
|
16
|
+
answer = $stdin.gets.chomp.downcase
|
17
|
+
|
18
|
+
if answer.start_with?("y")
|
19
|
+
File.write(initializer_path, initializer_content)
|
20
|
+
puts colorize("[ActualDbSchema] Initializer updated successfully at #{initializer_path}", :green)
|
21
|
+
else
|
22
|
+
puts colorize("[ActualDbSchema] Skipped overwriting the initializer.", :yellow)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
File.write(initializer_path, initializer_content)
|
26
|
+
puts colorize("[ActualDbSchema] Initializer created successfully at #{initializer_path}", :green)
|
27
|
+
end
|
28
|
+
|
29
|
+
Rake::Task["actual_db_schema:install_git_hooks"].invoke
|
30
|
+
end
|
31
|
+
|
4
32
|
desc "Install ActualDbSchema post-checkout git hook that rolls back phantom migrations when switching branches."
|
5
33
|
task :install_git_hooks do
|
6
34
|
extend ActualDbSchema::OutputFormatter
|
data/lib/tasks/test.rake
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :test do # rubocop:disable Metrics/BlockLength
|
4
|
+
desc "Run tests with SQLite3"
|
5
|
+
task :sqlite3 do
|
6
|
+
ENV["DB_ADAPTER"] = "sqlite3"
|
7
|
+
Rake::Task["test"].invoke
|
8
|
+
Rake::Task["test"].reenable
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Run tests with PostgreSQL"
|
12
|
+
task :postgresql do
|
13
|
+
sh "docker-compose up -d postgres"
|
14
|
+
wait_for_postgres
|
15
|
+
|
16
|
+
begin
|
17
|
+
ENV["DB_ADAPTER"] = "postgresql"
|
18
|
+
Rake::Task["test"].invoke
|
19
|
+
Rake::Task["test"].reenable
|
20
|
+
ensure
|
21
|
+
sh "docker-compose down"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Run tests with MySQL"
|
26
|
+
task :mysql2 do
|
27
|
+
sh "docker-compose up -d mysql"
|
28
|
+
wait_for_mysql
|
29
|
+
|
30
|
+
begin
|
31
|
+
ENV["DB_ADAPTER"] = "mysql2"
|
32
|
+
Rake::Task["test"].invoke
|
33
|
+
Rake::Task["test"].reenable
|
34
|
+
ensure
|
35
|
+
sh "docker-compose down"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "Run tests with all adapters (SQLite3, PostgreSQL, MySQL)"
|
40
|
+
task all: %i[sqlite3 postgresql mysql2]
|
41
|
+
|
42
|
+
def wait_for_postgres
|
43
|
+
retries = 10
|
44
|
+
begin
|
45
|
+
sh "docker-compose exec -T postgres pg_isready -U postgres"
|
46
|
+
rescue StandardError
|
47
|
+
retries -= 1
|
48
|
+
|
49
|
+
raise "PostgreSQL is not ready after several attempts." if retries < 1
|
50
|
+
|
51
|
+
sleep 2
|
52
|
+
retry
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def wait_for_mysql
|
57
|
+
retries = 10
|
58
|
+
begin
|
59
|
+
sh "docker-compose exec -T mysql mysqladmin ping -h 127.0.0.1 --silent"
|
60
|
+
rescue StandardError
|
61
|
+
retries -= 1
|
62
|
+
|
63
|
+
raise "MySQL is not ready after several attempts." if retries < 1
|
64
|
+
|
65
|
+
sleep 2
|
66
|
+
retry
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
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.8.
|
4
|
+
version: 0.8.1
|
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: 2025-01-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -136,6 +136,9 @@ files:
|
|
136
136
|
- app/views/actual_db_schema/shared/_js.html
|
137
137
|
- app/views/actual_db_schema/shared/_style.html
|
138
138
|
- config/routes.rb
|
139
|
+
- docker-compose.yml
|
140
|
+
- docker/mysql-init/create_secondary_db.sql
|
141
|
+
- docker/postgres-init/create_secondary_db.sql
|
139
142
|
- gemfiles/rails.6.0.gemfile
|
140
143
|
- gemfiles/rails.6.1.gemfile
|
141
144
|
- gemfiles/rails.7.0.gemfile
|
@@ -145,20 +148,24 @@ files:
|
|
145
148
|
- lib/actual_db_schema/commands/base.rb
|
146
149
|
- lib/actual_db_schema/commands/list.rb
|
147
150
|
- lib/actual_db_schema/commands/rollback.rb
|
151
|
+
- lib/actual_db_schema/configuration.rb
|
148
152
|
- lib/actual_db_schema/engine.rb
|
149
153
|
- lib/actual_db_schema/failed_migration.rb
|
150
154
|
- lib/actual_db_schema/git.rb
|
151
155
|
- lib/actual_db_schema/git_hooks.rb
|
152
156
|
- lib/actual_db_schema/migration.rb
|
153
157
|
- lib/actual_db_schema/migration_context.rb
|
158
|
+
- lib/actual_db_schema/multi_tenant.rb
|
154
159
|
- lib/actual_db_schema/output_formatter.rb
|
155
160
|
- lib/actual_db_schema/patches/migration_context.rb
|
156
161
|
- lib/actual_db_schema/patches/migration_proxy.rb
|
157
162
|
- lib/actual_db_schema/patches/migrator.rb
|
158
163
|
- lib/actual_db_schema/store.rb
|
159
164
|
- lib/actual_db_schema/version.rb
|
165
|
+
- lib/generators/actual_db_schema/templates/actual_db_schema.rb
|
160
166
|
- lib/tasks/actual_db_schema.rake
|
161
167
|
- lib/tasks/db.rake
|
168
|
+
- lib/tasks/test.rake
|
162
169
|
- sig/actual_db_schema.rbs
|
163
170
|
homepage: https://blog.widefix.com/actual-db-schema/
|
164
171
|
licenses:
|
@@ -166,14 +173,14 @@ licenses:
|
|
166
173
|
metadata:
|
167
174
|
homepage_uri: https://blog.widefix.com/actual-db-schema/
|
168
175
|
source_code_uri: https://github.com/widefix/actual_db_schema
|
169
|
-
changelog_uri: https://
|
176
|
+
changelog_uri: https://github.com/widefix/actual_db_schema/blob/main/CHANGELOG.md
|
170
177
|
post_install_message: |+
|
171
178
|
Thank you for installing ActualDbSchema!
|
172
179
|
|
173
|
-
|
174
|
-
1.
|
175
|
-
|
176
|
-
2.
|
180
|
+
Next steps:
|
181
|
+
1. Run `rake actual_db_schema:install` to generate the initializer file and install
|
182
|
+
the post-checkout Git hook for automatic phantom migration rollback when switching branches.
|
183
|
+
2. Or, if you prefer environment variables, skip this step.
|
177
184
|
|
178
185
|
For more information, see the README.
|
179
186
|
|