after_migrate 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/AGENTS.md +74 -0
- data/CHANGELOG.md +51 -0
- data/README.md +14 -2
- data/lib/after_migrate/railtie.rb +21 -3
- data/lib/after_migrate/version.rb +1 -1
- data/lib/after_migrate.rb +109 -5
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 453b86e338e7eb2ba4eb0af92cde363cee1de07ade6bcaaec31f953cc6938e32
|
|
4
|
+
data.tar.gz: f2673ef800205a658f30d54865724e34b77b73a5fcd6df7e7b608af1d5df3fb7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 219bcd2a38ec717a6837024b6c3d969d0e6ec04d23f153ee2ff1eb1a096bcd221b7ec396f428db4906a65afa317ad17abc13f2a3320de5ae1cd64d6c058d09e1
|
|
7
|
+
data.tar.gz: 290f5f231817b412be23ebe12235ed430fbf7f660e7be20bdb7c8aa5f2cc731bae4a55ffeaae9888fbc3ccfa4722dbf6c9aabe595f1305d8a5d01a7ca31f0a5f
|
data/.gitignore
CHANGED
data/AGENTS.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bundle install # install dependencies
|
|
9
|
+
bundle exec rspec # run all tests
|
|
10
|
+
bundle exec rspec spec/after_migrate_spec.rb # run a single spec file
|
|
11
|
+
bundle exec rake # default task (runs spec)
|
|
12
|
+
bundle exec rubocop # lint
|
|
13
|
+
bundle exec rubocop -a # lint with auto-fix
|
|
14
|
+
rake release # build and push gem to RubyGems
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
This is a Rails gem that automatically runs database maintenance (`ANALYZE`, `VACUUM`, `PRAGMA optimize`) after `db:migrate` tasks. The core flow is:
|
|
20
|
+
|
|
21
|
+
1. **`Railtie`** (`lib/after_migrate/railtie.rb`) — the entry point. On Rails init, it subscribes to `sql.active_record` notifications so every SQL statement during a migration is intercepted. It enhances `db:migrate`, `db:migrate:up`, and `db:migrate:redo` to call `AfterMigrate.run!` when they complete — unless `defer: true` (the default), in which case the rake task only collects. It also registers the `after_migrate:run` task.
|
|
22
|
+
|
|
23
|
+
2. **`Collector`** (`lib/after_migrate/collector.rb`) — receives every SQL notification, filters to DDL/DML statements (CREATE/ALTER/DROP/INSERT/UPDATE/DELETE), calls the adapter-specific parser, and accumulates table names into `AfterMigrate.affected_tables` (a `Concurrent::Map<schema, Concurrent::Set<table_name>>`).
|
|
24
|
+
|
|
25
|
+
3. **`AfterMigrate.store`** (`lib/after_migrate/store.rb`) — defaults to an in-memory `Concurrent::Map` wrapped by `AfterMigrate::Stores::Memory`. `AfterMigrate.affected_tables`, `merge_tables`, and `reset!` delegate to the store. The memory store persists across multiple rake task invocations inside one Ruby process and is cleared by `AfterMigrate.run!` (or `AfterMigrate.reset!`). This replaces the old `Current` (`ActiveSupport::CurrentAttributes`) which was reset after every migration. Alternate backends: `FileStore` (JSON, cross-invocation) and `RedisStore` (cross-process, for parallel/multi-tenant runs). `RedisStore#resolved_redis` memoizes the resolved client per store instance, so `config.redis` is only materialized once — pass a `ConnectionPool` (the store calls `pool.with`) or a memoized client, never a `-> { Redis.new }` proc that builds a connection per call (that opens one connection per SQL statement and exhausts the Redis connection limit under concurrency).
|
|
26
|
+
|
|
27
|
+
4. **`Executor`** (`lib/after_migrate/executor.rb`) — iterates `AfterMigrate.affected_tables` and calls the correct adapter's `optimize_tables`. Respects the `analyze` config option (`only_affected_tables` / `all_tables` / `none`). Always calls `AfterMigrate.reset!` in its `ensure` block.
|
|
28
|
+
|
|
29
|
+
5. **Adapters** (`lib/after_migrate/adapters/`) — one module per database:
|
|
30
|
+
- `Sql` — shared regex-based table parser (used by MySQL and SQLite, which can't use pg_query)
|
|
31
|
+
- `Postgresql` — uses `pg_query` gem for accurate SQL parsing; runs `VACUUM` (checking `pg_stat_all_tables` for dead tuples) then `ANALYZE VERBOSE` per table
|
|
32
|
+
- `Mysql` — runs `ANALYZE TABLE` per table; lists tables from `information_schema`
|
|
33
|
+
- `Sqlite` — runs `PRAGMA optimize` (SQLite ≥ 3.35.0) or `VACUUM; ANALYZE;`
|
|
34
|
+
|
|
35
|
+
## Key design decisions
|
|
36
|
+
|
|
37
|
+
- PostgreSQL uses `pg_query` (the actual Postgres parser) for table extraction, which avoids false positives from regex. MySQL and SQLite fall back to the shared `Sql` regex patterns.
|
|
38
|
+
- `pg_query` is a hard runtime dependency (listed in gemspec), not optional — even though it's only used for PostgreSQL.
|
|
39
|
+
- Table collection uses `Concurrent::Map` + `Concurrent::Set` (from `concurrent-ruby`) for thread-safe accumulation across parallel migration workers.
|
|
40
|
+
- The gem does **not** monkey-patch ActiveRecord. It only uses public `ActiveSupport::Notifications` and `Rake::Task#enhance` APIs.
|
|
41
|
+
- `defer: true` (the default) is the multi-tenant-friendly mode: rake tasks only collect, never execute. Call `AfterMigrate.run!` (or `rake after_migrate:run`) once after all tenant migrations complete.
|
|
42
|
+
- The `app.executor.to_run` unsubscription in `Railtie` ensures the SQL subscription is dropped before the app starts serving web requests, so normal traffic is never collected.
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
Config values are in `AfterMigrate::Configuration` (initialized in `lib/after_migrate.rb`):
|
|
47
|
+
|
|
48
|
+
| Option | Default | Values |
|
|
49
|
+
|-----------------------|--------------------------|---------------------------------------------------------|
|
|
50
|
+
| `enabled` | `true` | bool |
|
|
51
|
+
| `verbose` | `true` | bool |
|
|
52
|
+
| `vacuum` | `true` | bool (PostgreSQL only) |
|
|
53
|
+
| `analyze` | `"only_affected_tables"` | `"only_affected_tables"`, `"all_tables"`, `"none"` |
|
|
54
|
+
| `rake_tasks_enhanced` | `true` | bool |
|
|
55
|
+
| `defer` | `true` | bool — skip auto-run; call `AfterMigrate.run!` manually |
|
|
56
|
+
|
|
57
|
+
## Multi-tenant usage
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
# In your tenant migration runner:
|
|
61
|
+
Tenant.each do |tenant|
|
|
62
|
+
tenant.switch { ActiveRecord::MigrationContext.new(...).migrate }
|
|
63
|
+
end
|
|
64
|
+
# Tables are now accumulated per schema in AfterMigrate.affected_tables
|
|
65
|
+
AfterMigrate.run! # or: Rake::Task['after_migrate:run'].invoke
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Set `defer: false` to restore the v0.1 behaviour of running after each `db:migrate`.
|
|
69
|
+
|
|
70
|
+
## Dependencies
|
|
71
|
+
|
|
72
|
+
- Ruby ≥ 3.2, Rails ≥ 7.0
|
|
73
|
+
- `pg_query ≥ 6.1` (required even for non-Postgres installs)
|
|
74
|
+
- Dev: `rspec ~> 3.0`, `rubocop ~> 1.81`, `bundler ~> 4`
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.3.0] - 2026-08-07
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Worker/web processes could hang indefinitely.** The railtie registered
|
|
9
|
+
`app.executor.to_run { ActiveSupport::Notifications.unsubscribe(subscription) }`, which fires on
|
|
10
|
+
*every* executor invocation - every web request and, through `Sidekiq::Rails::Reloader`, every
|
|
11
|
+
background job. Each call takes the process-global `ActiveSupport::Notifications::Fanout` mutex and
|
|
12
|
+
clears the listener cache, forcing every subsequent instrumented statement to contend on the same
|
|
13
|
+
mutex via `Fanout#all_listeners_for`. Under concurrency this collapsed: a production Sidekiq
|
|
14
|
+
process lost all five worker threads for 7+ hours, each parked on `fanout.rb:82`
|
|
15
|
+
`Thread::Mutex#synchronize` inside this callback. The unsubscribe is now one-shot, so every call
|
|
16
|
+
after the first is a boolean check.
|
|
17
|
+
|
|
18
|
+
- **Store failures no longer abort migrations.** `Collector` runs inside an `sql.active_record`
|
|
19
|
+
subscriber, and ActiveSupport propagates subscriber exceptions to whoever emitted the event -- so a
|
|
20
|
+
Redis error aborted the SQL statement and with it the migration. Running 25 parallel
|
|
21
|
+
`rails apartment:migrate` workers against one Redis produced
|
|
22
|
+
`Connection refused - connect(2) for <host>:6379` and killed migrations mid-run. `merge_tables`,
|
|
23
|
+
`affected_tables` and `reset!` now degrade to a warning: the first failure is logged in full,
|
|
24
|
+
subsequent ones are counted (`AfterMigrate.store_failure_count`), and `AfterMigrate.store_degraded?`
|
|
25
|
+
reports that collected tables are incomplete. Losing table names only costs some ANALYZE coverage;
|
|
26
|
+
a half-applied migration costs far more.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- `config.raise_on_store_error` (default `false`) to restore the old fatal behaviour.
|
|
30
|
+
- `AfterMigrate.store_degraded?` and `AfterMigrate.store_failure_count`.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- **BREAKING: `enabled` is now auto-detected**, resolved at `Configuration` construction time:
|
|
34
|
+
|
|
35
|
+
| `AFTER_MIGRATE_ENABLED` | result |
|
|
36
|
+
|-------------------------|-----------------------------------------------|
|
|
37
|
+
| `true` | always on |
|
|
38
|
+
| `false` | always off - an explicit opt-out always wins |
|
|
39
|
+
| unset / blank | **on only while a migration task is running** |
|
|
40
|
+
|
|
41
|
+
Auto-detection matches the running task against `db:migrate`, `db:migrate:up`, `db:rollback`,
|
|
42
|
+
`apartment:migrate`, `db:schema:load`, `db:structure:load` and `after_migrate:*`, reading
|
|
43
|
+
`Rake.application.top_level_tasks` with `ARGV` as a fallback. It deliberately does not match
|
|
44
|
+
`db:seed`, `assets:precompile`, `server`, `console`, or a Sidekiq command line -- so a web or
|
|
45
|
+
worker process never arms the gem, while `rails db:migrate` needs no configuration at all.
|
|
46
|
+
|
|
47
|
+
Previously, there was no way to keep the gem out of long-running processes.
|
|
48
|
+
|
|
49
|
+
This gem is a migration-time tool, and there was previously no way to keep it out of long-running
|
|
50
|
+
processes: railtie initializers run *before* `:load_config_initializers`
|
|
51
|
+
(`Rails::Application#ordered_railties` pushes the application last), so a host app calling
|
|
52
|
+
`AfterMigrate.configure { |c| c.enabled = false }` in `config/initializers/` was always too late --
|
|
53
|
+
the railtie had already subscribed and registered the executor hook. Reading the environment
|
|
54
|
+
variable directly is the only way the railtie can see the intended value.
|
|
55
|
+
|
|
5
56
|
## [0.2.6] - 2026-07-16
|
|
6
57
|
|
|
7
58
|
### Fixed
|
data/README.md
CHANGED
|
@@ -59,10 +59,22 @@ Out of the box, it runs `ANALYZE` on **only the tables touched** during the migr
|
|
|
59
59
|
|
|
60
60
|
Create `config/initializers/after_migrate.rb`:
|
|
61
61
|
|
|
62
|
+
> **`enabled` is auto-detected -- do not set it here.**
|
|
63
|
+
>
|
|
64
|
+
> | `AFTER_MIGRATE_ENABLED` | result |
|
|
65
|
+
> |-------------------------|-----------------------------------------------------------------------------------------------------------------|
|
|
66
|
+
> | `true` | always on |
|
|
67
|
+
> | `false` | always off (explicit opt-out wins) |
|
|
68
|
+
> | unset | **on only while running `db:migrate`, `db:rollback`, `apartment:migrate`, `db:schema:load`, `after_migrate:*`** |
|
|
69
|
+
>
|
|
70
|
+
> It must be resolved from the environment, because Rails runs railtie initializers *before*
|
|
71
|
+
> `config/initializers/*.rb` -- by the time this block executes, the gem has already decided whether
|
|
72
|
+
> to subscribe. Assigning `config.enabled` here still affects `AfterMigrate.run!`, but **cannot**
|
|
73
|
+
> switch collection on or off, and setting it to a fixed value will override auto-detection for
|
|
74
|
+
> `run!`. Leave it alone unless you mean it.
|
|
75
|
+
|
|
62
76
|
```ruby
|
|
63
77
|
AfterMigrate.configure do |config|
|
|
64
|
-
# Enable/disable the gem
|
|
65
|
-
config.enabled = true
|
|
66
78
|
|
|
67
79
|
# Log what’s happening
|
|
68
80
|
config.verbose = true
|
|
@@ -12,9 +12,27 @@ module AfterMigrate
|
|
|
12
12
|
AfterMigrate::Collector.call(*args)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
# Unsubscribe
|
|
16
|
-
#
|
|
17
|
-
|
|
15
|
+
# Unsubscribe once, the first time the app starts doing work, so normal web/worker traffic is
|
|
16
|
+
# never collected.
|
|
17
|
+
#
|
|
18
|
+
# THIS MUST STAY ONE-SHOT. `executor.to_run` fires on EVERY executor invocation -- every web
|
|
19
|
+
# request and, via Sidekiq::Rails::Reloader, every single background job. Calling
|
|
20
|
+
# `Notifications.unsubscribe` there takes the process-global
|
|
21
|
+
# `ActiveSupport::Notifications::Fanout` mutex and calls `clear_cache`, which invalidates the
|
|
22
|
+
# listener cache so every subsequent instrumented statement must take the same mutex through
|
|
23
|
+
# `Fanout#all_listeners_for`.
|
|
24
|
+
done = false
|
|
25
|
+
lock = Mutex.new
|
|
26
|
+
app.executor.to_run do
|
|
27
|
+
next if done
|
|
28
|
+
|
|
29
|
+
lock.synchronize do
|
|
30
|
+
next if done
|
|
31
|
+
|
|
32
|
+
done = true
|
|
33
|
+
ActiveSupport::Notifications.unsubscribe(subscription)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
18
36
|
end
|
|
19
37
|
|
|
20
38
|
rake_tasks do
|
data/lib/after_migrate.rb
CHANGED
|
@@ -9,15 +9,80 @@ require 'after_migrate/railtie'
|
|
|
9
9
|
module AfterMigrate
|
|
10
10
|
class Configuration
|
|
11
11
|
attr_accessor :enabled, :verbose, :vacuum, :analyze, :rake_tasks_enhanced, :defer,
|
|
12
|
-
:store, :run_id, :store_options
|
|
12
|
+
:store, :run_id, :store_options, :raise_on_store_error
|
|
13
|
+
|
|
14
|
+
# Task names that mean "this process is applying schema changes". Matches `db:migrate`,
|
|
15
|
+
# `db:migrate:up`, `db:rollback`, `apartment:migrate`, `db:schema:load`, `after_migrate:*` --
|
|
16
|
+
# and deliberately NOT `db:seed`, `assets:precompile`, `server`, `console`, or a bare Sidekiq
|
|
17
|
+
# command line.
|
|
18
|
+
MIGRATION_TASK_PATTERN = /
|
|
19
|
+
(?:\A|:)(?:migrate|rollback)(?::|\z)
|
|
20
|
+
| \Adb:(?:schema|structure):load\z
|
|
21
|
+
| \Aafter_migrate:
|
|
22
|
+
/x
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
# Three states, so the common case needs no configuration at all:
|
|
26
|
+
#
|
|
27
|
+
# AFTER_MIGRATE_ENABLED=true -> always on (force it, e.g. from a console)
|
|
28
|
+
# AFTER_MIGRATE_ENABLED=false -> always off (explicit opt-out always wins)
|
|
29
|
+
# unset -> ON only while running migrations
|
|
30
|
+
#
|
|
31
|
+
# Auto-detection is safe because a web or worker process never has a migration task on its
|
|
32
|
+
# command line, so the gem simply never arms itself there.
|
|
33
|
+
def resolve_enabled
|
|
34
|
+
override = env_override
|
|
35
|
+
return override unless override.nil?
|
|
36
|
+
|
|
37
|
+
migration_task?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# nil when unset/blank so "not configured" stays distinguishable from "configured false".
|
|
41
|
+
def env_override
|
|
42
|
+
raw = ENV['AFTER_MIGRATE_ENABLED'].to_s.strip
|
|
43
|
+
return nil if raw.empty?
|
|
44
|
+
|
|
45
|
+
raw.casecmp('true').zero?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def migration_task?
|
|
49
|
+
task_names.any? { |name| MIGRATION_TASK_PATTERN.match?(name) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Rake's parsed task list is the reliable source (`rails db:migrate` dispatches through Rake
|
|
53
|
+
# and the environment is loaded by the `environment` prerequisite, so top_level_tasks is
|
|
54
|
+
# already populated by the time our railtie runs). ARGV is the belt-and-braces fallback for
|
|
55
|
+
# `rake db:migrate` and for anything that bypasses Rake's parser.
|
|
56
|
+
def task_names
|
|
57
|
+
names = []
|
|
58
|
+
if defined?(::Rake) && ::Rake.respond_to?(:application)
|
|
59
|
+
top_level = ::Rake.application.top_level_tasks
|
|
60
|
+
names.concat(Array(top_level))
|
|
61
|
+
end
|
|
62
|
+
names.concat(Array(ARGV))
|
|
63
|
+
names.map(&:to_s)
|
|
64
|
+
rescue StandardError
|
|
65
|
+
Array(ARGV).map(&:to_s)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
13
68
|
|
|
69
|
+
# Resolved here, not in a host app's `config/initializers/*.rb`.
|
|
70
|
+
#
|
|
71
|
+
# Railtie initializers run BEFORE `:load_config_initializers` (Rails::Application#ordered_railties
|
|
72
|
+
# pushes the application last), so by the time an app calls `AfterMigrate.configure`, our railtie
|
|
73
|
+
# has already decided whether to subscribe. An app setting `config.enabled = false` therefore
|
|
74
|
+
# could NOT switch the subscription off -- the only value the railtie ever sees is this one.
|
|
75
|
+
#
|
|
76
|
+
# Off outside migrations: leaving this armed in every web and worker process.
|
|
14
77
|
def initialize
|
|
15
|
-
@enabled =
|
|
78
|
+
@enabled = self.class.resolve_enabled
|
|
16
79
|
@verbose = true
|
|
17
80
|
@vacuum = true
|
|
18
81
|
@analyze = 'only_affected_tables'
|
|
19
82
|
@rake_tasks_enhanced = true
|
|
20
83
|
@defer = true
|
|
84
|
+
# Store failures degrade to a warning by default; they must not abort a migration.
|
|
85
|
+
@raise_on_store_error = false
|
|
21
86
|
@store = :memory
|
|
22
87
|
@run_id = ENV.fetch('AFTER_MIGRATE_RUN_ID', 'default')
|
|
23
88
|
@store_options = {
|
|
@@ -84,11 +149,21 @@ module AfterMigrate
|
|
|
84
149
|
|
|
85
150
|
# Persistent cross-migration store: schema_name => Concurrent::Set<table_name>
|
|
86
151
|
def affected_tables
|
|
87
|
-
store.affected_tables
|
|
152
|
+
with_store_rescue('affected_tables', default: Concurrent::Map.new) { store.affected_tables }
|
|
88
153
|
end
|
|
89
154
|
|
|
90
155
|
def merge_tables(schema, table_names)
|
|
91
|
-
store.merge_tables(schema, table_names)
|
|
156
|
+
with_store_rescue('merge_tables') { store.merge_tables(schema, table_names) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# True, when at least one store operation failed and was swallowed, so the collected table list
|
|
160
|
+
# is incomplete and maintenance coverage will be partial.
|
|
161
|
+
def store_degraded?
|
|
162
|
+
@store_degraded == true
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def store_failure_count
|
|
166
|
+
@store_failure_count.to_i
|
|
92
167
|
end
|
|
93
168
|
|
|
94
169
|
# Trigger database maintenance on all collected tables, then clear the store.
|
|
@@ -100,7 +175,36 @@ module AfterMigrate
|
|
|
100
175
|
end
|
|
101
176
|
|
|
102
177
|
def reset!
|
|
103
|
-
store.reset!
|
|
178
|
+
with_store_rescue('reset!') { store.reset! }
|
|
179
|
+
@store_degraded = false
|
|
180
|
+
@store_failure_count = 0
|
|
181
|
+
nil
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# A maintenance tool must never break the thing it is measuring.
|
|
185
|
+
#
|
|
186
|
+
# `Collector` runs inside an `sql.active_record` notification subscriber, and ActiveSupport
|
|
187
|
+
# propagates subscriber exceptions to the code that emitted the event — so a store error here
|
|
188
|
+
# aborts the SQL statement, which aborts the migration.
|
|
189
|
+
#
|
|
190
|
+
# Losing collected table names only means some tables miss their ANALYZE -- recoverable, and far
|
|
191
|
+
# cheaper than a half-applied migration. Set `config.raise_on_store_error = true` to opt out.
|
|
192
|
+
#
|
|
193
|
+
# The first failure logs in full; the rest are counted only, so a long migration cannot flood
|
|
194
|
+
# the log with one line per statement.
|
|
195
|
+
def with_store_rescue(operation, default: nil)
|
|
196
|
+
yield
|
|
197
|
+
rescue StandardError => e
|
|
198
|
+
raise if configuration.raise_on_store_error
|
|
199
|
+
|
|
200
|
+
@store_failure_count = store_failure_count + 1
|
|
201
|
+
unless @store_degraded
|
|
202
|
+
@store_degraded = true
|
|
203
|
+
log("store #{operation} failed (#{e.class}: #{e.message}). Continuing without the store — " \
|
|
204
|
+
'collected tables will be incomplete and some maintenance may be skipped. ' \
|
|
205
|
+
'Set config.raise_on_store_error = true to make this fatal.')
|
|
206
|
+
end
|
|
207
|
+
default
|
|
104
208
|
end
|
|
105
209
|
|
|
106
210
|
def store
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: after_migrate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nikolay Moskvin
|
|
@@ -52,6 +52,7 @@ files:
|
|
|
52
52
|
- ".rubocop.yml"
|
|
53
53
|
- ".ruby-version"
|
|
54
54
|
- ".travis.yml"
|
|
55
|
+
- AGENTS.md
|
|
55
56
|
- CHANGELOG.md
|
|
56
57
|
- CLAUDE.md
|
|
57
58
|
- Gemfile
|