From fcebb20b28fb1a8c41e6a6f7aad3b8007a69b2e5 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Wed, 17 Jun 2026 19:59:42 -0700 Subject: [PATCH] Fix missing_presence_validation crash when inclusion/exclusion :in is a method name Signed-off-by: Sai Asish Y --- .../detectors/missing_presence_validation.rb | 10 ++++++++-- .../detectors/missing_presence_validation_test.rb | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/active_record_doctor/detectors/missing_presence_validation.rb b/lib/active_record_doctor/detectors/missing_presence_validation.rb index ab1b9130..dc83a809 100644 --- a/lib/active_record_doctor/detectors/missing_presence_validation.rb +++ b/lib/active_record_doctor/detectors/missing_presence_validation.rb @@ -93,14 +93,14 @@ def detect when ActiveModel::Validations::InclusionValidator validator_items = inclusion_or_exclusion_validator_items(validator) (validator.attributes & attribute_names).present? && - (validator_items.is_a?(Proc) || validator_items.exclude?(nil)) + (dynamic_validator_items?(validator_items) || validator_items.exclude?(nil)) # An exclusion validator ensures the column is not nil if it covers # the column and excludes nil as an allowed value explicitly. when ActiveModel::Validations::ExclusionValidator validator_items = inclusion_or_exclusion_validator_items(validator) (validator.attributes & attribute_names).present? && - (validator_items.is_a?(Proc) || validator_items.include?(nil)) + (dynamic_validator_items?(validator_items) || validator_items.include?(nil)) end end @@ -172,6 +172,12 @@ def inclusion_or_exclusion_validator_items(validator) validator.options[:in] || validator.options[:within] || [] end + # The allowed values can be a Proc or a method name (Symbol) that's resolved + # at validation time, so the set of values isn't known statically. + def dynamic_validator_items?(validator_items) + validator_items.is_a?(Proc) || validator_items.is_a?(Symbol) + end + # Determines whether the given column is used as a counter cache column by # a has_many association on the model. def counter_cache_column?(model, column) diff --git a/test/active_record_doctor/detectors/missing_presence_validation_test.rb b/test/active_record_doctor/detectors/missing_presence_validation_test.rb index a52b0f19..5aae9172 100644 --- a/test/active_record_doctor/detectors/missing_presence_validation_test.rb +++ b/test/active_record_doctor/detectors/missing_presence_validation_test.rb @@ -133,6 +133,20 @@ def test_non_null_boolean_is_not_reported_if_inclusion_is_proc refute_problems end + def test_non_null_is_not_reported_if_inclusion_in_is_a_method + Context.create_table(:users) do |t| + t.string :role, null: false + end.define_model do + validates :role, inclusion: { in: :allowed_roles } + + def allowed_roles + ["admin", "user"] + end + end + + refute_problems + end + def test_non_null_boolean_is_reported_if_nil_not_excluded Context.create_table(:users) do |t| t.boolean :active, null: false