Skip to content

Commit bfaa309

Browse files
author
David Heinemeier Hansson
committed
Added Array#including, Array#excluding, Enumerable#including, Enumerable#excluding
1 parent 91ed21b commit bfaa309

5 files changed

Lines changed: 77 additions & 11 deletions

File tree

activesupport/CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
* Allow Array#excluding and Enumerable#excluding to deal with a passed array gracefully.
2+
3+
[ 1, 2, 3, 4, 5 ].excluding([4, 5]) => [ 1, 2, 3 ]
4+
5+
*DHH*
6+
7+
* Renamed Array#without and Enumerable#without to Array#excluding and Enumerable#excluding, to create parity with
8+
Array#including and Enumerable#including. Retained the old names as aliases.
9+
10+
*DHH*
11+
12+
* Added Array#including and Enumerable#including to conveniently enlarge a collection with more members using a method rather than an operator:
13+
14+
[ 1, 2, 3 ].including(4, 5) => [ 1, 2, 3, 4, 5 ]
15+
post.authors.including(Current.person) => All the authors plus the current person!
16+
17+
*DHH*
18+
119
## Rails 6.0.0.beta2 (February 25, 2019) ##
220

321
* New autoloading based on [Zeitwerk](https://github.com/fxn/zeitwerk).

activesupport/lib/active_support/core_ext/array/access.rb

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,28 @@ def to(position)
2929
end
3030
end
3131

32-
# Returns a copy of the Array without the specified elements.
32+
# Returns a new array that includes the passed elements.
33+
#
34+
# Example: [ 1, 2, 3 ].including(4, 5) => [ 1, 2, 3, 4, 5 ]
35+
def including(*elements)
36+
self + elements.flatten
37+
end
38+
39+
# Returns a copy of the Array excluding the specified elements.
3340
#
3441
# people = ["David", "Rafael", "Aaron", "Todd"]
35-
# people.without "Aaron", "Todd"
42+
# people.excluding "Aaron", "Todd"
3643
# # => ["David", "Rafael"]
3744
#
38-
# Note: This is an optimization of <tt>Enumerable#without</tt> that uses <tt>Array#-</tt>
45+
# Note: This is an optimization of <tt>Enumerable#excluding</tt> that uses <tt>Array#-</tt>
3946
# instead of <tt>Array#reject</tt> for performance reasons.
47+
def excluding(*elements)
48+
self - elements.flatten
49+
end
50+
51+
# Alias for #excluding.
4052
def without(*elements)
41-
self - elements
53+
excluding(*elements)
4254
end
4355

4456
# Equal to <tt>self[1]</tt>.

activesupport/lib/active_support/core_ext/enumerable.rb

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,23 +97,43 @@ def many?
9797
end
9898
end
9999

100+
# Returns a new array that includes the passed elements.
101+
#
102+
# [ 1, 2, 3 ].including(4, 5)
103+
# # => [ 1, 2, 3, 4, 5 ]
104+
#
105+
# ["David", "Rafael"].including %w[ Aaron Todd ]
106+
# # => ["David", "Rafael", "Aaron", "Todd"]
107+
def including(*elements)
108+
to_a.including(*elements)
109+
end
110+
100111
# The negative of the <tt>Enumerable#include?</tt>. Returns +true+ if the
101112
# collection does not include the object.
102113
def exclude?(object)
103114
!include?(object)
104115
end
105116

106-
# Returns a copy of the enumerable without the specified elements.
117+
# Returns a copy of the enumerable excluding the specified elements.
118+
#
119+
# ["David", "Rafael", "Aaron", "Todd"].excluding "Aaron", "Todd"
120+
# # => ["David", "Rafael"]
107121
#
108-
# ["David", "Rafael", "Aaron", "Todd"].without "Aaron", "Todd"
122+
# ["David", "Rafael", "Aaron", "Todd"].excluding %w[ Aaron Todd ]
109123
# # => ["David", "Rafael"]
110124
#
111-
# {foo: 1, bar: 2, baz: 3}.without :bar
125+
# {foo: 1, bar: 2, baz: 3}.excluding :bar
112126
# # => {foo: 1, baz: 3}
113-
def without(*elements)
127+
def excluding(*elements)
128+
elements.flatten!
114129
reject { |element| elements.include?(element) }
115130
end
116131

132+
# Alias for #excluding.
133+
def without(*elements)
134+
excluding(*elements)
135+
end
136+
117137
# Convert an enumerable to an array based on the given key.
118138
#
119139
# [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)

activesupport/test/core_ext/array/access_test.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ def test_specific_accessor
3232
assert_equal array[-2], array.second_to_last
3333
end
3434

35+
def test_including
36+
assert_equal [1, 2, 3, 4, 5], [1, 2, 4].including(3, 5).sort
37+
assert_equal [1, 2, 3, 4, 5], [1, 2, 4].including([3, 5]).sort
38+
end
39+
40+
def test_excluding
41+
assert_equal [1, 2, 4], [1, 2, 3, 4, 5].excluding(3, 5)
42+
assert_equal [1, 2, 4], [1, 2, 3, 4, 5].excluding([3, 5])
43+
end
44+
3545
def test_without
3646
assert_equal [1, 2, 4], [1, 2, 3, 4, 5].without(3, 5)
3747
end

activesupport/test/core_ext/enumerable_test.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,17 @@ def test_exclude?
217217
assert_equal false, GenericEnumerable.new([ 1 ]).exclude?(1)
218218
end
219219

220+
def test_excluding
221+
assert_equal [1, 2, 4], GenericEnumerable.new((1..5).to_a).excluding(3, 5)
222+
assert_equal [3, 4, 5], GenericEnumerable.new((1..5).to_a).excluding([1, 2])
223+
assert_equal [1, 2, 4], (1..5).to_a.excluding(3, 5)
224+
assert_equal [1, 2, 4], (1..5).to_set.excluding(3, 5)
225+
assert_equal({ foo: 1, baz: 3 }, { foo: 1, bar: 2, baz: 3 }.excluding(:bar))
226+
end
227+
220228
def test_without
221229
assert_equal [1, 2, 4], GenericEnumerable.new((1..5).to_a).without(3, 5)
222-
assert_equal [1, 2, 4], (1..5).to_a.without(3, 5)
223-
assert_equal [1, 2, 4], (1..5).to_set.without(3, 5)
224-
assert_equal({ foo: 1, baz: 3 }, { foo: 1, bar: 2, baz: 3 }.without(:bar))
230+
assert_equal [3, 4, 5], GenericEnumerable.new((1..5).to_a).without([1, 2])
225231
end
226232

227233
def test_pluck

0 commit comments

Comments
 (0)