From 5411565d44d9a60ed00ba3158abcb6ed80129b33 Mon Sep 17 00:00:00 2001 From: Petrik Date: Tue, 19 May 2020 09:03:46 +0200 Subject: [PATCH] Add DidYouMean for ParameterMissing If a parameter isn't found we can suggest similar params: ``` class BooksController < ActionController::Base def create params.require(:book).require(:name) head :ok end end post :create, params: { magazine: { name: "Mjallo!" } } param is missing or the value is empty: book Did you mean? controller action magazine post :create, params: { book: { title: "Mjallo!" } } param is missing or the value is empty: name Did you mean? title ``` --- .../metal/strong_parameters.rb | 32 ++++++++++++++++--- .../test/controller/required_params_test.rb | 14 ++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index a9aeb28256..d5acb929db 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -19,12 +19,36 @@ module ActionController # params.require(:a) # # => ActionController::ParameterMissing: param is missing or the value is empty: a class ParameterMissing < KeyError - attr_reader :param # :nodoc: + attr_reader :param, :keys # :nodoc: - def initialize(param) # :nodoc: + def initialize(param, keys = nil) # :nodoc: @param = param + @keys = keys super("param is missing or the value is empty: #{param}") end + + class Correction + def initialize(error) + @error = error + end + + def corrections + if @error.param && @error.keys + maybe_these = @error.keys + + maybe_these.sort_by { |n| + DidYouMean::Jaro.distance(@error.param.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 # Raised when a supplied parameter is not expected and @@ -480,7 +504,7 @@ module ActionController if value.present? || value == false value else - raise ParameterMissing.new(key) + raise ParameterMissing.new(key, @parameters.keys) end end @@ -617,7 +641,7 @@ module ActionController if block_given? yield else - args.fetch(0) { raise ActionController::ParameterMissing.new(key) } + args.fetch(0) { raise ActionController::ParameterMissing.new(key, @parameters.keys) } end } ) diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb index 4a83d07e7d..c4ec4fdf74 100644 --- a/actionpack/test/controller/required_params_test.rb +++ b/actionpack/test/controller/required_params_test.rb @@ -22,6 +22,20 @@ class ActionControllerRequiredParamsTest < ActionController::TestCase end end + if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error) + test "exceptions have suggestions for fix" do + error = assert_raise ActionController::ParameterMissing do + post :create, params: { magazine: { name: "Mjallo!" } } + end + assert_match "Did you mean?", error.message + + error = assert_raise ActionController::ParameterMissing do + post :create, params: { book: { title: "Mjallo!" } } + end + assert_match "Did you mean?", error.message + end + end + test "required parameters that are present will not raise" do post :create, params: { book: { name: "Mjallo!" } } assert_response :ok