active_record_doctor 1.8.0 → 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +316 -54
- data/lib/active_record_doctor/config/default.rb +76 -0
- data/lib/active_record_doctor/config/loader.rb +137 -0
- data/lib/active_record_doctor/config.rb +14 -0
- data/lib/active_record_doctor/detectors/base.rb +142 -21
- data/lib/active_record_doctor/detectors/extraneous_indexes.rb +59 -48
- data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +31 -23
- data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +102 -35
- data/lib/active_record_doctor/detectors/incorrect_length_validation.rb +63 -0
- data/lib/active_record_doctor/detectors/mismatched_foreign_key_type.rb +45 -0
- data/lib/active_record_doctor/detectors/missing_foreign_keys.rb +32 -23
- data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +41 -28
- data/lib/active_record_doctor/detectors/missing_presence_validation.rb +29 -23
- data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +92 -32
- data/lib/active_record_doctor/detectors/short_primary_key_type.rb +45 -0
- data/lib/active_record_doctor/detectors/undefined_table_references.rb +17 -20
- data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +43 -18
- data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +31 -20
- data/lib/active_record_doctor/detectors.rb +12 -4
- data/lib/active_record_doctor/errors.rb +226 -0
- data/lib/active_record_doctor/help.rb +39 -0
- data/lib/active_record_doctor/rake/task.rb +78 -0
- data/lib/active_record_doctor/runner.rb +41 -0
- data/lib/active_record_doctor/version.rb +1 -1
- data/lib/active_record_doctor.rb +8 -3
- data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +34 -21
- data/lib/tasks/active_record_doctor.rake +9 -18
- data/test/active_record_doctor/config/loader_test.rb +120 -0
- data/test/active_record_doctor/config_test.rb +116 -0
- data/test/active_record_doctor/detectors/disable_test.rb +30 -0
- data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +165 -8
- data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +48 -5
- data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +288 -12
- data/test/active_record_doctor/detectors/incorrect_length_validation_test.rb +105 -0
- data/test/active_record_doctor/detectors/mismatched_foreign_key_type_test.rb +82 -0
- data/test/active_record_doctor/detectors/missing_foreign_keys_test.rb +50 -4
- data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +172 -24
- data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +111 -14
- data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +223 -10
- data/test/active_record_doctor/detectors/short_primary_key_type_test.rb +72 -0
- data/test/active_record_doctor/detectors/undefined_table_references_test.rb +34 -21
- data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +118 -8
- data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +56 -4
- data/test/active_record_doctor/runner_test.rb +42 -0
- data/test/generators/active_record_doctor/add_indexes/add_indexes_generator_test.rb +131 -0
- data/test/model_factory.rb +73 -23
- data/test/setup.rb +65 -71
- metadata +43 -7
- data/lib/active_record_doctor/printers/io_printer.rb +0 -133
- data/lib/active_record_doctor/task.rb +0 -28
- data/test/active_record_doctor/printers/io_printer_test.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e1bf7642b1c7b471fbf78956825067ff070d261d4c47a182d1aaf00f10b98b7
|
4
|
+
data.tar.gz: 6e6fc746327d6db30c282d7964ae778b254107281d9626a251f8593d6ce85db4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0a2d71947728b9d6a130901d94d1ed8c2ede9dde209e91897a8beb8b757da2fea854b89a3a3f6223d658c203e75e687edbee2af37b2203ddb24b28e831439e8
|
7
|
+
data.tar.gz: f0fe87dd6acf379ef946bc513504342c3977bf5c4d7f71eca79290c693b318f0a838c31b95111f68635579cdfa26249d5a95fd456817b6a3c32023c1b4fe0fd1
|
data/README.md
CHANGED
@@ -1,42 +1,149 @@
|
|
1
1
|
# Active Record Doctor
|
2
2
|
|
3
3
|
Active Record Doctor helps to keep the database in a good shape. Currently, it
|
4
|
-
can:
|
4
|
+
can detect:
|
5
|
+
|
6
|
+
* extraneous indexes - [`active_record_doctor:extraneous_indexes`](#removing-extraneous-indexes)
|
7
|
+
* unindexed `deleted_at` columns - [`active_record_doctor:unindexed_deleted_at`](#detecting-unindexed-deleted_at-columns)
|
8
|
+
* missing foreign key constraints - [`active_record_doctor:missing_foreign_keys`](#detecting-missing-foreign-key-constraints)
|
9
|
+
* models referencing undefined tables - [`active_record_doctor:undefined_table_references`](#detecting-models-referencing-undefined-tables)
|
10
|
+
* uniqueness validations not backed by an unique index - [`active_record_doctor:missing_unique_indexes`](#detecting-uniqueness-validations-not-backed-by-an-index)
|
11
|
+
* missing non-`NULL` constraints - [`active_record_doctor:missing_non_null_constraint`](#detecting-missing-non-null-constraints)
|
12
|
+
* missing presence validations - [`active_record_doctor:missing_presence_validation`](#detecting-missing-presence-validations)
|
13
|
+
* incorrect presence validations on boolean columns - [`active_record_doctor:incorrect_boolean_presence_validation`](#detecting-incorrect-presence-validations-on-boolean-columns)
|
14
|
+
* mismatches between model length validations and database validation constraints - [`active_record_doctor:incorrect_length_validation`](#detecting-incorrect-length-validation)
|
15
|
+
* incorrect values of `dependent` on associations - [`active_record_doctor:incorrect_dependent_option`](#detecting-incorrect-dependent-option-on-associations)
|
16
|
+
* primary keys having short integer types - [`active_record_doctor:short_primary_key_type`](#detecting-primary-keys-having-short-integer-types)
|
17
|
+
* mismatched foreign key types - [`active_record_doctor:mismatched_foreign_key_type`](#detecting-mismatched-foreign-key-types)
|
18
|
+
|
19
|
+
It can also:
|
5
20
|
|
6
21
|
* index unindexed foreign keys - [`active_record_doctor:unindexed_foreign_keys`](#indexing-unindexed-foreign-keys)
|
7
|
-
* detect extraneous indexes - [`active_record_doctor:extraneous_indexes`](#removing-extraneous-indexes)
|
8
|
-
* detect unindexed `deleted_at` columns - [`active_record_doctor:unindexed_deleted_at`](#detecting-unindexed-deleted_at-columns)
|
9
|
-
* detect missing foreign key constraints - [`active_record_doctor:missing_foreign_keys`](#detecting-missing-foreign-key-constraints)
|
10
|
-
* detect models referencing undefined tables - [`active_record_doctor:undefined_table_references`](#detecting-models-referencing-undefined-tables)
|
11
|
-
* detect uniqueness validations not backed by an unique index - [`active_record_doctor:missing_unique_indexes`](#detecting-uniqueness-validations-not-backed-by-an-index)
|
12
|
-
* detect missing non-`NULL` constraints - [`active_record_doctor:missing_non_null_constraint`](#detecting-missing-non-null-constraints)
|
13
|
-
* detect missing presence validations - [`active_record_doctor:missing_presence_validation`](#detecting-missing-presence-validations)
|
14
|
-
* detect incorrect presence validations on boolean columns - [`active_record_doctor:incorrect_boolean_presence_validation`](#detecting-incorrect-presence-validations-on-boolean-columns)
|
15
|
-
* detect incorrect values of `dependent` on associations - [`active_record_doctor:incorrect_dependent_option`](#detecting-incorrect-dependent-option-on-associations)
|
16
22
|
|
17
|
-
|
18
|
-
|
19
|
-
Want to suggest a feature? Just shoot me [an email](mailto:contact@gregnavis.com).
|
20
|
-
|
21
|
-
[<img src="https://travis-ci.org/gregnavis/active_record_doctor.svg?branch=master" alt="Build Status" />](https://travis-ci.org/gregnavis/active_record_doctor)
|
23
|
+
[![Build Status](https://github.com/gregnavis/active_record_doctor/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/gregnavis/active_record_doctor/actions/workflows/test.yml)
|
22
24
|
|
23
25
|
## Installation
|
24
26
|
|
25
|
-
|
26
|
-
`Gemfile`:
|
27
|
+
In order to use the latest production release, please add the following to
|
28
|
+
your `Gemfile`:
|
27
29
|
|
28
30
|
```ruby
|
29
31
|
gem 'active_record_doctor', group: :development
|
30
32
|
```
|
31
33
|
|
32
|
-
|
34
|
+
and run `bundle install`. If you'd like to use the most recent development
|
35
|
+
version then use this instead:
|
33
36
|
|
34
|
-
```
|
35
|
-
|
37
|
+
```ruby
|
38
|
+
gem 'active_record_doctor', github: 'gregnavis/active_record_doctor'
|
36
39
|
```
|
37
40
|
|
41
|
+
That's it when it comes to Rails projects. If your project doesn't use Rails
|
42
|
+
then you can use `active_record_doctor` via `Rakefile`.
|
43
|
+
|
44
|
+
### Additional Installation Steps for non-Rails Projects
|
45
|
+
|
46
|
+
If your project uses Rake then you can add the following to `Rakefile` in order
|
47
|
+
to use `active_record_doctor`:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
require "active_record_doctor"
|
51
|
+
|
52
|
+
ActiveRecordDoctor::Rake::Task.new do |task|
|
53
|
+
# Add project-specific Rake dependencies that should be run before running
|
54
|
+
# active_record_doctor.
|
55
|
+
task.deps = []
|
56
|
+
|
57
|
+
# A path to your active_record_doctor configuration file.
|
58
|
+
task.config_path = ::Rails.root.join(".active_record_doctor")
|
59
|
+
|
60
|
+
# A Proc called right before running detectors that should ensure your Active
|
61
|
+
# Record models are preloaded and a database connection is ready.
|
62
|
+
task.setup = -> { ::Rails.application.eager_load! }
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
**IMPORTANT**. `active_record_doctor` expects that after running `deps` and
|
67
|
+
calling `setup` your Active Record models are loaded and a database connection
|
68
|
+
is established.
|
69
|
+
|
38
70
|
## Usage
|
39
71
|
|
72
|
+
`active_record_doctor` can be used via `rake` or `rails`.
|
73
|
+
|
74
|
+
You can run all available detectors via:
|
75
|
+
|
76
|
+
```
|
77
|
+
bundle exec rake active_record_doctor
|
78
|
+
```
|
79
|
+
|
80
|
+
You can run a specific detector via:
|
81
|
+
|
82
|
+
```
|
83
|
+
bundle exec rake active_record_doctor:extraneous_indexes
|
84
|
+
```
|
85
|
+
|
86
|
+
### Continuous Integration
|
87
|
+
|
88
|
+
If you want to use `active_record_doctor` in a Continuous Integration setting
|
89
|
+
then ensure the configuration file is committed and run the tool as one of your
|
90
|
+
build steps -- it returns a non-zero exit status if any errors were reported.
|
91
|
+
|
92
|
+
### Obtaining Help
|
93
|
+
|
94
|
+
If you'd like to obtain help on a specific detector then use the `help`
|
95
|
+
sub-task:
|
96
|
+
|
97
|
+
```
|
98
|
+
bundle exec rake active_record_doctor:extraneous_indexes:help
|
99
|
+
```
|
100
|
+
|
101
|
+
This will show the detector help text in the terminal, along with supported
|
102
|
+
configuration options, their meaning, and whether they're global or local.
|
103
|
+
|
104
|
+
### Configuration
|
105
|
+
|
106
|
+
`active_record_doctor` can be configured to better suit your project's needs.
|
107
|
+
For example, if it complains about a model that you want ignored then you can
|
108
|
+
add that model to the configuration file.
|
109
|
+
|
110
|
+
If you want to use the default configuration then you don't have to do anything.
|
111
|
+
Just run `active_record_doctor` in your project directory.
|
112
|
+
|
113
|
+
If you want to customize the tool you should create a file named
|
114
|
+
`.active_record_doctor` in your project root directory with content like:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
ActiveRecordDoctor.configure do
|
118
|
+
# Global settings affect all detectors.
|
119
|
+
global :ignore_tables, [
|
120
|
+
# Ignore internal Rails-related tables.
|
121
|
+
"ar_internal_metadata",
|
122
|
+
"schema_migrations",
|
123
|
+
"active_storage_blobs",
|
124
|
+
"active_storage_attachments",
|
125
|
+
"action_text_rich_texts",
|
126
|
+
|
127
|
+
# Add project-specific tables here.
|
128
|
+
"legacy_users"
|
129
|
+
]
|
130
|
+
|
131
|
+
# Detector-specific settings affect only one specific detector.
|
132
|
+
detector :extraneous_indexes,
|
133
|
+
ignore_tables: ["users"],
|
134
|
+
ignore_indexes: ["accounts_on_email_organization_id"]
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
The configuration file above will make `active_record_doctor` ignore internal
|
139
|
+
Rails tables (which are ignored by default) and also the `legacy_users` table.
|
140
|
+
It'll also make the `extraneous_indexes` detector skip the `users` table
|
141
|
+
entirely and will not report the index named `accounts_on_email_organization_id`
|
142
|
+
as extraneous.
|
143
|
+
|
144
|
+
Configuration options for each detector are listed below. They can also be
|
145
|
+
obtained via the help mechanism described in the previous section.
|
146
|
+
|
40
147
|
### Indexing Unindexed Foreign Keys
|
41
148
|
|
42
149
|
Foreign keys should be indexed unless it's proven ineffective. However, Rails
|
@@ -66,6 +173,13 @@ three-step process:
|
|
66
173
|
bundle exec rake db:migrate
|
67
174
|
```
|
68
175
|
|
176
|
+
Supported configuration options:
|
177
|
+
|
178
|
+
- `enabled` - set to `false` to disable the detector altogether
|
179
|
+
- `ignore_tables` - tables whose foreign keys should not be checked
|
180
|
+
- `ignore_columns` - columns, written as table.column, that should not be
|
181
|
+
checked.
|
182
|
+
|
69
183
|
### Removing Extraneous Indexes
|
70
184
|
|
71
185
|
Let me illustrate with an example. Consider a `users` table with columns
|
@@ -105,14 +219,25 @@ reported.
|
|
105
219
|
Note that a unique index can _never be replaced by a non-unique one_. For
|
106
220
|
example, if there's a unique index on `users.login` and a non-unique index on
|
107
221
|
`users.login, users.domain` then the tool will _not_ suggest dropping
|
108
|
-
`users.login` as it could violate the uniqueness assumption.
|
222
|
+
`users.login` as it could violate the uniqueness assumption. However, a unique
|
223
|
+
index on `users.login, user.domain` might be replaceable with `users.login` as
|
224
|
+
the uniqueness of the latter implies the uniqueness of the former (if a given
|
225
|
+
`login` can appear only once then it can be present in only one `login, domain`
|
226
|
+
pair).
|
227
|
+
|
228
|
+
Supported configuration options:
|
229
|
+
|
230
|
+
- `enabled` - set to `false` to disable the detector altogether
|
231
|
+
- `ignore_tables` - tables whose indexes should never be reported as extraneous.
|
232
|
+
- `ignore_columns` - indexes that should never be reported as extraneous.
|
109
233
|
|
110
234
|
### Detecting Unindexed `deleted_at` Columns
|
111
235
|
|
112
236
|
If you soft-delete some models (e.g. with `paranoia`) then you need to modify
|
113
237
|
your indexes to include only non-deleted rows. Otherwise they will include
|
114
238
|
logically non-existent rows. This will make them larger and slower to use. Most
|
115
|
-
of the time they should only cover columns satisfying `deleted_at IS NULL
|
239
|
+
of the time they should only cover columns satisfying `deleted_at IS NULL` (to
|
240
|
+
cover existing records) or `deleted_at IS NOT NULL` (to cover deleted records).
|
116
241
|
|
117
242
|
`active_record_doctor` can automatically detect indexes on tables with a
|
118
243
|
`deleted_at` column. Just run:
|
@@ -125,6 +250,16 @@ This will print a list of indexes that don't have the `deleted_at IS NULL`
|
|
125
250
|
clause. Currently, `active_record_doctor` cannot automatically generate
|
126
251
|
appropriate migrations. You need to do that manually.
|
127
252
|
|
253
|
+
Supported configuration options:
|
254
|
+
|
255
|
+
- `enabled` - set to `false` to disable the detector altogether
|
256
|
+
- `ignore_tables` - tables whose indexes should not be checked.
|
257
|
+
- `ignore_columns` - specific columns, written as table.column, that should not
|
258
|
+
be reported as unindexed.
|
259
|
+
- `ignore_indexes` - specific indexes that should not be reported as excluding a
|
260
|
+
timestamp column.
|
261
|
+
- `column_names` - deletion timestamp column names.
|
262
|
+
|
128
263
|
### Detecting Missing Foreign Key Constraints
|
129
264
|
|
130
265
|
If `users.profile_id` references a row in `profiles` then this can be expressed
|
@@ -142,20 +277,8 @@ keys with the following command:
|
|
142
277
|
bundle exec rake active_record_doctor:missing_foreign_keys
|
143
278
|
```
|
144
279
|
|
145
|
-
|
146
|
-
|
147
|
-
```
|
148
|
-
users profile_id
|
149
|
-
comments user_id article_id
|
150
|
-
```
|
151
|
-
|
152
|
-
Tables are listed one per line. Each line starts with a table name followed by
|
153
|
-
column names that should have a foreign key constraint. In the example above,
|
154
|
-
`users.profile_id`, `comments.user_id`, and `comments.article_id` lack a foreign
|
155
|
-
key constraint.
|
156
|
-
|
157
|
-
In order to add a foreign key constraint to `users.profile_id` use the following
|
158
|
-
migration:
|
280
|
+
In order to add a foreign key constraint to `users.profile_id` use a migration
|
281
|
+
like:
|
159
282
|
|
160
283
|
```ruby
|
161
284
|
class AddForeignKeyConstraintToUsersProfileId < ActiveRecord::Migration
|
@@ -165,6 +288,13 @@ class AddForeignKeyConstraintToUsersProfileId < ActiveRecord::Migration
|
|
165
288
|
end
|
166
289
|
```
|
167
290
|
|
291
|
+
Supported configuration options:
|
292
|
+
|
293
|
+
- `enabled` - set to `false` to disable the detector altogether
|
294
|
+
- `ignore_tables` - tables whose columns should not be checked.
|
295
|
+
- `ignore_columns` - columns, written as table.column, that should not be
|
296
|
+
checked.
|
297
|
+
|
168
298
|
### Detecting Models Referencing Undefined Tables
|
169
299
|
|
170
300
|
Active Record guesses the table name based on the class name. There are a few
|
@@ -187,18 +317,23 @@ If there a model references an undefined table then you'll see a message like
|
|
187
317
|
this:
|
188
318
|
|
189
319
|
```
|
190
|
-
|
191
|
-
Contract (the table contract_records is undefined)
|
320
|
+
Contract references a non-existent table or view named contract_records
|
192
321
|
```
|
193
322
|
|
194
323
|
On top of that `rake` will exit with status code of 1. This allows you to use
|
195
324
|
this check as part of your Continuous Integration pipeline.
|
196
325
|
|
326
|
+
Supported configuration options:
|
327
|
+
|
328
|
+
- `enabled` - set to `false` to disable the detector altogether
|
329
|
+
- `ignore_models` - models whose underlying tables should not be checked for
|
330
|
+
existence.
|
331
|
+
|
197
332
|
### Detecting Uniqueness Validations not Backed by an Index
|
198
333
|
|
199
|
-
|
200
|
-
order to be robust. Otherwise you risk inserting
|
201
|
-
load.
|
334
|
+
Model-level uniqueness validations and `has_one` associations should be backed
|
335
|
+
by a database index in order to be robust. Otherwise you risk inserting
|
336
|
+
duplicate values under heavy load.
|
202
337
|
|
203
338
|
In order to detect such validations run:
|
204
339
|
|
@@ -209,16 +344,23 @@ bundle exec rake active_record_doctor:missing_unique_indexes
|
|
209
344
|
If there are such indexes then the command will print:
|
210
345
|
|
211
346
|
```
|
212
|
-
|
213
|
-
users: email
|
347
|
+
add a unique index on users(email) - validating uniqueness in the model without an index can lead to duplicates
|
214
348
|
```
|
215
349
|
|
216
350
|
This means that you should create a unique index on `users.email`.
|
217
351
|
|
352
|
+
Supported configuration options:
|
353
|
+
|
354
|
+
- `enabled` - set to `false` to disable the detector altogether
|
355
|
+
- `ignore_models` - models whose uniqueness validators should not be checked.
|
356
|
+
- `ignore_columns` - specific validators, written as Model(column1, ...), that
|
357
|
+
should not be checked.
|
358
|
+
|
218
359
|
### Detecting Missing Non-`NULL` Constraints
|
219
360
|
|
220
361
|
If there's an unconditional presence validation on a column then it should be
|
221
|
-
marked as non-`NULL`-able at the database level
|
362
|
+
marked as non-`NULL`-able at the database level or should have a `IS NOT NULL`
|
363
|
+
constraint.
|
222
364
|
|
223
365
|
In order to detect columns whose presence is required but that are marked
|
224
366
|
`null: true` in the database run the following command:
|
@@ -230,9 +372,7 @@ bundle exec rake active_record_doctor:missing_non_null_constraint
|
|
230
372
|
The output of the command is similar to:
|
231
373
|
|
232
374
|
```
|
233
|
-
|
234
|
-
users: name
|
235
|
-
|
375
|
+
add `NOT NULL` to users.name - models validates its presence but it's not non-NULL in the database
|
236
376
|
```
|
237
377
|
|
238
378
|
You can mark the columns mentioned in the output as `null: false` by creating a
|
@@ -240,6 +380,13 @@ migration and calling `change_column_null`.
|
|
240
380
|
|
241
381
|
This validator skips models whose corresponding database tables don't exist.
|
242
382
|
|
383
|
+
Supported configuration options:
|
384
|
+
|
385
|
+
- `enabled` - set to `false` to disable the detector altogether
|
386
|
+
- `ignore_tables` - tables whose columns should not be checked.
|
387
|
+
- `ignore_columns` - columns, written as table.column, that should not be
|
388
|
+
checked.
|
389
|
+
|
243
390
|
### Detecting Missing Presence Validations
|
244
391
|
|
245
392
|
If a column is marked as `null: false` then it's likely it should have the
|
@@ -254,14 +401,21 @@ bundle exec rake active_record_doctor:missing_presence_validation
|
|
254
401
|
The output of the command looks like this:
|
255
402
|
|
256
403
|
```
|
257
|
-
|
258
|
-
|
404
|
+
add a `presence` validator to User.email - it's NOT NULL but lacks a validator
|
405
|
+
add a `presence` validator to User.name - it's NOT NULL but lacks a validator
|
259
406
|
```
|
260
407
|
|
261
408
|
This means `User` should have a presence validator on `email` and `name`.
|
262
409
|
|
263
410
|
This validator skips models whose corresponding database tables don't exist.
|
264
411
|
|
412
|
+
Supported configuration options:
|
413
|
+
|
414
|
+
- `enabled` - set to `false` to disable the detector altogether
|
415
|
+
- `ignore_models` - models whose underlying tables' columns should not be checked.
|
416
|
+
- `ignore_attributes` - specific attributes, written as Model.attribute, that
|
417
|
+
should not be checked.
|
418
|
+
|
265
419
|
### Detecting Incorrect Presence Validations on Boolean Columns
|
266
420
|
|
267
421
|
A boolean column's presence should be validated using inclusion or exclusion
|
@@ -276,8 +430,7 @@ bundle exec rake active_record_doctor:incorrect_boolean_presence_validation
|
|
276
430
|
The output of the command looks like this:
|
277
431
|
|
278
432
|
```
|
279
|
-
|
280
|
-
User: active
|
433
|
+
replace the `presence` validator on User.active with `inclusion` - `presence` can't be used on booleans
|
281
434
|
```
|
282
435
|
|
283
436
|
This means `active` is validated with `presence: true` instead of
|
@@ -285,6 +438,46 @@ This means `active` is validated with `presence: true` instead of
|
|
285
438
|
|
286
439
|
This validator skips models whose corresponding database tables don't exist.
|
287
440
|
|
441
|
+
Supported configuration options:
|
442
|
+
|
443
|
+
- `enabled` - set to `false` to disable the detector altogether
|
444
|
+
- `ignore_models` - models whose validators should not be checked.
|
445
|
+
- `ignore_columns` - attributes, written as Model.attribute, whose validators
|
446
|
+
should not be checked.
|
447
|
+
|
448
|
+
### Detecting Incorrect Length Validations
|
449
|
+
|
450
|
+
String length can be enforced by both the database and the application. If
|
451
|
+
there's a database limit then it's a good idea to add a model validation to
|
452
|
+
ensure user-friendly error messages. Similarly, if there's a model validator
|
453
|
+
without the corresponding database constraint then it's a good idea to add one
|
454
|
+
to avoid saving invalid models.
|
455
|
+
|
456
|
+
In order to detect columns whose length isn't validated properly run:
|
457
|
+
|
458
|
+
```
|
459
|
+
bundle exec rake active_record_doctor:incorrect_length_validation
|
460
|
+
```
|
461
|
+
|
462
|
+
The output of the command looks like this:
|
463
|
+
|
464
|
+
```
|
465
|
+
set the maximum length in the validator of User.email (currently 32) and the database limit on users.email (currently 64) to the same value
|
466
|
+
add a length validator on User.address to enforce a maximum length of 64 defined on users.address
|
467
|
+
```
|
468
|
+
|
469
|
+
The first message means the validator on `User.email` is checking for a
|
470
|
+
different maximum than the database limit on `users.email`. The second message
|
471
|
+
means there's a database limit on `users.address` without the corresponding
|
472
|
+
model validation.
|
473
|
+
|
474
|
+
Supported configuration options:
|
475
|
+
|
476
|
+
- `enabled` - set to `false` to disable the detector altogether
|
477
|
+
- `ignore_models` - models whose validators should not be checked.
|
478
|
+
- `ignore_columns` - attributes, written as Model.attribute, whose validators
|
479
|
+
should not be checked.
|
480
|
+
|
288
481
|
### Detecting Incorrect `dependent` Option on Associations
|
289
482
|
|
290
483
|
Cascading model deletions can be sped up with `dependent: :delete_all` (to
|
@@ -308,11 +501,80 @@ bundle exec rake active_record_doctor:incorrect_dependent_option
|
|
308
501
|
The output of the command looks like this:
|
309
502
|
|
310
503
|
```
|
311
|
-
|
312
|
-
|
313
|
-
|
504
|
+
use `dependent: :delete_all` or similar on Company.users - associated models have no validations and can be deleted in bulk
|
505
|
+
use `dependent: :destroy` or similar on Post.comments - the associated model has callbacks that are currently skipped
|
506
|
+
```
|
507
|
+
|
508
|
+
Supported configuration options:
|
509
|
+
|
510
|
+
- `enabled` - set to `false` to disable the detector altogether
|
511
|
+
- `ignore_models` - models whose associations should not be checked.
|
512
|
+
- `ignore_columns` - associations, written as Model.association, that should not
|
513
|
+
be checked.
|
514
|
+
|
515
|
+
### Detecting Primary Keys Having Short Integer Types
|
516
|
+
|
517
|
+
Active Record 5.1 changed the default primary and foreign key type from INTEGER
|
518
|
+
to BIGINT. The reason is to reduce the risk of running out of IDs on inserts.
|
519
|
+
|
520
|
+
In order to detect primary keys using shorter integer types, for example created
|
521
|
+
before migrating to 5.1, you can run the following command:
|
522
|
+
|
523
|
+
```
|
524
|
+
bundle exec rake active_record_doctor:short_primary_key_type
|
525
|
+
```
|
526
|
+
|
527
|
+
The output of the command looks like this:
|
528
|
+
|
529
|
+
```
|
530
|
+
change the type of companies.id to bigint
|
314
531
|
```
|
315
532
|
|
533
|
+
The above means `comanies.id` should be migrated to a wider integer type. An
|
534
|
+
example migration to accomplish this looks likes this:
|
535
|
+
|
536
|
+
```ruby
|
537
|
+
class ChangeCompaniesPrimaryKeyType < ActiveRecord::Migration[5.1]
|
538
|
+
def change
|
539
|
+
change_column :companies, :id, :bigint
|
540
|
+
end
|
541
|
+
end
|
542
|
+
```
|
543
|
+
|
544
|
+
**IMPORTANT**. Running the above migration on a large table can cause downtime
|
545
|
+
as all rows need to be rewritten.
|
546
|
+
|
547
|
+
Supported configuration options:
|
548
|
+
|
549
|
+
- `enabled` - set to `false` to disable the detector altogether
|
550
|
+
- `ignore_tables` - tables whose primary keys should not be checked.
|
551
|
+
|
552
|
+
### Detecting Mismatched Foreign Key Types
|
553
|
+
|
554
|
+
Foreign keys should be of the same type as the referenced primary key.
|
555
|
+
Otherwise, there's a risk of bugs caused by IDs representable by one type but
|
556
|
+
not the other.
|
557
|
+
|
558
|
+
Running the command below will list all foreign keys whose type is different
|
559
|
+
from the referenced primary key:
|
560
|
+
|
561
|
+
```
|
562
|
+
bundle exec rake active_record_doctor:mismatched_foreign_key_type
|
563
|
+
```
|
564
|
+
|
565
|
+
The output of the command looks like this:
|
566
|
+
|
567
|
+
```
|
568
|
+
companies.user_id references a column of different type - foreign keys should be of the same type as the referenced column
|
569
|
+
```
|
570
|
+
|
571
|
+
Supported configuration options:
|
572
|
+
|
573
|
+
- `enabled` - set to `false` to disable the detector altogether
|
574
|
+
- `ignore_tables` - tables whose foreign keys should not be checked.
|
575
|
+
- `ignore_columns` - foreign keys, written as table.column, that should not be
|
576
|
+
checked.
|
577
|
+
|
316
578
|
## Ruby and Rails Compatibility Policy
|
317
579
|
|
318
580
|
The goal of the policy is to ensure proper functioning in reasonable
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ActiveRecordDoctor.configure do
|
4
|
+
global :ignore_tables, [
|
5
|
+
"ar_internal_metadata",
|
6
|
+
"schema_migrations",
|
7
|
+
"active_storage_blobs",
|
8
|
+
"active_storage_attachments",
|
9
|
+
"action_text_rich_texts"
|
10
|
+
]
|
11
|
+
|
12
|
+
detector :extraneous_indexes,
|
13
|
+
enabled: true,
|
14
|
+
ignore_tables: [],
|
15
|
+
ignore_indexes: []
|
16
|
+
|
17
|
+
detector :incorrect_boolean_presence_validation,
|
18
|
+
enabled: true,
|
19
|
+
ignore_models: [],
|
20
|
+
ignore_attributes: []
|
21
|
+
|
22
|
+
detector :incorrect_length_validation,
|
23
|
+
enabled: true,
|
24
|
+
ignore_models: [],
|
25
|
+
ignore_attributes: []
|
26
|
+
|
27
|
+
detector :incorrect_dependent_option,
|
28
|
+
enabled: true,
|
29
|
+
ignore_models: [],
|
30
|
+
ignore_associations: []
|
31
|
+
|
32
|
+
detector :mismatched_foreign_key_type,
|
33
|
+
enabled: true,
|
34
|
+
ignore_tables: [],
|
35
|
+
ignore_columns: []
|
36
|
+
|
37
|
+
detector :missing_foreign_keys,
|
38
|
+
enabled: true,
|
39
|
+
ignore_tables: [],
|
40
|
+
ignore_columns: []
|
41
|
+
|
42
|
+
detector :missing_non_null_constraint,
|
43
|
+
enabled: true,
|
44
|
+
ignore_tables: [],
|
45
|
+
ignore_columns: []
|
46
|
+
|
47
|
+
detector :missing_presence_validation,
|
48
|
+
enabled: true,
|
49
|
+
ignore_models: [],
|
50
|
+
ignore_attributes: []
|
51
|
+
|
52
|
+
detector :missing_unique_indexes,
|
53
|
+
enabled: true,
|
54
|
+
ignore_models: [],
|
55
|
+
ignore_columns: []
|
56
|
+
|
57
|
+
detector :short_primary_key_type,
|
58
|
+
enabled: true,
|
59
|
+
ignore_tables: []
|
60
|
+
|
61
|
+
detector :undefined_table_references,
|
62
|
+
enabled: true,
|
63
|
+
ignore_models: []
|
64
|
+
|
65
|
+
detector :unindexed_deleted_at,
|
66
|
+
enabled: true,
|
67
|
+
ignore_tables: [],
|
68
|
+
ignore_columns: [],
|
69
|
+
ignore_indexes: [],
|
70
|
+
column_names: ["deleted_at", "discarded_at"]
|
71
|
+
|
72
|
+
detector :unindexed_foreign_keys,
|
73
|
+
enabled: true,
|
74
|
+
ignore_tables: [],
|
75
|
+
ignore_columns: []
|
76
|
+
end
|