everythingrb 0.8.3 → 0.9.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: 6523ad82c57c693afadd2a17649c6628b49c0f775d0c3af09b5b207d8bace8a7
4
- data.tar.gz: 8c0c363214cf537d6ada6ad78d0da899ddbfdc17066325774b9e8f6f97b9f6f1
3
+ metadata.gz: cd5be3e26181ed61cc29aee8e316f3fd10d3bc9f5ea1d424069c4aefdc1c8125
4
+ data.tar.gz: 86517a10235873c3075237ff7f774315dc390d68fe3cf23ee751f15ca750508f
5
5
  SHA512:
6
- metadata.gz: 8a9f2620930ed9635287815679639cbd5403dc31b382717e107d8fbebf5fad0b6ba4be5398478f9b240141bbbbd2889cccbfc86d9330aeb1547386ab114bf034
7
- data.tar.gz: 8b6f1facb28da84ad99d7e52d14196ae023521c8ea26c075e1649f02364a29595f424b8aa9719438742ae2f5cc8ce047336534e94357693c7a678f225fdbc353
6
+ metadata.gz: 643b0c34ffa81f136d1abb91def2e6bdbb65a00888a735cdef95950db9877d3ea0d06c22119c36f3909283985711792bc0530944107ed76d744742daff40dd3c
7
+ data.tar.gz: 40db61bcf4b8c77a791b07fa30fcdd8e4d3b1f0054ff9124030d15b67ad8194db312cbb6f1ba2261499e5431132952746a5ef184ced15e212401207c15bf6dd6
data/CHANGELOG.md CHANGED
@@ -15,6 +15,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
  ### Removed
16
16
  -->
17
17
 
18
+ ## [0.9.0] - 12025-08-01
19
+
20
+ ### Added
21
+
22
+ - **Added `Hash#compact_blank_merge` and `Hash#compact_blank_merge!`** - Merge only present (non-blank) values when ActiveSupport is loaded. Filters out nil, empty strings, empty arrays, false, and other blank values according to ActiveSupport's definition.
23
+ - Added `with_index` parameter to `Hash#join_map` for API consistency with `Array#join_map`.
24
+
25
+ ### Changed
26
+
27
+ - **BREAKING: Renamed `Hash#merge_compact` to `Hash#compact_merge`** - Updated method naming for consistency with other operation-first methods like `compact_prefix`, `trim_nils`, etc. The old method names will be removed in this version.
28
+ - **BREAKING: Renamed `Hash#merge_compact!` to `Hash#compact_merge!`** - In-place version follows the same naming convention.
29
+
30
+ ### Removed
31
+
32
+ - **BREAKING: Removed `Hash#merge_compact` and `Hash#merge_compact!`** - These methods have been renamed to `Hash#compact_merge` and `Hash#compact_merge!` respectively for naming consistency.
33
+ - **Removed deprecated Hash value filtering methods** - `Hash#select_values`, `Hash#select_values!`, `Hash#reject_values`, `Hash#reject_values!`, and `Hash#filter_values`, `Hash#filter_values!` have been removed as planned. These methods were deprecated in v0.8.3. Use the standard Ruby alternatives instead:
34
+ - `hash.select_values { |v| condition }` → `hash.select { |k, v| condition }`
35
+ - `hash.reject_values { |v| condition }` → `hash.reject { |k, v| condition }`
36
+ - For blank filtering: `hash.reject_values(&:blank?)` → `hash.compact_blank` (with ActiveSupport)
37
+
18
38
  ## [0.8.3] - 12025-05-31
19
39
 
20
40
  ### Added
@@ -22,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
22
42
  ### Changed
23
43
 
24
44
  - **Deprecated Hash value filtering methods** - `Hash#select_values`, `Hash#reject_values`, `Hash#select_values!` and `Hash#reject_values!` are now deprecated and will be removed in v0.9.0. These methods largely duplicate existing Ruby/ActiveSupport functionality:
45
+
25
46
  - `hash.reject_values(&:blank?)` → use `hash.compact_blank` instead
26
47
  - `hash.select_values { |v| condition }` → use `hash.select { |k, v| condition }`
27
48
  - `hash.reject_values { |v| condition }` → use `hash.reject { |k, v| condition }`
@@ -95,6 +116,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
95
116
  - `Hash#reject_values!` - Same as `reject_values` but modifies the hash in place
96
117
  - Added `filter_values` and `filter_values!` as aliases for `select_values` and `select_values!` respectively
97
118
  - Added `Kernel#morph` as an alias for `then` (and `yield_self`) that better communicates transformation intent:
119
+
98
120
  ```ruby
99
121
  # Instead of this:
100
122
  value.then { |v| transform_somehow(v) }
@@ -124,12 +146,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
124
146
  The chainable `.with_key` method approach has been replaced with a more straightforward parameter-based approach.
125
147
 
126
148
  **Before:**
149
+
127
150
  ```ruby
128
151
  hash.transform_values.with_key { |value, key| "#{key}:#{value}" }
129
152
  hash.transform_values!.with_key { |value, key| "#{key}:#{value}" }
130
153
  ```
131
154
 
132
155
  **After:**
156
+
133
157
  ```ruby
134
158
  hash.transform_values(with_key: true) { |value, key| "#{key}:#{value}" }
135
159
  hash.transform_values!(with_key: true) { |value, key| "#{key}:#{value}" }
@@ -148,11 +172,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
148
172
  The parameter order in `Hash#transform_values.with_key` has been changed to yield `|value, key|` instead of `|key, value|` to maintain consistency with Ruby's standard enumeration methods like `each_with_index`.
149
173
 
150
174
  **Before:**
175
+
151
176
  ```ruby
152
177
  hash.transform_values.with_key { |key, value| "#{key}: #{value}" }
153
178
  ```
154
179
 
155
180
  **After:**
181
+
156
182
  ```ruby
157
183
  hash.transform_values.with_key { |value, key| "#{key}: #{value}" }
158
184
  ```
@@ -164,11 +190,11 @@ This change aligns our method signatures with Ruby's conventions and matches our
164
190
  - Added `Hash#transform` and `Hash#transform!` for transforming a hash's keys and values at the same time.
165
191
 
166
192
  ### Changed
193
+
167
194
  - Changed parameter order in `Hash#transform_values.with_key` to yield `|value, key|` instead of `|key, value|` for consistency with Ruby conventions.
168
195
 
169
196
  ### Removed
170
197
 
171
-
172
198
  ## [0.4.0] - 12025-04-11
173
199
 
174
200
  ### Added
@@ -316,7 +342,9 @@ This change aligns our method signatures with Ruby's conventions and matches our
316
342
 
317
343
  - Added alias `each` to `each_pair` in OpenStruct for better enumerable compatibility
318
344
 
319
- [unreleased]: https://github.com/itsthedevman/everythingrb/compare/v0.8.3...HEAD
345
+ [unreleased]: https://github.com/itsthedevman/everythingrb/compare/v0.9.0...HEAD
346
+ [0.9.0]: https://github.com/itsthedevman/everythingrb/compare/v0.8.3...v0.9.0
347
+ [0.8.3]: https://github.com/itsthedevman/everythingrb/compare/v0.8.2...v0.8.3
320
348
  [0.8.3]: https://github.com/itsthedevman/everythingrb/compare/v0.8.2...v0.8.3
321
349
  [0.8.2]: https://github.com/itsthedevman/everythingrb/compare/v0.8.1...v0.8.2
322
350
  [0.8.1]: https://github.com/itsthedevman/everythingrb/compare/v0.8.0...v0.8.1
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  ![Ruby Version](https://img.shields.io/badge/ruby-3.3.7-ruby)
5
5
  [![Tests](https://github.com/itsthedevman/everythingrb/actions/workflows/main.yml/badge.svg)](https://github.com/everythingrb/sortsmith/actions/workflows/main.yml)
6
6
 
7
- Super handy extensions to Ruby core classes that make your code more expressive, readable, and fun to write. Stop writing boilerplate and start writing code that *actually matters*!
7
+ Super handy extensions to Ruby core classes that make your code more expressive, readable, and fun to write. Stop writing boilerplate and start writing code that _actually matters_!
8
8
 
9
9
  ## Express Your Intent, Not Your Logic
10
10
 
@@ -31,7 +31,7 @@ users.join_map(", ") { |u| u[:name] if u[:role] == "admin" }
31
31
  # => "Alice, Charlie"
32
32
  ```
33
33
 
34
- *Methods used: [`join_map`](https://itsthedevman.com/docs/everythingrb/Array.html#join_map-instance_method)*
34
+ _Methods used: [`join_map`](https://itsthedevman.com/docs/everythingrb/Array.html#join_map-instance_method)_
35
35
 
36
36
  Because life's too short to write all that boilerplate!
37
37
 
@@ -84,6 +84,7 @@ require "everythingrb/string" # Just String extensions
84
84
  ```
85
85
 
86
86
  Available modules:
87
+
87
88
  - `array`: Array extensions (join_map, key_map, etc.)
88
89
  - `boolean`: Boolean extensions (in_quotes, with_quotes)
89
90
  - `data`: Data extensions (to_deep_h, in_quotes)
@@ -143,7 +144,7 @@ With EverythingRB, it's one elegant step:
143
144
  '{"user":{"name":"Alice","roles":["admin"]}}'.to_ostruct.user.name # => "Alice"
144
145
  ```
145
146
 
146
- *Methods used: [`to_ostruct`](https://itsthedevman.com/docs/everythingrb/String.html#to_ostruct-instance_method)*
147
+ _Methods used: [`to_ostruct`](https://itsthedevman.com/docs/everythingrb/String.html#to_ostruct-instance_method)_
147
148
 
148
149
  Convert between data structures with ease:
149
150
 
@@ -161,7 +162,7 @@ config = { server: { host: "example.com", port: 443 } }.to_struct
161
162
  config.server.host # => "example.com"
162
163
  ```
163
164
 
164
- *Methods used: [`to_struct`](https://itsthedevman.com/docs/everythingrb/Hash.html#to_struct-instance_method)*
165
+ _Methods used: [`to_struct`](https://itsthedevman.com/docs/everythingrb/Hash.html#to_struct-instance_method)_
165
166
 
166
167
  Deep conversion to plain hashes is just as easy:
167
168
 
@@ -181,7 +182,7 @@ data = OpenStruct.new(user: Data.define(:name).new(name: "Bob"))
181
182
  data.to_deep_h # => {user: {name: "Bob"}}
182
183
  ```
183
184
 
184
- *Methods used: [`to_deep_h`](https://itsthedevman.com/docs/everythingrb/OpenStruct.html#to_deep_h-instance_method)*
185
+ _Methods used: [`to_deep_h`](https://itsthedevman.com/docs/everythingrb/OpenStruct.html#to_deep_h-instance_method)_
185
186
 
186
187
  **Extensions:** [`to_struct`](https://itsthedevman.com/docs/everythingrb/Hash.html#to_struct-instance_method), [`to_ostruct`](https://itsthedevman.com/docs/everythingrb/Hash.html#to_ostruct-instance_method), [`to_istruct`](https://itsthedevman.com/docs/everythingrb/Hash.html#to_istruct-instance_method), [`to_h`](https://itsthedevman.com/docs/everythingrb/String.html#to_h-instance_method), [`to_deep_h`](https://itsthedevman.com/docs/everythingrb/Hash.html#to_deep_h-instance_method)
187
188
 
@@ -191,7 +192,7 @@ Extract and transform data with elegant, expressive code:
191
192
 
192
193
  ```ruby
193
194
  # BEFORE
194
- users = [{ name: "Alice", roles: ["admin"] }, { name: "Bob", roles: ["user"] }]
195
+ users = [{ name: "Alice", role: "admin" }, { name: "Bob", role: "user" }]
195
196
  names = users.map { |user| user[:name] }
196
197
  # => ["Alice", "Bob"]
197
198
  ```
@@ -201,7 +202,7 @@ names = users.map { |user| user[:name] }
201
202
  users.key_map(:name) # => ["Alice", "Bob"]
202
203
  ```
203
204
 
204
- *Methods used: [`key_map`](https://itsthedevman.com/docs/everythingrb/Array.html#key_map-instance_method)*
205
+ _Methods used: [`key_map`](https://itsthedevman.com/docs/everythingrb/Array.html#key_map-instance_method)_
205
206
 
206
207
  Simplify nested data extraction:
207
208
 
@@ -220,7 +221,7 @@ names = users.map { |u| u.dig(:user, :profile, :name) }
220
221
  users.dig_map(:user, :profile, :name) # => ["Alice", "Bob"]
221
222
  ```
222
223
 
223
- *Methods used: [`dig_map`](https://itsthedevman.com/docs/everythingrb/Array.html#dig_map-instance_method)*
224
+ _Methods used: [`dig_map`](https://itsthedevman.com/docs/everythingrb/Array.html#dig_map-instance_method)_
224
225
 
225
226
  Combine filter, map, and join operations in one step:
226
227
 
@@ -237,7 +238,24 @@ result = data.compact.filter_map { |n| "Item #{n}" if n.odd? }.join(" | ")
237
238
  # => "Item 1 | Item 3"
238
239
  ```
239
240
 
240
- *Methods used: [`join_map`](https://itsthedevman.com/docs/everythingrb/Array.html#join_map-instance_method)*
241
+ _Methods used: [`join_map`](https://itsthedevman.com/docs/everythingrb/Array.html#join_map-instance_method)_
242
+
243
+ Need position-aware processing? Both Array and Hash support `with_index`:
244
+
245
+ ```ruby
246
+ # BEFORE
247
+ users = {alice: "Alice", bob: "Bob", charlie: "Charlie"}
248
+ users.filter_map.with_index { |(k, v), i| "#{i + 1}. #{v}" }.join(", ")
249
+ # => "1. Alice, 2. Bob, 3. Charlie"
250
+ ```
251
+
252
+ ```ruby
253
+ # AFTER
254
+ users.join_map(", ", with_index: true) { |(k, v), i| "#{i + 1}. #{v}" }
255
+ # => "1. Alice, 2. Bob, 3. Charlie"
256
+ ```
257
+
258
+ _Methods used: [`join_map`](https://itsthedevman.com/docs/everythingrb/Hash.html#join_map-instance_method)_
241
259
 
242
260
  Group elements with natural syntax:
243
261
 
@@ -258,7 +276,7 @@ users.group_by_key(:department, :name)
258
276
  # => {"Engineering"=>[{name: "Alice",...}, {name: "Charlie",...}], "Sales"=>[{name: "Bob",...}]}
259
277
  ```
260
278
 
261
- *Methods used: [`group_by_key`](https://itsthedevman.com/docs/everythingrb/Enumerable.html#group_by_key-instance_method)*
279
+ _Methods used: [`group_by_key`](https://itsthedevman.com/docs/everythingrb/Enumerable.html#group_by_key-instance_method)_
262
280
 
263
281
  Create natural language lists:
264
282
 
@@ -283,7 +301,7 @@ end
283
301
  ["red", "blue", "green"].to_or_sentence # => "red, blue, or green"
284
302
  ```
285
303
 
286
- *Methods used: [`to_or_sentence`](https://itsthedevman.com/docs/everythingrb/Array.html#to_or_sentence-instance_method)*
304
+ _Methods used: [`to_or_sentence`](https://itsthedevman.com/docs/everythingrb/Array.html#to_or_sentence-instance_method)_
287
305
 
288
306
  **Extensions:** [`join_map`](https://itsthedevman.com/docs/everythingrb/Array.html#join_map-instance_method), [`key_map`](https://itsthedevman.com/docs/everythingrb/Array.html#key_map-instance_method), [`dig_map`](https://itsthedevman.com/docs/everythingrb/Array.html#dig_map-instance_method), [`to_or_sentence`](https://itsthedevman.com/docs/everythingrb/Array.html#to_or_sentence-instance_method), [`group_by_key`](https://itsthedevman.com/docs/everythingrb/Enumerable.html#group_by_key-instance_method)
289
307
 
@@ -308,7 +326,7 @@ stats = Hash.new_nested_hash(depth: 3)
308
326
  # No need to initialize each level first!
309
327
  ```
310
328
 
311
- *Methods used: [`new_nested_hash`](https://itsthedevman.com/docs/everythingrb/Hash.html#new_nested_hash-class_method)*
329
+ _Methods used: [`new_nested_hash`](https://itsthedevman.com/docs/everythingrb/Hash.html#new_nested_hash-class_method)_
312
330
 
313
331
  Transform values with access to their keys:
314
332
 
@@ -328,7 +346,7 @@ users.transform_values(with_key: true) { |v, k| "User #{k}: #{v[:name]}" }
328
346
  # => {alice: "User alice: Alice", bob: "User bob: Bob"}
329
347
  ```
330
348
 
331
- *Methods used: [`transform_values(with_key: true)`](https://itsthedevman.com/docs/everythingrb/Hash.html#transform_values-instance_method)*
349
+ _Methods used: [`transform_values(with_key: true)`](https://itsthedevman.com/docs/everythingrb/Hash.html#transform_values-instance_method)_
332
350
 
333
351
  Find values based on conditions:
334
352
 
@@ -349,7 +367,7 @@ users.values_where { |_k, v| v[:role] == "admin" }
349
367
  # => [{name: "Alice", role: "admin"}, {name: "Charlie", role: "admin"}]
350
368
  ```
351
369
 
352
- *Methods used: [`values_where`](https://itsthedevman.com/docs/everythingrb/Hash.html#values_where-instance_method)*
370
+ _Methods used: [`values_where`](https://itsthedevman.com/docs/everythingrb/Hash.html#values_where-instance_method)_
353
371
 
354
372
  Just want the first match? Even simpler:
355
373
 
@@ -365,7 +383,7 @@ users.value_where { |_k, v| v[:role] == "admin" }
365
383
  # => {name: "Alice", role: "admin"}
366
384
  ```
367
385
 
368
- *Methods used: [`value_where`](https://itsthedevman.com/docs/everythingrb/Hash.html#value_where-instance_method)*
386
+ _Methods used: [`value_where`](https://itsthedevman.com/docs/everythingrb/Hash.html#value_where-instance_method)_
369
387
 
370
388
  Rename keys while preserving order:
371
389
 
@@ -395,23 +413,7 @@ config.rename_keys(api_key: :key, timeout: :request_timeout)
395
413
  # => {key: "secret", request_timeout: 30}
396
414
  ```
397
415
 
398
- *Methods used: [`rename_keys`](https://itsthedevman.com/docs/everythingrb/Hash.html#rename_keys-instance_method)*
399
-
400
- Filter hash by values:
401
-
402
- ```ruby
403
- # BEFORE
404
- result = {a: 1, b: nil, c: 2}.select { |_k, v| v.is_a?(Integer) && v > 1 }
405
- # => {c: 2}
406
- ```
407
-
408
- ```ruby
409
- # AFTER
410
- {a: 1, b: nil, c: 2}.select_values { |v| v.is_a?(Integer) && v > 1 }
411
- # => {c: 2}
412
- ```
413
-
414
- *Methods used: [`select_values`](https://itsthedevman.com/docs/everythingrb/Hash.html#select_values-instance_method)*
416
+ _Methods used: [`rename_keys`](https://itsthedevman.com/docs/everythingrb/Hash.html#rename_keys-instance_method)_
415
417
 
416
418
  Conditionally merge hashes with clear intent:
417
419
 
@@ -430,7 +432,7 @@ user_params.merge_if(verified: true, admin: true) { |k, v| v == true && k == :ve
430
432
  # => {name: "Alice", role: "user", verified: true}
431
433
  ```
432
434
 
433
- *Methods used: [`merge_if`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_if-instance_method)*
435
+ _Methods used: [`merge_if`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_if-instance_method)_
434
436
 
435
437
  The nil-filtering pattern we've all written dozens of times:
436
438
 
@@ -445,13 +447,33 @@ params.merge(filtered)
445
447
  ```ruby
446
448
  # AFTER
447
449
  params = {sort: "created_at"}
448
- params.merge_compact(filter: "active", search: nil)
450
+ params.compact_merge(filter: "active", search: nil)
449
451
  # => {sort: "created_at", filter: "active"}
450
452
  ```
451
453
 
452
- *Methods used: [`merge_compact`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_compact-instance_method)*
454
+ _Methods used: [`compact_merge`](https://itsthedevman.com/docs/everythingrb/Hash.html#compact_merge-instance_method)_
455
+
456
+ Filter out blank values too when ActiveSupport is loaded:
457
+
458
+ ```ruby
459
+ # BEFORE
460
+ config = {api_key: "secret", timeout: 30}
461
+ user_settings = {timeout: "", retries: nil, debug: true, tags: []}
462
+ clean_settings = user_settings.reject { |k, v| v.blank? }
463
+ config.merge(clean_settings)
464
+ # => {api_key: "secret", timeout: 30, debug: true}
465
+ ```
466
+
467
+ ```ruby
468
+ # AFTER
469
+ config = {api_key: "secret", timeout: 30}
470
+ config.compact_blank_merge(timeout: "", retries: nil, debug: true, tags: [])
471
+ # => {api_key: "secret", timeout: 30, debug: true}
472
+ ```
473
+
474
+ _Methods used: [`compact_blank_merge`](https://itsthedevman.com/docs/everythingrb/Hash.html#compact_blank_merge-instance_method)_
453
475
 
454
- **Extensions:** [`new_nested_hash`](https://itsthedevman.com/docs/everythingrb/Hash.html#new_nested_hash-class_method), [`transform_values(with_key: true)`](https://itsthedevman.com/docs/everythingrb/Hash.html#transform_values-instance_method), [`value_where`](https://itsthedevman.com/docs/everythingrb/Hash.html#value_where-instance_method), [`values_where`](https://itsthedevman.com/docs/everythingrb/Hash.html#values_where-instance_method), [`rename_key`](https://itsthedevman.com/docs/everythingrb/Hash.html#rename_key-instance_method), [`rename_keys`](https://itsthedevman.com/docs/everythingrb/Hash.html#rename_keys-instance_method), [`select_values`](https://itsthedevman.com/docs/everythingrb/Hash.html#select_values-instance_method), [`reject_values`](https://itsthedevman.com/docs/everythingrb/Hash.html#reject_values-instance_method), [`merge_if`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_if-instance_method), [`merge_if!`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_if%21-instance_method), [`merge_if_values`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_if_values-instance_method), [`merge_if_values!`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_if_values%21-instance_method), [`merge_compact`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_compact-instance_method), [`merge_compact!`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_compact%21-instance_method)
476
+ **Extensions:** [`new_nested_hash`](https://itsthedevman.com/docs/everythingrb/Hash.html#new_nested_hash-class_method), [`transform_values(with_key: true)`](https://itsthedevman.com/docs/everythingrb/Hash.html#transform_values-instance_method), [`value_where`](https://itsthedevman.com/docs/everythingrb/Hash.html#value_where-instance_method), [`values_where`](https://itsthedevman.com/docs/everythingrb/Hash.html#values_where-instance_method), [`rename_key`](https://itsthedevman.com/docs/everythingrb/Hash.html#rename_key-instance_method), [`rename_keys`](https://itsthedevman.com/docs/everythingrb/Hash.html#rename_keys-instance_method), [`merge_if`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_if-instance_method), [`merge_if!`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_if%21-instance_method), [`merge_if_values`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_if_values-instance_method), [`merge_if_values!`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_if_values%21-instance_method), [`compact_merge`](https://itsthedevman.com/docs/everythingrb/Hash.html#compact_merge-instance_method), [`compact_merge!`](https://itsthedevman.com/docs/everythingrb/Hash.html#compact_merge%21-instance_method), [`compact_blank_merge`](https://itsthedevman.com/docs/everythingrb/Hash.html#compact_blank_merge-instance_method), [`compact_blank_merge!`](https://itsthedevman.com/docs/everythingrb/Hash.html#compact_blank_merge%21-instance_method)
455
477
 
456
478
  ### Array Cleaning
457
479
 
@@ -469,7 +491,7 @@ data.drop_while(&:nil?).reverse.drop_while(&:nil?).reverse
469
491
  [nil, nil, 1, nil, 2, nil, nil].trim_nils # => [1, nil, 2]
470
492
  ```
471
493
 
472
- *Methods used: [`trim_nils`](https://itsthedevman.com/docs/everythingrb/Array.html#trim_nils-instance_method)*
494
+ _Methods used: [`trim_nils`](https://itsthedevman.com/docs/everythingrb/Array.html#trim_nils-instance_method)_
473
495
 
474
496
  With ActiveSupport, remove blank values too:
475
497
 
@@ -485,7 +507,7 @@ data.drop_while(&:blank?).reverse.drop_while(&:blank?).reverse
485
507
  [nil, "", 1, "", 2, nil, ""].trim_blanks # => [1, "", 2]
486
508
  ```
487
509
 
488
- *Methods used: [`trim_blanks`](https://itsthedevman.com/docs/everythingrb/Array.html#trim_blanks-instance_method)*
510
+ _Methods used: [`trim_blanks`](https://itsthedevman.com/docs/everythingrb/Array.html#trim_blanks-instance_method)_
489
511
 
490
512
  **Extensions:** [`trim_nils`](https://itsthedevman.com/docs/everythingrb/Array.html#trim_nils-instance_method), [`compact_prefix`](https://itsthedevman.com/docs/everythingrb/Array.html#compact_prefix-instance_method), [`compact_suffix`](https://itsthedevman.com/docs/everythingrb/Array.html#compact_suffix-instance_method), [`trim_blanks`](https://itsthedevman.com/docs/everythingrb/Array.html#trim_blanks-instance_method) (with ActiveSupport)
491
513
 
@@ -528,7 +550,7 @@ Time.now.in_quotes # => "\"2025-05-04 12:34:56 +0000\""
528
550
  message = "You selected #{selection.in_quotes}"
529
551
  ```
530
552
 
531
- *Methods used: [`in_quotes`](https://itsthedevman.com/docs/everythingrb/Everythingrb/InspectQuotable.html#in_quotes-instance_method), [`with_quotes`](https://itsthedevman.com/docs/everythingrb/Everythingrb/InspectQuotable.html#with_quotes-instance_method)*
553
+ _Methods used: [`in_quotes`](https://itsthedevman.com/docs/everythingrb/Everythingrb/InspectQuotable.html#in_quotes-instance_method), [`with_quotes`](https://itsthedevman.com/docs/everythingrb/Everythingrb/InspectQuotable.html#with_quotes-instance_method)_
532
554
 
533
555
  Convert strings to camelCase with ease:
534
556
 
@@ -555,7 +577,7 @@ camel_case
555
577
  "please-WAIT while_loading...".to_camelcase # => "PleaseWaitWhileLoading"
556
578
  ```
557
579
 
558
- *Methods used: [`to_camelcase`](https://itsthedevman.com/docs/everythingrb/String.html#to_camelcase-instance_method)*
580
+ _Methods used: [`to_camelcase`](https://itsthedevman.com/docs/everythingrb/String.html#to_camelcase-instance_method)_
559
581
 
560
582
  **Extensions:** [`in_quotes`](https://itsthedevman.com/docs/everythingrb/Everythingrb/InspectQuotable.html#in_quotes-instance_method), [`with_quotes`](https://itsthedevman.com/docs/everythingrb/Everythingrb/InspectQuotable.html#with_quotes-instance_method) (alias), [`to_camelcase`](https://itsthedevman.com/docs/everythingrb/String.html#to_camelcase-instance_method)
561
583
 
@@ -590,7 +612,7 @@ user.admin = true
590
612
  user.admin? # => true
591
613
  ```
592
614
 
593
- *Methods used: [`attr_predicate`](https://itsthedevman.com/docs/everythingrb/Module.html#attr_predicate-instance_method)*
615
+ _Methods used: [`attr_predicate`](https://itsthedevman.com/docs/everythingrb/Module.html#attr_predicate-instance_method)_
594
616
 
595
617
  Works with Data objects too:
596
618
 
@@ -610,7 +632,7 @@ person = Person.new(active: false)
610
632
  person.active? # => false
611
633
  ```
612
634
 
613
- *Methods used: [`attr_predicate`](https://itsthedevman.com/docs/everythingrb/Module.html#attr_predicate-instance_method)*
635
+ _Methods used: [`attr_predicate`](https://itsthedevman.com/docs/everythingrb/Module.html#attr_predicate-instance_method)_
614
636
 
615
637
  **Extensions:** [`attr_predicate`](https://itsthedevman.com/docs/everythingrb/Module.html#attr_predicate-instance_method)
616
638
 
@@ -628,7 +650,7 @@ result = value.then { |v| transform_it(v) }
628
650
  result = value.morph { |v| transform_it(v) }
629
651
  ```
630
652
 
631
- *Methods used: [`morph`](https://itsthedevman.com/docs/everythingrb/Kernel.html#morph-instance_method)*
653
+ _Methods used: [`morph`](https://itsthedevman.com/docs/everythingrb/Kernel.html#morph-instance_method)_
632
654
 
633
655
  **Extensions:** [`morph`](https://itsthedevman.com/docs/everythingrb/Kernel.html#morph-instance_method) (alias for `then`/`yield_self`)
634
656
 
data/flake.lock CHANGED
@@ -20,11 +20,11 @@
20
20
  },
21
21
  "nixpkgs": {
22
22
  "locked": {
23
- "lastModified": 1742422364,
24
- "narHash": "sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ=",
23
+ "lastModified": 1750506804,
24
+ "narHash": "sha256-VLFNc4egNjovYVxDGyBYTrvVCgDYgENp5bVi9fPTDYc=",
25
25
  "owner": "NixOS",
26
26
  "repo": "nixpkgs",
27
- "rev": "a84ebe20c6bc2ecbcfb000a50776219f48d134cc",
27
+ "rev": "4206c4cb56751df534751b058295ea61357bbbaa",
28
28
  "type": "github"
29
29
  },
30
30
  "original": {
data/flake.nix CHANGED
@@ -21,7 +21,7 @@
21
21
  devShells.default = pkgs.mkShell {
22
22
  buildInputs = with pkgs; [
23
23
  (ruby_3_2.override {
24
- jemallocSupport = true;
24
+ jemallocSupport = false;
25
25
  docSupport = false;
26
26
  })
27
27
 
@@ -13,7 +13,8 @@
13
13
  # - ::new_nested_hash: Create automatically nesting hashes
14
14
  # - #merge_if, #merge_if!: Conditionally merge based on key-value pairs
15
15
  # - #merge_if_values, #merge_if_values!: Conditionally merge based on values
16
- # - #merge_compact, #merge_compact!: Merge only non-nil values
16
+ # - #compact_merge, #compact_merge!: Merge only non-nil values
17
+ # - #compact_blank_merge, #compact_blank_merge!: Merge only present values (ActiveSupport)
17
18
  #
18
19
  # @example
19
20
  # require "everythingrb/hash"
@@ -93,25 +94,37 @@ class Hash
93
94
  # Combines filter_map and join operations
94
95
  #
95
96
  # @param join_with [String] The delimiter to join elements with (defaults to empty string)
97
+ # @param with_index [Boolean] Whether to include the index in the block (defaults to false)
96
98
  #
97
- # @yield [key, value] Block that filters and transforms hash entries
98
- # @yieldparam key [Object] The current key
99
- # @yieldparam value [Object] The current value
99
+ # @yield [key_value_pair, index] Block that filters and transforms hash entries
100
+ # @yieldparam key_value_pair [Array] Two-element array containing [key, value] (can be destructured as |key, value|)
101
+ # @yieldparam index [Integer] The index of the current entry (only if with_index: true)
100
102
  #
101
103
  # @return [String] Joined string of filtered and transformed entries
102
104
  #
103
- # @example
105
+ # @example Basic usage without index
104
106
  # { a: 1, b: nil, c: 2, d: nil, e: 3 }.join_map(", ") { |k, v| "#{k}-#{v}" if v }
105
107
  # # => "a-1, c-2, e-3"
106
108
  #
107
- # @example Without a block
109
+ # @example With index for position-aware formatting
110
+ # users = {alice: "Alice", bob: "Bob", charlie: "Charlie"}
111
+ # users.join_map(", ", with_index: true) do |(k, v), i|
112
+ # "#{i + 1}. #{v}"
113
+ # end
114
+ # # => "1. Alice, 2. Bob, 3. Charlie"
115
+ #
116
+ # @example Without a block (default behavior)
108
117
  # { a: 1, b: nil, c: 2 }.join_map(" ")
109
118
  # # => "a 1 b c 2"
110
119
  #
111
- def join_map(join_with = "", &block)
120
+ def join_map(join_with = "", with_index: false, &block)
112
121
  block = ->(kv_pair) { kv_pair.compact } if block.nil?
113
122
 
114
- filter_map(&block).join(join_with)
123
+ if with_index
124
+ filter_map.with_index(&block).join(join_with)
125
+ else
126
+ filter_map(&block).join(join_with)
127
+ end
115
128
  end
116
129
 
117
130
  #
@@ -661,70 +674,6 @@ class Hash
661
674
  self
662
675
  end
663
676
 
664
- #
665
- # Selects hash entries based only on their values
666
- #
667
- # @deprecated Hash#select_values will be removed in 0.9.0. Please use `select { |k, v| condition }` instead.
668
- #
669
- def select_values(&block)
670
- Everythingrb.deprecator.warn(
671
- "Hash#select_values will be removed in 0.9.0. Please use `select { |k, v| condition }` instead."
672
- )
673
-
674
- return to_enum(:select_values) if block.nil?
675
-
676
- select { |_k, v| block.call(v) }
677
- end
678
-
679
- alias_method :filter_values, :select_values
680
-
681
- #
682
- # Selects hash entries based only on their values, modifying the hash in place
683
- #
684
- # @deprecated Hash#select_values! will be removed in 0.9.0. Please use `select! { |k, v| condition }` instead.
685
- #
686
- def select_values!(&block)
687
- Everythingrb.deprecator.warn(
688
- "Hash#select_values! will be removed in 0.9.0. Please use `select! { |k, v| condition }` instead."
689
- )
690
-
691
- return to_enum(:select_values!) if block.nil?
692
-
693
- select! { |_k, v| block.call(v) }
694
- end
695
-
696
- alias_method :filter_values!, :select_values!
697
-
698
- #
699
- # Rejects hash entries based only on their values
700
- #
701
- # @deprecated Hash#reject_values will be removed in 0.9.0. Please use `reject { |k, v| condition }` instead.
702
- #
703
- def reject_values(&block)
704
- Everythingrb.deprecator.warn(
705
- "Hash#reject_values will be removed in 0.9.0. Please use `reject { |k, v| condition }` instead."
706
- )
707
-
708
- return to_enum(:reject_values) if block.nil?
709
-
710
- reject { |_k, v| block.call(v) }
711
- end
712
-
713
- #
714
- # Rejects hash entries based only on their values, modifying the hash in place
715
- #
716
- # @deprecated Hash#reject_values! will be removed in 0.9.0. Please use `reject! { |k, v| condition }` instead.
717
- #
718
- def reject_values!(&block)
719
- Everythingrb.deprecator.warn(
720
- "Hash#reject_values! will be removed in 0.9.0. Please use `reject! { |k, v| condition }` instead."
721
- )
722
-
723
- return to_enum(:reject_values!) if block.nil?
724
-
725
- reject! { |_k, v| block.call(v) }
726
- end
727
-
728
677
  #
729
678
  # Conditionally merges key-value pairs from another hash based on a block
730
679
  #
@@ -828,15 +777,15 @@ class Hash
828
777
  # email = nil
829
778
  # name = "Alice"
830
779
  #
831
- # {}.merge_compact(
780
+ # {}.compact_merge(
832
781
  # id: user_id,
833
782
  # email: email,
834
783
  # name: name
835
784
  # )
836
785
  # # => {id: 42, name: "Alice"}
837
786
  #
838
- def merge_compact(other = {})
839
- merge_if_values(other, &:itself)
787
+ def compact_merge(other = {})
788
+ merge_if_values(other) { |i| i.itself }
840
789
  end
841
790
 
842
791
  #
@@ -851,14 +800,76 @@ class Hash
851
800
  #
852
801
  # @example Merge only non-nil values in place
853
802
  # params = {format: "json"}
854
- # params.merge_compact!(
803
+ # params.compact_merge!(
855
804
  # page: 1,
856
805
  # per_page: nil,
857
806
  # sort: "created_at"
858
807
  # )
859
808
  # # => {format: "json", page: 1, sort: "created_at"}
860
809
  #
861
- def merge_compact!(other = {})
862
- merge_if_values!(other, &:itself)
810
+ def compact_merge!(other = {})
811
+ merge_if_values!(other) { |i| i.itself }
812
+ end
813
+
814
+ # ActiveSupport integrations
815
+ if defined?(ActiveSupport)
816
+ #
817
+ # Merges only present (non-blank) values from another hash
818
+ #
819
+ # This method merges key-value pairs from another hash, but only includes
820
+ # values that are present according to ActiveSupport's definition (not nil,
821
+ # not empty strings, not empty arrays, etc.).
822
+ #
823
+ # @param other [Hash] The hash to merge from
824
+ #
825
+ # @return [Hash] A new hash with present values merged
826
+ #
827
+ # @note Only available when ActiveSupport is loaded
828
+ #
829
+ # @example Building API responses without blank values
830
+ # user_data = {name: "Alice", role: "admin"}
831
+ # user_data.compact_blank_merge(
832
+ # bio: "", # excluded - blank string
833
+ # tags: [], # excluded - empty array
834
+ # email: "a@example.com", # included - present
835
+ # phone: nil # excluded - nil
836
+ # )
837
+ # # => {name: "Alice", role: "admin", email: "a@example.com"}
838
+ #
839
+ # @example Configuration merging with blank filtering
840
+ # defaults = {timeout: 30, retries: 3}
841
+ # user_config = {timeout: "", retries: 5, debug: true}
842
+ # defaults.compact_blank_merge(user_config)
843
+ # # => {timeout: 30, retries: 5, debug: true}
844
+ #
845
+ def compact_blank_merge(other = {})
846
+ merge_if_values(other) { |i| i.present? }
847
+ end
848
+
849
+ #
850
+ # Merges only present (non-blank) values from another hash, in place
851
+ #
852
+ # This method merges key-value pairs from another hash into the current hash,
853
+ # but only includes values that are present according to ActiveSupport's
854
+ # definition (not nil, not empty strings, not empty arrays, etc.).
855
+ #
856
+ # @param other [Hash] The hash to merge from
857
+ #
858
+ # @return [self] The modified hash
859
+ #
860
+ # @note Only available when ActiveSupport is loaded
861
+ #
862
+ # @example Building parameters while excluding blanks
863
+ # params = {action: "search", format: "json"}
864
+ # params.compact_blank_merge!(
865
+ # query: "", # excluded - blank
866
+ # page: 2, # included - present
867
+ # tags: [] # excluded - empty array
868
+ # )
869
+ # # => {action: "search", format: "json", page: 2}
870
+ #
871
+ def compact_blank_merge!(other = {})
872
+ merge_if_values!(other) { |i| i.present? }
873
+ end
863
874
  end
864
875
  end
@@ -7,5 +7,5 @@
7
7
  #
8
8
  module Everythingrb
9
9
  # Current version of the everythingrb gem
10
- VERSION = "0.8.3"
10
+ VERSION = "0.9.0"
11
11
  end
data/lib/railtie.rb CHANGED
@@ -21,9 +21,7 @@ module Everythingrb
21
21
  config.everythingrb.extensions = nil # Default to loading all
22
22
 
23
23
  initializer "everythingrb.initialize" do
24
- # I learned that, whereas ActiveSupport is defined at this point, the core_ext files are
25
- # required later down the line.
26
- ActiveSupport.on_load(:active_record) do
24
+ ActiveSupport.on_load(:after_initialize) do
27
25
  require_relative "everythingrb/prelude"
28
26
 
29
27
  extensions = Rails.configuration.everythingrb.extensions
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: everythingrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.3
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-06-01 00:00:00.000000000 Z
11
+ date: 2025-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ostruct