Skip to content

Commit

Permalink
Add single shortcut for reducing single element enumerables to that e…
Browse files Browse the repository at this point in the history
…lement

I frequently find myself and my team working with enumerables that we know should only contain a single element. Using #first or #last does not adequately protect against those times when the enum has length 0 or > 1, and does nothing to indicate that the enumerable is expected to have exactly 1 element.
  • Loading branch information
rynonl committed Jun 5, 2020
1 parent 95af87f commit 1bc376c
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 1 deletion.
2 changes: 1 addition & 1 deletion activerecord/test/models/book.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Book < ActiveRecord::Base

enum status: [:proposed, :written, :published]
enum last_read: { unread: 0, reading: 2, read: 3, forgotten: nil }
enum nullable_status: [:single, :married]
enum nullable_status: [:single, :married], _prefix: :true
enum language: [:english, :spanish, :french], _prefix: :in
enum author_visibility: [:visible, :invisible], _prefix: true
enum illustrator_visibility: [:visible, :invisible], _prefix: true
Expand Down
5 changes: 5 additions & 0 deletions activesupport/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
* Add `Enumerable#single` to return the only element contained in an Enumerable.
Will raise RangeError if the Enumerable is empty or contins multiple elements.

*Ryan O'Neill*

* `require_dependency` has been documented to be _obsolete_ in `:zeitwerk`
mode. The method is not deprecated as such (yet), but applications are
encouraged to not use it.
Expand Down
12 changes: 12 additions & 0 deletions activesupport/lib/active_support/core_ext/enumerable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@ def index_with(default = INDEX_WITH_DEFAULT)
end
end

# Returns the only element of the enumerable and raises if there is not exactly
# 1 element in the enumerable
#
# [1].single # => 1
# [].single # => RangeError: Empty enumerable
# [1, 2].single # => RangeError: Multiple elements in enumerable
def single
raise RangeError, "Empty enumerable" if count == 0
raise RangeError, "Multiple elements in enumerable" if count > 1
first
end

# Returns +true+ if the enumerable has more than 1 element. Functionally
# equivalent to <tt>enum.to_a.size > 1</tt>. Can be called with a block too,
# much like any?, so <tt>people.many? { |p| p.age > 26 }</tt> returns +true+
Expand Down
9 changes: 9 additions & 0 deletions activesupport/test/core_ext/enumerable_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,15 @@ def test_index_with
assert_equal({ Payment.new(5) => 5, Payment.new(15) => 15, Payment.new(10) => 10 }, payments.index_with.each(&:price))
end

def test_single
assert_equal 1, [1].single

expected_raise = RangeError

assert_raise(expected_raise) { [].single }
assert_raise(expected_raise) { [ 1, 2 ].single }
end

def test_many
assert_equal false, GenericEnumerable.new([]).many?
assert_equal false, GenericEnumerable.new([ 1 ]).many?
Expand Down

0 comments on commit 1bc376c

Please sign in to comment.