Enumerable mixin in Ruby provides several searching and sorting methods to classes. The Enumerable module is useful in classes where there are collections of data that might require sorting or searching collections.
However #compact method was not available in Enumerable even though
classes like Array already has the #compact method.
Ruby 3.1
adds
#compact directly to the Enumerable module and Enumerator::Lazy class.
To understand the use case of Enumerable,
let’s assume we have a Reviews class with the reviews attribute.
Few reviews may be empty.
Now let’s assume we want the first five non-empty reviews.
The Ruby implementation of this Reviews class will be something like this.
# reviews.rb
class Reviews
include Enumerable
def initialize(reviews = Array.new)
@reviews = reviews
end
def each &block
@reviews.each { |review| block.call(review) }
end
endOur solution for this would vary based on our Ruby version.
Before Ruby 3.1
Before Ruby 3.1,
considering the above example if we had to delete nil
reviews from the collection class,
we would have to use the #reject method as shown below:
reviews = Reviews.new(["Nice touchscreen", "Excellent", nil, "Very good", nil, "Nice sound"])
# => #<Reviews:0x00007fb77f0548d8 @reviews=["Nice touchscreen", "Excellent", nil, "Very good", nil, "Nice sound"]>
reviews.reject { |review| review.nil? }.first(5)
# => ["Nice touchscreen", "Excellent", "Very good", "Nice sound"]After Ruby 3.1
After the introduction of #compact in the Enumerable module,
we can call compact on the class instance itself.
reviews = Reviews.new(["Nice touchscreen", "Excellent", nil, "Very good", nil, "Nice sound"])
# => #<Reviews:0x00007fb77f0548d8 @reviews=["Nice touchscreen", "Excellent", nil, "Very good", nil, "Nice sound"]>
reviews.compact.first(5)
# => ["Nice touchscreen", "Excellent", "Very good", "Nice sound"]Ruby 3.1 also adds #compact to Enumerable::Lazy class.
Now Enumerator::Lazy classes can chain #compact to return only non-nil values.
Now let’s imagine our Reviews class has more than 1000 objects,
our compact.first(5) method will go through the entire 1000 reviews
and select only the first 5.
This issue can be optimized using Enumerable::Lazy class.
reviews = Reviews.new(["Nice touchscreen", "Excellent", nil, "Very good", nil, "Nice sound", ...])
# => #<Reviews:0x00007fb77f0548d8 @reviews=["Nice touchscreen", "Excellent", nil, "Very good", nil, "Nice sound", ...]>
reviews.compact.first(5)
# => ["Nice touchscreen", "Excellent", "Very good", "Nice sound"]The lazy method is fetching items one-by-one until
five items are fetched instead of iterating over 1000 items at once.
