DeeplyEnumerable
Extend Enumerable objects with recursive versions of common operations.
DeeplyEnumerable walks a nested structure of Hash and Array objects and
applies an operation at every level. Nested Enumerable objects are converted
into DeeplyEnumerable types during the operation so the recursion can continue
all the way down.
Compaction
Ruby's #compact removes nil. Rails' #compact_blank removes anything that is
blank? (nil, false, "", " ", [], {}). DeeplyEnumerable provides
recursive versions of both, plus two methods in between for finer control over
which blanks are removed:
| Method | Removes | Keeps |
|---|---|---|
#deep_compact |
nil only |
every other value, including "", false, [], {} |
#deep_compact_existing_blank |
nil and values that are already blank |
collections that only become blank through compaction |
#deep_compact_blanked |
nil and collections emptied by compaction |
values that were already blank |
#deep_compact_blank |
every blank value | nothing blank |
The distinction between "existing" and "emptied" blanks matters once nesting is
involved. Consider { a: { b: nil }, c: [] }:
aholds{ b: nil }, which is not blank until itsnilis removed — an emptied blank.cholds[], which is blank in the input — an existing blank.
source = { a: :b, c: nil, d: { e: nil, f: [] }, g: [1, nil], h: {}, i: "", j: [nil] }
DeeplyEnumerable::Hash.deep_compact(source)
# => { a: :b, d: { f: [] }, g: [1], h: {}, i: "", j: [] } # only nils removed
DeeplyEnumerable::Hash.deep_compact_existing_blank(source)
# => { a: :b, d: {}, g: [1], j: [] } # pre-existing [], {} and "" removed; emptied kept
DeeplyEnumerable::Hash.deep_compact_blanked(source)
# => { a: :b, d: { f: [] }, g: [1], h: {}, i: "" } # emptied collections removed; pre-existing blanks kept
DeeplyEnumerable::Hash.deep_compact_blank(source)
# => { a: :b, g: [1] } # every blank removed
Each method has an in-place ! variant (#deep_compact!, #deep_compact_blank!,
etc.) that mutates the receiver and returns it, mirroring #compact!.
Merging
DeeplyEnumerable::Hash#reverse_deep_merge(other_hash) # recursively fill in only the missing keys
DeeplyEnumerable::Hash#reverse_deep_merge!(other_hash) # in place
#deep_reverse_merge / #deep_reverse_merge! are aliases.
Usage
Call a class method to operate on a plain Hash or Array. The result is the
matching DeeplyEnumerable type (Hash returns a DeeplyEnumerable::Hash,
Array returns a DeeplyEnumerable::Array):
DeeplyEnumerable::Hash.deep_compact_blank({ a: 1, b: nil, c: { d: nil } })
# => { a: 1 }
DeeplyEnumerable::Array.deep_compact([1, nil, [2, nil]])
# => [1, [2]]
Or extend the base Hash and Array classes (see Installation) and call the
methods directly:
{ a: 1, b: nil }.deep_compact_blank # => { a: 1 }
[1, nil, [nil]].deep_compact # => [1, []]
Upgrading from v1
v2 replaces the boolean arguments of #deep_compact with four named methods.
Map your old calls as follows:
| v1 call | v2 equivalent |
|---|---|
deep_compact / deep_compact(true, true) |
deep_compact_blank |
deep_compact(true, false) |
deep_compact_blanked |
deep_compact(false, true) |
deep_compact_existing_blank |
deep_compact(false, false) |
deep_compact |
⚠️ #deep_compact changed meaning. In v1, bare deep_compact defaulted to
the most aggressive behavior. In v2, #deep_compact removes only nils
(matching Ruby's #compact). If you relied on the old default, switch to
#deep_compact_blank.
⚠️ Blank now means blank?, not just empty. v1 only ever removed nil and
empty collections ([], {}). The v2 _blank family also removes false and
blank strings ("", " "), matching Rails' #compact_blank. Blankness is
determined by an internal equivalent of ActiveSupport's Object#blank?, so the
gem still has no ActiveSupport dependency.
Installation
Add this line to your application's Gemfile:
gem "deeply_enumerable"
or this line to mix the methods into the base Hash and Array classes:
gem "deeply_enumerable", require: "base_extensions"
And then execute:
$ bundle install
Or install it yourself as:
$ gem install deeply_enumerable
License
The gem is available as open source under the terms of the MIT License.