mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add ActiveRecord::FinderMethods#sole
, #find_sole_by
Co-authored-by: Kasper Timm Hansen <kaspth@gmail.com>
This commit is contained in:
parent
401ce9dd3d
commit
e2ccc48246
5 changed files with 80 additions and 1 deletions
|
@ -1,3 +1,22 @@
|
|||
* Add `FinderMethods#sole` and `#find_sole_by` to find and assert the
|
||||
presence of exactly one record.
|
||||
|
||||
Used when you need a single row, but also want to assert that there aren't
|
||||
multiple rows matching the condition; especially for when database
|
||||
constraints aren't enough or are impractical.
|
||||
|
||||
```ruby
|
||||
Product.where(["price = %?", price]).sole
|
||||
# => ActiveRecord::RecordNotFound (if no Product with given price)
|
||||
# => #<Product ...> (if one Product with given price)
|
||||
# => ActiveRecord::SoleRecordExceeded (if more than one Product with given price)
|
||||
|
||||
user.api_keys.find_by_sole(key: key)
|
||||
# as above
|
||||
```
|
||||
|
||||
*Asherah Connor*
|
||||
|
||||
* Makes `ActiveRecord::AttributeMethods::Query` respect the getter overrides defined in the model.
|
||||
|
||||
Fixes #40771.
|
||||
|
|
|
@ -118,6 +118,16 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
# Raised when Active Record finds multiple records but only expected one.
|
||||
class SoleRecordExceeded < ActiveRecordError
|
||||
attr_reader :record
|
||||
|
||||
def initialize(record = nil)
|
||||
@record = record
|
||||
super "Wanted only one #{record&.name || "record"}"
|
||||
end
|
||||
end
|
||||
|
||||
# Superclass for all database execution errors.
|
||||
#
|
||||
# Wraps the underlying database error as +cause+.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module ActiveRecord
|
||||
module Querying
|
||||
QUERYING_METHODS = [
|
||||
:find, :find_by, :find_by!, :take, :take!, :first, :first!, :last, :last!,
|
||||
:find, :find_by, :find_by!, :take, :take!, :sole, :find_sole_by, :first, :first!, :last, :last!,
|
||||
:second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!,
|
||||
:forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!,
|
||||
:exists?, :any?, :many?, :none?, :one?,
|
||||
|
|
|
@ -104,6 +104,33 @@ module ActiveRecord
|
|||
take || raise_record_not_found_exception!
|
||||
end
|
||||
|
||||
# Finds the sole matching record. Raises ActiveRecord::RecordNotFound if no
|
||||
# record is found. Raises ActiveRecord::SoleRecordExceeded if more than one
|
||||
# record is found.
|
||||
#
|
||||
# Product.where(["price = %?", price]).sole
|
||||
def sole
|
||||
found, undesired = first(2)
|
||||
|
||||
case
|
||||
when found.nil?
|
||||
raise_record_not_found_exception!
|
||||
when undesired.present?
|
||||
raise ActiveRecord::SoleRecordExceeded.new(self)
|
||||
else
|
||||
found
|
||||
end
|
||||
end
|
||||
|
||||
# Finds the sole matching record. Raises ActiveRecord::RecordNotFound if no
|
||||
# record is found. Raises ActiveRecord::SoleRecordExceeded if more than one
|
||||
# record is found.
|
||||
#
|
||||
# Product.find_sole_by(["price = %?", price])
|
||||
def find_sole_by(arg, *args)
|
||||
where(arg, *args).sole
|
||||
end
|
||||
|
||||
# Find the first record (or first N records if a parameter is supplied).
|
||||
# If no order is defined it will order by primary key.
|
||||
#
|
||||
|
|
|
@ -625,6 +625,29 @@ class FinderTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_sole
|
||||
assert_equal topics(:first), Topic.where("title = 'The First Topic'").sole
|
||||
assert_equal topics(:first), Topic.find_sole_by("title = 'The First Topic'")
|
||||
end
|
||||
|
||||
def test_sole_failing_none
|
||||
assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
|
||||
Topic.where("title = 'This title does not exist'").sole
|
||||
end
|
||||
assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
|
||||
Topic.find_sole_by("title = 'This title does not exist'")
|
||||
end
|
||||
end
|
||||
|
||||
def test_sole_failing_many
|
||||
assert_raises_with_message ActiveRecord::SoleRecordExceeded, "Wanted only one Topic" do
|
||||
Topic.where("author_name = 'Carl'").sole
|
||||
end
|
||||
assert_raises_with_message ActiveRecord::SoleRecordExceeded, "Wanted only one Topic" do
|
||||
Topic.find_sole_by("author_name = 'Carl'")
|
||||
end
|
||||
end
|
||||
|
||||
def test_first
|
||||
assert_equal topics(:second).title, Topic.where("title = 'The Second Topic of the day'").first.title
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue