Skip to content

Commit 2e14c53

Browse files
authored
Add Enumerable#sole (rails#40914)
* Add `Enumerable#sole`, from `ActiveRecord::FinderMethods#sole` * distinguish single-item Enumerable and two-item with nil last Add a test for same. * add symmetry, against rubocop's wishes
1 parent 9a263e9 commit 2e14c53

File tree

3 files changed

+33
-0
lines changed

3 files changed

+33
-0
lines changed

activesupport/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
* Add `Enumerable#sole`, per `ActiveRecord::FinderMethods#sole`. Returns the
2+
sole item of the enumerable, raising if no items are found, or if more than
3+
one is.
4+
5+
*Asherah Connor*
6+
17
* Freeze `ActiveSupport::Duration#parts` and remove writer methods.
28

39
Durations are meant to be value objects and should not be mutated.

activesupport/lib/active_support/core_ext/enumerable.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ module Enumerable
44
INDEX_WITH_DEFAULT = Object.new
55
private_constant :INDEX_WITH_DEFAULT
66

7+
# Error generated by +sole+ when called on an enumerable that doesn't have
8+
# exactly one item.
9+
class SoleItemExpectedError < StandardError; end
10+
711
# Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements
812
# when we omit an identity.
913

@@ -215,6 +219,20 @@ def compact_blank
215219
def in_order_of(key, series)
216220
index_by(&key).values_at(*series).compact
217221
end
222+
223+
# Returns the sole item in the enumerable. If there are no items, or more
224+
# than one item, raises +Enumerable::SoleItemExpectedError+.
225+
#
226+
# ["x"].sole # => "x"
227+
# Set.new.sole # => Enumerable::SoleItemExpectedError: no item found
228+
# { a: 1, b: 2 }.sole # => Enumerable::SoleItemExpectedError: multiple items found
229+
def sole
230+
case count
231+
when 1 then return first # rubocop:disable Style/RedundantReturn
232+
when 0 then raise SoleItemExpectedError, "no item found"
233+
when 2.. then raise SoleItemExpectedError, "multiple items found"
234+
end
235+
end
218236
end
219237

220238
class Hash

activesupport/test/core_ext/enumerable_test.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,4 +319,13 @@ def test_in_order_of_drops_elements_not_named_in_series
319319
values = [ Payment.new(5), Payment.new(1), Payment.new(3) ]
320320
assert_equal [ Payment.new(1), Payment.new(5) ], values.in_order_of(:price, [ 1, 5 ])
321321
end
322+
323+
def test_sole
324+
expected_raise = Enumerable::SoleItemExpectedError
325+
326+
assert_raise(expected_raise) { GenericEnumerable.new([]).sole }
327+
assert_equal 1, GenericEnumerable.new([1]).sole
328+
assert_raise(expected_raise) { GenericEnumerable.new([1, 2]).sole }
329+
assert_raise(expected_raise) { GenericEnumerable.new([1, nil]).sole }
330+
end
322331
end

0 commit comments

Comments
 (0)