schema_reaper 1.0.14 → 1.0.16
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/.rubocop.yml +1 -1
- data/CHANGELOG.md +84 -0
- data/lib/schema_reaper/analyzers/missing_fk_index.rb +8 -4
- data/lib/schema_reaper/finding.rb +5 -1
- data/lib/schema_reaper/introspect/postgres.rb +7 -6
- data/lib/schema_reaper/migration_generator.rb +14 -1
- data/lib/schema_reaper/railtie.rb +11 -4
- data/lib/schema_reaper/reporters/trend.rb +1 -1
- data/lib/schema_reaper/static/scanner.rb +37 -25
- data/lib/schema_reaper/version.rb +1 -1
- data/schema_reaper.gemspec +11 -9
- metadata +16 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df6a3127bc5e379e183a6f525870c5726bbf016223b6c8531b1be8cb97da8ca5
|
|
4
|
+
data.tar.gz: 2810a69e8b382c10ecf52bc35717efd53fc9b826b8aae580b15773f4ffb8e246
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4dc2789fcdd897e213a4b050ef98a05d94f6c213e145bdc6ac6e8a5168d355b7fc86a2b18aa0799b76ff1183255e64b5d6bbc2789c9d5a063dac91c9fcabdf9
|
|
7
|
+
data.tar.gz: f8026dcfb27f4a9a828f519bef9a78f8e626522f8cadb5082ee39b393652d5ff0224a669fc8a4b7d109f20f5b6bc910bcea336971de0603a85af4d1c4ad2280a
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,89 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.16] - 2026-09-22
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **A bare index on a polymorphic `*_id` column alone silently suppressed the
|
|
7
|
+
`missing_fk_index` finding it should have raised.** The check tested the
|
|
8
|
+
generic "is this column indexed at all" before checking whether the column
|
|
9
|
+
was part of a polymorphic pair, so an index on `commentable_id` by itself
|
|
10
|
+
— never sufficient, since Rails always queries the pair together — read as
|
|
11
|
+
"covered" and the analyzer's own doc comment went unenforced. Now gates on
|
|
12
|
+
the composite `(type, id)` check whenever a `*_type` column is present.
|
|
13
|
+
(#17, mitkush)
|
|
14
|
+
- **`Postgres#indexes_for` was the one introspection query missing the
|
|
15
|
+
`public`-schema filter** every sibling query (`columns_for`,
|
|
16
|
+
`foreign_keys_for`, `table_names`) already has. In a database with more
|
|
17
|
+
than one schema containing a same-named table, it could pull in and union
|
|
18
|
+
indexes from the wrong table. Verified live against a two-schema database
|
|
19
|
+
with a colliding table name and a planted bogus index. (#17, mitkush)
|
|
20
|
+
- **The `SCHEMA_REAPER_TRACK=1` runtime-tracker initializer could crash a
|
|
21
|
+
host app's entire boot**, not just disable the optional feature, on a
|
|
22
|
+
malformed `.schema_reaper.yml` or an unwritable log directory — nothing
|
|
23
|
+
in the initializer was rescued. Now rescues and logs a warning instead.
|
|
24
|
+
(#18, mitkush)
|
|
25
|
+
- **`schema_reaper scan --format json` could not distinguish a genuinely
|
|
26
|
+
zero-byte reclaim estimate from an unmeasured one.** `Finding#to_h` read
|
|
27
|
+
the zero-defaulted accessor instead of the raw value, silently flattening
|
|
28
|
+
"unknown" into `0` for every CI/tooling consumer of the JSON output, even
|
|
29
|
+
though every other reporter already preserves that distinction. The
|
|
30
|
+
payload now carries the raw value plus an explicit `reclaim_known` flag.
|
|
31
|
+
Verified live: an unmeasured finding now serializes `reclaimable_bytes`
|
|
32
|
+
as `null` with `reclaim_known: false`. (#18, mitkush)
|
|
33
|
+
- **A query-time Postgres error (anything past the initial connection)
|
|
34
|
+
propagated as a raw `PG::Error`** instead of the wrapped `SchemaReaper::Error`
|
|
35
|
+
every other failure path produces, leaking a Ruby backtrace instead of a
|
|
36
|
+
clean CLI error message. `primary_key_for`'s existing fallback-to-nil
|
|
37
|
+
rescue is updated to match. (#18, mitkush)
|
|
38
|
+
- Cosmetic: `MigrationGenerator#model` mangled irregular plural table names
|
|
39
|
+
(`addresses` → "Addresse") in the generated migration's comment text;
|
|
40
|
+
`Reporters::Trend#bytes` printed `-0.0 B` instead of `+0.0 B` for a zero
|
|
41
|
+
delta. (#17, mitkush)
|
|
42
|
+
|
|
43
|
+
## [1.0.15] - 2026-09-18
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- **`dead_column` still missed the two gems its own macro-detection feature
|
|
47
|
+
named as motivation.** 1.0.14 added recognition for `has_secure_password`,
|
|
48
|
+
`attr_encrypted`, and Lockbox macro columns, but only the first actually
|
|
49
|
+
worked. `attr_encrypted`'s real default column naming is a *prefix*
|
|
50
|
+
(`encrypted_<attr>`), confirmed against the gem's own
|
|
51
|
+
`attr_encrypted_default_options`; the feature generated a suffix guess
|
|
52
|
+
that never matched it. Lockbox's real macro is `has_encrypted`; the
|
|
53
|
+
feature checked for `lockbox_encrypts`, which is not a method that
|
|
54
|
+
exists in the gem, so it never even fired for genuine Lockbox code.
|
|
55
|
+
Rewritten as a macro-name -> column-template table so each macro's real
|
|
56
|
+
naming shape is expressed explicitly, and dropped an unnecessary
|
|
57
|
+
suffix guess for Rails' native `encrypts` (which stores ciphertext in
|
|
58
|
+
the original column and creates no extra one). Eight new specs cover
|
|
59
|
+
all four macros -- this had no test coverage before. (#15, mitkush)
|
|
60
|
+
|
|
61
|
+
## [1.0.14] - 2026-09-18
|
|
62
|
+
|
|
63
|
+
Published directly without a version-bump commit or tag; this entry
|
|
64
|
+
reconciles the record after the fact. Confirmed by diffing the
|
|
65
|
+
published gem against `main` at the time -- the only difference was the
|
|
66
|
+
version string.
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
- `duplicate_index` respecting uniqueness and partial `WHERE` clauses --
|
|
70
|
+
see 1.0.13's entry for the underlying issue; this release adds the fix.
|
|
71
|
+
(#12, mitkush)
|
|
72
|
+
- `dead_column` missing `has_secure_password`, `encrypts`, and
|
|
73
|
+
`attr_encrypted` macro columns -- a virtual attribute like
|
|
74
|
+
`has_secure_password` never has its generated column name
|
|
75
|
+
(`password_digest`) written anywhere in application code, so it looked
|
|
76
|
+
unreferenced. (aksshatt)
|
|
77
|
+
|
|
78
|
+
### Known issue
|
|
79
|
+
- The `attr_encrypted`/Lockbox part of the macro-column fix above does
|
|
80
|
+
not work as intended: it derives a suffix-based column name
|
|
81
|
+
(`<attr>_encrypted`) that does not match `attr_encrypted`'s actual
|
|
82
|
+
default naming (`encrypted_<attr>`, a prefix), and it references a
|
|
83
|
+
`lockbox_encrypts` macro name that does not exist in Lockbox -- the
|
|
84
|
+
gem's real method is `has_encrypted`. `has_secure_password` and Rails'
|
|
85
|
+
native `encrypts` are unaffected and work correctly. Tracked for 1.0.15.
|
|
86
|
+
|
|
3
87
|
## [1.0.13] - 2026-09-18
|
|
4
88
|
|
|
5
89
|
### Fixed
|
|
@@ -32,11 +32,15 @@ module SchemaReaper
|
|
|
32
32
|
|
|
33
33
|
def missing_in(table)
|
|
34
34
|
fk_columns(table).filter_map do |col|
|
|
35
|
-
next if indexed?(table, col)
|
|
36
|
-
next if empty_column?(table, col) # never advise indexing a column with no data
|
|
37
|
-
|
|
38
35
|
type_col = polymorphic_type_for(table, col)
|
|
39
|
-
|
|
36
|
+
|
|
37
|
+
# A bare index on the id alone doesn't cover a polymorphic pair --
|
|
38
|
+
# only the composite (type, id) index satisfies it. Check that
|
|
39
|
+
# instead of the generic `indexed?`, or a bare id-only index would
|
|
40
|
+
# wrongly suppress this finding.
|
|
41
|
+
covered = type_col ? polymorphic_indexed?(table, type_col, col) : indexed?(table, col)
|
|
42
|
+
next if covered
|
|
43
|
+
next if empty_column?(table, col) # never advise indexing a column with no data
|
|
40
44
|
|
|
41
45
|
finding_for(table, col, type_col, declared: table.foreign_keys.include?(col))
|
|
42
46
|
end
|
|
@@ -50,8 +50,12 @@ module SchemaReaper
|
|
|
50
50
|
parts.empty? ? nil : parts.join(" · ")
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# Keeps the raw (possibly nil) reclaimable_bytes from Struct#to_h rather
|
|
54
|
+
# than the zero-defaulted accessor above, so JSON/SARIF consumers can tell
|
|
55
|
+
# "genuinely zero" apart from "unknown" the same way every other reporter
|
|
56
|
+
# does -- reclaim_known? makes that distinction explicit in the payload.
|
|
53
57
|
def to_h
|
|
54
|
-
super.merge(id: id,
|
|
58
|
+
super.merge(id: id, reclaim_known: reclaim_known?)
|
|
55
59
|
end
|
|
56
60
|
end
|
|
57
61
|
end
|
|
@@ -8,13 +8,12 @@ module SchemaReaper
|
|
|
8
8
|
AVG_TYPE_BYTES = {
|
|
9
9
|
"boolean" => 1, "smallint" => 2, "integer" => 4, "bigint" => 8,
|
|
10
10
|
"real" => 4, "double precision" => 8, "numeric" => 8,
|
|
11
|
-
"date" => 4, "timestamp without time zone" => 8,
|
|
12
|
-
"timestamp with time zone" => 8, "uuid" => 16
|
|
11
|
+
"date" => 4, "timestamp without time zone" => 8, "timestamp with time zone" => 8, "uuid" => 16
|
|
13
12
|
}.freeze
|
|
14
13
|
|
|
15
14
|
NO_URL = "no database connection found. schema_reaper looks, in order, for: " \
|
|
16
|
-
"database_url: in .schema_reaper.yml; the DATABASE_URL env var; " \
|
|
17
|
-
"
|
|
15
|
+
"database_url: in .schema_reaper.yml; the DATABASE_URL env var; config/database.yml " \
|
|
16
|
+
"for RAILS_ENV (default: development, Postgres only)."
|
|
18
17
|
|
|
19
18
|
# A record separator that cannot appear inside a plain identifier and is
|
|
20
19
|
# exceedingly unlikely inside an expression, so splitting the aggregated
|
|
@@ -119,7 +118,7 @@ module SchemaReaper
|
|
|
119
118
|
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
120
119
|
JOIN LATERAL generate_series(1, ix.indnkeyatts) AS k(ord) ON TRUE
|
|
121
120
|
LEFT JOIN pg_stat_user_indexes s ON s.indexrelid = i.oid
|
|
122
|
-
WHERE t.relname = $1
|
|
121
|
+
WHERE t.relname = $1 AND t.relnamespace = 'public'::regnamespace
|
|
123
122
|
GROUP BY i.relname, ix.indisunique, ix.indisprimary, ix.indpred, s.idx_scan
|
|
124
123
|
SQL
|
|
125
124
|
Index.new(
|
|
@@ -142,7 +141,7 @@ module SchemaReaper
|
|
|
142
141
|
WHERE i.indrelid = $1::regclass AND i.indisprimary
|
|
143
142
|
ORDER BY k.ord
|
|
144
143
|
SQL
|
|
145
|
-
rescue PG::Error
|
|
144
|
+
rescue Error, PG::Error
|
|
146
145
|
nil
|
|
147
146
|
end
|
|
148
147
|
|
|
@@ -172,6 +171,8 @@ module SchemaReaper
|
|
|
172
171
|
|
|
173
172
|
def exec(sql, params = nil)
|
|
174
173
|
(params ? @conn.exec_params(sql, params) : @conn.exec(sql)).to_a
|
|
174
|
+
rescue PG::Error => e
|
|
175
|
+
raise Error, "query against the database failed: #{e.message.strip}"
|
|
175
176
|
end
|
|
176
177
|
end
|
|
177
178
|
end
|
|
@@ -5,6 +5,14 @@ require "fileutils"
|
|
|
5
5
|
module SchemaReaper
|
|
6
6
|
# Emits a two-step, reversible migration pair for a dead column.
|
|
7
7
|
class MigrationGenerator
|
|
8
|
+
# Enough of Rails' inflector for table names in this comment text -- a
|
|
9
|
+
# wrong guess doesn't break anything functional, just reads oddly.
|
|
10
|
+
SINGULAR_RULES = [
|
|
11
|
+
[/ies\z/, "y"], # activities -> activity
|
|
12
|
+
[/(ss|sh|ch|x|z)es\z/, '\1'], # addresses -> address, boxes -> box
|
|
13
|
+
[/s\z/, ""] # employees -> employee
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
8
16
|
def initialize(table:, column:, dir: "db/migrate")
|
|
9
17
|
@table = table
|
|
10
18
|
@column = column
|
|
@@ -77,7 +85,12 @@ module SchemaReaper
|
|
|
77
85
|
end
|
|
78
86
|
|
|
79
87
|
def model
|
|
80
|
-
|
|
88
|
+
singular.split("_").map(&:capitalize).join
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def singular
|
|
92
|
+
rule = SINGULAR_RULES.find { |pattern, _| @table.match?(pattern) }
|
|
93
|
+
rule ? @table.sub(rule[0], rule[1]) : @table
|
|
81
94
|
end
|
|
82
95
|
end
|
|
83
96
|
end
|
|
@@ -12,10 +12,17 @@ module SchemaReaper
|
|
|
12
12
|
initializer "schema_reaper.runtime_tracker" do
|
|
13
13
|
next unless ENV["SCHEMA_REAPER_TRACK"] == "1"
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
begin
|
|
16
|
+
config = SchemaReaper::Config.load
|
|
17
|
+
store = SchemaReaper::Runtime::Store.new(path: config.runtime_log)
|
|
18
|
+
rate = (ENV["SCHEMA_REAPER_SAMPLE"] || "0.05").to_f
|
|
19
|
+
SchemaReaper::Runtime::Tracker.install!(store: store, sample_rate: rate)
|
|
20
|
+
rescue StandardError => e
|
|
21
|
+
# Optional instrumentation must never take the host app down with it --
|
|
22
|
+
# a bad config file or an unwritable log directory disables tracking,
|
|
23
|
+
# it does not abort boot.
|
|
24
|
+
Rails.logger&.warn("[schema_reaper] runtime tracker disabled: #{e.class}: #{e.message}")
|
|
25
|
+
end
|
|
19
26
|
end
|
|
20
27
|
end
|
|
21
28
|
end
|
|
@@ -47,7 +47,7 @@ module SchemaReaper
|
|
|
47
47
|
|
|
48
48
|
def bytes
|
|
49
49
|
delta = @d[:bytes_change_total].to_i
|
|
50
|
-
sign = delta.
|
|
50
|
+
sign = delta.negative? ? "-" : "+"
|
|
51
51
|
@c.row("reclaimable", Bytes.human(@d.fetch(:latest_bytes, 0)),
|
|
52
52
|
"#{sign}#{Bytes.human(delta.abs)} since first run")
|
|
53
53
|
end
|
|
@@ -12,15 +12,37 @@ module SchemaReaper
|
|
|
12
12
|
RUBY_GLOB = "**/*.rb"
|
|
13
13
|
WORD_RE = /[a-z_][a-z0-9_]*/i.freeze
|
|
14
14
|
|
|
15
|
-
# Macros
|
|
16
|
-
#
|
|
17
|
-
# code
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
# Macros whose generated column name differs from the virtual attribute
|
|
16
|
+
# declared in the call, so the real column name never appears anywhere
|
|
17
|
+
# in application code and would otherwise look dead. %s is replaced with
|
|
18
|
+
# the declared attribute.
|
|
19
|
+
#
|
|
20
|
+
# Several shapes are listed for some macros on purpose: getting the real
|
|
21
|
+
# default right matters most, but an extra guess that never matches a
|
|
22
|
+
# real column is harmless -- over-collecting is this scanner's stated
|
|
23
|
+
# design, and both attr_encrypted and Lockbox let a call override their
|
|
24
|
+
# naming, which a static scan cannot always evaluate.
|
|
25
|
+
#
|
|
26
|
+
# attr_encrypted's own default is a PREFIX (encrypted_<attr>), not a
|
|
27
|
+
# suffix -- confirmed against the gem's own attr_encrypted_default_options
|
|
28
|
+
# (prefix: "encrypted_", suffix: ""). The suffix form is kept as a
|
|
29
|
+
# fallback for calls that override it.
|
|
30
|
+
#
|
|
31
|
+
# Lockbox's real macro is has_encrypted, not lockbox_encrypts.
|
|
32
|
+
#
|
|
33
|
+
# Rails' own `encrypts` has no entry: it stores ciphertext in the
|
|
34
|
+
# original column, so the bare attribute name is what needs to be
|
|
35
|
+
# "used", and that is already picked up as a symbol literal by
|
|
36
|
+
# tokens_for below -- do not add a suffix pattern for it.
|
|
37
|
+
MACRO_COLUMN_TEMPLATES = {
|
|
38
|
+
"has_secure_password" => ["%s_digest"],
|
|
39
|
+
"attr_encrypted" => ["encrypted_%s", "%s_encrypted", "%s_ciphertext"],
|
|
40
|
+
"has_encrypted" => ["%s_ciphertext", "%s_iv", "%s_tag"]
|
|
41
|
+
}.freeze
|
|
42
|
+
|
|
43
|
+
# The attribute a macro implies when called with no explicit name, e.g.
|
|
44
|
+
# bare `has_secure_password` -> :password.
|
|
45
|
+
MACRO_DEFAULT_ATTR = { "has_secure_password" => "password" }.freeze
|
|
24
46
|
|
|
25
47
|
# Node classes whose #name (or #unescaped) is a bare identifier we treat
|
|
26
48
|
# as a possible column/table reference.
|
|
@@ -89,24 +111,14 @@ module SchemaReaper
|
|
|
89
111
|
end
|
|
90
112
|
end
|
|
91
113
|
|
|
92
|
-
# `has_secure_password` / `encrypts :field` / `attr_encrypted :field`
|
|
93
|
-
# never write their generated column name (password_digest,
|
|
94
|
-
# field_ciphertext, ...) anywhere in source -- only the virtual
|
|
95
|
-
# attribute name. Derive the column names a macro call implies so they
|
|
96
|
-
# count as "used" instead of looking dead.
|
|
97
114
|
def macro_derived_tokens(node)
|
|
98
115
|
call_name = node.name&.to_s
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
elsif ENCRYPTED_MACROS.include?(call_name)
|
|
106
|
-
macro_symbol_args(node).flat_map { |a| ENCRYPTED_SUFFIXES.map { |s| "#{a}#{s}" } }
|
|
107
|
-
else
|
|
108
|
-
[]
|
|
109
|
-
end
|
|
116
|
+
templates = call_name && MACRO_COLUMN_TEMPLATES[call_name]
|
|
117
|
+
return [] unless templates
|
|
118
|
+
|
|
119
|
+
attrs = macro_symbol_args(node)
|
|
120
|
+
attrs = [MACRO_DEFAULT_ATTR[call_name]].compact if attrs.empty?
|
|
121
|
+
attrs.product(templates).map { |attr, template| format(template, attr) }
|
|
110
122
|
end
|
|
111
123
|
|
|
112
124
|
# Leading bare symbol arguments of a call, e.g. `encrypts :a, :b, purpose: :x`
|
data/schema_reaper.gemspec
CHANGED
|
@@ -9,15 +9,17 @@ Gem::Specification.new do |spec|
|
|
|
9
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
|
-
spec.description =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
spec.description = <<~DESC.strip
|
|
13
|
+
schema_reaper scans a Rails + PostgreSQL app for schema debt that's easy to accumulate and hard to find by hand.
|
|
14
|
+
|
|
15
|
+
It checks your live database against your codebase and flags three kinds of problems: things nothing references anymore (dead columns and dead tables), index trouble (indexes nobody queries, indexes made redundant by a wider index that already covers them, and foreign-key columns with no index at all), and degenerate data (columns that are always NULL, or hold the exact same value in every row).
|
|
16
|
+
|
|
17
|
+
Every finding comes with a confidence score, an estimate of the disk space removing it would reclaim, and a concrete fix. For a column, that's a two-step migration: stop reading it first, then drop it once you've confirmed nothing broke. An optional runtime tracker can sample real production traffic to raise confidence further, for cases a static code scan alone can't settle.
|
|
18
|
+
|
|
19
|
+
Reports come as a colored terminal summary, JSON, Markdown for a PR comment, or SARIF for GitHub code scanning -- plus a CI baseline gate and a trend log to track progress release over release.
|
|
20
|
+
|
|
21
|
+
PostgreSQL only for now. Requires Ruby 2.7 or later. Full usage is in the README.
|
|
22
|
+
DESC
|
|
21
23
|
spec.homepage = "https://github.com/aksshatt/schema_reaper"
|
|
22
24
|
spec.license = "MIT"
|
|
23
25
|
spec.required_ruby_version = ">= 2.7.0"
|
metadata
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: schema_reaper
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.16
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- aksshatt
|
|
8
8
|
- mitkush
|
|
9
|
-
autorequire:
|
|
9
|
+
autorequire:
|
|
10
10
|
bindir: exe
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-09-
|
|
12
|
+
date: 2026-09-22 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: prism
|
|
@@ -79,14 +79,16 @@ dependencies:
|
|
|
79
79
|
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '1.5'
|
|
82
|
-
description:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
estimate, and
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
description: |-
|
|
83
|
+
schema_reaper scans a Rails + PostgreSQL app for schema debt that's easy to accumulate and hard to find by hand.
|
|
84
|
+
|
|
85
|
+
It checks your live database against your codebase and flags three kinds of problems: things nothing references anymore (dead columns and dead tables), index trouble (indexes nobody queries, indexes made redundant by a wider index that already covers them, and foreign-key columns with no index at all), and degenerate data (columns that are always NULL, or hold the exact same value in every row).
|
|
86
|
+
|
|
87
|
+
Every finding comes with a confidence score, an estimate of the disk space removing it would reclaim, and a concrete fix. For a column, that's a two-step migration: stop reading it first, then drop it once you've confirmed nothing broke. An optional runtime tracker can sample real production traffic to raise confidence further, for cases a static code scan alone can't settle.
|
|
88
|
+
|
|
89
|
+
Reports come as a colored terminal summary, JSON, Markdown for a PR comment, or SARIF for GitHub code scanning -- plus a CI baseline gate and a trend log to track progress release over release.
|
|
90
|
+
|
|
91
|
+
PostgreSQL only for now. Requires Ruby 2.7 or later. Full usage is in the README.
|
|
90
92
|
email:
|
|
91
93
|
- akshatpegwar5@gmail.com
|
|
92
94
|
- mitanshukushwah@gmail.com
|
|
@@ -157,7 +159,7 @@ metadata:
|
|
|
157
159
|
wiki_uri: https://github.com/aksshatt/schema_reaper/wiki
|
|
158
160
|
funding_uri: https://github.com/sponsors/aksshatt
|
|
159
161
|
rubygems_mfa_required: 'true'
|
|
160
|
-
post_install_message:
|
|
162
|
+
post_install_message:
|
|
161
163
|
rdoc_options: []
|
|
162
164
|
require_paths:
|
|
163
165
|
- lib
|
|
@@ -172,8 +174,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
172
174
|
- !ruby/object:Gem::Version
|
|
173
175
|
version: '0'
|
|
174
176
|
requirements: []
|
|
175
|
-
rubygems_version: 3.
|
|
176
|
-
signing_key:
|
|
177
|
+
rubygems_version: 3.3.3
|
|
178
|
+
signing_key:
|
|
177
179
|
specification_version: 4
|
|
178
180
|
summary: Find and safely remove schema dead-weight in Rails + PostgreSQL apps.
|
|
179
181
|
test_files: []
|