active_record_doctor 1.5.0 → 1.8.0
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 +5 -5
- data/README.md +152 -12
- data/lib/active_record_doctor.rb +20 -2
- data/lib/active_record_doctor/detectors.rb +13 -0
- data/lib/active_record_doctor/detectors/base.rb +64 -0
- data/lib/active_record_doctor/{tasks → detectors}/extraneous_indexes.rb +12 -29
- data/lib/active_record_doctor/detectors/incorrect_boolean_presence_validation.rb +40 -0
- data/lib/active_record_doctor/detectors/incorrect_dependent_option.rb +71 -0
- data/lib/active_record_doctor/{tasks → detectors}/missing_foreign_keys.rb +17 -35
- data/lib/active_record_doctor/detectors/missing_non_null_constraint.rb +60 -0
- data/lib/active_record_doctor/detectors/missing_presence_validation.rb +78 -0
- data/lib/active_record_doctor/detectors/missing_unique_indexes.rb +61 -0
- data/lib/active_record_doctor/detectors/undefined_table_references.rb +34 -0
- data/lib/active_record_doctor/detectors/unindexed_deleted_at.rb +29 -0
- data/lib/active_record_doctor/detectors/unindexed_foreign_keys.rb +48 -0
- data/lib/active_record_doctor/printers.rb +3 -1
- data/lib/active_record_doctor/printers/io_printer.rb +101 -26
- data/lib/active_record_doctor/railtie.rb +3 -1
- data/lib/active_record_doctor/task.rb +28 -0
- data/lib/active_record_doctor/version.rb +3 -1
- data/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb +15 -11
- data/lib/tasks/active_record_doctor.rake +33 -0
- data/test/active_record_doctor/detectors/extraneous_indexes_test.rb +67 -0
- data/test/active_record_doctor/detectors/incorrect_boolean_presence_validation_test.rb +36 -0
- data/test/active_record_doctor/detectors/incorrect_dependent_option_test.rb +117 -0
- data/test/active_record_doctor/detectors/missing_foreign_keys_test.rb +24 -0
- data/test/active_record_doctor/detectors/missing_non_null_constraint_test.rb +102 -0
- data/test/active_record_doctor/detectors/missing_presence_validation_test.rb +107 -0
- data/test/active_record_doctor/detectors/missing_unique_indexes_test.rb +114 -0
- data/test/active_record_doctor/detectors/undefined_table_references_test.rb +44 -0
- data/test/active_record_doctor/detectors/unindexed_deleted_at_test.rb +67 -0
- data/test/active_record_doctor/detectors/unindexed_foreign_keys_test.rb +26 -0
- data/test/active_record_doctor/printers/io_printer_test.rb +23 -10
- data/test/model_factory.rb +78 -0
- data/test/setup.rb +126 -0
- metadata +93 -149
- data/Rakefile +0 -28
- data/lib/active_record_doctor/compatibility.rb +0 -11
- data/lib/active_record_doctor/tasks.rb +0 -4
- data/lib/active_record_doctor/tasks/undefined_table_references.rb +0 -34
- data/lib/active_record_doctor/tasks/unindexed_deleted_at.rb +0 -40
- data/lib/active_record_doctor/tasks/unindexed_foreign_keys.rb +0 -66
- data/lib/tasks/active_record_doctor_tasks.rake +0 -27
- data/test/active_record_doctor/tasks/extraneous_indexes_test.rb +0 -27
- data/test/active_record_doctor/tasks/missing_foreign_keys_test.rb +0 -19
- data/test/active_record_doctor/tasks/undefined_table_references_test.rb +0 -19
- data/test/active_record_doctor/tasks/unindexed_deleted_at_test.rb +0 -19
- data/test/active_record_doctor/tasks/unindexed_foreign_keys_test.rb +0 -19
- data/test/dummy/README.rdoc +0 -28
- data/test/dummy/Rakefile +0 -6
- data/test/dummy/app/assets/javascripts/application.js +0 -13
- data/test/dummy/app/assets/stylesheets/application.css +0 -15
- data/test/dummy/app/controllers/application_controller.rb +0 -5
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/models/application_record.rb +0 -3
- data/test/dummy/app/models/comment.rb +0 -3
- data/test/dummy/app/models/contract.rb +0 -3
- data/test/dummy/app/models/employer.rb +0 -2
- data/test/dummy/app/models/profile.rb +0 -2
- data/test/dummy/app/models/user.rb +0 -3
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/bin/bundle +0 -3
- data/test/dummy/bin/rails +0 -4
- data/test/dummy/bin/rake +0 -4
- data/test/dummy/bin/setup +0 -29
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -23
- data/test/dummy/config/boot.rb +0 -5
- data/test/dummy/config/database.yml +0 -19
- data/test/dummy/config/database.yml.travis +0 -5
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -41
- data/test/dummy/config/environments/production.rb +0 -79
- data/test/dummy/config/environments/test.rb +0 -47
- data/test/dummy/config/initializers/assets.rb +0 -11
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/cookies_serializer.rb +0 -3
- data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
- data/test/dummy/config/initializers/inflections.rb +0 -16
- data/test/dummy/config/initializers/mime_types.rb +0 -4
- data/test/dummy/config/initializers/session_store.rb +0 -3
- data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/test/dummy/config/locales/en.yml +0 -23
- data/test/dummy/config/routes.rb +0 -56
- data/test/dummy/config/secrets.yml +0 -22
- data/test/dummy/db/migrate/20160213101213_create_employers.rb +0 -15
- data/test/dummy/db/migrate/20160213101221_create_users.rb +0 -23
- data/test/dummy/db/migrate/20160213101232_create_profiles.rb +0 -15
- data/test/dummy/db/migrate/20160604081452_create_comments.rb +0 -11
- data/test/dummy/db/migrate/base_migration.rb +0 -5
- data/test/dummy/db/schema.rb +0 -68
- data/test/dummy/log/development.log +0 -532
- data/test/dummy/log/test.log +0 -2699
- data/test/dummy/public/404.html +0 -67
- data/test/dummy/public/422.html +0 -67
- data/test/dummy/public/500.html +0 -66
- data/test/dummy/public/favicon.ico +0 -0
- data/test/support/spy_printer.rb +0 -52
- data/test/test_helper.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 296a8bbb4a326fd78afb08638435754646e22a4416dc18b02dc66f54198a9130
|
4
|
+
data.tar.gz: 5548b71ee3bb95344e34896a7c972cf39ce33f8a4bd1b03a1a2bf02b4081a1dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48c447ca18f35ee44db354414980f54d0ee9416ecc81b6b3fef9401afbaeb44b2951f0fabe43c89027d8783430bdbb6b055ca1000779e37d3729a171649ce795
|
7
|
+
data.tar.gz: 89ab1d3e4cca4df9c94022e0f11217b41239a3b5f2b3c579d8e466a4fe337909ab48f0b05d28eb5a67fc93afa4afe88a47c7c0ebbd3dc7d45c6d0259db22de40
|
data/README.md
CHANGED
@@ -3,11 +3,16 @@
|
|
3
3
|
Active Record Doctor helps to keep the database in a good shape. Currently, it
|
4
4
|
can:
|
5
5
|
|
6
|
-
* index unindexed foreign keys
|
7
|
-
* detect extraneous indexes
|
8
|
-
* detect unindexed `deleted_at` columns
|
9
|
-
* detect missing foreign key constraints
|
10
|
-
* detect models referencing undefined tables
|
6
|
+
* 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)
|
11
16
|
|
12
17
|
More features coming soon!
|
13
18
|
|
@@ -42,7 +47,7 @@ three-step process:
|
|
42
47
|
1. Generate a list of unindexed foreign keys by running
|
43
48
|
|
44
49
|
```bash
|
45
|
-
rake active_record_doctor:unindexed_foreign_keys > unindexed_foreign_keys.txt
|
50
|
+
bundle exec rake active_record_doctor:unindexed_foreign_keys > unindexed_foreign_keys.txt
|
46
51
|
```
|
47
52
|
|
48
53
|
2. Remove columns that should _not_ be indexed from `unindexed_foreign_keys.txt`
|
@@ -58,7 +63,7 @@ three-step process:
|
|
58
63
|
4. Run the migrations
|
59
64
|
|
60
65
|
```bash
|
61
|
-
rake db:migrate
|
66
|
+
bundle exec rake db:migrate
|
62
67
|
```
|
63
68
|
|
64
69
|
### Removing Extraneous Indexes
|
@@ -83,7 +88,7 @@ To discover such indexes automatically just follow these steps:
|
|
83
88
|
1. List extraneous indexes by running:
|
84
89
|
|
85
90
|
```bash
|
86
|
-
rake active_record_doctor:extraneous_indexes
|
91
|
+
bundle exec rake active_record_doctor:extraneous_indexes
|
87
92
|
```
|
88
93
|
|
89
94
|
2. Confirm that each of the indexes can be indeed dropped.
|
@@ -113,7 +118,7 @@ of the time they should only cover columns satisfying `deleted_at IS NULL`.
|
|
113
118
|
`deleted_at` column. Just run:
|
114
119
|
|
115
120
|
```
|
116
|
-
rake active_record_doctor:
|
121
|
+
bundle exec rake active_record_doctor:unindexed_deleted_at
|
117
122
|
```
|
118
123
|
|
119
124
|
This will print a list of indexes that don't have the `deleted_at IS NULL`
|
@@ -134,7 +139,7 @@ add the constraint; for now, it's your job). You can obtain the list of foreign
|
|
134
139
|
keys with the following command:
|
135
140
|
|
136
141
|
```bash
|
137
|
-
rake active_record_doctor:missing_foreign_keys
|
142
|
+
bundle exec rake active_record_doctor:missing_foreign_keys
|
138
143
|
```
|
139
144
|
|
140
145
|
The output will look like:
|
@@ -167,10 +172,15 @@ cases where the name can be wrong (e.g. you forgot to commit a migration or
|
|
167
172
|
changed the table name). Active Record Doctor can help you identify these cases
|
168
173
|
before they hit production.
|
169
174
|
|
170
|
-
|
175
|
+
**IMPORTANT**. Models backed by views are supported only in:
|
176
|
+
|
177
|
+
* Rails 5+ and _any_ database or
|
178
|
+
* Rails 4.2 with PostgreSQL.
|
179
|
+
|
180
|
+
The only think you need to do is run:
|
171
181
|
|
172
182
|
```
|
173
|
-
rake active_record_doctor:undefined_table_references
|
183
|
+
bundle exec rake active_record_doctor:undefined_table_references
|
174
184
|
```
|
175
185
|
|
176
186
|
If there a model references an undefined table then you'll see a message like
|
@@ -184,6 +194,136 @@ The following models reference undefined tables:
|
|
184
194
|
On top of that `rake` will exit with status code of 1. This allows you to use
|
185
195
|
this check as part of your Continuous Integration pipeline.
|
186
196
|
|
197
|
+
### Detecting Uniqueness Validations not Backed by an Index
|
198
|
+
|
199
|
+
A model-level uniqueness validations should be backed by a database index in
|
200
|
+
order to be robust. Otherwise you risk inserting duplicate values under heavy
|
201
|
+
load.
|
202
|
+
|
203
|
+
In order to detect such validations run:
|
204
|
+
|
205
|
+
```
|
206
|
+
bundle exec rake active_record_doctor:missing_unique_indexes
|
207
|
+
```
|
208
|
+
|
209
|
+
If there are such indexes then the command will print:
|
210
|
+
|
211
|
+
```
|
212
|
+
The following indexes should be created to back model-level uniqueness validations:
|
213
|
+
users: email
|
214
|
+
```
|
215
|
+
|
216
|
+
This means that you should create a unique index on `users.email`.
|
217
|
+
|
218
|
+
### Detecting Missing Non-`NULL` Constraints
|
219
|
+
|
220
|
+
If there's an unconditional presence validation on a column then it should be
|
221
|
+
marked as non-`NULL`-able at the database level.
|
222
|
+
|
223
|
+
In order to detect columns whose presence is required but that are marked
|
224
|
+
`null: true` in the database run the following command:
|
225
|
+
|
226
|
+
```
|
227
|
+
bundle exec rake active_record_doctor:missing_non_null_constraint
|
228
|
+
```
|
229
|
+
|
230
|
+
The output of the command is similar to:
|
231
|
+
|
232
|
+
```
|
233
|
+
The following columns should be marked as `null: false`:
|
234
|
+
users: name
|
235
|
+
|
236
|
+
```
|
237
|
+
|
238
|
+
You can mark the columns mentioned in the output as `null: false` by creating a
|
239
|
+
migration and calling `change_column_null`.
|
240
|
+
|
241
|
+
This validator skips models whose corresponding database tables don't exist.
|
242
|
+
|
243
|
+
### Detecting Missing Presence Validations
|
244
|
+
|
245
|
+
If a column is marked as `null: false` then it's likely it should have the
|
246
|
+
corresponding presence validator.
|
247
|
+
|
248
|
+
In order to detect models lacking these validations run:
|
249
|
+
|
250
|
+
```
|
251
|
+
bundle exec rake active_record_doctor:missing_presence_validation
|
252
|
+
```
|
253
|
+
|
254
|
+
The output of the command looks like this:
|
255
|
+
|
256
|
+
```
|
257
|
+
The following models and columns should have presence validations:
|
258
|
+
User: email, name
|
259
|
+
```
|
260
|
+
|
261
|
+
This means `User` should have a presence validator on `email` and `name`.
|
262
|
+
|
263
|
+
This validator skips models whose corresponding database tables don't exist.
|
264
|
+
|
265
|
+
### Detecting Incorrect Presence Validations on Boolean Columns
|
266
|
+
|
267
|
+
A boolean column's presence should be validated using inclusion or exclusion
|
268
|
+
validators instead of the usual presence validator.
|
269
|
+
|
270
|
+
In order to detect boolean columns whose presence is validated incorrectly run:
|
271
|
+
|
272
|
+
```
|
273
|
+
bundle exec rake active_record_doctor:incorrect_boolean_presence_validation
|
274
|
+
```
|
275
|
+
|
276
|
+
The output of the command looks like this:
|
277
|
+
|
278
|
+
```
|
279
|
+
The presence of the following boolean columns is validated incorrectly:
|
280
|
+
User: active
|
281
|
+
```
|
282
|
+
|
283
|
+
This means `active` is validated with `presence: true` instead of
|
284
|
+
`inclusion: { in: [true, false] }` or `exclusion: { in: [nil] }`.
|
285
|
+
|
286
|
+
This validator skips models whose corresponding database tables don't exist.
|
287
|
+
|
288
|
+
### Detecting Incorrect `dependent` Option on Associations
|
289
|
+
|
290
|
+
Cascading model deletions can be sped up with `dependent: :delete_all` (to
|
291
|
+
delete all dependent models with one SQL query) but only if the deleted models
|
292
|
+
have no callbacks as they're skipped.
|
293
|
+
|
294
|
+
This can lead to two types of errors:
|
295
|
+
|
296
|
+
- Using `delete_all` when dependent models define callbacks - they will NOT be
|
297
|
+
invoked.
|
298
|
+
- Using `destroy` when dependent models define no callbacks - dependent models
|
299
|
+
will be loaded one-by-one with no reason
|
300
|
+
|
301
|
+
In order to detect associations affected by the two aforementioned problems run
|
302
|
+
the following command:
|
303
|
+
|
304
|
+
```
|
305
|
+
bundle exec rake active_record_doctor:incorrect_dependent_option
|
306
|
+
```
|
307
|
+
|
308
|
+
The output of the command looks like this:
|
309
|
+
|
310
|
+
```
|
311
|
+
The following associations might be using invalid dependent settings:
|
312
|
+
Company: users loads models one-by-one to invoke callbacks even though the related model defines none - consider using `dependent: :delete_all`
|
313
|
+
Post: comments skips callbacks that are defined on the associated model - consider changing to `dependent: :destroy` or similar
|
314
|
+
```
|
315
|
+
|
316
|
+
## Ruby and Rails Compatibility Policy
|
317
|
+
|
318
|
+
The goal of the policy is to ensure proper functioning in reasonable
|
319
|
+
combinations of Ruby and Rails versions. Specifically:
|
320
|
+
|
321
|
+
1. If a Rails version is officially supported by the Rails Core Team then it's
|
322
|
+
supported by `active_record_doctor`.
|
323
|
+
2. If a Ruby version is compatible with a supported Rails version then it's
|
324
|
+
also supported by `active_record_doctor`.
|
325
|
+
3. Only most recent teeny Ruby versions and patch Rails versions are supported.
|
326
|
+
|
187
327
|
## Author
|
188
328
|
|
189
329
|
This gem is developed and maintained by [Greg Navis](http://www.gregnavis.com).
|
data/lib/active_record_doctor.rb
CHANGED
@@ -1,4 +1,22 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "active_record_doctor/printers"
|
4
|
+
require "active_record_doctor/printers/io_printer"
|
5
|
+
require "active_record_doctor/railtie" if defined?(Rails) && defined?(Rails::Railtie)
|
6
|
+
require "active_record_doctor/detectors"
|
7
|
+
require "active_record_doctor/detectors/base"
|
8
|
+
require "active_record_doctor/detectors/missing_presence_validation"
|
9
|
+
require "active_record_doctor/detectors/missing_foreign_keys"
|
10
|
+
require "active_record_doctor/detectors/missing_unique_indexes"
|
11
|
+
require "active_record_doctor/detectors/incorrect_boolean_presence_validation"
|
12
|
+
require "active_record_doctor/detectors/extraneous_indexes"
|
13
|
+
require "active_record_doctor/detectors/unindexed_deleted_at"
|
14
|
+
require "active_record_doctor/detectors/undefined_table_references"
|
15
|
+
require "active_record_doctor/detectors/missing_non_null_constraint"
|
16
|
+
require "active_record_doctor/detectors/unindexed_foreign_keys"
|
17
|
+
require "active_record_doctor/detectors/incorrect_dependent_option"
|
18
|
+
require "active_record_doctor/task"
|
19
|
+
require "active_record_doctor/version"
|
20
|
+
|
21
|
+
module ActiveRecordDoctor # :nodoc:
|
4
22
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/core_ext/class/subclasses"
|
5
|
+
|
6
|
+
module ActiveRecordDoctor
|
7
|
+
# Container module for all detectors, implemented as separate classes.
|
8
|
+
module Detectors
|
9
|
+
def self.all
|
10
|
+
ActiveRecordDoctor::Detectors::Base.subclasses
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordDoctor
|
4
|
+
module Detectors
|
5
|
+
# Base class for all active_record_doctor detectors.
|
6
|
+
class Base
|
7
|
+
class << self
|
8
|
+
attr_reader :description
|
9
|
+
|
10
|
+
def run
|
11
|
+
new.run
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def problems(problems, options = {})
|
18
|
+
[problems, options]
|
19
|
+
end
|
20
|
+
|
21
|
+
def connection
|
22
|
+
@connection ||= ActiveRecord::Base.connection
|
23
|
+
end
|
24
|
+
|
25
|
+
def indexes(table_name)
|
26
|
+
connection.indexes(table_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def tables
|
30
|
+
connection.tables
|
31
|
+
end
|
32
|
+
|
33
|
+
def table_exists?(table_name)
|
34
|
+
connection.table_exists?(table_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def views
|
38
|
+
@views ||=
|
39
|
+
if connection.respond_to?(:views)
|
40
|
+
connection.views
|
41
|
+
elsif connection.adapter_name == "PostgreSQL"
|
42
|
+
ActiveRecord::Base.connection.execute(<<-SQL).map { |tuple| tuple.fetch("relname") }
|
43
|
+
SELECT c.relname FROM pg_class c WHERE c.relkind IN ('m', 'v')
|
44
|
+
SQL
|
45
|
+
else # rubocop:disable Style/EmptyElse
|
46
|
+
# We don't support this Rails/database combination yet.
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def hash_from_pairs(pairs)
|
52
|
+
Hash[*pairs.flatten(1)]
|
53
|
+
end
|
54
|
+
|
55
|
+
def eager_load!
|
56
|
+
Rails.application.eager_load!
|
57
|
+
end
|
58
|
+
|
59
|
+
def models
|
60
|
+
ActiveRecord::Base.descendants
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,32 +1,23 @@
|
|
1
|
-
|
2
|
-
require "active_record_doctor/printers/io_printer"
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
module Tasks
|
6
|
-
class ExtraneousIndexes
|
7
|
-
include Compatibility
|
8
|
-
|
9
|
-
def self.run
|
10
|
-
new.run
|
11
|
-
end
|
3
|
+
require "active_record_doctor/detectors/base"
|
12
4
|
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
module ActiveRecordDoctor
|
6
|
+
module Detectors
|
7
|
+
# Detect indexes whose function can be overtaken by other indexes. For example, an index on columns A, B, and C
|
8
|
+
# can also serve as an index on A and A, B.
|
9
|
+
class ExtraneousIndexes < Base
|
10
|
+
@description = "Detect extraneous indexes"
|
16
11
|
|
17
12
|
def run
|
18
|
-
|
13
|
+
problems(subindexes_of_multi_column_indexes + indexed_primary_keys)
|
19
14
|
end
|
20
15
|
|
21
16
|
private
|
22
17
|
|
23
|
-
def extraneous_indexes
|
24
|
-
subindexes_of_multi_column_indexes + indexed_primary_keys
|
25
|
-
end
|
26
|
-
|
27
18
|
def subindexes_of_multi_column_indexes
|
28
19
|
tables.reject do |table|
|
29
|
-
"schema_migrations"
|
20
|
+
table == "schema_migrations"
|
30
21
|
end.flat_map do |table|
|
31
22
|
indexes = indexes(table)
|
32
23
|
maximum_indexes = indexes.select do |index|
|
@@ -51,7 +42,7 @@ module ActiveRecordDoctor
|
|
51
42
|
|
52
43
|
def indexed_primary_keys
|
53
44
|
@indexed_primary_keys ||= tables.reject do |table|
|
54
|
-
"schema_migrations"
|
45
|
+
table == "schema_migrations"
|
55
46
|
end.map do |table|
|
56
47
|
[
|
57
48
|
table,
|
@@ -90,15 +81,7 @@ module ActiveRecordDoctor
|
|
90
81
|
end
|
91
82
|
|
92
83
|
def indexes(table_name)
|
93
|
-
|
94
|
-
end
|
95
|
-
|
96
|
-
def tables
|
97
|
-
@tables ||= connection_tables
|
98
|
-
end
|
99
|
-
|
100
|
-
def connection
|
101
|
-
@connection ||= ActiveRecord::Base.connection
|
84
|
+
super.select { |index| index.columns.is_a?(Array) }
|
102
85
|
end
|
103
86
|
end
|
104
87
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record_doctor/detectors/base"
|
4
|
+
|
5
|
+
module ActiveRecordDoctor
|
6
|
+
module Detectors
|
7
|
+
# Find instances of boolean column presence validations that use presence/absence instead of includes/excludes.
|
8
|
+
class IncorrectBooleanPresenceValidation < Base
|
9
|
+
@description = "Detect boolean columns with presence/absence instead of includes/excludes validators"
|
10
|
+
|
11
|
+
def run
|
12
|
+
eager_load!
|
13
|
+
|
14
|
+
problems(hash_from_pairs(models.reject do |model|
|
15
|
+
model.table_name.nil? ||
|
16
|
+
model.table_name == "schema_migrations" ||
|
17
|
+
!table_exists?(model.table_name)
|
18
|
+
end.map do |model|
|
19
|
+
[
|
20
|
+
model.name,
|
21
|
+
connection.columns(model.table_name).select do |column|
|
22
|
+
column.type == :boolean &&
|
23
|
+
has_presence_validator?(model, column)
|
24
|
+
end.map(&:name)
|
25
|
+
]
|
26
|
+
end.reject do |_model_name, columns|
|
27
|
+
columns.empty?
|
28
|
+
end))
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def has_presence_validator?(model, column)
|
34
|
+
model.validators.any? do |validator|
|
35
|
+
validator.kind == :presence && validator.attributes.include?(column.name.to_sym)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|