after_migrate 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2602aa9ee3d2ffa2c1ea85ca8cbfa1b101679aa42ed05e0a4cfa4c742f258f59
4
- data.tar.gz: d581242202742eb6a62be1cd1799a49c196ca82c99e8b36428ee6390fd12c415
3
+ metadata.gz: a13db746f7bfcbcb7367f028493efd2b464e564b32f09be5debb62da1326f8bf
4
+ data.tar.gz: b1f0db683bb9b12ed407f14f3f1b1c0baba0a80cfd445c6182b9bd5ad7378821
5
5
  SHA512:
6
- metadata.gz: d81dce8555f469b190b3cfd37f7eb2b919aab714e6fcfb1842271c323495b213be2c2e779c8400ad67d28832c11dbc7ad7147e90f93b6950069b6b6b9ea8d558
7
- data.tar.gz: 77f23e51b264cf80b5cbf028a0e43106d13498f9f7d109b1eafe26c6380269c08bb3574946ec534d08b8ac175a09c008823e64d0c862c4e4441e66c1df145399
6
+ metadata.gz: '085b3be36d4031981294d5a5ddc929aae3d781c9d1c3d8446f4508f7c7c526064c8b6529b68237e3e3517885f22251b3db071839dae1bcdf8df654d1e79b2e70'
7
+ data.tar.gz: 212614cb1145bbe3f22d9b005e6524af133b5d50f659ca8da2efff0d6416efd7ff90fccf39575719d4237df08029a5dcab6b8ef8dfd5072027bce902bc2b3bd8
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.4.7
1
+ 3.4.9
data/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.2.0] - 2026-04-28
6
+
7
+ ### Added
8
+ - `defer` configuration option (default: `true`) — when enabled, rake task enhancements only collect touched tables; they do not run maintenance automatically
9
+ - `AfterMigrate.run!` public API to explicitly trigger maintenance on all collected tables across all schemas, then clear the store — intended to be called once after all tenant migrations complete in multi-tenant setups
10
+ - `after_migrate:run` rake task as a convenience wrapper around `AfterMigrate.run!`
11
+
12
+ ### Changed
13
+ - **Breaking**: replaced `AfterMigrate::Current` (`ActiveSupport::CurrentAttributes`) with a module-level persistent store (`Concurrent::Map`). The store accumulates touched tables across multiple rake task invocations and only resets when `AfterMigrate.run!` is called.
14
+ - Fixed `all_tables` analyze mode - previously iterated `.each_value` (yielding `Concurrent::Set` objects) instead of schema names, producing incorrect SQL queries.
15
+
16
+ ### Removed
17
+ - `AfterMigrate::Current` - no longer needed. If you referenced this class directly, use `AfterMigrate.affected_tables` instead.
18
+ - `Executor.call(reset:)` parameter - the store is always cleared after `run!`; pass `schema:` to scope execution to a single schema.
19
+
5
20
  ## [0.1.0] - 2025-04-05
6
21
 
7
22
  ### Added
@@ -8,13 +8,13 @@ module AfterMigrate
8
8
  /x
9
9
 
10
10
  PATTERNS = {
11
- update: /update\s+(?:only\s+)?(#{IDENT})(?!\s*\()/ix,
12
- insert: /insert\s+into\s+(#{IDENT})(?!\s*\()/ix,
13
- delete: /delete\s+from\s+(#{IDENT})(?!\s*\()/ix,
14
- drop_table: /drop\s+table\s+(?:if\s+exists\s+)?(#{IDENT})(?!\s*\()/ix,
15
- alter_table: /alter\s+table\s+(#{IDENT})(?!\s*\()/ix,
16
- create_table: /create\s+table\s+(?:if\s+not\s+exists\s+)?(#{IDENT})(?!\s*\()/ix,
17
- from_join: /(?:from|join)\s+(#{IDENT})(?!\s*\()/ix
11
+ update: /update\s+(?:only\s+)?(#{IDENT})(?!\()/ix,
12
+ insert: /insert\s+into\s+(#{IDENT})(?!\()/ix,
13
+ delete: /delete\s+from\s+(#{IDENT})(?!\()/ix,
14
+ drop_table: /drop\s+table\s+(?:if\s+exists\s+)?(#{IDENT})(?!\()/ix,
15
+ alter_table: /alter\s+table\s+(#{IDENT})(?!\()/ix,
16
+ create_table: /create\s+table\s+(?:if\s+not\s+exists\s+)?(#{IDENT})(?!\()/ix,
17
+ from_join: /(?:from|join)\s+(#{IDENT})(?!\()/ix
18
18
  }.freeze
19
19
 
20
20
  def parse_tables(sql)
@@ -13,24 +13,15 @@ module AfterMigrate
13
13
  return unless sql.match?(/\A\s*(CREATE|ALTER|DROP|INSERT|UPDATE|DELETE|RENAME\s+TABLE|TRUNCATE)/i)
14
14
 
15
15
  table_names = parse_tables(sql)
16
- schema ||= fetch_schema
17
- # AfterMigrate.log("[#{schema}] Detected change from '#{sql}' to tables: #{table_names}") if table_names.present?
18
- collect_tables(schema:, table_names:)
16
+ schema = fetch_schema
17
+ AfterMigrate.merge_tables(schema, table_names)
19
18
  end
20
19
 
21
20
  private
22
21
 
23
- def collect_tables(schema:, table_names:)
24
- return if table_names.blank?
25
-
26
- AfterMigrate::Current.affected_tables ||= Hash.new { |h, k| h[k] = Concurrent::Set.new }
27
- AfterMigrate::Current.affected_tables[schema].merge(table_names)
28
- end
29
-
30
22
  def fetch_schema
31
23
  connection = ActiveRecord::Base.connection
32
- adapter = connection.adapter_name
33
- case adapter
24
+ case connection.adapter_name
34
25
  when 'PostgreSQL'
35
26
  quoted = connection.schema_search_path.split(',').first
36
27
  quoted&.delete('"')
@@ -39,8 +30,7 @@ module AfterMigrate
39
30
 
40
31
  def parse_tables(sql)
41
32
  connection = ActiveRecord::Base.connection
42
- adapter = connection.adapter_name
43
- case adapter
33
+ case connection.adapter_name
44
34
  when 'PostgreSQL'
45
35
  AfterMigrate::Postgresql.parse_tables(sql)
46
36
  when 'SQLite'
@@ -48,7 +38,7 @@ module AfterMigrate
48
38
  when 'Mysql2', 'Trilogy'
49
39
  AfterMigrate::Mysql.parse_tables(sql)
50
40
  else
51
- AfterMigrate.log("No maintenance implemented for #{adapter}")
41
+ AfterMigrate.log("No maintenance implemented for #{connection.adapter_name}")
52
42
  end
53
43
  end
54
44
  end
@@ -11,16 +11,17 @@ module AfterMigrate
11
11
 
12
12
  module_function
13
13
 
14
- def call(reset: true, schema: nil)
15
- # AfterMigrate.log("Executing schema: #{schema} -> #{target_tables}...")
16
- return if target_tables.blank?
17
- return run_optimize(schema:, tables: target_tables[schema]) if schema.present?
14
+ def call(schema: nil)
15
+ tables = target_tables
16
+ return if tables.blank?
18
17
 
19
- target_tables.each do |s, tables|
20
- run_optimize(schema: s, tables:)
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) }
21
22
  end
22
23
  ensure
23
- AfterMigrate::Current.reset if reset
24
+ AfterMigrate.reset!
24
25
  end
25
26
 
26
27
  public :call
@@ -31,14 +32,13 @@ module AfterMigrate
31
32
  table_names = tables.to_a.sort
32
33
  return if table_names.empty?
33
34
 
34
- AfterMigrate.log("Migration touched #{table_names.size} table(s): #{table_names.join(', ')}")
35
+ AfterMigrate.log("Migration touched #{table_names.size} table(s) in schema #{schema.inspect}: #{table_names.join(', ')}")
35
36
  optimize_tables(schema:, table_names:)
36
37
  end
37
38
 
38
39
  def optimize_tables(schema:, table_names:)
39
40
  connection = ActiveRecord::Base.connection
40
- adapter = connection.adapter_name
41
- case adapter
41
+ case connection.adapter_name
42
42
  when 'PostgreSQL'
43
43
  AfterMigrate::Postgresql.optimize_tables(schema:, table_names:, connection:)
44
44
  when 'SQLite'
@@ -46,27 +46,26 @@ module AfterMigrate
46
46
  when 'Mysql2', 'Trilogy'
47
47
  AfterMigrate::Mysql.optimize_tables(schema:, table_names:, connection:)
48
48
  else
49
- AfterMigrate.log("No maintenance implemented for #{adapter}")
49
+ AfterMigrate.log("No maintenance implemented for #{connection.adapter_name}")
50
50
  end
51
51
  end
52
52
 
53
53
  def target_tables
54
54
  case AfterMigrate.configuration.analyze
55
55
  when 'all_tables'
56
- AfterMigrate::Current.affected_tables.each_value do |schema|
57
- all_tables(schema:)
56
+ AfterMigrate.affected_tables.keys.each_with_object({}) do |schema, hash|
57
+ hash[schema] = all_tables(schema:)
58
58
  end
59
59
  when 'only_affected_tables'
60
- AfterMigrate::Current.affected_tables
60
+ AfterMigrate.affected_tables
61
61
  else
62
- []
62
+ {}
63
63
  end
64
64
  end
65
65
 
66
66
  def all_tables(schema:)
67
67
  connection = ActiveRecord::Base.connection
68
- adapter = connection.adapter_name
69
- case adapter
68
+ case connection.adapter_name
70
69
  when 'PostgreSQL'
71
70
  AfterMigrate::Postgresql.all_tables(schema:)
72
71
  when 'SQLite'
@@ -12,6 +12,8 @@ module AfterMigrate
12
12
  AfterMigrate::Collector.call(*args)
13
13
  end
14
14
 
15
+ # Unsubscribe when the app starts serving requests so normal web traffic
16
+ # is never collected.
15
17
  app.executor.to_run { ActiveSupport::Notifications.unsubscribe(subscription) }
16
18
  end
17
19
 
@@ -24,8 +26,16 @@ module AfterMigrate
24
26
  Rake::Task[task_name].enhance do
25
27
  next unless AfterMigrate.configuration.enabled
26
28
  next unless AfterMigrate.configuration.rake_tasks_enhanced
29
+ next if AfterMigrate.configuration.defer
27
30
 
28
- AfterMigrate::Executor.call(reset: true)
31
+ AfterMigrate.run!
32
+ end
33
+ end
34
+
35
+ namespace :after_migrate do
36
+ desc 'Run database maintenance (ANALYZE/VACUUM) on all tables collected across migrations'
37
+ task run: :environment do
38
+ AfterMigrate.run!
29
39
  end
30
40
  end
31
41
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AfterMigrate
4
- VERSION = '0.1.1'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/after_migrate.rb CHANGED
@@ -1,14 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'after_migrate/version'
4
- require 'after_migrate/current'
5
4
  require 'after_migrate/collector'
6
5
  require 'after_migrate/executor'
7
6
  require 'after_migrate/railtie'
8
7
 
9
8
  module AfterMigrate
10
9
  class Configuration
11
- attr_accessor :enabled, :verbose, :vacuum, :analyze, :rake_tasks_enhanced
10
+ attr_accessor :enabled, :verbose, :vacuum, :analyze, :rake_tasks_enhanced, :defer
12
11
 
13
12
  def initialize
14
13
  @enabled = true
@@ -16,6 +15,7 @@ module AfterMigrate
16
15
  @vacuum = true
17
16
  @analyze = 'only_affected_tables'
18
17
  @rake_tasks_enhanced = true
18
+ @defer = true
19
19
  end
20
20
  end
21
21
 
@@ -31,5 +31,29 @@ module AfterMigrate
31
31
  def log(msg)
32
32
  warn "[after_migrate] #{msg}" if AfterMigrate.configuration.verbose
33
33
  end
34
+
35
+ # Persistent cross-migration store: schema_name => Concurrent::Set<table_name>
36
+ def affected_tables
37
+ @affected_tables ||= Concurrent::Map.new
38
+ end
39
+
40
+ 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
+ end
46
+
47
+ # Trigger database maintenance on all collected tables, then clear the store.
48
+ # In multi-tenant setups call this once after all tenant migrations complete.
49
+ def run!(schema: nil)
50
+ return unless configuration.enabled
51
+
52
+ Executor.call(schema:)
53
+ end
54
+
55
+ def reset!
56
+ @affected_tables = nil
57
+ end
34
58
  end
35
59
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: after_migrate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikolay Moskvin
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-12-11 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: pg_query
@@ -66,7 +65,6 @@ files:
66
65
  - lib/after_migrate/adapters/sql.rb
67
66
  - lib/after_migrate/adapters/sqlite.rb
68
67
  - lib/after_migrate/collector.rb
69
- - lib/after_migrate/current.rb
70
68
  - lib/after_migrate/executor.rb
71
69
  - lib/after_migrate/railtie.rb
72
70
  - lib/after_migrate/version.rb
@@ -79,7 +77,6 @@ metadata:
79
77
  source_code_uri: https://github.com/moskvin/after_migrate
80
78
  changelog_uri: https://github.com/moskvin/after_migrate/blob/master/CHANGELOG.md
81
79
  rubygems_mfa_required: 'true'
82
- post_install_message:
83
80
  rdoc_options: []
84
81
  require_paths:
85
82
  - lib
@@ -94,8 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
91
  - !ruby/object:Gem::Version
95
92
  version: '0'
96
93
  requirements: []
97
- rubygems_version: 3.4.19
98
- signing_key:
94
+ rubygems_version: 3.6.9
99
95
  specification_version: 4
100
96
  summary: Automatically ANALYZE and VACUUM tables touched during Rails migrations.
101
97
  test_files: []
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/current_attributes'
4
-
5
- module AfterMigrate
6
- class Current < ActiveSupport::CurrentAttributes
7
- attribute :affected_tables
8
-
9
- resets do
10
- self.affected_tables = Hash.new { |h, k| h[k] = Concurrent::Set.new }
11
- end
12
- end
13
- end