From 9f94bba33e167f81b95ca153bbdb867824fcbef7 Mon Sep 17 00:00:00 2001 From: Joe Sak Date: Thu, 14 Jul 2022 18:35:07 -0500 Subject: [PATCH] Add `allow_nil: true` option to the finder (#995) This PR adds an optional `allow_nil: true` option to the finder From discussion in #937 the idea is to add functionality that behaves *like* Rails' `find_by` which returns `nil` when a record is not found. This is useful in conditions where the developer is allowing the primary key ID and the friendly slug ID but is not sure if the record will actually be found, and wants a nil instead of a raised exception. ### Usage ```ruby MyModel.friendly.find("friendly-slug") # => still works as expected, raises an ActiveRecord::RecordNotFound exception MyModel.friendly.find("friendly-slug", allow_nil: true) # => same as above, but some devs may find using this option to be more self-documenting of their intentions ``` --- Changelog.md | 1 + README.md | 21 +++++++++++++++++++++ lib/friendly_id/finder_methods.rb | 20 ++++++++++++++++---- test/finders_test.rb | 12 ++++++++++++ 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index 6f1d6ee0c..07af279c5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,7 @@ suggestions, ideas and improvements to FriendlyId. * SimpleI18n: Handle regional locales ([#965](https://github.com/norman/friendly_id/pull/965)) * Fix: "unknown column" exception ([#943](https://github.com/norman/friendly_id/pull/943)) +* Add: `allow_nil: true` to the Finder ([#995])(https://github.com/norman/friendly_id/pull/995) ## 5.4.2 (2021-01-07) diff --git a/README.md b/README.md index 257077318..3f0577e97 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,27 @@ existing users, do this from the console, runner, or add a Rake task: User.find_each(&:save) ``` +## Options + +### `:allow_nil` + +You can pass `allow_nil: true` to the `friendly.find()` method if you're want to +avoid raising `ActiveRecord::RecordNotFound` and accept a `nil`. + +#### Example + +```ruby +MyModel.friendly.find("bad-slug") # where bad-slug is not a valid slug +MyModel.friendly.find(123) # where 123 is not a valid primary key ID +MyModel.friendly.find(nil) # when you have a variable/param that's possibly nil +#=> raise ActiveRecord::RecordNotFound + +MyModel.friendly.find("bad-slug", allow_nil: true) +MyModel.friendly.find(123, allow_nil: true) +MyModel.friendly.find(nil, allow_nil: true) +#=> nil +``` + ## Bugs Please report them on the [Github issue diff --git a/lib/friendly_id/finder_methods.rb b/lib/friendly_id/finder_methods.rb index 2a0589736..46cd362de 100644 --- a/lib/friendly_id/finder_methods.rb +++ b/lib/friendly_id/finder_methods.rb @@ -7,19 +7,31 @@ module FinderMethods # id matching '123' and then fall back to looking for a record with the # numeric id '123'. # + # @param [Boolean] allow_nil (default: false) + # Use allow_nil: true if you'd like the finder to return nil instead of + # raising ActivRecord::RecordNotFound + # + # ### Example + # + # MyModel.friendly.find("bad-slug") + # #=> raise ActiveRecord::RecordNotFound + # + # MyModel.friendly.find("bad-slug", allow_nil: true) + # #=> nil + # # Since FriendlyId 5.0, if the id is a nonnumeric string like '123-foo' it # will *only* search by friendly id and not fall back to the regular find # method. # # If you want to search only by the friendly id, use {#find_by_friendly_id}. # @raise ActiveRecord::RecordNotFound - def find(*args) + def find(*args, allow_nil: false) id = args.first - return super if args.count != 1 || id.unfriendly_id? + return super(*args) if args.count != 1 || id.unfriendly_id? first_by_friendly_id(id).tap { |result| return result unless result.nil? } - return super if potential_primary_key?(id) + return super(*args) if potential_primary_key?(id) - raise_not_found_exception(id) + raise_not_found_exception(id) unless allow_nil end # Returns true if a record with the given id exists. diff --git a/test/finders_test.rb b/test/finders_test.rb index 9271f6ef9..d40c1626e 100644 --- a/test/finders_test.rb +++ b/test/finders_test.rb @@ -25,4 +25,16 @@ def model_class assert model_class.existing.find(record.friendly_id) end end + + test "allows nil with allow_nil: true" do + with_instance_of(model_class) do |record| + assert_nil model_class.find("foo", allow_nil: true) + end + end + + test "allows nil on relations with allow_nil: true" do + with_instance_of(model_class) do |record| + assert_nil model_class.existing.find("foo", allow_nil: true) + end + end end