diff --git a/lib/factory_bot/definition.rb b/lib/factory_bot/definition.rb index dfad55c91..5a91777cc 100644 --- a/lib/factory_bot/definition.rb +++ b/lib/factory_bot/definition.rb @@ -136,7 +136,6 @@ def additional_trait_names raise error_with_definition_name(error) end - def all_registered_trait_names (defined_traits_names + Internal.traits.map(&:name)).uniq end @@ -163,31 +162,37 @@ def registered_trait_message(all_registered_traits) "Registered traits: #{all_registered_traits.map(&:to_sym).sort.inspect}" end end - # detailed_message introduced in Ruby 3.2 for cleaner integration with - # did_you_mean. See https://bugs.ruby-lang.org/issues/18564 - if KeyError.method_defined?(:detailed_message) - def error_with_definition_name(error) - message = error.message.rstrip - message += "." unless message.end_with?(".") - message += " #{registered_trait_message(all_registered_trait_names)}." - message += " Referenced within \"#{name}\" definition" - - error.class.new(message, **error_options(error)) - .tap { |new_error| new_error.set_backtrace(error.backtrace) } - end - else - def error_with_definition_name(error) - message = error.message - message += " #{registered_trait_message(all_registered_trait_names)}." - message.insert( - message.index("\nDid you mean?") || message.length, - " referenced within \"#{name}\" definition" - ) - - error.class.new(message).tap do |new_error| - new_error.set_backtrace(error.backtrace) - end - end + + def decorated_error_message(message) + message, separator, suggestions = message.partition("\nDid you mean?") + + message = message.rstrip + message += "." unless message.end_with?(".") + message += " #{registered_trait_message(all_registered_trait_names)}." + message += " Referenced within \"#{name}\" definition" + + message + separator + suggestions + end + + def error_with_definition_name(error) + options = error_options(error) + message = decorated_error_message(suggested_message(error, options)) + + error.class.new(message, **options) + .tap { |new_error| new_error.set_backtrace(error.backtrace) } + end + + # Reading #message is what makes did_you_mean build its suggestions, out of + # the key and receiver carried in options. Ruby 3.2 moved them to + # #detailed_message, so there this hands back error.message as-is: + # + # KeyError.new(message, key: "traiz_1", receiver: {"trait_1" => nil}).message + # # Ruby 3.1 => message + "\nDid you mean? \"trait_1\"" + # # Ruby 3.2 => message + # + # See https://bugs.ruby-lang.org/issues/18564 + def suggested_message(error, options) + error.class.new(error.message.partition("\nDid you mean?").first, **options).message end def trait_by_name(name) diff --git a/lib/factory_bot/registry.rb b/lib/factory_bot/registry.rb index 2b1eea49a..c9b1693d9 100644 --- a/lib/factory_bot/registry.rb +++ b/lib/factory_bot/registry.rb @@ -44,16 +44,8 @@ def key_error_with_custom_message(key_error) end end - # detailed_message introduced in Ruby 3.2 for cleaner integration with - # did_you_mean. See https://bugs.ruby-lang.org/issues/18564 - if KeyError.method_defined?(:detailed_message) - def new_key_error(message, key_error) - KeyError.new(message, key: key_error.key, receiver: key_error.receiver) - end - else - def new_key_error(message, _) - KeyError.new(message) - end + def new_key_error(message, key_error) + KeyError.new(message, key: key_error.key, receiver: key_error.receiver) end end end