after_migrate 0.2.4 → 0.2.5

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: 52bea11a45a6765c9bff5e82d42d527595e7ef6c91e84028ca2dde978d8d527e
4
- data.tar.gz: 3dcd46239d300faa855998e427585870899a0ea0d013e21fb6eb770ecd5f5be4
3
+ metadata.gz: 23144c2fac25e5be5c8965155dc54b03b8859063fe00a43d033e9c3bed415f8a
4
+ data.tar.gz: e40e59a694376551d73261e59d214e8ebfe460ee0ab5961116873a22bc9b019f
5
5
  SHA512:
6
- metadata.gz: e21f8c6f9db4d2db100b5ef4c93f87ce0a7b69af5a7da29b79ce91a4a47e585ffdce9f33726929aed3962693ad9afee40418a40dc76a813dc2ee717b5333a59d
7
- data.tar.gz: d6da80e272cc1895bb753b6fa01b423fc8eb9c3ea4f93d34d691f64743d1269689c3147764d953114957907d36fea5cac8910b03baac009fea08984924c4154b
6
+ metadata.gz: a1ff650538cacd552d26399df13507a0e4f5a71581e32bff0d1e2686ea3d8b8a86e44ef96a7143892ef7cef79a720135489ac174b09ade876b722465b916fc18
7
+ data.tar.gz: 977f7a020023cad34941b24046982d0563f30340cfefe6a196feb83915c7ec5d6a148707cc596749ad0d2ced9a5e64ec55c926d6869d3bd0c853edbd4617dbd8
data/.gitlocalexclude ADDED
@@ -0,0 +1,2 @@
1
+ .ai/
2
+ .claude/
data/CLAUDE.md CHANGED
@@ -22,7 +22,7 @@ This is a Rails gem that automatically runs database maintenance (`ANALYZE`, `VA
22
22
 
23
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
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.
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
26
 
27
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
28
 
@@ -45,13 +45,13 @@ This is a Rails gem that automatically runs database maintenance (`ANALYZE`, `VA
45
45
 
46
46
  Config values are in `AfterMigrate::Configuration` (initialized in `lib/after_migrate.rb`):
47
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 |
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
55
  | `defer` | `true` | bool — skip auto-run; call `AfterMigrate.run!` manually |
56
56
 
57
57
  ## Multi-tenant usage
data/README.md CHANGED
@@ -81,6 +81,41 @@ AfterMigrate.configure do |config|
81
81
  config.rake_tasks_enhanced = true
82
82
  end
83
83
  ```
84
+
85
+ ### Store backends
86
+
87
+ The collected table names are kept in a store. Choose one with `config.store`:
88
+
89
+ - `:memory` (default) — a process-local `Concurrent::Map`. Fine for a single-process migration.
90
+ - `:file` — JSON file, shared across rake invocations in the same run.
91
+ - `:redis` — shared across processes; use this for parallel / multi-tenant migrations.
92
+
93
+ ```ruby
94
+ config.store = :redis
95
+ ```
96
+
97
+ > [!IMPORTANT]
98
+ > **Redis: always pass a pooled or memoized client — never a proc that builds a new connection per call.**
99
+ >
100
+ > `config.redis` is read on *every* collected SQL statement. If it returns a
101
+ > fresh `Redis.new` each time (e.g. `config.redis = -> { Redis.new(...) }`),
102
+ > every statement opens a new, unpooled connection. Under parallel or
103
+ > multi-tenant migrations this exhausts the Redis server connection limit
104
+ > (`Connection refused` / `max number of clients reached`).
105
+ >
106
+ > Recommended — a thread-safe `ConnectionPool` (the store uses `pool.with`):
107
+ >
108
+ > ```ruby
109
+ > config.store = :redis
110
+ > config.redis = ConnectionPool.new(size: 2, timeout: 5) { Redis.new(url: ENV["REDIS_URL"]) }
111
+ > ```
112
+ >
113
+ > A memoized single client also works for sequential migrations:
114
+ >
115
+ > ```ruby
116
+ > config.redis = -> { @after_migrate_redis ||= Redis.new(url: ENV["REDIS_URL"]) }
117
+ > ```
118
+
84
119
  ---
85
120
 
86
121
  ## 🚢 Releasing
@@ -181,12 +181,18 @@ module AfterMigrate
181
181
  block.call(redis)
182
182
  end
183
183
 
184
+ # Memoized so a plain client or a `-> { Redis.new }` proc only ever
185
+ # produces one connection per store instance. Without this, `with_redis`
186
+ # re-invokes the proc on every merge_tables/affected_tables call, opening
187
+ # a fresh unpooled connection per SQL statement — which exhausts the
188
+ # Redis server's connection limit under parallel/multi-tenant migrations.
189
+ # Pass a ConnectionPool as config.redis for thread-safe concurrency.
184
190
  def resolved_redis
185
- client = @redis.respond_to?(:call) ? @redis.call : @redis
186
- return client if client
187
- return Redis.new if defined?(Redis)
188
-
189
- raise 'AfterMigrate Redis store requires config.redis or the redis gem'
191
+ @resolved_redis ||= begin
192
+ client = @redis.respond_to?(:call) ? @redis.call : @redis
193
+ client ||= Redis.new if defined?(Redis)
194
+ client || raise('AfterMigrate Redis store requires config.redis or the redis gem')
195
+ end
190
196
  end
191
197
  end
192
198
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AfterMigrate
4
- VERSION = '0.2.4'
4
+ VERSION = '0.2.5'
5
5
  end
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.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikolay Moskvin
@@ -48,6 +48,7 @@ extensions: []
48
48
  extra_rdoc_files: []
49
49
  files:
50
50
  - ".gitignore"
51
+ - ".gitlocalexclude"
51
52
  - ".rspec"
52
53
  - ".rubocop.yml"
53
54
  - ".ruby-version"