gregnavis / active_record_doctor

Identify database issues before they hit production.
MIT License
1.71k stars 56 forks source link

Crashes when using invalid `source` in a `has_many through:` relation #181

Open mediafinger opened 1 month ago

mediafinger commented 1 month ago

Crashes when using invalid source in a has_many through: relation

I misconfigured an ActiveRecord relation and didn't notice, as the app and specs were running fine (the app doesn't use the relation directly yet). But when running active_record_doctor I only received an error message which left me quite clueless - see below:


$> 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 `active_record' for nil (NoMethodError)

                [[association.source_reflection.active_record], "join"]
                                               ^^^^^^^^^^^^^^
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/incorrect_dependent_option.rb:88:in `block (2 levels) in detect'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:318:in `block (3 levels) in each_association'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/logger/dummy.rb:7:in `log'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:96:in `log'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:317:in `block (2 levels) in each_association'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:304:in `each'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:304:in `block in each_association'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/logger/dummy.rb:7:in `log'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:96:in `log'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:297:in `each_association'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/incorrect_dependent_option.rb:55:in `block in detect'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:183:in `block (3 levels) in each_model'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/logger/dummy.rb:7:in `log'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:96:in `log'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:182:in `block (2 levels) in each_model'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:169:in `each'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:169:in `block in each_model'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/logger/dummy.rb:7:in `log'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:96:in `log'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:168:in `each_model'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/incorrect_dependent_option.rb:54:in `detect'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:53:in `block in run'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/logger/dummy.rb:7:in `log'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:96:in `log'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:49:in `run'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/detectors/base.rb:17:in `run'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/runner.rb:16:in `block in run_one'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/errors.rb:5:in `handle_exception'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/runner.rb:15:in `run_one'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/runner.rb:31:in `block in run_all'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/runner.rb:30:in `each'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/runner.rb:30:in `run_all'
/Users/andy/.gem/ruby/3.3.4/gems/active_record_doctor-1.14.0/lib/active_record_doctor/rake/task.rb:60:in `block in define'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/task.rb:281:in `block in execute'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/task.rb:281:in `each'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/task.rb:281:in `execute'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/task.rb:219:in `block in invoke_with_call_chain'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/task.rb:199:in `synchronize'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/task.rb:199:in `invoke_with_call_chain'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/task.rb:188:in `invoke'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/application.rb:188:in `invoke_task'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/application.rb:138:in `block (2 levels) in top_level'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/application.rb:138:in `each'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/application.rb:138:in `block in top_level'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/application.rb:147:in `run_with_threads'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/application.rb:132:in `top_level'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/application.rb:83:in `block in run'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/application.rb:214:in `standard_exception_handling'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/lib/rake/application.rb:80:in `run'
/Users/andy/.gem/ruby/3.3.4/gems/rake-13.2.1/exe/rake:27:in `<top (required)>'
/Users/andy/.gem/ruby/3.3.4/bin/rake:25:in `load'
/Users/andy/.gem/ruby/3.3.4/bin/rake:25:in `<top (required)>'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/lib/bundler/cli/exec.rb:58:in `load'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/lib/bundler/cli/exec.rb:58:in `kernel_load'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/lib/bundler/cli/exec.rb:23:in `run'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/lib/bundler/cli.rb:455:in `exec'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/lib/bundler/vendor/thor/lib/thor/command.rb:28:in `run'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/lib/bundler/vendor/thor/lib/thor.rb:527:in `dispatch'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/lib/bundler/cli.rb:35:in `dispatch'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/lib/bundler/vendor/thor/lib/thor/base.rb:584:in `start'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/lib/bundler/cli.rb:29:in `start'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/exe/bundle:28:in `block in <top (required)>'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/lib/bundler/friendly_errors.rb:117:in `with_friendly_errors'
/Users/andy/.gem/ruby/3.3.4/gems/bundler-2.5.16/exe/bundle:20:in `<top (required)>'
/Users/andy/.gem/ruby/3.3.4/bin/bundle:25:in `load'
/Users/andy/.gem/ruby/3.3.4/bin/bundle:25:in `<main>'
Tasks: TOP => active_record_doctor

My code, including the wrongly named source

class ChangeRequest < ApplicationRecord
  has_many :approvals, class_name: "ChangeRequestApproval", inverse_of: :change_request, dependent: :delete_all
  has_many :approver, through: :approvals, source: :member # <-- :member is wrong

class ChangeRequestApproval < ApplicationRecord
  belongs_to :approver, class_name: "Member", inverse_of: :change_request_approvals
  belongs_to :change_request, class_name: "ChangeRequest", inverse_of: :approvals

class Member < ApplicationRecord
  has_many :change_request_approvals, class_name: "ChangeRequestApproval", inverse_of: :approver, dependent: :delete_all  
  has_many :approved_change_requests, through: :change_request_approvals, source: :change_request

After correcting the relation

class ChangeRequest < ApplicationRecord
  has_many :approvals, ...
  has_many :approver, through: :approvals, source: :approver # <-- :approver is correct

It worked again.

Next Steps

Would be great, if someone can find the motivation and time to rescue the error and instead put out a clear error message. 😅

fatkodima commented 1 month ago

I think, this will be solved by https://github.com/gregnavis/active_record_doctor/issues/179