after_migrate 0.2.3 → 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 +4 -4
- data/.gitlocalexclude +2 -0
- data/CHANGELOG.md +9 -0
- data/CLAUDE.md +8 -8
- data/README.md +35 -0
- data/lib/after_migrate/store.rb +11 -5
- data/lib/after_migrate/version.rb +1 -1
- data/lib/after_migrate.rb +63 -14
- 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: 23144c2fac25e5be5c8965155dc54b03b8859063fe00a43d033e9c3bed415f8a
|
|
4
|
+
data.tar.gz: e40e59a694376551d73261e59d214e8ebfe460ee0ab5961116873a22bc9b019f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a1ff650538cacd552d26399df13507a0e4f5a71581e32bff0d1e2686ea3d8b8a86e44ef96a7143892ef7cef79a720135489ac174b09ade876b722465b916fc18
|
|
7
|
+
data.tar.gz: 977f7a020023cad34941b24046982d0563f30340cfefe6a196feb83915c7ec5d6a148707cc596749ad0d2ced9a5e64ec55c926d6869d3bd0c853edbd4617dbd8
|
data/.gitlocalexclude
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.2.4] - 2026-06-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `config.store_options` for store-specific settings, e.g. `config.store_options_for(:redis)[:client]`
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Store-specific configuration now flows through `config.store_options` while preserving compatibility accessors such as `config.store_path`, `config.redis`, `config.redis_key_prefix`, and `config.redis_ttl`
|
|
12
|
+
- `config.run_id` now defaults from `AFTER_MIGRATE_RUN_ID`, falling back to `default`
|
|
13
|
+
|
|
5
14
|
## [0.2.3] - 2026-06-01
|
|
6
15
|
|
|
7
16
|
### Added
|
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
|
data/lib/after_migrate/store.rb
CHANGED
|
@@ -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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
data/lib/after_migrate.rb
CHANGED
|
@@ -9,7 +9,7 @@ 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, :
|
|
12
|
+
:store, :run_id, :store_options
|
|
13
13
|
|
|
14
14
|
def initialize
|
|
15
15
|
@enabled = true
|
|
@@ -19,11 +19,53 @@ module AfterMigrate
|
|
|
19
19
|
@rake_tasks_enhanced = true
|
|
20
20
|
@defer = true
|
|
21
21
|
@store = :memory
|
|
22
|
-
@
|
|
23
|
-
@
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
@run_id = ENV.fetch('AFTER_MIGRATE_RUN_ID', 'default')
|
|
23
|
+
@store_options = {
|
|
24
|
+
file: {
|
|
25
|
+
path: 'tmp/after_migrate/affected_tables.json'
|
|
26
|
+
},
|
|
27
|
+
redis: {
|
|
28
|
+
client: nil,
|
|
29
|
+
key_prefix: 'after_migrate',
|
|
30
|
+
ttl: 24 * 60 * 60
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def store_path
|
|
36
|
+
store_options_for(:file)[:path]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def store_path=(value)
|
|
40
|
+
store_options_for(:file)[:path] = value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def redis
|
|
44
|
+
store_options_for(:redis)[:client]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def redis=(value)
|
|
48
|
+
store_options_for(:redis)[:client] = value
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def redis_key_prefix
|
|
52
|
+
store_options_for(:redis)[:key_prefix]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def redis_key_prefix=(value)
|
|
56
|
+
store_options_for(:redis)[:key_prefix] = value
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def redis_ttl
|
|
60
|
+
store_options_for(:redis)[:ttl]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def redis_ttl=(value)
|
|
64
|
+
store_options_for(:redis)[:ttl] = value
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def store_options_for(store_name)
|
|
68
|
+
store_options[store_name.to_sym] ||= {}
|
|
27
69
|
end
|
|
28
70
|
end
|
|
29
71
|
|
|
@@ -72,17 +114,15 @@ module AfterMigrate
|
|
|
72
114
|
def store_key
|
|
73
115
|
[
|
|
74
116
|
configuration.store.to_s,
|
|
75
|
-
configuration.store_path.to_s,
|
|
76
117
|
configuration.run_id.to_s,
|
|
77
|
-
configuration.
|
|
78
|
-
configuration.redis_ttl.to_s
|
|
118
|
+
configuration.store_options_for(configuration.store).hash
|
|
79
119
|
]
|
|
80
120
|
end
|
|
81
121
|
|
|
82
122
|
def build_store
|
|
83
123
|
case configuration.store.to_s
|
|
84
124
|
when 'file'
|
|
85
|
-
|
|
125
|
+
file_store
|
|
86
126
|
when 'redis'
|
|
87
127
|
redis_store
|
|
88
128
|
else
|
|
@@ -90,12 +130,21 @@ module AfterMigrate
|
|
|
90
130
|
end
|
|
91
131
|
end
|
|
92
132
|
|
|
133
|
+
def file_store
|
|
134
|
+
options = configuration.store_options_for(:file)
|
|
135
|
+
Stores::FileStore.new(
|
|
136
|
+
path: options.fetch(:path),
|
|
137
|
+
run_id: options.fetch(:run_id, configuration.run_id)
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
|
|
93
141
|
def redis_store
|
|
142
|
+
options = configuration.store_options_for(:redis)
|
|
94
143
|
Stores::RedisStore.new(
|
|
95
|
-
redis:
|
|
96
|
-
key_prefix:
|
|
97
|
-
run_id: configuration.run_id,
|
|
98
|
-
ttl:
|
|
144
|
+
redis: options[:client],
|
|
145
|
+
key_prefix: options.fetch(:key_prefix),
|
|
146
|
+
run_id: options.fetch(:run_id, configuration.run_id),
|
|
147
|
+
ttl: options.fetch(:ttl)
|
|
99
148
|
)
|
|
100
149
|
end
|
|
101
150
|
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
|
+
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"
|