Skip to content

Commit 0409ed5

Browse files
p8yuki24
andcommitted
Clean up checks to see if DidYouMean is defined
As of Ruby 2.7 DidYouMean is included as a default gem, so there is no need to check if DidYouMean is defined in the test suite. We still need to check if the DidYouMean modules are defined in the actual code, as someone might run Rails with DidYouMean disabled by using the `--disable-did_you_mean` flag. This is ussually done for performance reasons. This commit also includes some of the changes made by Yuki in: rails#39555 These changes include replacing Jaro with the more accurate SpellChecker, and using DidYouMean::Correctable for simplere corrections. The DidYouMean::SpellChecker does have a treshold for corrections. If there is not enough similarity it might not return a suggestion. To stop the tests from failing some test data had to be changed. For example, `non_existent` does not meet the treshold for `hello`, but `ello` does: DidYouMean::SpellChecker.new(dictionary: %w[hello]).correct('non_existent') => [] DidYouMean::SpellChecker.new(dictionary: %w[hello]).correct('ello') => ["hello"] The treshold makes sense for spelling errors. But maybe we should add a different SpellChecker that helps to get a suggestion even if there is little overlap. For example for when a model only has 2 attributes (title and body), it's helpful to get a suggestion for `name` Co-Authored-By: Yuki Nishijima <[email protected]>
1 parent 7179dcb commit 0409ed5

File tree

16 files changed

+110
-197
lines changed

16 files changed

+110
-197
lines changed

actionpack/lib/abstract_controller/base.rb

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,13 @@ def initialize(message = nil, controller = nil, action = nil)
1616
super(message)
1717
end
1818

19-
class Correction
20-
def initialize(error)
21-
@error = error
22-
end
19+
if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
20+
include DidYouMean::Correctable
2321

2422
def corrections
25-
if @error.action
26-
maybe_these = @error.controller.class.action_methods
27-
28-
maybe_these.sort_by { |n|
29-
DidYouMean::Jaro.distance(@error.action.to_s, n)
30-
}.reverse.first(4)
31-
else
32-
[]
33-
end
23+
@corrections ||= DidYouMean::SpellChecker.new(dictionary: controller.class.action_methods).correct(action)
3424
end
3525
end
36-
37-
# We may not have DYM, and DYM might not let us register error handlers
38-
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
39-
DidYouMean.correct_error(self, Correction)
40-
end
4126
end
4227

4328
# AbstractController::Base is a low-level API. Nobody should be

actionpack/lib/action_controller/metal/exceptions.rb

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,18 @@ def initialize(message, routes = nil, route_name = nil, method_name = nil)
3333
super(message)
3434
end
3535

36-
class Correction
37-
def initialize(error)
38-
@error = error
39-
end
36+
if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
37+
include DidYouMean::Correctable
4038

4139
def corrections
42-
if @error.method_name
43-
maybe_these = @error.routes.named_routes.helper_names.grep(/#{@error.route_name}/)
44-
maybe_these -= [@error.method_name.to_s] # remove exact match
45-
46-
maybe_these.sort_by { |n|
47-
DidYouMean::Jaro.distance(@error.route_name, n)
48-
}.reverse.first(4)
49-
else
50-
[]
40+
@corrections ||= begin
41+
maybe_these = routes&.named_routes&.helper_names&.grep(/#{route_name}/) || []
42+
maybe_these -= [method_name.to_s] # remove exact match
43+
44+
DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(route_name)
5145
end
5246
end
5347
end
54-
55-
# We may not have DYM, and DYM might not let us register error handlers
56-
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
57-
DidYouMean.correct_error(self, Correction)
58-
end
5948
end
6049

6150
class MethodNotAllowed < ActionControllerError #:nodoc:

actionpack/lib/action_controller/metal/strong_parameters.rb

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,13 @@ def initialize(param, keys = nil) # :nodoc:
2727
super("param is missing or the value is empty: #{param}")
2828
end
2929

30-
class Correction
31-
def initialize(error)
32-
@error = error
33-
end
30+
if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
31+
include DidYouMean::Correctable
3432

3533
def corrections
36-
if @error.param && @error.keys
37-
maybe_these = @error.keys
38-
39-
maybe_these.sort_by { |n|
40-
DidYouMean::Jaro.distance(@error.param.to_s, n)
41-
}.reverse.first(4)
42-
else
43-
[]
44-
end
34+
@corrections ||= DidYouMean::SpellChecker.new(dictionary: keys).correct(param.to_s)
4535
end
4636
end
47-
48-
# We may not have DYM, and DYM might not let us register error handlers
49-
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
50-
DidYouMean.correct_error(self, Correction)
51-
end
5237
end
5338

5439
# Raised when a supplied parameter is not expected and

actionpack/test/controller/base_test.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,12 @@ def test_process_should_be_precise
172172
assert_equal "The action 'non_existent' could not be found for EmptyController", exception.message
173173
end
174174

175-
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
176-
def test_exceptions_have_suggestions_for_fix
177-
use_controller SimpleController
178-
exception = assert_raise AbstractController::ActionNotFound do
179-
get :non_existent
180-
end
181-
assert_match "Did you mean?", exception.message
175+
def test_exceptions_have_suggestions_for_fix
176+
use_controller SimpleController
177+
exception = assert_raise AbstractController::ActionNotFound do
178+
get :ello
182179
end
180+
assert_match "Did you mean?", exception.message
183181
end
184182

185183
def test_action_missing_should_work

actionpack/test/controller/required_params_test.rb

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,16 @@ class ActionControllerRequiredParamsTest < ActionController::TestCase
2222
end
2323
end
2424

25-
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
26-
test "exceptions have suggestions for fix" do
27-
error = assert_raise ActionController::ParameterMissing do
28-
post :create, params: { magazine: { name: "Mjallo!" } }
29-
end
30-
assert_match "Did you mean?", error.message
31-
32-
error = assert_raise ActionController::ParameterMissing do
33-
post :create, params: { book: { title: "Mjallo!" } }
34-
end
35-
assert_match "Did you mean?", error.message
25+
test "exceptions have suggestions for fix" do
26+
error = assert_raise ActionController::ParameterMissing do
27+
post :create, params: { boko: { name: "Mjallo!" } }
3628
end
29+
assert_match "Did you mean?", error.message
30+
31+
error = assert_raise ActionController::ParameterMissing do
32+
post :create, params: { book: { naem: "Mjallo!" } }
33+
end
34+
assert_match "Did you mean?", error.message
3735
end
3836

3937
test "required parameters that are present will not raise" do

actionpack/test/dispatch/debug_exceptions_test.rb

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def call(env)
6464
[404, { "X-Cascade" => "pass" }, self]
6565
when "/not_found"
6666
controller = SimpleController.new
67-
raise AbstractController::ActionNotFound.new(nil, controller, :not_found)
67+
raise AbstractController::ActionNotFound.new(nil, controller, :ello)
6868
when "/runtime_error"
6969
raise RuntimeError
7070
when "/method_not_allowed"
@@ -98,7 +98,7 @@ def call(env)
9898
when "/missing_keys"
9999
raise ActionController::UrlGenerationError, "No route matches"
100100
when "/parameter_missing"
101-
raise ActionController::ParameterMissing.new(:missing_param_key, %w(valid_param_key))
101+
raise ActionController::ParameterMissing.new(:invalid_param_key, %w(valid_param_key))
102102
when "/original_syntax_error"
103103
eval "broke_syntax =" # `eval` need for raise native SyntaxError at runtime
104104
when "/syntax_error_into_view"
@@ -318,20 +318,18 @@ def call(env)
318318
assert_match(/ActionDispatch::Http::MimeNegotiation::InvalidType/, body)
319319
end
320320

321-
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
322-
test "rescue with suggestions" do
323-
@app = DevelopmentApp
321+
test "rescue with suggestions" do
322+
@app = DevelopmentApp
324323

325-
get "/not_found", headers: { "action_dispatch.show_exceptions" => true }
326-
assert_response 404
327-
assert_select("b", /Did you mean\?/)
328-
assert_select("li", "hello")
324+
get "/not_found", headers: { "action_dispatch.show_exceptions" => true }
325+
assert_response 404
326+
assert_select("b", /Did you mean\?/)
327+
assert_select("li", "hello")
329328

330-
get "/parameter_missing", headers: { "action_dispatch.show_exceptions" => true }
331-
assert_response 400
332-
assert_select("b", /Did you mean\?/)
333-
assert_select("li", "valid_param_key")
334-
end
329+
get "/parameter_missing", headers: { "action_dispatch.show_exceptions" => true }
330+
assert_response 400
331+
assert_select("b", /Did you mean\?/)
332+
assert_select("li", "valid_param_key")
335333
end
336334

337335
test "rescue with HTML format for HTML API request" do

actionpack/test/dispatch/routing_test.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4913,11 +4913,9 @@ def app; APP end
49134913
assert_match message, error.message
49144914
end
49154915

4916-
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
4917-
test "exceptions have suggestions for fix" do
4918-
error = assert_raises(ActionController::UrlGenerationError) { product_path(nil, "id" => "url-tested") }
4919-
assert_match "Did you mean?", error.message
4920-
end
4916+
test "exceptions have suggestions for fix" do
4917+
error = assert_raises(ActionController::UrlGenerationError) { product_path(nil, "id" => "url-tested") }
4918+
assert_match "Did you mean?", error.message
49214919
end
49224920

49234921
# FIXME: we should fix all locations that raise this exception to provide
@@ -4926,8 +4924,8 @@ def app; APP end
49264924
# we don't want to break other code.
49274925
test "correct for empty UrlGenerationError" do
49284926
err = ActionController::UrlGenerationError.new("oh no!")
4929-
correction = ActionController::UrlGenerationError::Correction.new(err)
4930-
assert_equal [], correction.corrections
4927+
4928+
assert_equal [], err.corrections
49314929
end
49324930
end
49334931

actionview/test/template/render_test.rb

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -627,31 +627,29 @@ def test_render_partial_with_layout_raises_descriptive_error
627627
assert_match "Missing partial /_true with", e.message
628628
end
629629

630-
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
631-
def test_render_partial_provides_spellcheck
632-
e = assert_raises(ActionView::MissingTemplate) { @view.render(partial: "test/partail") }
633-
assert_match %r{Did you mean\? test/partial\n *test/partialhtml}, e.message
634-
end
630+
def test_render_partial_provides_spellcheck
631+
e = assert_raises(ActionView::MissingTemplate) { @view.render(partial: "test/partail") }
632+
assert_match %r{Did you mean\? test/partial\n *test/partialhtml}, e.message
633+
end
635634

636-
def test_spellcheck_doesnt_list_directories
637-
e = assert_raises(ActionView::MissingTemplate) { @view.render(partial: "test/directory") }
638-
assert_match %r{Did you mean\?}, e.message
639-
assert_no_match %r{Did you mean\? test/directory\n}, e.message # test/hello is a directory
640-
end
635+
def test_spellcheck_doesnt_list_directories
636+
e = assert_raises(ActionView::MissingTemplate) { @view.render(partial: "test/directory") }
637+
assert_match %r{Did you mean\?}, e.message
638+
assert_no_match %r{Did you mean\? test/directory\n}, e.message # test/hello is a directory
639+
end
641640

642-
def test_spellcheck_only_lists_templates
643-
e = assert_raises(ActionView::MissingTemplate) { @view.render(template: "test/partial") }
641+
def test_spellcheck_only_lists_templates
642+
e = assert_raises(ActionView::MissingTemplate) { @view.render(template: "test/partial") }
644643

645-
assert_match %r{Did you mean\?}, e.message
646-
assert_no_match %r{Did you mean\? test/partial\n}, e.message
647-
end
644+
assert_match %r{Did you mean\?}, e.message
645+
assert_no_match %r{Did you mean\? test/partial\n}, e.message
646+
end
648647

649-
def test_spellcheck_only_lists_partials
650-
e = assert_raises(ActionView::MissingTemplate) { @view.render(partial: "test/template") }
648+
def test_spellcheck_only_lists_partials
649+
e = assert_raises(ActionView::MissingTemplate) { @view.render(partial: "test/template") }
651650

652-
assert_match %r{Did you mean\?}, e.message
653-
assert_no_match %r{Did you mean\? test/template\n}, e.message
654-
end
651+
assert_match %r{Did you mean\?}, e.message
652+
assert_no_match %r{Did you mean\? test/template\n}, e.message
655653
end
656654

657655
def test_render_partial_wrong_details_no_spellcheck

activerecord/lib/active_record/associations.rb

Lines changed: 20 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,16 @@ def initialize(record = nil, association_name = nil)
1313
end
1414
end
1515

16-
class Correction
17-
def initialize(error)
18-
@error = error
19-
end
16+
if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
17+
include DidYouMean::Correctable
2018

2119
def corrections
22-
if @error.association_name
23-
maybe_these = @error.record.class.reflections.keys
24-
25-
maybe_these.sort_by { |n|
26-
DidYouMean::Jaro.distance(@error.association_name.to_s, n)
27-
}.reverse.first(4)
28-
else
29-
[]
20+
@corrections ||= begin
21+
maybe_these = record&.class&.reflections&.keys
22+
DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(association_name.to_s)
3023
end
3124
end
3225
end
33-
34-
# We may not have DYM, and DYM might not let us register error handlers
35-
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
36-
DidYouMean.correct_error(self, Correction)
37-
end
3826
end
3927

4028
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -49,28 +37,20 @@ def initialize(reflection = nil, associated_class = nil)
4937
end
5038
end
5139

52-
class Correction
53-
def initialize(error)
54-
@error = error
55-
end
40+
if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
41+
include DidYouMean::Correctable
5642

5743
def corrections
58-
if @error.reflection && @error.associated_class
59-
maybe_these = @error.associated_class.reflections.keys
60-
61-
maybe_these.sort_by { |n|
62-
DidYouMean::Jaro.distance(@error.reflection.options[:inverse_of].to_s, n)
63-
}.reverse.first(4)
44+
if reflection && associated_class
45+
@corrections ||= begin
46+
maybe_these = associated_class.reflections.keys
47+
DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(reflection.options[:inverse_of].to_s)
48+
end
6449
else
6550
[]
6651
end
6752
end
6853
end
69-
70-
# We may not have DYM, and DYM might not let us register error handlers
71-
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
72-
DidYouMean.correct_error(self, Correction)
73-
end
7454
end
7555

7656
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -86,29 +66,21 @@ def initialize(owner_class = nil, reflection = nil)
8666
end
8767
end
8868

89-
class Correction
90-
def initialize(error)
91-
@error = error
92-
end
69+
if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
70+
include DidYouMean::Correctable
9371

9472
def corrections
95-
if @error.reflection && @error.owner_class
96-
maybe_these = @error.owner_class.reflections.keys
97-
maybe_these -= [@error.reflection.name.to_s] # remove failing reflection
98-
99-
maybe_these.sort_by { |n|
100-
DidYouMean::Jaro.distance(@error.reflection.options[:through].to_s, n)
101-
}.reverse.first(4)
73+
if owner_class && reflection
74+
@corrections ||= begin
75+
maybe_these = owner_class.reflections.keys
76+
maybe_these -= [reflection.name.to_s] # remove failing reflection
77+
DidYouMean::SpellChecker.new(dictionary: maybe_these).correct(reflection.options[:through].to_s)
78+
end
10279
else
10380
[]
10481
end
10582
end
10683
end
107-
108-
# We may not have DYM, and DYM might not let us register error handlers
109-
if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
110-
DidYouMean.correct_error(self, Correction)
111-
end
11284
end
11385

11486
class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:

0 commit comments

Comments
 (0)