Skip to content

Commit 7ef210f

Browse files
committed
Add ignore list to missing_fk_constraint check
1 parent 8e42f9b commit 7ef210f

5 files changed

Lines changed: 144 additions & 5 deletions

File tree

lib/ruby-pg-extras.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require "ruby_pg_extras/diagnose_data"
88
require "ruby_pg_extras/diagnose_print"
99
require "ruby_pg_extras/detect_fk_column"
10+
require "ruby_pg_extras/ignore_list"
1011
require "ruby_pg_extras/missing_fk_indexes"
1112
require "ruby_pg_extras/missing_fk_constraints"
1213
require "ruby_pg_extras/index_info"
@@ -195,7 +196,7 @@ def self.missing_fk_indexes(args: {}, in_format: :display_table)
195196
end
196197

197198
def self.missing_fk_constraints(args: {}, in_format: :display_table)
198-
RubyPgExtras::MissingFkConstraints.call(args[:table_name])
199+
RubyPgExtras::MissingFkConstraints.call(args[:table_name], ignore_list: args[:ignore_list])
199200
end
200201

201202
def self.display_result(result, title:, in_format:)

lib/ruby_pg_extras/ignore_list.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
module RubyPgExtras
4+
# Parses and matches ignore patterns like:
5+
# - "*" (ignore everything)
6+
# - "posts.*" (ignore all columns on a table)
7+
# - "category_id" (ignore this column name on all tables)
8+
# - "posts.topic_id" (ignore a specific table+column)
9+
class IgnoreList
10+
def initialize(ignore_list)
11+
@rules = normalize(ignore_list)
12+
end
13+
14+
def ignored?(table:, column_name:)
15+
@rules.any? do |rule|
16+
next true if rule == "*"
17+
next true if rule == "#{table}.*"
18+
next true if rule == column_name
19+
next true if rule == "#{table}.#{column_name}"
20+
false
21+
end
22+
end
23+
24+
private
25+
26+
def normalize(ignore_list)
27+
entries =
28+
case ignore_list
29+
when nil
30+
[]
31+
when String
32+
ignore_list.split(",")
33+
when Array
34+
ignore_list
35+
else
36+
raise ArgumentError, "ignore_list must be a String or Array"
37+
end
38+
39+
entries
40+
.map { |v| v.to_s.strip }
41+
.reject(&:empty?)
42+
.uniq
43+
end
44+
end
45+
end
46+
47+

lib/ruby_pg_extras/missing_fk_constraints.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22

33
module RubyPgExtras
44
class MissingFkConstraints
5-
def self.call(table_name)
6-
new.call(table_name)
5+
# ignore_list: array (or comma-separated string) of entries like:
6+
# - "posts.category_id" (ignore a specific table+column)
7+
# - "category_id" (ignore this column name for all tables)
8+
# - "posts.*" (ignore all columns on a table)
9+
def self.call(table_name, ignore_list: nil)
10+
new.call(table_name, ignore_list: ignore_list)
711
end
812

9-
def call(table_name)
13+
def call(table_name, ignore_list: nil)
14+
ignore_list_matcher = IgnoreList.new(ignore_list)
15+
1016
tables =
1117
if table_name
1218
[table_name]
@@ -26,10 +32,14 @@ def call(table_name)
2632
tables.each_with_object([]) do |table, agg|
2733
schema = schemas_by_table.fetch(table, [])
2834
fk_columns_for_table = fk_columns_by_table.fetch(table, [])
35+
schema_column_names = schema.map { |row| row.fetch("column_name") }
2936

3037
candidate_fk_columns = schema.filter_map do |row|
3138
column_name = row.fetch("column_name")
3239

40+
# Skip columns explicitly excluded via ignore list.
41+
next if ignore_list_matcher.ignored?(table: table, column_name: column_name)
42+
3343
# Skip columns that already have a foreign key constraint on this table.
3444
next if fk_columns_for_table.include?(column_name)
3545

@@ -39,7 +49,6 @@ def call(table_name)
3949
# Rails polymorphic associations use <name>_id + <name>_type and can't have FK constraints.
4050
candidate_prefix = column_name.delete_suffix("_id")
4151
polymorphic_type_column = "#{candidate_prefix}_type"
42-
schema_column_names = schema.map { |row| row.fetch("column_name") }
4352
# Skip polymorphic associations (cannot be expressed as a real FK constraint).
4453
next if schema_column_names.include?(polymorphic_type_column)
4554

spec/ignore_list_spec.rb

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# frozen_string_literal: true
2+
3+
require "spec_helper"
4+
require "ruby-pg-extras"
5+
6+
describe RubyPgExtras::IgnoreList do
7+
describe "#ignored?" do
8+
it "returns false when ignore_list is nil" do
9+
list = described_class.new(nil)
10+
expect(list.ignored?(table: "posts", column_name: "topic_id")).to eq(false)
11+
end
12+
13+
it "supports '*' to ignore everything" do
14+
list = described_class.new(["*"])
15+
expect(list.ignored?(table: "posts", column_name: "topic_id")).to eq(true)
16+
expect(list.ignored?(table: "users", column_name: "customer_id")).to eq(true)
17+
end
18+
19+
it "supports 'table.*' to ignore all columns for a table" do
20+
list = described_class.new(["posts.*"])
21+
expect(list.ignored?(table: "posts", column_name: "topic_id")).to eq(true)
22+
expect(list.ignored?(table: "posts", column_name: "user_id")).to eq(true)
23+
expect(list.ignored?(table: "users", column_name: "topic_id")).to eq(false)
24+
end
25+
26+
it "supports 'column' to ignore a column name globally" do
27+
list = described_class.new(["topic_id"])
28+
expect(list.ignored?(table: "posts", column_name: "topic_id")).to eq(true)
29+
expect(list.ignored?(table: "users", column_name: "topic_id")).to eq(true)
30+
expect(list.ignored?(table: "posts", column_name: "user_id")).to eq(false)
31+
end
32+
33+
it "supports 'table.column' to ignore a specific table+column" do
34+
list = described_class.new(["posts.topic_id"])
35+
expect(list.ignored?(table: "posts", column_name: "topic_id")).to eq(true)
36+
expect(list.ignored?(table: "users", column_name: "topic_id")).to eq(false)
37+
expect(list.ignored?(table: "posts", column_name: "user_id")).to eq(false)
38+
end
39+
40+
it "accepts ignore_list as a comma-separated string" do
41+
list = described_class.new("posts.topic_id, customer_id")
42+
expect(list.ignored?(table: "posts", column_name: "topic_id")).to eq(true)
43+
expect(list.ignored?(table: "users", column_name: "customer_id")).to eq(true)
44+
expect(list.ignored?(table: "users", column_name: "topic_id")).to eq(false)
45+
end
46+
47+
it "strips whitespace, drops empty entries and de-duplicates" do
48+
list = described_class.new([" posts.topic_id ", "", "posts.topic_id", " "])
49+
expect(list.ignored?(table: "posts", column_name: "topic_id")).to eq(true)
50+
expect(list.ignored?(table: "users", column_name: "topic_id")).to eq(false)
51+
end
52+
53+
it "raises ArgumentError for unsupported ignore_list types" do
54+
expect { described_class.new(123) }.to raise_error(ArgumentError)
55+
expect { described_class.new({}) }.to raise_error(ArgumentError)
56+
end
57+
end
58+
end
59+
60+

spec/missing_fk_constraints_spec.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,26 @@
3131
result = RubyPgExtras.missing_fk_constraints(args: { table_name: "events" }, in_format: :hash)
3232
expect(result).to eq([])
3333
end
34+
35+
it "supports ignoring a specific table+column via args" do
36+
result = RubyPgExtras.missing_fk_constraints(
37+
args: { ignore_list: ["posts.category_id"] },
38+
in_format: :hash
39+
)
40+
41+
expect(result).to eq([
42+
{ table: "users", column_name: "customer_id" },
43+
])
44+
end
45+
46+
it "supports ignoring a column name globally via args" do
47+
result = RubyPgExtras.missing_fk_constraints(
48+
args: { ignore_list: ["customer_id"] },
49+
in_format: :hash
50+
)
51+
52+
expect(result).to eq([
53+
{ table: "posts", column_name: "category_id" },
54+
])
55+
end
3456
end

0 commit comments

Comments
 (0)