Skip to content

Commit 32d9623

Browse files
authored
Merge pull request rails#54341 from fatkodima/enumerable-sole-infinite-collections
Fix `Enumerable#sole` for infinite collections
2 parents fd886fe + 044d2ea commit 32d9623

File tree

5 files changed

+47
-4
lines changed

5 files changed

+47
-4
lines changed

activesupport/lib/active_support/core_ext/enumerable.rb

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,22 @@ def in_order_of(key, series, filter: true)
209209
# Set.new.sole # => Enumerable::SoleItemExpectedError: no item found
210210
# { a: 1, b: 2 }.sole # => Enumerable::SoleItemExpectedError: multiple items found
211211
def sole
212-
case count
213-
when 1 then return first # rubocop:disable Style/RedundantReturn
214-
when 0 then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "no item found"
215-
when 2.. then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "multiple items found"
212+
result = nil
213+
found = false
214+
215+
each do |element|
216+
if found
217+
raise SoleItemExpectedError, "multiple items found"
218+
end
219+
220+
result = element
221+
found = true
222+
end
223+
224+
if found
225+
result
226+
else
227+
raise SoleItemExpectedError, "no item found"
216228
end
217229
end
218230
end

activesupport/lib/active_support/core_ext/range.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
require "active_support/core_ext/range/conversions"
44
require "active_support/core_ext/range/compare_range"
55
require "active_support/core_ext/range/overlap"
6+
require "active_support/core_ext/range/sole"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# frozen_string_literal: true
2+
3+
class Range
4+
# Returns the sole item in the range. If there are no items, or more
5+
# than one item, raises Enumerable::SoleItemExpectedError.
6+
#
7+
# (1..1).sole # => 1
8+
# (2..1).sole # => Enumerable::SoleItemExpectedError: no item found
9+
# (..1).sole # => Enumerable::SoleItemExpectedError: infinite range cannot represent a sole item
10+
def sole
11+
if self.begin.nil? || self.end.nil?
12+
raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "infinite range '#{inspect}' cannot represent a sole item"
13+
end
14+
15+
super
16+
end
17+
end

activesupport/test/core_ext/enumerable_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ def test_sole
398398
assert_equal 1, GenericEnumerable.new([1]).sole
399399
assert_raise(expected_raise) { GenericEnumerable.new([1, 2]).sole }
400400
assert_raise(expected_raise) { GenericEnumerable.new([1, nil]).sole }
401+
assert_raise(expected_raise) { GenericEnumerable.new(1..).sole }
401402
end
402403

403404
def test_doesnt_bust_constant_cache

activesupport/test/core_ext/range_ext_test.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,4 +287,16 @@ def test_date_time_with_step
287287
datetime = DateTime.now
288288
assert(((datetime - 1.hour)..datetime).step(1) { })
289289
end
290+
291+
def test_sole
292+
assert_equal 1, (1..1).sole
293+
294+
assert_raises(Enumerable::SoleItemExpectedError, match: "no item found") do
295+
(2..1).sole
296+
end
297+
298+
assert_raises(Enumerable::SoleItemExpectedError, match: "infinite range '..1' cannot represent a sole item") do
299+
(..1).sole
300+
end
301+
end
290302
end

0 commit comments

Comments
 (0)