deeply_enumerable 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f9655518ad0bb3d8c2d3eb6616c2d9862e439d4b0ab4b45be6752b18628ecb1
4
- data.tar.gz: 280db3869191e9c9fb318baed1b99b6ebc3f90ff24c16993523fe47eceff26fb
3
+ metadata.gz: 6dc0e555ef198f9c8a08f8d3567a6ee2b3d24b06dd777617d4b41a2a1d32b4fd
4
+ data.tar.gz: 7a22638bdff5ce80f5f0bcaaa3cec78e8541c5402fa3f615415528b130f3628e
5
5
  SHA512:
6
- metadata.gz: 6939a317d815e1c0d6cfdc65f75f506cb549978b8338ef4f604086cb5efe7b4959d3ab3112691d068fa8f28082cb6753d3ae9177c5909ec56e0dd537c9c557b5
7
- data.tar.gz: 3d07a34bb72ed5c3f37ce106daf0c30eef47d46af22e29e3278cfcefbbafdd607db395abbb38b4b34531aceaef9af8c46a29d37587b170703dc4aafb17edafd6
6
+ metadata.gz: 1efe5ef4b790f4e936ef616dda532c2a50d22c0e244521abd22c7df3429b45aa81537d714bed552b398b5f21d1c3e8750246feed24bb2b1b3b044c6db065625d
7
+ data.tar.gz: cdaa3281558c05f8e58de0edb33912b7a939004881d7555dd70400b114422d3a35782e9f7914d31887356b03b3856ab34c28d4a7eeb23dbf1a1a267fba083046
data/CHANGELOG.md ADDED
@@ -0,0 +1,74 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [2.0.0] - 2026-06-17
9
+
10
+ This is a **major, breaking release**. The argument-based `#deep_compact` API has
11
+ been replaced with four clearly-named methods, and "blank" now matches Rails'
12
+ `#compact_blank` semantics.
13
+
14
+ ### Breaking
15
+
16
+ - **`#deep_compact` changed meaning.** In v1, bare `deep_compact` defaulted to the
17
+ *most aggressive* behavior. In v2 it removes **only `nil`** (matching Ruby's
18
+ `#compact`). If you relied on the old default, switch to `#deep_compact_blank`.
19
+ - **The boolean arguments are gone.** `deep_compact(remove_emptied_elements,
20
+ remove_empty_elements)` is replaced by four dedicated methods (see Added).
21
+ - **"Blank" now means `blank?`, not just empty.** The `_blank` family now also
22
+ removes `false` and blank strings (`""`, `" "`), in addition to `nil` and empty
23
+ collections — matching Rails' `#compact_blank`.
24
+
25
+ ### Added
26
+
27
+ - Four named compaction methods, each with an in-place `!` variant, available as
28
+ instance methods, class methods, and (via `require: "base_extensions"`) directly
29
+ on core `Hash`/`Array`:
30
+ - `#deep_compact` — removes `nil` only.
31
+ - `#deep_compact_existing_blank` — removes `nil` and values that are already
32
+ blank; keeps collections that only become blank through compaction.
33
+ - `#deep_compact_blanked` — removes `nil` and collections emptied by compaction;
34
+ keeps values that were already blank.
35
+ - `#deep_compact_blank` — removes every blank value.
36
+ - GitHub Actions CI running RSpec on Ruby 3.3, 3.4, and 4.0.
37
+ - A real, comprehensive test suite (43 examples) replacing the previous
38
+ placeholder specs.
39
+
40
+ ### Changed
41
+
42
+ - Rewrote the internals around a single recursive engine, with doc comments
43
+ linking each method to the Ruby/Rails source it mirrors. Blankness is determined
44
+ by an internal equivalent of ActiveSupport's `Object#blank?`, so the gem still
45
+ has **no ActiveSupport dependency**.
46
+ - `base_extensions` now mixes into `Hash`/`Array` the same way ActiveSupport does
47
+ (reopening the class).
48
+ - `rake` now runs the RSpec suite (`rake` / `rake spec`); dropped the unused
49
+ Minitest and RDoc tasks.
50
+ - Expanded the README with a semantics table, worked examples, and an
51
+ "Upgrading from v1" guide.
52
+
53
+ ### Fixed
54
+
55
+ - Fixed a latent bug in `#reverse_deep_merge!` (`ebuild` → `rebuild`) that was
56
+ hidden by placeholder specs.
57
+
58
+ ### Upgrading from v1
59
+
60
+ | v1 call | v2 equivalent |
61
+ | --- | --- |
62
+ | `deep_compact` / `deep_compact(true, true)` | `deep_compact_blank` |
63
+ | `deep_compact(true, false)` | `deep_compact_blanked` |
64
+ | `deep_compact(false, true)` | `deep_compact_existing_blank` |
65
+ | `deep_compact(false, false)` | `deep_compact` |
66
+
67
+ ## [1.0.0] - 2023-01-18
68
+
69
+ - Initial release: recursive `#deep_compact` (with `remove_emptied_elements` /
70
+ `remove_empty_elements` flags) and `#reverse_deep_merge` for nested `Hash` and
71
+ `Array` objects.
72
+
73
+ [2.0.0]: https://github.com/chaunce/deeply_enumerable/compare/1.0.0...2.0.0
74
+ [1.0.0]: https://github.com/chaunce/deeply_enumerable/releases/tag/1.0.0
data/README.md CHANGED
@@ -1,53 +1,112 @@
1
1
  # DeeplyEnumerable
2
- Extend Enumerable objects with recursive methods
2
+ Extend `Enumerable` objects with recursive versions of common operations.
3
+
4
+ `DeeplyEnumerable` walks a nested structure of `Hash` and `Array` objects and
5
+ applies an operation at every level. Nested `Enumerable` objects are converted
6
+ into `DeeplyEnumerable` types during the operation so the recursion can continue
7
+ all the way down.
8
+
9
+ ## Compaction
10
+ Ruby's `#compact` removes `nil`. Rails' `#compact_blank` removes anything that is
11
+ `blank?` (`nil`, `false`, `""`, `" "`, `[]`, `{}`). `DeeplyEnumerable` provides
12
+ recursive versions of both, plus two methods in between for finer control over
13
+ *which* blanks are removed:
14
+
15
+ | Method | Removes | Keeps |
16
+ | --- | --- | --- |
17
+ | `#deep_compact` | `nil` only | every other value, including `""`, `false`, `[]`, `{}` |
18
+ | `#deep_compact_existing_blank` | `nil` and values that are **already** blank | collections that only become blank through compaction |
19
+ | `#deep_compact_blanked` | `nil` and collections **emptied** by compaction | values that were already blank |
20
+ | `#deep_compact_blank` | every blank value | nothing blank |
21
+
22
+ The distinction between "existing" and "emptied" blanks matters once nesting is
23
+ involved. Consider `{ a: { b: nil }, c: [] }`:
24
+
25
+ * `a` holds `{ b: nil }`, which is *not* blank until its `nil` is removed — an
26
+ **emptied** blank.
27
+ * `c` holds `[]`, which is blank in the input — an **existing** blank.
3
28
 
4
- ## Usage
5
- Provides the following methods
6
29
  ```ruby
7
- $ DeeplyEnumerable::Hash#reverse_deep_merge(DeeplyEnumerable::Hash)
8
- $ DeeplyEnumerable::Hash#reverse_deep_merge!(DeeplyEnumerable::Hash)
9
- $ DeeplyEnumerable::Hash#deep_compact
10
- $ DeeplyEnumerable::Hash#deep_compact!
30
+ source = { a: :b, c: nil, d: { e: nil, f: [] }, g: [1, nil], h: {}, i: "", j: [nil] }
31
+
32
+ DeeplyEnumerable::Hash.deep_compact(source)
33
+ # => { a: :b, d: { f: [] }, g: [1], h: {}, i: "", j: [] } # only nils removed
34
+
35
+ DeeplyEnumerable::Hash.deep_compact_existing_blank(source)
36
+ # => { a: :b, d: {}, g: [1], j: [] } # pre-existing [], {} and "" removed; emptied kept
37
+
38
+ DeeplyEnumerable::Hash.deep_compact_blanked(source)
39
+ # => { a: :b, d: { f: [] }, g: [1], h: {}, i: "" } # emptied collections removed; pre-existing blanks kept
11
40
 
12
- $ DeeplyEnumerable::Array#deep_compact
13
- $ DeeplyEnumerable::Array#deep_compact!
41
+ DeeplyEnumerable::Hash.deep_compact_blank(source)
42
+ # => { a: :b, g: [1] } # every blank removed
14
43
  ```
15
44
 
16
- Nested `Enumerable` objects will be converted into `DeeplyEnumerable` type objects during merge or compact operations to allow recursive method calls
45
+ Each method has an in-place `!` variant (`#deep_compact!`, `#deep_compact_blank!`,
46
+ etc.) that mutates the receiver and returns it, mirroring `#compact!`.
17
47
 
18
- Class methods may be used for base ruby enumerable objects, such as `Hash` or `Array` objects
48
+ ## Merging
19
49
  ```ruby
20
- $ DeeplyEnumerable::Hash.deep_compact(Hash)
21
- $ DeeplyEnumerable::Array.deep_compact(Array)
50
+ DeeplyEnumerable::Hash#reverse_deep_merge(other_hash) # recursively fill in only the missing keys
51
+ DeeplyEnumerable::Hash#reverse_deep_merge!(other_hash) # in place
22
52
  ```
53
+ `#deep_reverse_merge` / `#deep_reverse_merge!` are aliases.
23
54
 
24
- These methos will retrun a `DeeplyEnumerable` type object matching the object passed, e.g: `Array` will return a `DeeplyEnumerable::Array`
25
-
26
- You may also extend base classes if you `require: "base_extensions"` as described in *Installation*
55
+ ## Usage
56
+ Call a class method to operate on a plain `Hash` or `Array`. The result is the
57
+ matching `DeeplyEnumerable` type (`Hash` returns a `DeeplyEnumerable::Hash`,
58
+ `Array` returns a `DeeplyEnumerable::Array`):
27
59
  ```ruby
28
- $ Hash#reverse_deep_merge(DeeplyEnumerable::Hash)
29
- $ Hash#reverse_deep_merge!(DeeplyEnumerable::Hash)
30
- $ Hash#deep_compact
31
- $ Hash#deep_compact!
60
+ DeeplyEnumerable::Hash.deep_compact_blank({ a: 1, b: nil, c: { d: nil } })
61
+ # => { a: 1 }
32
62
 
33
- $ Array#deep_compact
34
- $ Array#deep_compact!
63
+ DeeplyEnumerable::Array.deep_compact([1, nil, [2, nil]])
64
+ # => [1, [2]]
65
+ ```
66
+
67
+ Or extend the base `Hash` and `Array` classes (see *Installation*) and call the
68
+ methods directly:
69
+ ```ruby
70
+ { a: 1, b: nil }.deep_compact_blank # => { a: 1 }
71
+ [1, nil, [nil]].deep_compact # => [1, []]
35
72
  ```
36
73
 
74
+ ## Upgrading from v1
75
+ v2 replaces the boolean arguments of `#deep_compact` with four named methods.
76
+ Map your old calls as follows:
77
+
78
+ | v1 call | v2 equivalent |
79
+ | --- | --- |
80
+ | `deep_compact` / `deep_compact(true, true)` | `deep_compact_blank` |
81
+ | `deep_compact(true, false)` | `deep_compact_blanked` |
82
+ | `deep_compact(false, true)` | `deep_compact_existing_blank` |
83
+ | `deep_compact(false, false)` | `deep_compact` |
84
+
85
+ ⚠️ **`#deep_compact` changed meaning.** In v1, bare `deep_compact` defaulted to
86
+ the *most aggressive* behavior. In v2, `#deep_compact` removes **only nils**
87
+ (matching Ruby's `#compact`). If you relied on the old default, switch to
88
+ `#deep_compact_blank`.
89
+
90
+ ⚠️ **Blank now means `blank?`, not just empty.** v1 only ever removed `nil` and
91
+ empty collections (`[]`, `{}`). The v2 `_blank` family also removes `false` and
92
+ blank strings (`""`, `" "`), matching Rails' `#compact_blank`. Blankness is
93
+ determined by an internal equivalent of ActiveSupport's `Object#blank?`, so the
94
+ gem still has **no ActiveSupport dependency**.
95
+
37
96
  ## Installation
38
97
  Add this line to your application's Gemfile:
39
98
  ```ruby
40
99
  gem "deeply_enumerable"
41
100
  ```
42
101
 
43
- or this line to extend the base `Enumerable` classes:
102
+ or this line to mix the methods into the base `Hash` and `Array` classes:
44
103
  ```ruby
45
104
  gem "deeply_enumerable", require: "base_extensions"
46
105
  ```
47
106
 
48
107
  And then execute:
49
108
  ```bash
50
- $ bundle
109
+ $ bundle install
51
110
  ```
52
111
 
53
112
  Or install it yourself as:
data/Rakefile CHANGED
@@ -1,33 +1,6 @@
1
- begin
2
- require "bundler/setup"
3
- rescue LoadError
4
- puts "You must `gem install bundler` and `bundle install` to run rake tasks"
5
- end
6
-
7
- require "rdoc/task"
8
-
9
- RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = "rdoc"
11
- rdoc.title = "DeeplyEnumerable"
12
- rdoc.options << "--line-numbers"
13
- rdoc.rdoc_files.include("README.md")
14
- rdoc.rdoc_files.include("lib/**/*.rb")
15
- end
16
-
17
-
18
-
19
-
20
-
21
-
22
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
23
3
 
24
- require "rake/testtask"
25
-
26
- Rake::TestTask.new(:test) do |t|
27
- t.libs << "test"
28
- t.pattern = "test/**/*_test.rb"
29
- t.verbose = false
30
- end
31
-
4
+ RSpec::Core::RakeTask.new(:spec)
32
5
 
33
- task default: :test
6
+ task default: :spec
@@ -1,4 +1,11 @@
1
1
  require "deeply_enumerable"
2
2
 
3
- Hash.send(:include, DeeplyEnumerable::HashExtension)
4
- Array.send(:include, DeeplyEnumerable::ArrayExtension)
3
+ # Mirror the way Rails (ActiveSupport) mixes its enumerable extensions into the
4
+ # core classes: reopen each class and include the corresponding module.
5
+ class Hash
6
+ include DeeplyEnumerable::HashExtension
7
+ end
8
+
9
+ class Array
10
+ include DeeplyEnumerable::ArrayExtension
11
+ end
@@ -8,31 +8,63 @@ module DeeplyEnumerable
8
8
  end
9
9
  end
10
10
 
11
- def deep_compact!(remove_emptied_elements = true, remove_empty_elements = remove_emptied_elements)
12
- each.with_index do |value, index|
13
- next if unenumerable_object?(value)
14
-
15
- value = rebuild(value) unless value.respond_to?(:reverse_deep_merge)
16
- compact_method = %i[deep_compact! deep_compact compact! compact].detect{ |m| value.respond_to?(m) }
17
- next unless compact_method
18
-
19
- original_empty = value.respond_to?(:empty?) ? value.empty? : value.respond_to?(:none?) ? value.none? : false
20
- compact_value = value.send(*[compact_method].concat(value.method(compact_method).parameters.collect { |_,param| binding.local_variable_get(param) } ).compact) || value
21
- compact_empty = compact_value.respond_to?(:empty?) ? compact_value.empty? : compact_value.respond_to?(:none?) ? compact_value.none? : false
22
-
23
- if (original_empty && remove_empty_elements) || (!original_empty && compact_empty && remove_emptied_elements)
24
- self.delete_at(index)
25
- else
26
- self[index] = compact_value
27
- end
28
- end
29
- compact!
11
+ # Recursively removes nil elements, leaving every other element (including
12
+ # already-blank ones such as "", [] and {}) untouched. This is the
13
+ # recursive counterpart of Ruby's Array#compact.
14
+ # https://docs.ruby-lang.org/en/master/Array.html#method-i-compact
15
+ def deep_compact
16
+ deep_compact_each(false, false)
17
+ end
18
+
19
+ # In-place variant of #deep_compact, mirroring Array#compact!.
20
+ def deep_compact!
21
+ replace(deep_compact)
22
+ end
23
+
24
+ # Recursively removes nils and elements that are already blank, but keeps
25
+ # collections that only become blank as a result of compaction.
26
+ def deep_compact_existing_blank
27
+ deep_compact_each(false, true)
28
+ end
29
+
30
+ # In-place variant of #deep_compact_existing_blank.
31
+ def deep_compact_existing_blank!
32
+ replace(deep_compact_existing_blank)
33
+ end
30
34
 
31
- self
35
+ # Recursively removes nils and collections that became blank through
36
+ # compaction, but keeps elements that were already blank.
37
+ def deep_compact_blanked
38
+ deep_compact_each(true, false)
32
39
  end
33
40
 
34
- def deep_compact(remove_emptied_elements = true, remove_empty_elements = remove_emptied_elements)
35
- dup.deep_compact!(remove_emptied_elements, remove_empty_elements)
41
+ # In-place variant of #deep_compact_blanked.
42
+ def deep_compact_blanked!
43
+ replace(deep_compact_blanked)
44
+ end
45
+
46
+ # Recursively removes every blank element (nil, false, "", " ", [], {}).
47
+ # This is the recursive counterpart of Rails' Array#compact_blank.
48
+ # https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/enumerable.rb
49
+ def deep_compact_blank
50
+ deep_compact_each(true, true)
51
+ end
52
+
53
+ # In-place variant of #deep_compact_blank, mirroring Array#compact_blank!.
54
+ def deep_compact_blank!
55
+ replace(deep_compact_blank)
56
+ end
57
+
58
+ private
59
+
60
+ # Shared engine for the deep_compact family: rebuilds an array of the same
61
+ # class, recursively compacting each element and discarding the ones that
62
+ # should be removed for the given combination of flags.
63
+ def deep_compact_each(remove_emptied_elements, remove_empty_elements)
64
+ each_with_object(self.class.new) do |element, result|
65
+ keep, element = deep_compact_element(element, remove_emptied_elements, remove_empty_elements)
66
+ result.push(element) if keep
67
+ end
36
68
  end
37
69
  end
38
70
 
@@ -1,7 +1,13 @@
1
1
  module DeeplyEnumerable
2
2
  module Enumerable
3
+ # Collection-like objects that should be treated as opaque elements rather
4
+ # than recursed into during deep operations.
3
5
  UNENUMERABLE = ["ActiveRecord::Relation", "Range"]
4
6
 
7
+ # Whitespace-only strings are considered blank, mirroring ActiveSupport.
8
+ # https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/object/blank.rb
9
+ BLANK_RE = /\A[[:space:]]*\z/
10
+
5
11
  def self.included(klass)
6
12
  klass.extend(ClassMethods)
7
13
  end
@@ -24,9 +30,28 @@ module DeeplyEnumerable
24
30
  new.tap { |deeply_enumerable_object| object.each { |value| deeply_enumerable_object.push(rebuild(value)) } }
25
31
  end
26
32
 
27
- def deep_compact(object, remove_emptied_elements = true, remove_empty_elements = remove_emptied_elements)
28
- check_object_class(object)
29
- deep_rebuild(object).deep_compact(remove_emptied_elements, remove_empty_elements)
33
+ # Recursively removes nil values.
34
+ # Mirrors Ruby's Array#compact / Hash#compact.
35
+ def deep_compact(object)
36
+ deep_rebuild(object).deep_compact
37
+ end
38
+
39
+ # Recursively removes nils and values that are already blank, while
40
+ # keeping collections that only become blank as a result of compaction.
41
+ def deep_compact_existing_blank(object)
42
+ deep_rebuild(object).deep_compact_existing_blank
43
+ end
44
+
45
+ # Recursively removes nils and collections that became blank through
46
+ # compaction, while keeping values that were already blank.
47
+ def deep_compact_blanked(object)
48
+ deep_rebuild(object).deep_compact_blanked
49
+ end
50
+
51
+ # Recursively removes every blank value.
52
+ # Mirrors Rails' Enumerable#compact_blank.
53
+ def deep_compact_blank(object)
54
+ deep_rebuild(object).deep_compact_blank
30
55
  end
31
56
 
32
57
  def unenumerable
@@ -47,5 +72,42 @@ module DeeplyEnumerable
47
72
  def unenumerable_object?(object)
48
73
  self.class.unenumerable.any? { |unenumerable_klass| object.is_a?(unenumerable_klass) }
49
74
  end
75
+
76
+ private
77
+
78
+ # Decides what to do with a single element during deep compaction and
79
+ # returns [keep?, element], where +element+ may be a rebuilt/compacted
80
+ # copy.
81
+ #
82
+ # nil is always dropped. Nested enumerables are compacted recursively, then
83
+ # dropped if they were already blank (+remove_empty_elements+) or only
84
+ # became blank through compaction (+remove_emptied_elements+).
85
+ # Non-enumerable elements are dropped only when already blank and
86
+ # +remove_empty_elements+ is set.
87
+ def deep_compact_element(element, remove_emptied_elements, remove_empty_elements)
88
+ return [false, element] if element.nil?
89
+
90
+ element = rebuild(element) unless unenumerable_object?(element) || element.respond_to?(:deep_compact_each, true)
91
+
92
+ if !unenumerable_object?(element) && element.respond_to?(:deep_compact_each, true)
93
+ blank_before = deeply_blank?(element)
94
+ element = element.send(:deep_compact_each, remove_emptied_elements, remove_empty_elements)
95
+ keep = !((blank_before && remove_empty_elements) ||
96
+ (!blank_before && remove_emptied_elements && deeply_blank?(element)))
97
+ [keep, element]
98
+ else
99
+ [!(remove_empty_elements && deeply_blank?(element)), element]
100
+ end
101
+ end
102
+
103
+ # Internal equivalent of ActiveSupport's Object#blank? (with
104
+ # String#blank?), implemented here so the gem needs no ActiveSupport
105
+ # dependency.
106
+ # https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/object/blank.rb
107
+ def deeply_blank?(value)
108
+ return value.match?(BLANK_RE) if value.is_a?(::String)
109
+
110
+ value.respond_to?(:empty?) ? !!value.empty? : !value
111
+ end
50
112
  end
51
113
  end
@@ -13,7 +13,7 @@ module DeeplyEnumerable
13
13
  this_value = self[current_key]
14
14
 
15
15
  self[current_key] = if this_value.is_a?(::Hash) && other_value.is_a?(::Hash)
16
- this_value = ebuild(this_value) unless this_value.respond_to?(:reverse_deep_merge)
16
+ this_value = rebuild(this_value) unless this_value.respond_to?(:reverse_deep_merge)
17
17
  this_value.reverse_deep_merge(other_value)
18
18
  else
19
19
  key?(current_key) ? this_value : other_value
@@ -29,27 +29,63 @@ module DeeplyEnumerable
29
29
  end
30
30
  alias_method :deep_reverse_merge, :reverse_deep_merge
31
31
 
32
- def deep_compact!(remove_emptied_elements = true, remove_empty_elements = remove_emptied_elements)
33
- each do |key, value|
34
- next if unenumerable_object?(value)
32
+ # Recursively removes nil values, leaving every other value (including
33
+ # already-blank ones such as "", [] and {}) untouched. This is the
34
+ # recursive counterpart of Ruby's Hash#compact.
35
+ # https://docs.ruby-lang.org/en/master/Hash.html#method-i-compact
36
+ def deep_compact
37
+ deep_compact_each(false, false)
38
+ end
35
39
 
36
- value = rebuild(value) unless value.respond_to?(:reverse_deep_merge)
37
- compact_method = %i[deep_compact! deep_compact compact! compact].detect{ |m| value.respond_to?(m) }
38
- next unless compact_method
40
+ # In-place variant of #deep_compact, mirroring Hash#compact!.
41
+ def deep_compact!
42
+ replace(deep_compact)
43
+ end
39
44
 
40
- original_empty = value.respond_to?(:empty?) ? value.empty? : value.respond_to?(:none?) ? value.none? : false rescue false
41
- compact_value = value.send(*[compact_method].concat(value.method(compact_method).parameters.collect { |_,param| binding.local_variable_get(param) } ).compact) || value
42
- compact_empty = compact_value.respond_to?(:empty?) ? compact_value.empty? : compact_value.respond_to?(:none?) ? compact_value.none? : false
45
+ # Recursively removes nils and values that are already blank, but keeps
46
+ # collections that only become blank as a result of compaction.
47
+ def deep_compact_existing_blank
48
+ deep_compact_each(false, true)
49
+ end
43
50
 
44
- self[key] = (original_empty && remove_empty_elements) || (!original_empty && compact_empty && remove_emptied_elements) ? nil : compact_value
45
- end
46
- compact!
51
+ # In-place variant of #deep_compact_existing_blank.
52
+ def deep_compact_existing_blank!
53
+ replace(deep_compact_existing_blank)
54
+ end
47
55
 
48
- self
56
+ # Recursively removes nils and collections that became blank through
57
+ # compaction, but keeps values that were already blank.
58
+ def deep_compact_blanked
59
+ deep_compact_each(true, false)
60
+ end
61
+
62
+ # In-place variant of #deep_compact_blanked.
63
+ def deep_compact_blanked!
64
+ replace(deep_compact_blanked)
49
65
  end
50
66
 
51
- def deep_compact(remove_emptied_elements = true, remove_empty_elements = remove_emptied_elements)
52
- dup.deep_compact!(remove_emptied_elements, remove_empty_elements)
67
+ # Recursively removes every blank value (nil, false, "", " ", [], {}). This
68
+ # is the recursive counterpart of Rails' Hash#compact_blank.
69
+ # https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/hash/keys.rb
70
+ def deep_compact_blank
71
+ deep_compact_each(true, true)
72
+ end
73
+
74
+ # In-place variant of #deep_compact_blank, mirroring Hash#compact_blank!.
75
+ def deep_compact_blank!
76
+ replace(deep_compact_blank)
77
+ end
78
+
79
+ private
80
+
81
+ # Shared engine for the deep_compact family: rebuilds a hash of the same
82
+ # class, recursively compacting each value and discarding the keys whose
83
+ # value should be removed for the given combination of flags.
84
+ def deep_compact_each(remove_emptied_elements, remove_empty_elements)
85
+ each_with_object(self.class.new) do |(key, value), result|
86
+ keep, value = deep_compact_element(value, remove_emptied_elements, remove_empty_elements)
87
+ result[key] = value if keep
88
+ end
53
89
  end
54
90
  end
55
91
 
@@ -70,5 +106,3 @@ module DeeplyEnumerable
70
106
  end
71
107
  end
72
108
  end
73
-
74
-
@@ -1,3 +1,3 @@
1
1
  module DeeplyEnumerable
2
- VERSION = "1.0.0"
2
+ VERSION = "2.0.0"
3
3
  end
metadata CHANGED
@@ -1,15 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deeply_enumerable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - chaunce
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-01-19 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: rspec
15
28
  requirement: !ruby/object:Gem::Requirement
@@ -24,13 +37,14 @@ dependencies:
24
37
  - - ">="
25
38
  - !ruby/object:Gem::Version
26
39
  version: '0'
27
- description: recusrive support for enumerable operations
40
+ description: recursive support for enumerable operations
28
41
  email:
29
42
  - chaunce.slc@gmail.com
30
43
  executables: []
31
44
  extensions: []
32
45
  extra_rdoc_files: []
33
46
  files:
47
+ - CHANGELOG.md
34
48
  - MIT-LICENSE
35
49
  - README.md
36
50
  - Rakefile
@@ -43,8 +57,9 @@ files:
43
57
  homepage: https://github.com/chaunce/deeply_enumerable
44
58
  licenses:
45
59
  - MIT
46
- metadata: {}
47
- post_install_message:
60
+ metadata:
61
+ source_code_uri: https://github.com/chaunce/deeply_enumerable
62
+ changelog_uri: https://github.com/chaunce/deeply_enumerable/blob/master/CHANGELOG.md
48
63
  rdoc_options: []
49
64
  require_paths:
50
65
  - lib
@@ -59,8 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
59
74
  - !ruby/object:Gem::Version
60
75
  version: '0'
61
76
  requirements: []
62
- rubygems_version: 3.4.3
63
- signing_key:
77
+ rubygems_version: 4.0.6
64
78
  specification_version: 4
65
- summary: recusrive support for enumerable operations
79
+ summary: recursive support for enumerable operations
66
80
  test_files: []