Skip to content

Commit f3b8ba5

Browse files
authored
Regexp-based ignores
This commit adds the ability to ignore columns, indexes, and other database objects based on regular expressions matched against their names, so that it's easier to ignore objects in bulk.
1 parent f4dc215 commit f3b8ba5

File tree

8 files changed

+160
-23
lines changed

8 files changed

+160
-23
lines changed

lib/active_record_doctor/detectors/base.rb

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def each_model(except: [], abstract: nil, existing_tables_only: false)
170170
case
171171
when model.name.start_with?("HABTM_")
172172
log("#{model.name} - has-belongs-to-many model; skipping")
173-
when except.include?(model.name)
173+
when ignored?(model.name, except)
174174
log("#{model.name} - ignored via the configuration; skipping")
175175
when abstract && !model.abstract_class?
176176
log("#{model.name} - non-abstract model; skipping")
@@ -200,7 +200,7 @@ def each_index(table_name, except: [], multicolumn_only: false)
200200
log(message) do
201201
indexes.each do |index|
202202
case
203-
when except.include?(index.name)
203+
when ignored?(index.name, except)
204204
log("#{index.name} - ignored via the configuration; skipping")
205205
when multicolumn_only && !index.columns.is_a?(Array)
206206
log("#{index.name} - single-column index; skipping")
@@ -217,7 +217,7 @@ def each_attribute(model, except: [], type: nil)
217217
log("Iterating over attributes of #{model.name}") do
218218
connection.columns(model.table_name).each do |column|
219219
case
220-
when except.include?("#{model.name}.#{column.name}")
220+
when ignored?("#{model.name}.#{column.name}", except)
221221
log("#{model.name}.#{column.name} - ignored via the configuration; skipping")
222222
when type && !Array(type).include?(column.type)
223223
log("#{model.name}.#{column.name} - ignored due to the #{column.type} type; skipping")
@@ -234,7 +234,7 @@ def each_column(table_name, only: nil, except: [])
234234
log("Iterating over columns of #{table_name}") do
235235
connection.columns(table_name).each do |column|
236236
case
237-
when except.include?("#{table_name}.#{column.name}")
237+
when ignored?("#{table_name}.#{column.name}", except)
238238
log("#{column.name} - ignored via the configuration; skipping")
239239
when only.nil? || only.include?(column.name)
240240
log(column.name.to_s) do
@@ -266,7 +266,7 @@ def each_table(except: [])
266266
log("Iterating over tables") do
267267
tables.each do |table|
268268
case
269-
when except.include?(table)
269+
when ignored?(table, except)
270270
log("#{table} - ignored via the configuration; skipping")
271271
else
272272
log(table) do
@@ -280,7 +280,7 @@ def each_table(except: [])
280280
def each_data_source(except: [])
281281
log("Iterating over data sources") do
282282
connection.data_sources.each do |data_source|
283-
if except.include?(data_source)
283+
if ignored?(data_source, except)
284284
log("#{data_source} - ignored via the configuration; skipping")
285285
else
286286
log(data_source) do
@@ -303,7 +303,7 @@ def each_association(model, except: [], type: [:has_many, :has_one, :belongs_to]
303303

304304
associations.each do |association|
305305
case
306-
when except.include?("#{model.name}.#{association.name}")
306+
when ignored?("#{model.name}.#{association.name}", except)
307307
log("#{model.name}.#{association.name} - ignored via the configuration; skipping")
308308
when through && !association.is_a?(ActiveRecord::Reflection::ThroughReflection)
309309
log("#{model.name}.#{association.name} - is not a through association; skipping")
@@ -321,6 +321,10 @@ def each_association(model, except: [], type: [:has_many, :has_one, :belongs_to]
321321
end
322322
end
323323
end
324+
325+
def ignored?(name, patterns)
326+
patterns.any? { |pattern| pattern === name } # rubocop:disable Style/CaseEquality
327+
end
324328
end
325329
end
326330
end

lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def detect
2929
each_foreign_key(table) do |foreign_key|
3030
from_column = column(table, foreign_key.column)
3131

32-
next if config(:ignore_columns).include?("#{table}.#{from_column.name}")
32+
next if ignored?("#{table}.#{from_column.name}", config(:ignore_columns))
3333

3434
to_table = foreign_key.to_table
3535
to_column = column(to_table, foreign_key.primary_key)

lib/active_record_doctor/detectors/missing_non_null_constraint.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ def detect
2626
table_models = models.select(&:table_exists?).group_by(&:table_name)
2727

2828
table_models.each do |table, models|
29-
next if config(:ignore_tables).include?(table)
29+
next if ignored?(table, config(:ignore_tables))
3030

3131
concrete_models = models.reject do |model|
3232
model.abstract_class? || sti_base_model?(model)
3333
end
3434

3535
connection.columns(table).each do |column|
36-
next if config(:ignore_columns).include?("#{table}.#{column.name}")
36+
next if ignored?("#{table}.#{column.name}", config(:ignore_columns))
3737
next if !column.null
3838
next if !concrete_models.all? { |model| non_null_needed?(model, column) }
3939
next if not_null_check_constraint_exists?(table, column)

lib/active_record_doctor/detectors/missing_unique_indexes.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@ def validations_without_indexes
8181
def has_ones_without_indexes # rubocop:disable Naming/PredicateName
8282
each_model do |model|
8383
each_association(model, type: :has_one, has_scope: false, through: false) do |has_one|
84-
next if config(:ignore_models).include?(has_one.klass.name)
84+
next if ignored?(has_one.klass.name, config(:ignore_models))
8585

8686
columns =
8787
if has_one.options[:as]
8888
[has_one.type.to_s, has_one.foreign_key.to_s]
8989
else
9090
[has_one.foreign_key.to_s]
9191
end
92-
next if ignore_columns.include?("#{model.name}(#{columns.join(',')})")
92+
next if ignored?("#{model.name}(#{columns.join(',')})", ignore_columns)
9393

9494
table_name = has_one.klass.table_name
9595
next if unique_index?(table_name, columns)
@@ -136,7 +136,11 @@ def unique_index?(table_name, columns, scope = nil)
136136

137137
def ignore_columns
138138
@ignore_columns ||= config(:ignore_columns).map do |column|
139-
column.gsub(" ", "")
139+
if column.is_a?(String)
140+
column.gsub(" ", "")
141+
else
142+
column
143+
end
140144
end
141145
end
142146

test/active_record_doctor/detectors/extraneous_indexes_test.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,38 @@ def test_config_ignore_tables
215215
refute_problems
216216
end
217217

218+
def test_config_ignore_tables_regexp
219+
create_table(:users_tmp) do |t|
220+
t.index :id
221+
end
222+
223+
config_file(<<-CONFIG)
224+
ActiveRecordDoctor.configure do |config|
225+
config.detector :extraneous_indexes,
226+
ignore_tables: [/_tmp\\z/]
227+
end
228+
CONFIG
229+
230+
refute_problems
231+
end
232+
233+
def test_config_ignore_tables_string_ignores_exact_match
234+
create_table(:users) do |t|
235+
t.index :id
236+
end
237+
238+
config_file(<<-CONFIG)
239+
ActiveRecordDoctor.configure do |config|
240+
config.detector :extraneous_indexes,
241+
ignore_tables: ["users_profiles"]
242+
end
243+
CONFIG
244+
245+
assert_problems(<<OUTPUT)
246+
remove index_users_on_id from users - coincides with the primary key on the table
247+
OUTPUT
248+
end
249+
218250
def test_config_global_ignore_tables
219251
create_table(:users) do |t|
220252
t.index :id
@@ -274,4 +306,24 @@ def test_config_detector_ignore_indexes
274306

275307
refute_problems
276308
end
309+
310+
def test_config_detector_ignore_indexes_regexp
311+
create_table(:users) do |t|
312+
t.index :id
313+
t.string :email
314+
t.string :api_key
315+
316+
t.index :email, name: "index_users_on_email"
317+
t.index [:email, :api_key], name: "index_users_on_email_and_api_key"
318+
end
319+
320+
config_file(<<-CONFIG)
321+
ActiveRecordDoctor.configure do |config|
322+
config.detector :extraneous_indexes,
323+
ignore_indexes: [/\\Aindex_users_/]
324+
end
325+
CONFIG
326+
327+
refute_problems
328+
end
277329
end

test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,32 @@ def test_models_with_non_existent_tables_are_skipped
3535

3636
def test_config_ignore_models
3737
create_table(:users) do |t|
38-
t.string :email, null: false
39-
end.define_model
38+
t.boolean :active, null: false
39+
end.define_model do
40+
validates :active, presence: true
41+
end
42+
43+
config_file(<<-CONFIG)
44+
ActiveRecordDoctor.configure do |config|
45+
config.detector :incorrect_boolean_presence_validation,
46+
ignore_models: ["TransientRecord::Models::User"]
47+
end
48+
CONFIG
49+
50+
refute_problems
51+
end
52+
53+
def test_config_ignore_models_regexp
54+
create_table(:users) do |t|
55+
t.boolean :active, null: false
56+
end.define_model do
57+
validates :active, presence: true
58+
end
4059

4160
config_file(<<-CONFIG)
4261
ActiveRecordDoctor.configure do |config|
4362
config.detector :incorrect_boolean_presence_validation,
44-
ignore_models: ["ModelFactory.User"]
63+
ignore_models: [/User/]
4564
end
4665
CONFIG
4766

@@ -50,12 +69,14 @@ def test_config_ignore_models
5069

5170
def test_global_ignore_models
5271
create_table(:users) do |t|
53-
t.string :email, null: false
54-
end.define_model
72+
t.boolean :active, null: false
73+
end.define_model do
74+
validates :active, presence: true
75+
end
5576

5677
config_file(<<-CONFIG)
5778
ActiveRecordDoctor.configure do |config|
58-
config.global :ignore_models, ["ModelFactory.User"]
79+
config.global :ignore_models, ["TransientRecord::Models::User"]
5980
end
6081
CONFIG
6182

@@ -64,13 +85,32 @@ def test_global_ignore_models
6485

6586
def test_config_ignore_attributes
6687
create_table(:users) do |t|
67-
t.string :email, null: false
68-
end.define_model
88+
t.boolean :active, null: false
89+
end.define_model do
90+
validates :active, presence: true
91+
end
92+
93+
config_file(<<-CONFIG)
94+
ActiveRecordDoctor.configure do |config|
95+
config.detector :incorrect_boolean_presence_validation,
96+
ignore_attributes: ["TransientRecord::Models::User.active"]
97+
end
98+
CONFIG
99+
100+
refute_problems
101+
end
102+
103+
def test_config_ignore_attributes_regexp
104+
create_table(:users) do |t|
105+
t.boolean :active_old, null: false
106+
end.define_model do
107+
validates :active_old, presence: true
108+
end
69109

70110
config_file(<<-CONFIG)
71111
ActiveRecordDoctor.configure do |config|
72112
config.detector :incorrect_boolean_presence_validation,
73-
ignore_attributes: ["ModelFactory.User.email"]
113+
ignore_attributes: [/_old\\z/]
74114
end
75115
CONFIG
76116

test/active_record_doctor/detectors/incorrect_dependent_option_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ def test_config_ignore_associations
502502
config_file(<<-CONFIG)
503503
ActiveRecordDoctor.configure do |config|
504504
config.detector :incorrect_dependent_option,
505-
ignore_associations: ["TransientRecord::Models::Company.users"]
505+
ignore_associations: [/users/]
506506
end
507507
CONFIG
508508

test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ def test_config_ignore_tables
7979
refute_problems
8080
end
8181

82+
def test_config_ignore_tables_regexp
83+
skip("MySQL always indexes foreign keys") if mysql?
84+
85+
create_table(:companies)
86+
create_table(:users_tmp) do |t|
87+
t.references :company, foreign_key: true, index: false
88+
end
89+
90+
config_file(<<-CONFIG)
91+
ActiveRecordDoctor.configure do |config|
92+
config.detector :unindexed_foreign_keys,
93+
ignore_tables: [/_tmp\\z/]
94+
end
95+
CONFIG
96+
97+
refute_problems
98+
end
99+
82100
def test_global_ignore_tables
83101
skip("MySQL always indexes foreign keys") if mysql?
84102

@@ -113,4 +131,23 @@ def test_config_ignore_columns
113131

114132
refute_problems
115133
end
134+
135+
def test_config_ignore_columns_regexp
136+
skip("MySQL always indexes foreign keys") if mysql?
137+
138+
create_table(:companies)
139+
create_table(:users) do |t|
140+
t.integer :company_id_tmp
141+
t.foreign_key :companies, column: :company_id_tmp, index: false
142+
end
143+
144+
config_file(<<-CONFIG)
145+
ActiveRecordDoctor.configure do |config|
146+
config.detector :unindexed_foreign_keys,
147+
ignore_columns: [/_tmp\\z/]
148+
end
149+
CONFIG
150+
151+
refute_problems
152+
end
116153
end

0 commit comments

Comments
 (0)