schema_reaper 1.0.4 → 1.0.6

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: ac78c6af3c6c9b2551d136e276c4f258e312e6fef60945ee1507b916a11349f4
4
- data.tar.gz: 63e813aba8d386a01a39c72aa46c6464c4b5af37268718ca14ed0da0ae6fbd30
3
+ metadata.gz: 1c56279086faa2b6772a769f586f78a0f3e332973b25ad2f347c7e2533740358
4
+ data.tar.gz: 95e06e2cbb13f0887ff773e1c0fcde931e9f97bc3c3de6a4739eecf7ff7d8990
5
5
  SHA512:
6
- metadata.gz: c72f7ba4b88d4848cc993745ee7e7abec08a5333ff1a3c58504789e981821c48143d905235addc74655d7e57278e2d412b17914a6e19c626770edf2e7250a26a
7
- data.tar.gz: 1e519017f31e2bdf150beb98889b980fe6d7d3de3e43a06800635955a8d2adfb1c469928eb1d9186aa720e3c8cfda12543487b93da5c772bd1370751d6ee4842
6
+ metadata.gz: c235d143cc6d32736a64f9e425c8fe1ffc3f0585f55716c1284bbb6896fbcf54fbc30361fbee92c3f8e280aca9a909b6c020d3d563c0df9b8ab67653e77d5dde
7
+ data.tar.gz: de7f08b01afeebd5c0638ccc15f2be1a077e151b9cd69525788da091af411087716a20c8fe36dde49bfb5432bb77b5cf7b5c90827fe38ababc6e882563d91675
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.6] - 2026-09-04
4
+
5
+ ### Changed
6
+ - Added `mitkush` (mitanshukushwah@gmail.com) as a gemspec author.
7
+
8
+ ## [1.0.5] - 2026-09-04
9
+
10
+ Fixes found by running the gem against a live PostgreSQL database.
11
+
12
+ ### Fixed
13
+ - **Duplicate findings for one object.** When several analyzers flagged the
14
+ same physical column (e.g. `dead_column` + `always_null_column`) or index
15
+ (`unused_index` + `duplicate_index`), each was reported separately *and* its
16
+ reclaimable bytes were counted more than once, inflating the run total.
17
+ Findings are now collapsed to the highest-confidence one per target; the
18
+ others are noted as `also flagged by: ...` in its evidence.
19
+ - **`generate-migration` output.** The generated STEP 1 migration contained a
20
+ fragile multi-line string (heredoc line-continuation) that rendered with a
21
+ stray gap. It is now a single clean string. Both files are verified valid
22
+ Ruby.
23
+ - **Migration schema version.** Generated migrations now inherit the host
24
+ app's Rails minor version (`ActiveRecord::Migration[X.Y]`) when ActiveRecord
25
+ is loaded, instead of a hard-coded `7.1`.
26
+
27
+ ### Added
28
+ - Specs covering target collapsing and migration generation, exercised end to
29
+ end through `Runner` with an injected schema.
30
+
3
31
  ## [1.0.4] - 2026-09-04
4
32
 
5
33
  ### Changed
@@ -19,6 +19,19 @@ module SchemaReaper
19
19
  [type, table, column, index].compact.join("/")
20
20
  end
21
21
 
22
+ # The physical object this finding is about, ignoring which analyzer raised
23
+ # it. Two findings with the same target_key are the same underlying problem
24
+ # (e.g. dead_column + always_null_column on one column) -- keep the strongest.
25
+ def target_key
26
+ if index
27
+ ["index", table, index]
28
+ elsif column
29
+ ["column", table, column]
30
+ else
31
+ ["table", table]
32
+ end
33
+ end
34
+
22
35
  def reclaimable_bytes
23
36
  self[:reclaimable_bytes] || 0
24
37
  end
@@ -19,35 +19,38 @@ module SchemaReaper
19
19
  private
20
20
 
21
21
  def stage_one
22
+ note = "Add `self.ignored_columns += %w[#{@column}]` to the #{model} model, " \
23
+ "then deploy STEP 2 after a soak period."
22
24
  write "ignore_#{@table}_#{@column}", <<~RUBY
23
25
  # frozen_string_literal: true
24
26
 
25
27
  # STEP 1 of 2. Deploy this alone and let it soak. It only tells
26
28
  # ActiveRecord to stop selecting the column; nothing is dropped.
27
- class Ignore#{camel}Column < ActiveRecord::Migration[7.1]
29
+ class Ignore#{camel}Column < ActiveRecord::Migration[#{migration_version}]
28
30
  def up
29
- say "Add `self.ignored_columns += %w[#{@column}]` to the #{model} model, " \
30
- "then deploy STEP 2 after a soak period."
31
+ say #{note.inspect}
31
32
  end
32
33
 
33
- def down; end
34
+ def down
35
+ end
34
36
  end
35
37
  RUBY
36
38
  end
37
39
 
38
40
  def stage_two
41
+ down_msg = "recreate :#{@column} on :#{@table} manually if you need it back"
39
42
  write "drop_#{@table}_#{@column}", <<~RUBY
40
43
  # frozen_string_literal: true
41
44
 
42
- # STEP 2 of 2. Run only after STEP 1 has been deployed and verified.
43
- class Drop#{camel}Column < ActiveRecord::Migration[7.1]
45
+ # STEP 2 of 2. Run only after STEP 1 has been deployed and verified in
46
+ # production (nothing reads the column, no errors).
47
+ class Drop#{camel}Column < ActiveRecord::Migration[#{migration_version}]
44
48
  def up
45
49
  remove_column :#{@table}, :#{@column}
46
50
  end
47
51
 
48
52
  def down
49
- raise ActiveRecord::IrreversibleMigration,
50
- "recreate :#{@column} on :#{@table} manually if you need it back"
53
+ raise ActiveRecord::IrreversibleMigration, #{down_msg.inspect}
51
54
  end
52
55
  end
53
56
  RUBY
@@ -61,6 +64,14 @@ module SchemaReaper
61
64
  path
62
65
  end
63
66
 
67
+ # Match the host app's Rails minor version when ActiveRecord is loaded,
68
+ # otherwise a sane recent default the developer can edit.
69
+ def migration_version
70
+ return "7.1" unless defined?(ActiveRecord::VERSION::STRING)
71
+
72
+ ActiveRecord::VERSION::STRING.split(".").first(2).join(".")
73
+ end
74
+
64
75
  def camel
65
76
  "#{@table}_#{@column}".split("_").map(&:capitalize).join
66
77
  end
@@ -5,6 +5,8 @@ require "set"
5
5
  module SchemaReaper
6
6
  # Ties introspection + scanning + runtime data + analyzers together.
7
7
  class Runner
8
+ SEVERITY_RANK = { high: 0, medium: 1, low: 2 }.freeze
9
+
8
10
  def initialize(config: Config.load, root: Dir.pwd, introspector: nil, runtime: nil)
9
11
  @config = config
10
12
  @root = root
@@ -29,13 +31,30 @@ module SchemaReaper
29
31
 
30
32
  private
31
33
 
34
+ def dedupe(findings)
35
+ collapse_targets(drop_findings_on_dead_tables(findings))
36
+ end
37
+
32
38
  # When a whole table is dead, its per-column and per-index findings are
33
39
  # noise -- keep only the table-level finding for that table.
34
- def dedupe(findings)
40
+ def drop_findings_on_dead_tables(findings)
35
41
  dead_tables = findings.select { |f| f.type == :dead_table }.to_set(&:table)
36
42
  findings.reject { |f| f.type != :dead_table && dead_tables.include?(f.table) }
37
43
  end
38
44
 
45
+ # Several analyzers can flag the same physical column or index (e.g.
46
+ # dead_column + always_null_column). Keep the highest-confidence finding per
47
+ # target so it is reported -- and its bytes counted -- exactly once, but note
48
+ # the other analyzers that agreed.
49
+ def collapse_targets(findings)
50
+ findings.group_by(&:target_key).map do |_key, group|
51
+ winner = group.max_by { |f| [f.confidence, -SEVERITY_RANK.fetch(f.severity, 9)] }
52
+ also = group.map(&:type).uniq - [winner.type]
53
+ winner.evidence += ["also flagged by: #{also.join(", ")}"] if also.any?
54
+ winner
55
+ end
56
+ end
57
+
39
58
  def schema
40
59
  return @introspector.call if @introspector
41
60
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SchemaReaper
4
- VERSION = "1.0.4"
4
+ VERSION = "1.0.6"
5
5
  end
@@ -5,8 +5,8 @@ require_relative "lib/schema_reaper/version"
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "schema_reaper"
7
7
  spec.version = SchemaReaper::VERSION
8
- spec.authors = ["aksshatt"]
9
- spec.email = ["akshatpegwar5@gmail.com"]
8
+ spec.authors = %w[aksshatt mitkush]
9
+ spec.email = ["akshatpegwar5@gmail.com", "mitanshukushwah@gmail.com"]
10
10
 
11
11
  spec.summary = "Find and safely remove schema dead-weight in Rails + PostgreSQL apps."
12
12
  spec.description =
metadata CHANGED
@@ -1,10 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: schema_reaper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - aksshatt
8
+ - mitkush
8
9
  autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
@@ -88,6 +89,7 @@ description: 'schema_reaper finds schema dead-weight in Rails/ActiveRecord + Pos
88
89
  only for now; Ruby >= 2.7. See the README for full usage.'
89
90
  email:
90
91
  - akshatpegwar5@gmail.com
92
+ - mitanshukushwah@gmail.com
91
93
  executables:
92
94
  - schema_reaper
93
95
  extensions: []