diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 7ff3b2b890..f6faed58c0 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -5,13 +5,39 @@ require "active_support/core_ext/string/conversions" module ActiveRecord class AssociationNotFoundError < ConfigurationError #:nodoc: + attr_reader :record, :association_name def initialize(record = nil, association_name = nil) + @record = record + @association_name = association_name if record && association_name super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?") else super("Association was not found.") end end + + class Correction + def initialize(error) + @error = error + end + + def corrections + if @error.association_name + maybe_these = @error.record.class.reflections.keys + + maybe_these.sort_by { |n| + DidYouMean::Jaro.distance(@error.association_name.to_s, n) + }.reverse.first(4) + else + [] + end + end + end + + # We may not have DYM, and DYM might not let us register error handlers + if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error) + DidYouMean.correct_error(self, Correction) + end end class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc: diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index cc88381ee0..2f7cb56e0e 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -814,22 +814,31 @@ class EagerAssociationTest < ActiveRecord::TestCase e = assert_raise(ActiveRecord::AssociationNotFoundError) { Post.all.merge!(includes: :monkeys).find(6) } - assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message) + assert_match(/Association named 'monkeys' was not found on Post; perhaps you misspelled it\?/, e.message) e = assert_raise(ActiveRecord::AssociationNotFoundError) { Post.all.merge!(includes: [ :monkeys ]).find(6) } - assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message) + assert_match(/Association named 'monkeys' was not found on Post; perhaps you misspelled it\?/, e.message) e = assert_raise(ActiveRecord::AssociationNotFoundError) { Post.all.merge!(includes: [ "monkeys" ]).find(6) } - assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message) + assert_match(/Association named 'monkeys' was not found on Post; perhaps you misspelled it\?/, e.message) e = assert_raise(ActiveRecord::AssociationNotFoundError) { Post.all.merge!(includes: [ :monkeys, :elephants ]).find(6) } - assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message) + assert_match(/Association named 'monkeys' was not found on Post; perhaps you misspelled it\?/, e.message) + end + + if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error) + test "exceptions have suggestions for fix" do + error = assert_raise(ActiveRecord::AssociationNotFoundError) { + Post.all.merge!(includes: :monkeys).find(6) + } + assert_match "Did you mean?", error.message + end end def test_eager_has_many_through_with_order