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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43a6d3a4c89ea984fe7b4cbc6160a9f459951bac207d2592b609ae0a7606fd76
4
- data.tar.gz: ac2dcb733bb87bdf8dcf5daa250eb386e0ac15f99297d0feb53815e308fb3915
3
+ metadata.gz: 9197558d7b71582d339535933b2186c9ce3db7d0c4dd492759db5a226931ddf8
4
+ data.tar.gz: ccd05c4164478a54130c6bee32a234abbb2f193292438b8d35b6d7edd313802c
5
5
  SHA512:
6
- metadata.gz: 1bd9b3d3ed5145aacc2d0a481742dc3a45e707257dc3290de245ca4e6ce8db26aaad75619bb0212273aedc6dc30c833e0e604d84c5a1711ea0d1bf8d0cef0dc2
7
- data.tar.gz: bbc183547eeb23566c326405519120af7e4e1aea2525ade5c7f8711985d90b6d3d142322bcc19b955a1605f58074420c28657646a16be8c667d47aeae2531ebc
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.0)
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
- ActualDbSchema.config[:ui_enabled] = true
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
- ActualDbSchema.config[:auto_rollback_disabled] = true
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
- ActualDbSchema.config[:git_hooks_enabled] = true
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
@@ -3,6 +3,8 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/testtask"
5
5
 
6
+ load "lib/tasks/test.rake"
7
+
6
8
  Rake::TestTask.new(:test) do |t|
7
9
  t.libs << "test"
8
10
  t.libs << "lib"
@@ -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"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
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
- To enable automatic rollback of phantom migrations when switching branches, follow these steps:
51
- 1. Set `export ACTUAL_DB_SCHEMA_GIT_HOOKS_ENABLED=true` in your environment, OR
52
- add `ActualDbSchema.config[:git_hooks_enabled] = true` to your initializer.
53
- 2. Run `rake actual_db_schema:install_git_hooks` to install the post-checkout Git hook.
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;
@@ -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
@@ -7,6 +7,8 @@ source "https://rubygems.org"
7
7
  gem "activerecord", "~> 6.0.0"
8
8
  gem "activesupport", "~> 6.0.0"
9
9
  gem "minitest", "~> 5.0"
10
+ gem "mysql2", "~> 0.5.2"
11
+ gem "pg", "~> 1.5"
10
12
  gem "rake"
11
13
  gem "rubocop", "~> 1.21"
12
14
  gem "sqlite3", "~> 1.4.0"
@@ -7,6 +7,8 @@ source "https://rubygems.org"
7
7
  gem "activerecord", "~> 6.1.0"
8
8
  gem "activesupport", "~> 6.1.0"
9
9
  gem "minitest", "~> 5.0"
10
+ gem "mysql2", "~> 0.5.2"
11
+ gem "pg", "~> 1.5"
10
12
  gem "rake"
11
13
  gem "rubocop", "~> 1.21"
12
14
  gem "sqlite3", "~> 1.4.0"
@@ -7,6 +7,8 @@ source "https://rubygems.org"
7
7
  gem "activerecord", "~> 7.0.0"
8
8
  gem "activesupport", "~> 7.0.0"
9
9
  gem "minitest", "~> 5.0"
10
+ gem "mysql2", "~> 0.5.2"
11
+ gem "pg", "~> 1.5"
10
12
  gem "rake"
11
13
  gem "rubocop", "~> 1.21"
12
14
  gem "sqlite3", "~> 1.4.0"
@@ -7,6 +7,8 @@ source "https://rubygems.org"
7
7
  gem "activerecord", "~> 7.1.0"
8
8
  gem "activesupport", "~> 7.1.0"
9
9
  gem "minitest", "~> 5.0"
10
+ gem "mysql2", "~> 0.5.2"
11
+ gem "pg", "~> 1.5"
10
12
  gem "rake"
11
13
  gem "rubocop", "~> 1.21"
12
14
  gem "sqlite3", "~> 1.4.0"
@@ -7,6 +7,8 @@ source "https://rubygems.org"
7
7
  gem "activerecord", ">= 7.2.0.beta"
8
8
  gem "activesupport", ">= 7.2.0.beta"
9
9
  gem "minitest", "~> 5.0"
10
+ gem "mysql2", "~> 0.5.2"
11
+ gem "pg", "~> 1.5"
10
12
  gem "rake"
11
13
  gem "rubocop", "~> 1.21"
12
14
  gem "rails", ">= 7.2.0.beta"
@@ -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
- <<~MIGRATION
47
- #{colorize("Migration ##{index}:", :yellow)}
48
- File: #{failed.short_filename}
49
- Branch: #{failed.branch}
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
- rolled_back = false
10
+ schemas = multi_tenant_schemas&.call || []
11
+ schema_count = schemas.any? ? schemas.size : 1
11
12
 
12
- phantom_migrations.reverse_each do |migration|
13
- next unless status_up?(migration)
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
- rolled_back = true
16
- show_info_for(migration) if manual_mode
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] Rolling back phantom migration #{migration.version} #{migration.name} " \
85
- "(from branch: #{branch_for(migration.version.to_s)})"
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
- File.delete(migration.filename)
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.gsub(/^An error has occurred, all later migrations canceled:\s*/, "").strip}
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActualDbSchema
4
- VERSION = "0.8.0"
4
+ VERSION = "0.8.1"
5
5
  end
@@ -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
- enabled: Rails.env.development?,
33
- auto_rollback_disabled: ENV["ACTUAL_DB_SCHEMA_AUTO_ROLLBACK_DISABLED"].present?,
34
- ui_enabled: Rails.env.development? || ENV["ACTUAL_DB_SCHEMA_UI_ENABLED"].present?,
35
- git_hooks_enabled: ENV["ACTUAL_DB_SCHEMA_GIT_HOOKS_ENABLED"].present?
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
@@ -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.0
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: 2024-12-30 00:00:00.000000000 Z
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://blog.widefix.com/actual-db-schema//blob/main/CHANGELOG.md
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
- To enable automatic rollback of phantom migrations when switching branches, follow these steps:
174
- 1. Set `export ACTUAL_DB_SCHEMA_GIT_HOOKS_ENABLED=true` in your environment, OR
175
- add `ActualDbSchema.config[:git_hooks_enabled] = true` to your initializer.
176
- 2. Run `rake actual_db_schema:install_git_hooks` to install the post-checkout Git hook.
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