gregnavis / active_record_doctor Goto Github PK
View Code? Open in Web Editor NEWIdentify database issues before they hit production.
License: MIT License
Identify database issues before they hit production.
License: MIT License
Hi, thanks for the gem, we find it very useful!
Just to let you know that a minor-version upgrade from 1.7.1 => 1.8.0 is not backwards compatible, with errors such as
cannot load such file -- active_record_doctor/tasks/extraneous_indexes
As I couldn't see anything mentioning this in the changelog, I had a dig and it looks like this came from b192090 -
if this project aims to use semantic versioning, then this change should have been part of a major version change.
For the record, the fix was easy, we just renamed /tasks/
to /detectors/
in our require paths for active_record_doctor
and the corresponding module name (ActiveRecordDoctor::Tasks
=> ActiveRecordDoctor::Detectors
) where we explicitly referred to one.
I'd like to run active_record_doctor as part of my CI, but need a way to ignore violations and/or false positives.
class User
has_one :account
end
class Account
belongs_to :user
end
has_one
should be backed by a unique index (on accounts.user_id
), because otherwise user can have multiple accounts in the database.
There is already a check MissingUniqueIndexes
, checking unique validations without indexes. It is pretty generically named and seems like this new check would be added into there? Or a new check created?
@gregnavis, wdyt?
So it is easier to incorporate into existing large codebases. Currently the user should manually copy-edit the error output into the ignore rules.
My model
# Table name: configs
#
# configurable_id :integer
# configurable_type :string
#
# Indexes
#
# index_configs_on_configurable_type_and_configurable_id (configurable_type,configurable_id)
but tool do not see this association and try to add index to configurable_id
i'm on rails 5.1.1
(ruby 2.4.1) and it seems bundler resolves active_record_doctor to 1.0.1
, which depends on rails 4.2.x
:
active_record_doctor was resolved to 1.0.1, which depends on rails (~> 4.2)
if i track master, then it resolves to 1.3.0
, but neither seem to support rails 5.1.x
:
active_record_doctor was resolved to 1.3.0, which depends on rails (< 5.1, >= 4.2)
an update would be appreciated! ๐
I have a Polymorphic belongs_to
like this:
belongs_to :subtype, polymorphic: true, dependent: :destroy, autosave: true
I get this error when running the rake task:
ArgumentError: Polymorphic associations do not support computing the class.
/usr/local/bundle/gems/activerecord-6.1.4.7/lib/active_record/reflection.rb:407:in `compute_class'
/usr/local/bundle/gems/activerecord-6.1.4.7/lib/active_record/reflection.rb:366:in `klass'
/usr/local/bundle/gems/active_record_doctor-1.9.0/lib/active_record_doctor/detectors/incorrect_dependent_option.rb:45:in `block (2 levels) in detect'
/usr/local/bundle/gems/active_record_doctor-1.9.0/lib/active_record_doctor/detectors/incorrect_dependent_option.rb:42:in `each'
/usr/local/bundle/gems/active_record_doctor-1.9.0/lib/active_record_doctor/detectors/incorrect_dependent_option.rb:42:in `block in detect'
/usr/local/bundle/gems/active_record_doctor-1.9.0/lib/active_record_doctor/detectors/incorrect_dependent_option.rb:35:in `each'
/usr/local/bundle/gems/active_record_doctor-1.9.0/lib/active_record_doctor/detectors/incorrect_dependent_option.rb:35:in `detect'
I guess a) it shouldn't error out and b) should give some advice on what to do.
We've introduced models backed by database views to improve some associations, and now the doctor is complaining that the model doesn't have a valid table.
Which fair point: it doesn't. But the model is valid and works well in production, so we've had to remove this task from our CI process as it was preventing the build from being successful.
undefined_table_references
should both get the list of tables and views in the database, since ActiveRecord works well with views to back a (read only) model.
It isn't currently possible to run the active_record_doctor:incorrect_length_validation
task:
Don't know how to build task 'active_record_doctor:incorrect_length_validation' (See the list of available tasks with `rake --tasks`)
Did you mean? active_record_doctor:incorrect_dependent_option
/usr/local/bundle/gems/rake-13.0.6/exe/rake:27:in `<top (required)>'
/usr/local/bin/bundle:23:in `load'
/usr/local/bin/bundle:23:in `<main>'
Tested on:
Ruby ruby-2.5.3
* active_record_doctor (1.9.0)
Summary: Identify database issues before they hit production.
Homepage: https://github.com/gregnavis/active_record_doctor
Path: /usr/local/bundle/gems/active_record_doctor-1.9.0
Here is a fragment from my schema.rb
file that is flagged by rails active_record_doctor:extraneous_indexes
as having an extraneous index that I believe is a false positive.
t.index ["code", "account_id"], name: "index_skus_on_code_and_account_id", unique: true
t.index ["code"], name: "index_skus_on_code_trgm", opclass: :gin_trgm_ops, using: :gin
The output from running rails active_record_doctor:extraneous_indexes
is:
The following indexes are extraneous and can be removed:
index_skus_on_code_trgm (can be handled by index_skus_on_code_and_account_id)
Thanks for making such a useful tool.
The checker makes an assumption that foreign-key like columns should be in the form of ..._id
.
It can:
_id
which is not a reference to other table, but, for example, some id from the external resource (users.stripe_id
).belongs_to :user, foreign_key: :user_key
I think, what should be really checked are models having non-polymorphic belongs_to
associations without foreign keys.
Checking only models would make it easier to also ignore the case, when underlying models belong to different databases.
We can use SchemaCache
internally to not request the same information from the db over and over. I am sure, this will improve runtime on decent sizeable projects with many models/tables.
Unfortunately, I do not have a decent project under my hands right now to test the actual improvement.
On Rails 7 (and probably on Rails 6 too) with new app I will get following errors, when I run bundle exec rails active_record_doctor:undefined_table_references
:
ActionMailbox::InboundEmail references a non-existent table or view named action_mailbox_inbound_emails
ActiveStorage::VariantRecord references a non-existent table or view named active_storage_variant_records
ActiveStorage::Blob references a non-existent table or view named active_storage_blobs
ActiveStorage::Attachment references a non-existent table or view named active_storage_attachments
ActionText::RichText references a non-existent table or view named action_text_rich_texts
ActionText::EncryptedRichText references a non-existent table or view named action_text_rich_texts
I think it is intended, but in many cases it should not be an error, because most new apps are not using those features a long time, if ever at all (e.g. InboundEmail
).
The documentation says that those tables are ignored by default for other tasks.
Maybe it is too much to ignore them in this case, then it should be only a warning?!
Probably it is hard to detect, if those models are used and only in this case to produce an error?
As part of our test suite we run ActiveRecordDoctor and also create anonymous subclasses of ActiveRecord::Base
- the latter causes an exception in the former
Traceback (most recent call last):
9: from repro.rb:34:in `<main>'
8: from /Users/owenstephens/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/active_record_doctor-1.9.0/lib/active_record_doctor/runner.rb:14:in `run_one'
7: from /Users/owenstephens/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/active_record_doctor-1.9.0/lib/active_record_doctor/errors.rb:5:in `handle_exception'
6: from /Users/owenstephens/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/active_record_doctor-1.9.0/lib/active_record_doctor/runner.rb:15:in `block in run_one'
5: from /Users/owenstephens/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/active_record_doctor-1.9.0/lib/active_record_doctor/detectors/base.rb:11:in `run'
4: from /Users/owenstephens/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/active_record_doctor-1.9.0/lib/active_record_doctor/detectors/base.rb:40:in `run'
3: from /Users/owenstephens/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/active_record_doctor-1.9.0/lib/active_record_doctor/detectors/missing_unique_indexes.rb:32:in `detect'
2: from /Users/owenstephens/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/active_record_doctor-1.9.0/lib/active_record_doctor/detectors/base.rb:145:in `models'
1: from /Users/owenstephens/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/active_record_doctor-1.9.0/lib/active_record_doctor/detectors/base.rb:145:in `reject'
/Users/owenstephens/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/active_record_doctor-1.9.0/lib/active_record_doctor/detectors/base.rb:146:in `block in models': undefined method `start_with?' for nil:NilClass (NoMethodError)
here's a simple reproduction script:
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
gem "rails", "= 5.2.6.2"
gem "active_record_doctor", "= 1.9.0"
end
require "active_record"
require "active_record_doctor"
some_anonymous_model = Class.new(ActiveRecord::Base)
runner = ActiveRecordDoctor::Runner.new(ActiveRecordDoctor.load_config_with_defaults(nil))
runner.run_one(:missing_unique_indexes)
Would be happy to help with PRs, if this is something you are looking for.
I just installed active_record_doctor 1.10.0 and ran it for the first time and the following error was thrown:
% bundle exec rake active_record_doctor --trace
** Invoke active_record_doctor (first_time)
** Invoke active_record_doctor:setup (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute active_record_doctor:setup
** Execute active_record_doctor
rake aborted!
NoMethodError: undefined method `include?' for #<Proc:0x0000000113da8568 /Users/manuel/code/myapp/app/app/models/admin_users/admin_user.rb:46 (lambda)>
!validator.options.fetch(:in, []).include?(nil)
^^^^^^^^^
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/detectors/missing_presence_validation.rb:57:in `block in inclusion_validator_present?'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/detectors/missing_presence_validation.rb:54:in `any?'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/detectors/missing_presence_validation.rb:54:in `inclusion_validator_present?'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/detectors/missing_presence_validation.rb:46:in `validator_present?'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/detectors/missing_presence_validation.rb:31:in `block (2 levels) in detect'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/detectors/missing_presence_validation.rb:29:in `each'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/detectors/missing_presence_validation.rb:29:in `block in detect'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/detectors/missing_presence_validation.rb:26:in `each'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/detectors/missing_presence_validation.rb:26:in `detect'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/detectors/base.rb:50:in `run'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/detectors/base.rb:17:in `run'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/runner.rb:15:in `block in run_one'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/errors.rb:5:in `handle_exception'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/runner.rb:14:in `run_one'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/runner.rb:26:in `block in run_all'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/runner.rb:25:in `each'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/runner.rb:25:in `run_all'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/active_record_doctor-1.10.0/lib/active_record_doctor/rake/task.rb:60:in `block in define'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:281:in `block in execute'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:281:in `each'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:281:in `execute'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/sentry-ruby-5.4.2/lib/sentry/rake.rb:26:in `execute'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:219:in `block in invoke_with_call_chain'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:199:in `synchronize'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:199:in `invoke_with_call_chain'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:188:in `invoke'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:160:in `invoke_task'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:116:in `block (2 levels) in top_level'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:116:in `each'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:116:in `block in top_level'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:125:in `run_with_threads'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:110:in `top_level'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:83:in `block in run'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:186:in `standard_exception_handling'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:80:in `run'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/rake-13.0.6/exe/rake:27:in `<top (required)>'
/Users/manuel/.rbenv/versions/3.1.2/bin/rake:25:in `load'
/Users/manuel/.rbenv/versions/3.1.2/bin/rake:25:in `<top (required)>'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/lib/bundler/cli/exec.rb:58:in `load'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/lib/bundler/cli/exec.rb:58:in `kernel_load'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/lib/bundler/cli/exec.rb:23:in `run'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/lib/bundler/cli.rb:485:in `exec'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/lib/bundler/cli.rb:31:in `dispatch'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/lib/bundler/cli.rb:25:in `start'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/exe/bundle:48:in `block in <top (required)>'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/lib/bundler/friendly_errors.rb:120:in `with_friendly_errors'
/Users/manuel/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bundler-2.3.20/exe/bundle:36:in `<top (required)>'
/Users/manuel/.rbenv/versions/3.1.2/bin/bundle:25:in `load'
/Users/manuel/.rbenv/versions/3.1.2/bin/bundle:25:in `<main>'
Tasks: TOP => active_record_doctor
The code at admin_user.rb:46 is:
validates :my_option, inclusion: { in: -> { _1.role?(:superadmin, :admin) ?
[true, false] :
[false] },
message: "can only be selected for admins with role superadmin or admin" }
Let me know if I can do anything else to debug!
This is PostgreSQL specific. The problem is described here - https://github.com/fatkodima/online_migrations#adding-a-json-column.
Also seems like jsonb
is the preferred type and it also shows a better performance.
In Rails 5 belongs_to
automatically adds presence validation.
The problem is that we check for company_id but Rails add presence validation for company
.
Would be enough to check for existence of validation without _id
?
Hi,
Does it support Rails 6 ?
Thanks.
Currently no active_record_doctor
tasks are showing up for me when running rake -T
We'd like to check multiple schemas in our DB. At present we are using the gem with great success in specs thus:
output = ActiveRecordDoctor::Tasks::UnindexedForeignKeys.new.run
However we can't see an easy way to move between schemas. Is that supported? Thanks!
It looks like ARD crashes when some relations don't exist:
C:\Users\Lucius Riccio\Documents\GitHub\COVID-CO2-tracker\COVID_CO2_TRACKER>bundle exec rake active_record_doctor:missing_non_null_constraint --trace
** Invoke active_record_doctor:missing_non_null_constraint (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute active_record_doctor:missing_non_null_constraint
rake aborted!
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: relation "action_text_rich_texts" does not exist
LINE 8: WHERE a.attrelid = '"action_text_rich_texts"'::regclass
^
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:19:in `exec'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:19:in `block (2 levels) in query'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/dependencies/interlock.rb:47:in `permit_concurrent_loads'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:18:in `block in query'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:696:in `block (2 levels) in log'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:695:in `block in log'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/notifications/instrumenter.rb:24:in `instrument'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:687:in `log'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:17:in `query'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:824:in `column_definitions'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/abstract/schema_statements.rb:116:in `columns'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/active_record_doctor-1.7.1/lib/active_record_doctor/tasks/missing_non_null_constraint.rb:14:in `block in run'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/active_record_doctor-1.7.1/lib/active_record_doctor/tasks/missing_non_null_constraint.rb:11:in `map'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/active_record_doctor-1.7.1/lib/active_record_doctor/tasks/missing_non_null_constraint.rb:11:in `run'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/active_record_doctor-1.7.1/lib/active_record_doctor/tasks/base.rb:7:in `run'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/active_record_doctor-1.7.1/lib/tasks/active_record_doctor.rake:17:in `block in mount'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:281:in `block in execute'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:281:in `each'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:281:in `execute'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:219:in `block in invoke_with_call_chain'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:199:in `synchronize'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:199:in `invoke_with_call_chain'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:188:in `invoke'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:160:in `invoke_task'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:116:in `block (2 levels) in top_level'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:116:in `each'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:116:in `block in top_level'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:125:in `run_with_threads'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:110:in `top_level'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:83:in `block in run'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:186:in `standard_exception_handling'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:80:in `run'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/exe/rake:27:in `<top (required)>'
C:/Ruby30-x64/bin/rake:31:in `load'
C:/Ruby30-x64/bin/rake:31:in `<main>'
Caused by:
PG::UndefinedTable: ERROR: relation "action_text_rich_texts" does not exist
LINE 8: WHERE a.attrelid = '"action_text_rich_texts"'::regclass
^
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:19:in `exec'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:19:in `block (2 levels) in query'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/dependencies/interlock.rb:47:in `permit_concurrent_loads'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:18:in `block in query'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:696:in `block (2 levels) in log'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:695:in `block in log'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activesupport-6.1.2.1/lib/active_support/notifications/instrumenter.rb:24:in `instrument'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/abstract_adapter.rb:687:in `log'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:17:in `query'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:824:in `column_definitions'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.2.1/lib/active_record/connection_adapters/abstract/schema_statements.rb:116:in `columns'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/active_record_doctor-1.7.1/lib/active_record_doctor/tasks/missing_non_null_constraint.rb:14:in `block in run'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/active_record_doctor-1.7.1/lib/active_record_doctor/tasks/missing_non_null_constraint.rb:11:in `map'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/active_record_doctor-1.7.1/lib/active_record_doctor/tasks/missing_non_null_constraint.rb:11:in `run'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/active_record_doctor-1.7.1/lib/active_record_doctor/tasks/base.rb:7:in `run'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/active_record_doctor-1.7.1/lib/tasks/active_record_doctor.rake:17:in `block in mount'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:281:in `block in execute'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:281:in `each'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:281:in `execute'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:219:in `block in invoke_with_call_chain'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:199:in `synchronize'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:199:in `invoke_with_call_chain'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/task.rb:188:in `invoke'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:160:in `invoke_task'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:116:in `block (2 levels) in top_level'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:116:in `each'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:116:in `block in top_level'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:125:in `run_with_threads'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:110:in `top_level'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:83:in `block in run'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:186:in `standard_exception_handling'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/lib/rake/application.rb:80:in `run'
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/rake-13.0.3/exe/rake:27:in `<top (required)>'
C:/Ruby30-x64/bin/rake:31:in `load'
C:/Ruby30-x64/bin/rake:31:in `<main>'
Tasks: TOP => active_record_doctor:missing_non_null_constraint
I ran into an issue today when I was checking for missing indexes:
bundle exec rake db:migrate
) per the docsThis generates an error message Multiple migrations have the name IndexForeignKeysIn<Model>
. The migration files themselves have different time stamps. but the migration names are the same inside those files.
It would be nice to support running the missing index migration multiple times, but by default it creates a generic name and it appears that Rails cares about the class name of the migration. I think it should be acceptable to append a timestamp into the name, so it's IndexForeignKeysIn<Model><timestamp>
, so that might be an easy solution to implement.
This is more of a minor convenience issue; I envision using this tool periodically just to check and make sure we're got the right indexes and it would be nice to not have to change the class name.
I'm using Rails 5.2 along with the latest stable release of active_record_doctor.
I'm happy to submit a PR for this in the next few days unless someone else grabs it
It isn't currently possible to run the active_record_doctor:missing_unique_indexes
task:
% be rake active_record_doctor:missing_unique_indexes --trace
rake aborted!
Don't know how to build task 'active_record_doctor:missing_unique_indexes' (see --tasks)
Did you mean? active_record_doctor:missing_foreign_keys
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/task_manager.rb:59:in `[]'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:159:in `invoke_task'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:116:in `block (2 levels) in top_level'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:116:in `each'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:116:in `block in top_level'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:125:in `run_with_threads'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:110:in `top_level'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:83:in `block in run'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:186:in `standard_exception_handling'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/rake-12.3.1/lib/rake/application.rb:80:in `run'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/rake-12.3.1/exe/rake:27:in `<top (required)>'
/Users/seandmr/.rbenv/versions/2.4.4/bin/rake:23:in `load'
/Users/seandmr/.rbenv/versions/2.4.4/bin/rake:23:in `<top (required)>'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/lib/bundler/cli/exec.rb:74:in `load'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/lib/bundler/cli/exec.rb:74:in `kernel_load'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/lib/bundler/cli/exec.rb:28:in `run'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/lib/bundler/cli.rb:424:in `exec'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/lib/bundler/vendor/thor/lib/thor/invocation.rb:126:in `invoke_command'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/lib/bundler/vendor/thor/lib/thor.rb:387:in `dispatch'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/lib/bundler/cli.rb:27:in `dispatch'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/lib/bundler/vendor/thor/lib/thor/base.rb:466:in `start'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/lib/bundler/cli.rb:18:in `start'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/exe/bundle:30:in `block in <top (required)>'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/lib/bundler/friendly_errors.rb:124:in `with_friendly_errors'
/Users/seandmr/.rbenv/versions/2.4.4/lib/ruby/gems/2.4.0/gems/bundler-1.16.2/exe/bundle:22:in `<top (required)>'
/Users/seandmr/.rbenv/versions/2.4.4/bin/bundle:23:in `load'
/Users/seandmr/.rbenv/versions/2.4.4/bin/bundle:23:in `<main>'
.../lib/group_state_manager.rb:32: warning: already initialized constant GroupStateManager::LOGGING_ENABLED
.../lib/group_state_manager.rb:32: warning: previous definition of LOGGING_ENABLED was here
I get duplicate initialization of files in my lib/
directory when running the undefined_table_references
task (and only that one). See above for an example sanitized snippet.
tl;dr: missing_non_null_constraint
gives a false positive when a column has a validated check constraint enforcing that the column is NOT NULL.
We use Postgres, and have added a (validated) check constraint that ensures there are no NULL values in a particular column, but we're seeing a false positive from missing_non_null_constraint
. We used a check constraint in place of a NOT NULL constraint since the table in question is large, and we didn't want to hold the ACCESS EXCLUSIVE
lock taken by SET NOT NULL
for the duration of scanning the whole table to assert that the column contains no NULL values. Adding a NOT VALID
constraint is very quick (no lengthy table scan) and VALIDATE CONSTRAINT
only takes a SHARE UPDATE EXCLUSIVE
lock, which doesn't block concurrent read/writes to the table. Interestingly, it sounds like with Postgres 12 (which we're not yet using), if there is a validated CHECK CONSTRAINT
enforcing NOT NULL on a column, SET NOT NULL
can be performed without the expensive table scan as it can rely on the validated check constraint.
N.b. A check constraint can assert an arbitrary boolean expression, so it may be slightly tricky to determine if a given constraint always enforces NOT NULL, but I think for basic constraints it should be straightforward.
Here's a reproduction script, which assumes a PG DB has been created using createdb my_test_db
and then the script is run as DB_NAME=my_test_db DB_USERNAME=... DB_PASSWORD=... ruby repro.rb
:
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
gem "rails", "= 5.2.6.2"
gem "pg", "= 1.3.2"
gem "active_record_doctor", "= 1.9.0"
end
require "active_record"
require "active_record_doctor"
ActiveRecord::Base.establish_connection(
adapter: "postgresql",
database: ENV["DB_NAME"],
username: ENV["DB_USERNAME"],
password: ENV["DB_PASSWORD"]
)
# ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.connection.execute(<<~SQL)
DROP TABLE IF EXISTS things;
CREATE TABLE things(name text, number integer NOT NULL);
-- Ensure any newly created rows have a name
ALTER TABLE things ADD CONSTRAINT name_not_null CHECK (name IS NOT NULL) NOT VALID;
-- Ensure all existing rows conform to the constraint (typically performed separately due to the
-- differing locks that are taken by both operations)
ALTER TABLE things VALIDATE CONSTRAINT name_not_null;
SQL
class Thing < ActiveRecord::Base
validates_presence_of :name
validates_presence_of :number
end
runner = ActiveRecordDoctor::Runner.new(ActiveRecordDoctor.load_config_with_defaults(nil))
runner.run_one(:missing_non_null_constraint)
Thing.create!(name: "present", number: 1)
thing = Thing.new(name: nil, number: 2)
puts "Thing without name is valid?: #{thing.valid?}"
begin
# Ignore the validation and save anyway - it should blow up
thing.save!(validate: false)
rescue => e
puts "Caught #{e.message}"
end
The relevant output:
add `NOT NULL` to things.name - models validates its presence but it's not non-NULL in the database
Thing without name is valid?: false
Caught PG::CheckViolation: ERROR: new row for relation "things" violates check constraint "name_not_null"
DETAIL: Failing row contains (null, 2).
: INSERT INTO "things" ("number") VALUES ($1)
Showing that there is a presence validation and that the DB prevents null values for name
, but there is a false positive.
Been using active_record_doctor:missing_presence_validation and find that over 75% of the columns listed have defaults defined in the db and in this case no active record validation is needed for these.
For me having a way to ignore any columns with defaults set would be a nice feature and filter out a lot of work not needed.
Great Gem and Thanks
Hey
I have this error:
/home/XXXX/.rvm/gems/ruby-2.3.3/gems/active_record_doctor-1.3.0/lib/generators/active_record_doctor/add_indexes/add_indexes_generator.rb:30:in
content': undefined method camelize' for nil:NilClass (NoMethodError)
When I execute the command:
rails generate active_record_doctor:add_indexes unindexed_foreign_keys.txt
Thanks !
Thanks a lot for this gem, really helpful ๐!
We added those to our test suite, ensuring we don't have new database problems in the future.
Here is an example of how we use it :
describe 'Unindexed Foreign Keys' do
before { Rails.application.load_tasks }
let(:allowed) do
{
'users' => ['allowed_id'],
'articles' => %w(other_id allowed_id),
}
end
let(:missing) { ActiveRecordDoctor::Tasks::UnindexedForeignKeys.run.first }
it do
expect(allowed.keys).to match_array missing.keys
allowed.each do |table, attributes|
expect(attributes).to match_array missing[table]
end
end
end
This works great, but I would prefer not having to load rake tasks to be able to do that (before { Rails.application.load_tasks }
).
Also it would be nice to document this usage in the Readme!
Indexes on booleans are not as useful as people tend to think. On my laptop using postgres (default configuration) index was only used when <= 20% of rows satisfied the target value.
Most of the time, people add indexes for boolean columns like admin
, deleted
, banned
, which have a distribution of 1% vs 99%, so the partial index would help.
So, I propose to suggest removing indexes on boolean fields or make them partial.
It will be a good idea to require field length validations (validates :name, length: { maximum: 64 }
) when a model has a field limit in the database (t.string :name, limit: 64
).
This way we will not make a useless save and rollback to the database for invalid fields, but also have properly populated validation errors.
I would suggest modifying 'active_record_doctor:unindexed_deleted_at' to also check for the 'discarded_at' column because paranoia is more or less deprecated in favor of the 'discard' gem.ย See the notice at the top of paranoia's readme here.
When belongs_to relationships are required by default, as they are in Rails 5.0+ config defaults, i.e.
Rails.application.config.active_record.belongs_to_required_by_default = true
Then belongs_to
relationships without optional: true
have a validation attached that is not detected by MissingNonNullConstraint
when that same column does not have a null: false
constraint in the database.
I think these should be detected and reported?
cat unindexed_foreign_keys.txt
Sidekiq concurrency: 25
membership_roles inherited_from_id
memberships member_id
projects parent_id
cat config/sidekiq.yml
---
:concurrency: <%= ENV['DB_POOL'] || ENV['RAILS_MAX_THREADS'] || 5 %>
(...omitted...)
My migration is:
add_index :users, 'lower(email)', unique: true
It translates in schema.rb
as:
t.index "lower((email)::text)", name: "index_users_on_lower_email", unique: true
Note there are no surrounding brackets like we have for other fields:
add_index :users, :auth_token, unique: true
t.index ["auth_token"], name: "index_users_on_auth_token", unique: true
The error is:
NoMethodError: undefined method `to_set' for "lower((email)::text)":String
index.columns.to_set == columns.to_set && index.unique
in active_record_doctor-1.9.0/lib/active_record_doctor/detectors/missing_unique_indexes.rb:66:in block in unique_index?
This can be fixed by making sure index.columns
is wrapped into an Array:
def unique_index?(table_name, columns, scope)
columns = (Array(scope) + columns).map(&:to_s)
indexes(table_name).any? do |index|
Array(index.columns).to_set == columns.to_set && index.unique
end
end
Example:
class Word < ActiveRecord::Base
end
class Adjective < Word
belongs_to :object
end
missing_non_null_constraint reports a missing null constraint on Adjective#object when it should not.
$ bundle exec rake active_record_doctor:missing_presence_validation
rake aborted!
ActiveRecord::StatementInvalid: Could not find table 'action_text_rich_texts'
~/.rvm/gems/ruby-2.6.6/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/sqlite3_adapter.rb:358:in `table_structure'
~/.rvm/gems/ruby-2.6.6/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/abstract/schema_statements.rb:114:in `columns'
~/.rvm/gems/ruby-2.6.6/gems/active_record_doctor-1.7.1/lib/active_record_doctor/tasks/missing_presence_validation.rb:14:in `block in run'
~/.rvm/gems/ruby-2.6.6/gems/active_record_doctor-1.7.1/lib/active_record_doctor/tasks/missing_presence_validation.rb:11:in `map'
~/.rvm/gems/ruby-2.6.6/gems/active_record_doctor-1.7.1/lib/active_record_doctor/tasks/missing_presence_validation.rb:11:in `run'
~/.rvm/gems/ruby-2.6.6/gems/active_record_doctor-1.7.1/lib/active_record_doctor/tasks/base.rb:7:in `run'
~/.rvm/gems/ruby-2.6.6/gems/active_record_doctor-1.7.1/lib/tasks/active_record_doctor.rake:17:in `block in mount'
~/.rvm/gems/ruby-2.6.6/gems/airbrake-10.0.2/lib/airbrake/rake.rb:19:in `execute'
~/.rvm/gems/ruby-2.6.6/gems/rake-13.0.1/exe/rake:27:in `<top (required)>'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/lib/bundler/cli/exec.rb:63:in `load'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/lib/bundler/cli/exec.rb:63:in `kernel_load'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/lib/bundler/cli/exec.rb:28:in `run'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/lib/bundler/cli.rb:476:in `exec'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/lib/bundler/vendor/thor/lib/thor.rb:399:in `dispatch'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/lib/bundler/cli.rb:30:in `dispatch'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/lib/bundler/vendor/thor/lib/thor/base.rb:476:in `start'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/lib/bundler/cli.rb:24:in `start'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/exe/bundle:46:in `block in <top (required)>'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/lib/bundler/friendly_errors.rb:123:in `with_friendly_errors'
~/.rvm/gems/ruby-2.6.6/gems/bundler-2.1.4/exe/bundle:34:in `<top (required)>'
~/.rvm/gems/ruby-2.6.6/bin/bundle:23:in `load'
~/.rvm/gems/ruby-2.6.6/bin/bundle:23:in `<main>'
Tasks: TOP => active_record_doctor:missing_presence_validation
(See full trace by running task with --trace)
Given no one is immune to it https://m.signalvnoise.com/update-on-basecamp-3-being-stuck-in-read-only-as-of-nov-8-922am-cst/
and there's a lot of people using Rails from prior this rails/rails#26266
So a check for it would be awesome!
Just tried on current project (ruby 2.0.0p598 / rails 4.2.5.1):
โ โ rake active_record_doctor:unindexed_foreign_keys > unindexed_foreign_keys.txt
rake aborted!
NoMethodError: undefined method to_h for #Array:0x007faf6c25abc0
/Users/salf/.rvm/gems/ruby-2.0.0-p598/gems/active_record_doctor-1.0.2/lib/active_record_doctor/tasks/unindexed_foreign_keys.rb:32:in `unindexed_foreign_keys'
Hi!
when running rake active_record_doctor:missing_presence_validation
, the gem reports a missing validation error on the FK columns on join tables (used in HABTM relations).
Is this really the intended behavior of this gem? Since there is no model for the join table, it shouldn't report an error IMHO.
Example with schema:
create_table "pets", force: :cascade do |t|
t.string "name", null: false
t.date "birth", null: false
end
create_table "pets_users", id: false, force: :cascade do |t|
t.integer "users_id", null: false
t.integer "pets_id", null: false
t.index ["pets_id"], name: "index_pets_users_on_pets_id"
t.index ["users_id"], name: "index_pets_users_on_users_id"
end
create_table "users", force: :cascade do |t|
t.string "email"
end
Pet:
class Pet < ApplicationRecord
has_and_belongs_to_many :users
validates :name, presence: true
validates :birth, presence: true
end
User model
class User < ApplicationRecord
has_and_belongs_to_many :pets
end
Output:
The following models and columns should have presence validations:
HABTM_Users: users_id, pets_id
HABTM_Pets: users_id, pets_id
Expected output: no error
active_record_doctor (1.8.0)
A table with two indexes both pointing to the same table column, crashes task extraneous_indexes
. Example indexes shown below, including error message:
CREATE INDEX others_thing_fkey
ON others
USING btree
(thing_id);
CREATE INDEX thing_fkey
ON others
USING btree
(thing_id);
NoMethodError: undefined method `name' for nil:NilClass
/ruby-2.2.0/gems/active_record_doctor-1.1.1/lib/active_record_doctor/tasks/extraneous_indexes.rb:39:in `block (2 levels) in extraneous_indexes'
/ruby-2.2.0/gems/active_record_doctor-1.1.1/lib/active_record_doctor/tasks/extraneous_indexes.rb:34:in `map'
/ruby-2.2.0/gems/active_record_doctor-1.1.1/lib/active_record_doctor/tasks/extraneous_indexes.rb:34:in `block in extraneous_indexes'
/ruby-2.2.0/gems/active_record_doctor-1.1.1/lib/active_record_doctor/tasks/extraneous_indexes.rb:24:in `map'
/ruby-2.2.0/gems/active_record_doctor-1.1.1/lib/active_record_doctor/tasks/extraneous_indexes.rb:24:in `extraneous_indexes'
/ruby-2.2.0/gems/active_record_doctor-1.1.1/lib/active_record_doctor/tasks/extraneous_indexes.rb:15:in `run'
/ruby-2.2.0/gems/active_record_doctor-1.1.1/lib/active_record_doctor/tasks/extraneous_indexes.rb:7:in `run'
/ruby-2.2.0/gems/active_record_doctor-1.1.1/lib/tasks/active_record_doctor_tasks.rake:10:in `block (2 levels) in <top (required)>'
Great Gem! very helpful. Let me know if you require further details.
Thanks
Any plans to update the gemfiles to Rails 5.2?
Applicable readme section: https://github.com/gregnavis/active_record_doctor#removing-extraneous-indexes.
For the sake of this explanation, let's say we have a rooms
table that has rooms.room_number
and rooms.floor_number
. We build a multi-column index with floor_number
first, room_number
second.
A multi-column index in SQL works by (essentially) concatenating the values of the two columns. Here's a simplified drawing of that:
floor_number_room_number_index
1;1
1;2
1;3
2;1
2;2
2;3
Indexes are fast in part because they group the values. In the example above, only the floor_number
is grouped together. So this index will work for floor_number
. But room_number
is not grouped, and the database is smart enough to know that. It will not use this index for room_number
, but will do a full table search instead.
The worst part of all this is that it is even more complicated than that. Check out this article for a more complete explanation. There's also a useful SO question on the topic.
At incorrect dependent option says that you can use the configurations enabled
, ignore_models
and ignore_columns
, on that case instead of ignore_columns
the configuration allows you to use ignore_associations
$ bundle install
Fetching gem metadata from https://rails-assets.org/...
Fetching version metadata from https://rails-assets.org/..
Fetching gem metadata from https://rails-assets.org/...
Fetching gem metadata from https://rubygems.org/
Fetching version metadata from https://rails-assets.org/..
Fetching version metadata from https://rubygems.org/
Fetching dependency metadata from https://rails-assets.org/..
Fetching dependency metadata from https://rubygems.org/
Resolving dependencies...
Bundler could not find compatible versions for gem "rails":
In snapshot (Gemfile.lock):
rails (= 5.0.0)
In Gemfile:
rails (~> 5.0.0)
active_record_doctor was resolved to 1.0.1, which depends on
rails (~> 4.2)
Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.
A non-unique index should never be considered an equivalent of a unique index. If the two indexes are on the same columns then the unique one should be preferred as dropping it would change the semantics of the app. There are a few cases to consider.
With indexes like:
add_index :users, :last_name
add_index :users, :last_name, unique: true, name: :unique_index_on_users_last_name
active_record_doctor is likely to recommend dropping the first index which is wrong. The first index should be dropped.
For example:
add_index :users, [:last_name, :first_name]
add_index :users, :last_name, unique: true
In this case no index should be dropped.
For example:
add_index :users, [:last_name, :first_name], unique: true
add_index :users, :last_name
In the situation above, the second index should be dropped. (Note that case 1 is a special case of case 3).
Scrolling through the README to find the name of the task I want to run is a little cumbersome. What do you think about listing them at the top of the file with links to the full descriptions?
10343 % rake active_record_doctor:missing_foreign_keys
rake aborted!
NotImplementedError: foreign_keys is not implemented
<gems>/activerecord-5.0.3/lib/active_record/connection_adapters/abstract/schema_statements.rb:864:in `foreign_keys'
<gems>/active_record_doctor-1.4.1/lib/active_record_doctor/tasks/missing_foreign_keys.rb:48:in `foreign_key?'
<gems>/active_record_doctor-1.4.1/lib/active_record_doctor/tasks/missing_foreign_keys.rb:34:in `block (2 levels) in missing_foreign_keys'
<gems>/active_record_doctor-1.4.1/lib/active_record_doctor/tasks/missing_foreign_keys.rb:29:in `select'
<gems>/active_record_doctor-1.4.1/lib/active_record_doctor/tasks/missing_foreign_keys.rb:29:in `block in missing_foreign_keys'
<gems>/active_record_doctor-1.4.1/lib/active_record_doctor/tasks/missing_foreign_keys.rb:26:in `map'
<gems>/active_record_doctor-1.4.1/lib/active_record_doctor/tasks/missing_foreign_keys.rb:26:in `missing_foreign_keys'
<gems>/active_record_doctor-1.4.1/lib/active_record_doctor/tasks/missing_foreign_keys.rb:18:in `run'
<gems>/active_record_doctor-1.4.1/lib/active_record_doctor/tasks/missing_foreign_keys.rb:10:in `run'
<gems>/active_record_doctor-1.4.1/lib/tasks/active_record_doctor_tasks.rake:16:in `block (2 levels) in <top (required)>'
Tasks: TOP => active_record_doctor:missing_foreign_keys
(See full trace by running task with --trace)
10357 %
I have some improvements in mind, but need to verify this is aligned with the project goals, before implementing.
bin/active_record_doctor
) to simply run all checks..yml
) to be able to setup, for example, which checks to skip or which table/columns to ignore.minitest-fork_executor
gem, test/model_factory.rb
, code in test/setup.rb
. I can reimplement how things are tested and get rid of all of that.rails
, railties
, activerecord
, activesupport
, but should really depend on activerecord
onlyBUNDLE_GEMFILE=... bundle exec rake
. May be a good idea to add a dev dependency on appraisals
gem to do this.When running bundle exec rake active_record_doctor:undefined_table_references
on an app (Rails 6, FWIW) with MySQL, it throws a NameError: uninitialized constant ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
exception. Obviously, there's a DB type check missing somewhere. All others are functional. This isn't something I'm worried about at the moment or I'd try to form a PR, but just passing it on.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.