after_migrate 0.2.0 → 0.2.2

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: a13db746f7bfcbcb7367f028493efd2b464e564b32f09be5debb62da1326f8bf
4
- data.tar.gz: b1f0db683bb9b12ed407f14f3f1b1c0baba0a80cfd445c6182b9bd5ad7378821
3
+ metadata.gz: 62f5a309584cee52788e0ea45c222648cc58c4db46bc08ce6188c6325f6c62dc
4
+ data.tar.gz: e36efca18e936086cdace33a4fe422c2d34f63502ddf01c53090cf8048ed92c6
5
5
  SHA512:
6
- metadata.gz: '085b3be36d4031981294d5a5ddc929aae3d781c9d1c3d8446f4508f7c7c526064c8b6529b68237e3e3517885f22251b3db071839dae1bcdf8df654d1e79b2e70'
7
- data.tar.gz: 212614cb1145bbe3f22d9b005e6524af133b5d50f659ca8da2efff0d6416efd7ff90fccf39575719d4237df08029a5dcab6b8ef8dfd5072027bce902bc2b3bd8
6
+ metadata.gz: 2590c965f71fc07b5171cea88869115516687331138b0a6a20b5c4bdd3008a13be1c7a7382dd8886112a02e076904d57f59e2f24db44bfa23d1c36dd30700b40
7
+ data.tar.gz: 4a2f1dd2df2bb18e8f663b1ca150db22a6d4d5474b651ea0ee693129640926266ce77168e370921cab4ac214620a601fe02f6204a97829575ec7e417c944a7d4
data/.gitignore CHANGED
@@ -9,3 +9,6 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+ .idea/
13
+ /Gemfile.lock
14
+ .private/
data/.rubocop.yml CHANGED
@@ -1,2 +1,14 @@
1
1
  AllCops:
2
2
  NewCops: enable
3
+ SuggestExtensions: false
4
+
5
+ Style/Documentation:
6
+ Enabled: false
7
+
8
+ Metrics/MethodLength:
9
+ Max: 20
10
+
11
+ Metrics/BlockLength:
12
+ Exclude:
13
+ - 'after_migrate.gemspec'
14
+ - 'spec/**/*.rb'
data/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.2.2] - 2026-05-31
6
+
7
+ ### Added
8
+ - Configurable store backend via `config.store`, with `:memory` as the default and `:file` for persistence across separate migration task processes
9
+ - `config.store_path` to control where the file-backed store writes collected table names
10
+ - `config.run_id` to isolate persisted file-store data between independent migration runs
11
+ - File-store locking and atomic writes to avoid corrupting persisted table data during concurrent access
12
+ - Specs for file-store persistence, corrupt JSON handling, adapter dispatch, unsupported adapters, and deferred rake task behavior
13
+
14
+ ### Changed
15
+ - `AfterMigrate.affected_tables`, `AfterMigrate.merge_tables`, and `AfterMigrate.reset!` now delegate through the configured store backend
16
+ - Executor resets the store when maintenance is disabled or no tables are pending, but keeps collected tables when adapter optimization raises so a later run can retry
17
+
18
+ ## [0.2.1] - 2026-05-29
19
+
20
+ ### Changed
21
+ - Refactored SQL identifier matching used by the parser without changing supported table-detection behavior
22
+ - Split executor migration logging message construction into a local variable for clearer formatting and maintenance
23
+ - Added RuboCop project configuration for documentation, method length, and spec block length rules
24
+
5
25
  ## [0.2.0] - 2026-04-28
6
26
 
7
27
  ### Added
data/CLAUDE.md ADDED
@@ -0,0 +1,74 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.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.
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/Gemfile CHANGED
@@ -11,3 +11,4 @@ gem 'bundler', '~> 4'
11
11
  gem 'rake', '~> 13.0'
12
12
  gem 'rspec', '~> 3.0'
13
13
  gem 'rubocop', '~> 1.81'
14
+ gem 'simplecov', group: 'test'
data/README.md CHANGED
@@ -83,6 +83,30 @@ end
83
83
  ```
84
84
  ---
85
85
 
86
+ ## 🚢 Releasing
87
+
88
+ Use the guarded helper to make sure git is pushed before RubyGems publish:
89
+
90
+ ```bash
91
+ bin/release
92
+ ```
93
+
94
+ What it does:
95
+ - requires a clean git worktree
96
+ - ensures you are on the default branch (`origin/HEAD` fallback)
97
+ - checks branch sync status vs `origin`
98
+ - runs `bundle exec rspec` and `bundle exec rubocop`
99
+ - pushes the branch first
100
+ - runs `bundle exec rake release` (build/tag/push/publish)
101
+
102
+ Optional:
103
+
104
+ ```bash
105
+ bin/release --skip-checks
106
+ ```
107
+
108
+ ---
109
+
86
110
  ## 🤝 Contributing
87
111
 
88
112
  Bug reports, feature requests, and pull requests are very welcome!
data/bin/release ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ root_dir=$(cd "$(dirname "$0")/.." && pwd)
6
+ cd "$root_dir"
7
+
8
+ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
9
+ cat <<'USAGE'
10
+ Usage: bin/release [--skip-checks]
11
+
12
+ Runs guardrails before releasing:
13
+ 1. Requires a clean git worktree
14
+ 2. Requires current branch to match origin/HEAD (or local HEAD fallback)
15
+ 3. Requires branch to be in sync with origin
16
+ 4. Runs test + lint checks (unless --skip-checks)
17
+ 5. Pushes the branch, then runs `bundle exec rake release`
18
+ USAGE
19
+ exit 0
20
+ fi
21
+
22
+ skip_checks=false
23
+ if [[ "${1:-}" == "--skip-checks" ]]; then
24
+ skip_checks=true
25
+ elif [[ $# -gt 0 ]]; then
26
+ echo "Unknown option: $1" >&2
27
+ exit 1
28
+ fi
29
+
30
+ if [[ -n "$(git status --porcelain)" ]]; then
31
+ echo "Working tree is not clean. Commit or stash changes before releasing." >&2
32
+ exit 1
33
+ fi
34
+
35
+ current_branch=$(git rev-parse --abbrev-ref HEAD)
36
+ default_branch=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||')
37
+ default_branch=${default_branch:-$current_branch}
38
+
39
+ if [[ "$current_branch" != "$default_branch" ]]; then
40
+ echo "Release must run from '$default_branch' (current: '$current_branch')." >&2
41
+ exit 1
42
+ fi
43
+
44
+ echo "Fetching latest refs from origin..."
45
+ git fetch origin
46
+
47
+ local_sha=$(git rev-parse @)
48
+ remote_sha=$(git rev-parse "origin/$default_branch")
49
+ base_sha=$(git merge-base @ "origin/$default_branch")
50
+
51
+ if [[ "$local_sha" == "$remote_sha" ]]; then
52
+ echo "Branch is in sync with origin/$default_branch."
53
+ elif [[ "$local_sha" == "$base_sha" ]]; then
54
+ echo "Local branch is behind origin/$default_branch. Pull/rebase first." >&2
55
+ exit 1
56
+ elif [[ "$remote_sha" == "$base_sha" ]]; then
57
+ echo "Local branch is ahead of origin/$default_branch and will be pushed."
58
+ else
59
+ echo "Local and origin/$default_branch have diverged. Rebase/merge first." >&2
60
+ exit 1
61
+ fi
62
+
63
+ version=$(ruby -e 'require_relative "lib/after_migrate/version"; puts AfterMigrate::VERSION')
64
+ tag="v$version"
65
+
66
+ if git rev-parse "$tag" >/dev/null 2>&1 || git ls-remote --tags origin "$tag" | grep -q "$tag"; then
67
+ echo "Tag '$tag' already exists locally or on origin. Bump version first." >&2
68
+ exit 1
69
+ fi
70
+
71
+ if [[ "$skip_checks" == "false" ]]; then
72
+ echo "Running specs..."
73
+ bundle exec rspec
74
+
75
+ echo "Running RuboCop..."
76
+ bundle exec rubocop
77
+ fi
78
+
79
+ echo "Pushing branch '$default_branch' first..."
80
+ git push origin "$default_branch"
81
+
82
+ echo "Releasing gem with rake task..."
83
+ bundle exec rake release
84
+
85
+ echo "Release complete: $tag"
@@ -9,7 +9,8 @@ module AfterMigrate
9
9
  module_function
10
10
 
11
11
  def vacuum(table_name, schema: nil, verbose: true)
12
- table = ActiveRecord::Base.connection.quote_table_name("#{schema}.#{table_name}")
12
+ qualified = schema.present? ? "#{schema}.#{table_name}" : table_name
13
+ table = ActiveRecord::Base.connection.quote_table_name(qualified)
13
14
  query = <<~SQL.squish
14
15
  VACUUM (#{'VERBOSE, ' if verbose}ANALYZE, INDEX_CLEANUP ON) #{table};
15
16
  SQL
@@ -41,10 +42,11 @@ module AfterMigrate
41
42
  ActiveRecord::Base.connection.execute(query)
42
43
  end
43
44
 
44
- def run_vacuum(schema:)
45
+ def run_vacuum(schema:, table_names: nil)
45
46
  tables_with_dead_tuples = dead_tuples(schema:).pluck('relname')
47
+ tables_with_dead_tuples &= Array(table_names) if table_names
46
48
  AfterMigrate.log("Vacuuming #{tables_with_dead_tuples.size} tables in schema #{schema}...")
47
- tables_with_dead_tuples.each { |t| vacuum(t) }
49
+ tables_with_dead_tuples.each { |t| vacuum(t, schema:, verbose: AfterMigrate.configuration.verbose) }
48
50
  tables_with_dead_tuples
49
51
  end
50
52
 
@@ -63,7 +65,9 @@ module AfterMigrate
63
65
 
64
66
  def optimize_tables(table_names:, schema:, **)
65
67
  cleaned_tables = []
66
- cleaned_tables = run_vacuum(schema:) if AfterMigrate.configuration.vacuum
68
+ cleaned_tables = run_vacuum(schema:, table_names:) if AfterMigrate.configuration.vacuum
69
+
70
+ return if AfterMigrate.configuration.analyze == 'none'
67
71
 
68
72
  tables = table_names - cleaned_tables
69
73
  run_analyze(schema:, tables:)
@@ -3,8 +3,8 @@
3
3
  module AfterMigrate
4
4
  module Sql
5
5
  IDENT = /
6
- (?:"[\w]+"|\w+)
7
- (?:\.(?:"[\w]+"|\w+))*
6
+ (?:"\w+"|\w+)
7
+ (?:\.(?:"\w+"|\w+))*
8
8
  /x
9
9
 
10
10
  PATTERNS = {
@@ -12,27 +12,39 @@ module AfterMigrate
12
12
  module_function
13
13
 
14
14
  def call(schema: nil)
15
+ return reset_store unless maintenance_enabled?
16
+
15
17
  tables = target_tables
16
- return if tables.blank?
18
+ return reset_store if tables.blank?
17
19
 
18
- if schema.present?
19
- run_optimize(schema:, tables: tables[schema]) if tables[schema].present?
20
- else
21
- tables.each { |s, t| run_optimize(schema: s, tables: t) }
22
- end
23
- ensure
24
- AfterMigrate.reset!
20
+ run_optimizations(schema:, tables:)
21
+ reset_store
25
22
  end
26
23
 
27
24
  public :call
28
25
 
29
26
  private
30
27
 
28
+ def maintenance_enabled?
29
+ AfterMigrate.configuration.vacuum || AfterMigrate.configuration.analyze != 'none'
30
+ end
31
+
32
+ def reset_store
33
+ AfterMigrate.reset!
34
+ end
35
+
36
+ def run_optimizations(schema:, tables:)
37
+ return run_optimize(schema:, tables: tables[schema]) if schema.present? && tables[schema].present?
38
+
39
+ tables.each { |s, t| run_optimize(schema: s, tables: t) } unless schema.present?
40
+ end
41
+
31
42
  def run_optimize(schema:, tables:)
32
43
  table_names = tables.to_a.sort
33
44
  return if table_names.empty?
34
45
 
35
- AfterMigrate.log("Migration touched #{table_names.size} table(s) in schema #{schema.inspect}: #{table_names.join(', ')}")
46
+ message = "Migration touched #{table_names.size} table(s) in schema #{schema.inspect}: #{table_names.join(', ')}"
47
+ AfterMigrate.log(message)
36
48
  optimize_tables(schema:, table_names:)
37
49
  end
38
50
 
@@ -56,10 +68,9 @@ module AfterMigrate
56
68
  AfterMigrate.affected_tables.keys.each_with_object({}) do |schema, hash|
57
69
  hash[schema] = all_tables(schema:)
58
70
  end
59
- when 'only_affected_tables'
60
- AfterMigrate.affected_tables
61
71
  else
62
- {}
72
+ # 'only_affected_tables' or 'none' — vacuum still needs the affected list
73
+ AfterMigrate.affected_tables
63
74
  end
64
75
  end
65
76
 
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'json'
5
+
6
+ module AfterMigrate
7
+ module Stores
8
+ class Memory
9
+ attr_reader :affected_tables
10
+
11
+ def initialize
12
+ @affected_tables = Concurrent::Map.new
13
+ end
14
+
15
+ def merge_tables(schema, table_names)
16
+ return if table_names.blank?
17
+
18
+ set = affected_tables.compute_if_absent(schema) { Concurrent::Set.new }
19
+ set.merge(table_names)
20
+ end
21
+
22
+ def reset!
23
+ @affected_tables = Concurrent::Map.new
24
+ end
25
+ end
26
+
27
+ class FileStore < Memory
28
+ attr_reader :path, :run_id
29
+
30
+ def initialize(path:, run_id: nil)
31
+ super()
32
+ @path = path.to_s
33
+ @run_id = run_id.to_s.presence
34
+ end
35
+
36
+ def affected_tables
37
+ load_into_memory
38
+ super
39
+ end
40
+
41
+ def merge_tables(schema, table_names)
42
+ return if table_names.blank?
43
+
44
+ with_lock do
45
+ merge_into_memory(read_schemas)
46
+ merge_into_memory(schema => table_names)
47
+ write_schemas(memory_to_hash)
48
+ end
49
+ end
50
+
51
+ def reset!
52
+ super
53
+ with_lock { ::FileUtils.rm_f(path) }
54
+ end
55
+
56
+ private
57
+
58
+ def load_into_memory
59
+ with_lock { merge_into_memory(read_schemas) }
60
+ end
61
+
62
+ def merge_into_memory(schemas)
63
+ schemas.each do |schema, table_names|
64
+ set = @affected_tables.compute_if_absent(schema) { Concurrent::Set.new }
65
+ set.merge(table_names)
66
+ end
67
+ end
68
+
69
+ def read_schemas
70
+ return {} unless ::File.exist?(path)
71
+
72
+ payload = JSON.parse(::File.read(path))
73
+ return {} if run_id && payload['run_id'].to_s != run_id
74
+
75
+ payload.fetch('schemas', {})
76
+ rescue JSON::ParserError
77
+ {}
78
+ end
79
+
80
+ def write_schemas(schemas)
81
+ ::FileUtils.mkdir_p(::File.dirname(path))
82
+ temp_path = "#{path}.#{$PROCESS_ID}.tmp"
83
+ ::File.write(temp_path, JSON.pretty_generate({ run_id:, schemas: }))
84
+ ::File.rename(temp_path, path)
85
+ ensure
86
+ ::FileUtils.rm_f(temp_path) if temp_path && ::File.exist?(temp_path)
87
+ end
88
+
89
+ def memory_to_hash
90
+ @affected_tables.keys.sort.each_with_object({}) do |schema, hash|
91
+ hash[schema] = @affected_tables[schema].to_a.sort
92
+ end.sort.to_h
93
+ end
94
+
95
+ def with_lock
96
+ ::FileUtils.mkdir_p(::File.dirname(path))
97
+ ::File.open("#{path}.lock", ::File::RDWR | ::File::CREAT, 0o644) do |file|
98
+ file.flock(::File::LOCK_EX)
99
+ yield
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AfterMigrate
4
- VERSION = '0.2.0'
4
+ VERSION = '0.2.2'
5
5
  end
data/lib/after_migrate.rb CHANGED
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'after_migrate/version'
4
+ require 'after_migrate/store'
4
5
  require 'after_migrate/collector'
5
6
  require 'after_migrate/executor'
6
7
  require 'after_migrate/railtie'
7
8
 
8
9
  module AfterMigrate
9
10
  class Configuration
10
- attr_accessor :enabled, :verbose, :vacuum, :analyze, :rake_tasks_enhanced, :defer
11
+ attr_accessor :enabled, :verbose, :vacuum, :analyze, :rake_tasks_enhanced, :defer, :store, :store_path, :run_id
11
12
 
12
13
  def initialize
13
14
  @enabled = true
@@ -16,6 +17,9 @@ module AfterMigrate
16
17
  @analyze = 'only_affected_tables'
17
18
  @rake_tasks_enhanced = true
18
19
  @defer = true
20
+ @store = :memory
21
+ @store_path = 'tmp/after_migrate/affected_tables.json'
22
+ @run_id = nil
19
23
  end
20
24
  end
21
25
 
@@ -34,14 +38,11 @@ module AfterMigrate
34
38
 
35
39
  # Persistent cross-migration store: schema_name => Concurrent::Set<table_name>
36
40
  def affected_tables
37
- @affected_tables ||= Concurrent::Map.new
41
+ store.affected_tables
38
42
  end
39
43
 
40
44
  def merge_tables(schema, table_names)
41
- return if table_names.blank?
42
-
43
- set = affected_tables.compute_if_absent(schema) { Concurrent::Set.new }
44
- set.merge(table_names)
45
+ store.merge_tables(schema, table_names)
45
46
  end
46
47
 
47
48
  # Trigger database maintenance on all collected tables, then clear the store.
@@ -53,7 +54,25 @@ module AfterMigrate
53
54
  end
54
55
 
55
56
  def reset!
56
- @affected_tables = nil
57
+ store.reset!
58
+ end
59
+
60
+ def store
61
+ key = [configuration.store.to_s, configuration.store_path.to_s, configuration.run_id.to_s]
62
+ @store = nil if @store_key != key
63
+ @store_key = key
64
+ @store ||= build_store
65
+ end
66
+
67
+ private
68
+
69
+ def build_store
70
+ case configuration.store.to_s
71
+ when 'file'
72
+ Stores::FileStore.new(path: configuration.store_path, run_id: configuration.run_id)
73
+ else
74
+ Stores::Memory.new
75
+ end
57
76
  end
58
77
  end
59
78
  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.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikolay Moskvin
@@ -53,11 +53,13 @@ files:
53
53
  - ".ruby-version"
54
54
  - ".travis.yml"
55
55
  - CHANGELOG.md
56
+ - CLAUDE.md
56
57
  - Gemfile
57
58
  - README.md
58
59
  - Rakefile
59
60
  - after_migrate.gemspec
60
61
  - bin/console
62
+ - bin/release
61
63
  - bin/setup
62
64
  - lib/after_migrate.rb
63
65
  - lib/after_migrate/adapters/mysql.rb
@@ -67,6 +69,7 @@ files:
67
69
  - lib/after_migrate/collector.rb
68
70
  - lib/after_migrate/executor.rb
69
71
  - lib/after_migrate/railtie.rb
72
+ - lib/after_migrate/store.rb
70
73
  - lib/after_migrate/version.rb
71
74
  - logo.png
72
75
  homepage: https://github.com/moskvin/after_migrate