actual_db_schema 0.8.0 → 0.8.1
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/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
|
|