everythingrb 0.8.2 → 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 +4 -4
- data/CHANGELOG.md +46 -2
- data/README.md +65 -43
- data/flake.lock +3 -3
- data/flake.nix +1 -1
- data/lib/everythingrb/hash.rb +89 -110
- data/lib/everythingrb/prelude.rb +9 -0
- data/lib/everythingrb/version.rb +1 -1
- data/lib/everythingrb.rb +1 -2
- data/lib/railtie.rb +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd5be3e26181ed61cc29aee8e316f3fd10d3bc9f5ea1d424069c4aefdc1c8125
|
4
|
+
data.tar.gz: 86517a10235873c3075237ff7f774315dc390d68fe3cf23ee751f15ca750508f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 643b0c34ffa81f136d1abb91def2e6bdbb65a00888a735cdef95950db9877d3ea0d06c22119c36f3909283985711792bc0530944107ed76d744742daff40dd3c
|
7
|
+
data.tar.gz: 40db61bcf4b8c77a791b07fa30fcdd8e4d3b1f0054ff9124030d15b67ad8194db312cbb6f1ba2261499e5431132952746a5ef184ced15e212401207c15bf6dd6
|
data/CHANGELOG.md
CHANGED
@@ -15,6 +15,42 @@ 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
|
+
|
38
|
+
## [0.8.3] - 12025-05-31
|
39
|
+
|
40
|
+
### Added
|
41
|
+
|
42
|
+
### Changed
|
43
|
+
|
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
|
+
|
46
|
+
- `hash.reject_values(&:blank?)` → use `hash.compact_blank` instead
|
47
|
+
- `hash.select_values { |v| condition }` → use `hash.select { |k, v| condition }`
|
48
|
+
- `hash.reject_values { |v| condition }` → use `hash.reject { |k, v| condition }`
|
49
|
+
|
50
|
+
See [Issue #61](https://github.com/itsthedevman/everythingrb/issues/61) for full details.
|
51
|
+
|
52
|
+
### Removed
|
53
|
+
|
18
54
|
## [0.8.2] - 12025-05-25
|
19
55
|
|
20
56
|
### Added
|
@@ -80,6 +116,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
80
116
|
- `Hash#reject_values!` - Same as `reject_values` but modifies the hash in place
|
81
117
|
- Added `filter_values` and `filter_values!` as aliases for `select_values` and `select_values!` respectively
|
82
118
|
- Added `Kernel#morph` as an alias for `then` (and `yield_self`) that better communicates transformation intent:
|
119
|
+
|
83
120
|
```ruby
|
84
121
|
# Instead of this:
|
85
122
|
value.then { |v| transform_somehow(v) }
|
@@ -109,12 +146,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
109
146
|
The chainable `.with_key` method approach has been replaced with a more straightforward parameter-based approach.
|
110
147
|
|
111
148
|
**Before:**
|
149
|
+
|
112
150
|
```ruby
|
113
151
|
hash.transform_values.with_key { |value, key| "#{key}:#{value}" }
|
114
152
|
hash.transform_values!.with_key { |value, key| "#{key}:#{value}" }
|
115
153
|
```
|
116
154
|
|
117
155
|
**After:**
|
156
|
+
|
118
157
|
```ruby
|
119
158
|
hash.transform_values(with_key: true) { |value, key| "#{key}:#{value}" }
|
120
159
|
hash.transform_values!(with_key: true) { |value, key| "#{key}:#{value}" }
|
@@ -133,11 +172,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
133
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`.
|
134
173
|
|
135
174
|
**Before:**
|
175
|
+
|
136
176
|
```ruby
|
137
177
|
hash.transform_values.with_key { |key, value| "#{key}: #{value}" }
|
138
178
|
```
|
139
179
|
|
140
180
|
**After:**
|
181
|
+
|
141
182
|
```ruby
|
142
183
|
hash.transform_values.with_key { |value, key| "#{key}: #{value}" }
|
143
184
|
```
|
@@ -149,11 +190,11 @@ This change aligns our method signatures with Ruby's conventions and matches our
|
|
149
190
|
- Added `Hash#transform` and `Hash#transform!` for transforming a hash's keys and values at the same time.
|
150
191
|
|
151
192
|
### Changed
|
193
|
+
|
152
194
|
- Changed parameter order in `Hash#transform_values.with_key` to yield `|value, key|` instead of `|key, value|` for consistency with Ruby conventions.
|
153
195
|
|
154
196
|
### Removed
|
155
197
|
|
156
|
-
|
157
198
|
## [0.4.0] - 12025-04-11
|
158
199
|
|
159
200
|
### Added
|
@@ -301,7 +342,10 @@ This change aligns our method signatures with Ruby's conventions and matches our
|
|
301
342
|
|
302
343
|
- Added alias `each` to `each_pair` in OpenStruct for better enumerable compatibility
|
303
344
|
|
304
|
-
[unreleased]: https://github.com/itsthedevman/everythingrb/compare/v0.
|
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
|
348
|
+
[0.8.3]: https://github.com/itsthedevman/everythingrb/compare/v0.8.2...v0.8.3
|
305
349
|
[0.8.2]: https://github.com/itsthedevman/everythingrb/compare/v0.8.1...v0.8.2
|
306
350
|
[0.8.1]: https://github.com/itsthedevman/everythingrb/compare/v0.8.0...v0.8.1
|
307
351
|
[0.8.0]: https://github.com/itsthedevman/everythingrb/compare/v0.7.0...v0.8.0
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|

|
5
5
|
[](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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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",
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
450
|
+
params.compact_merge(filter: "active", search: nil)
|
449
451
|
# => {sort: "created_at", filter: "active"}
|
450
452
|
```
|
451
453
|
|
452
|
-
|
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), [`
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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":
|
24
|
-
"narHash": "sha256-
|
23
|
+
"lastModified": 1750506804,
|
24
|
+
"narHash": "sha256-VLFNc4egNjovYVxDGyBYTrvVCgDYgENp5bVi9fPTDYc=",
|
25
25
|
"owner": "NixOS",
|
26
26
|
"repo": "nixpkgs",
|
27
|
-
"rev": "
|
27
|
+
"rev": "4206c4cb56751df534751b058295ea61357bbbaa",
|
28
28
|
"type": "github"
|
29
29
|
},
|
30
30
|
"original": {
|
data/flake.nix
CHANGED
data/lib/everythingrb/hash.rb
CHANGED
@@ -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
|
-
# - #
|
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 [
|
98
|
-
# @yieldparam
|
99
|
-
# @yieldparam
|
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
|
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
|
-
|
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,102 +674,6 @@ class Hash
|
|
661
674
|
self
|
662
675
|
end
|
663
676
|
|
664
|
-
#
|
665
|
-
# Selects hash entries based only on their values
|
666
|
-
#
|
667
|
-
# @yield [value] Block that determines whether to include the entry
|
668
|
-
# @yieldparam value [Object] The current value
|
669
|
-
# @yieldreturn [Boolean] Whether to include this entry
|
670
|
-
#
|
671
|
-
# @return [Hash] A new hash including only entries where the block returned truthy
|
672
|
-
# @return [Enumerator] If no block is given
|
673
|
-
#
|
674
|
-
# @example Filter to include only present values (with ActiveSupport)
|
675
|
-
# {name: "Alice", bio: nil, role: ""}.select_values(&:present?)
|
676
|
-
# # => {name: "Alice"}
|
677
|
-
#
|
678
|
-
# @example Filter using more complex logic
|
679
|
-
# {id: 1, count: 0, items: [1, 2, 3]}.select_values { |v| v.is_a?(Array) || v > 0 }
|
680
|
-
# # => {id: 1, items: [1, 2, 3]}
|
681
|
-
#
|
682
|
-
def select_values(&block)
|
683
|
-
return to_enum(:select_values) if block.nil?
|
684
|
-
|
685
|
-
select { |_k, v| block.call(v) }
|
686
|
-
end
|
687
|
-
|
688
|
-
alias_method :filter_values, :select_values
|
689
|
-
|
690
|
-
#
|
691
|
-
# Selects hash entries based only on their values, modifying the hash in place
|
692
|
-
#
|
693
|
-
# @yield [value] Block that determines whether to keep the entry
|
694
|
-
# @yieldparam value [Object] The current value
|
695
|
-
# @yieldreturn [Boolean] Whether to keep this entry
|
696
|
-
#
|
697
|
-
# @return [self, nil] The modified hash, or nil if no changes were made
|
698
|
-
# @return [Enumerator] If no block is given
|
699
|
-
#
|
700
|
-
# @example Remove entries with empty values (with ActiveSupport)
|
701
|
-
# hash = {name: "Alice", bio: nil, role: ""}
|
702
|
-
# hash.select_values!(&:present?)
|
703
|
-
# # => {name: "Alice"}
|
704
|
-
# # hash is now {name: "Alice"}
|
705
|
-
#
|
706
|
-
def select_values!(&block)
|
707
|
-
return to_enum(:select_values!) if block.nil?
|
708
|
-
|
709
|
-
select! { |_k, v| block.call(v) }
|
710
|
-
end
|
711
|
-
|
712
|
-
alias_method :filter_values!, :select_values!
|
713
|
-
|
714
|
-
#
|
715
|
-
# Rejects hash entries based only on their values
|
716
|
-
#
|
717
|
-
# @yield [value] Block that determines whether to exclude the entry
|
718
|
-
# @yieldparam value [Object] The current value
|
719
|
-
# @yieldreturn [Boolean] Whether to exclude this entry
|
720
|
-
#
|
721
|
-
# @return [Hash] A new hash excluding entries where the block returned truthy
|
722
|
-
# @return [Enumerator] If no block is given
|
723
|
-
#
|
724
|
-
# @example Remove blank values (with ActiveSupport)
|
725
|
-
# {name: "Alice", bio: nil, role: ""}.reject_values(&:blank?)
|
726
|
-
# # => {name: "Alice"}
|
727
|
-
#
|
728
|
-
# @example Remove specific types of values
|
729
|
-
# {id: 1, count: 0, items: [1, 2, 3]}.reject_values { |v| v.is_a?(Integer) && v == 0 }
|
730
|
-
# # => {id: 1, items: [1, 2, 3]}
|
731
|
-
#
|
732
|
-
def reject_values(&block)
|
733
|
-
return to_enum(:reject_values) if block.nil?
|
734
|
-
|
735
|
-
reject { |_k, v| block.call(v) }
|
736
|
-
end
|
737
|
-
|
738
|
-
#
|
739
|
-
# Rejects hash entries based only on their values, modifying the hash in place
|
740
|
-
#
|
741
|
-
# @yield [value] Block that determines whether to remove the entry
|
742
|
-
# @yieldparam value [Object] The current value
|
743
|
-
# @yieldreturn [Boolean] Whether to remove this entry
|
744
|
-
#
|
745
|
-
# @return [self, nil] The modified hash, or nil if no changes were made
|
746
|
-
# @return [Enumerator] If no block is given
|
747
|
-
#
|
748
|
-
# @example Remove blank values in place (with ActiveSupport)
|
749
|
-
# hash = {name: "Alice", bio: nil, role: ""}
|
750
|
-
# hash.reject_values!(&:blank?)
|
751
|
-
# # => {name: "Alice"}
|
752
|
-
# # hash is now {name: "Alice"}
|
753
|
-
#
|
754
|
-
def reject_values!(&block)
|
755
|
-
return to_enum(:reject_values!) if block.nil?
|
756
|
-
|
757
|
-
reject! { |_k, v| block.call(v) }
|
758
|
-
end
|
759
|
-
|
760
677
|
#
|
761
678
|
# Conditionally merges key-value pairs from another hash based on a block
|
762
679
|
#
|
@@ -860,15 +777,15 @@ class Hash
|
|
860
777
|
# email = nil
|
861
778
|
# name = "Alice"
|
862
779
|
#
|
863
|
-
# {}.
|
780
|
+
# {}.compact_merge(
|
864
781
|
# id: user_id,
|
865
782
|
# email: email,
|
866
783
|
# name: name
|
867
784
|
# )
|
868
785
|
# # => {id: 42, name: "Alice"}
|
869
786
|
#
|
870
|
-
def
|
871
|
-
merge_if_values(other
|
787
|
+
def compact_merge(other = {})
|
788
|
+
merge_if_values(other) { |i| i.itself }
|
872
789
|
end
|
873
790
|
|
874
791
|
#
|
@@ -883,14 +800,76 @@ class Hash
|
|
883
800
|
#
|
884
801
|
# @example Merge only non-nil values in place
|
885
802
|
# params = {format: "json"}
|
886
|
-
# params.
|
803
|
+
# params.compact_merge!(
|
887
804
|
# page: 1,
|
888
805
|
# per_page: nil,
|
889
806
|
# sort: "created_at"
|
890
807
|
# )
|
891
808
|
# # => {format: "json", page: 1, sort: "created_at"}
|
892
809
|
#
|
893
|
-
def
|
894
|
-
merge_if_values!(other
|
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
|
895
874
|
end
|
896
875
|
end
|
data/lib/everythingrb/prelude.rb
CHANGED
@@ -25,6 +25,15 @@ require "json"
|
|
25
25
|
# users.key_map(:name).join(", ") # => "Alice, Bob"
|
26
26
|
#
|
27
27
|
module Everythingrb
|
28
|
+
def self.deprecator
|
29
|
+
@deprecator ||= if defined?(ActiveSupport)
|
30
|
+
ActiveSupport::Deprecation.new(VERSION, "everythingrb")
|
31
|
+
else
|
32
|
+
proxy = Data.define
|
33
|
+
proxy.define_method(:warn) { |message| puts "DEPRECATION WARNING: #{message}" }
|
34
|
+
proxy.new
|
35
|
+
end
|
36
|
+
end
|
28
37
|
end
|
29
38
|
|
30
39
|
require_relative "extensions"
|
data/lib/everythingrb/version.rb
CHANGED
data/lib/everythingrb.rb
CHANGED
data/lib/railtie.rb
CHANGED
@@ -21,9 +21,9 @@ module Everythingrb
|
|
21
21
|
config.everythingrb.extensions = nil # Default to loading all
|
22
22
|
|
23
23
|
initializer "everythingrb.initialize" do
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
ActiveSupport.on_load(:after_initialize) do
|
25
|
+
require_relative "everythingrb/prelude"
|
26
|
+
|
27
27
|
extensions = Rails.configuration.everythingrb.extensions
|
28
28
|
|
29
29
|
if extensions.is_a?(Array)
|
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.
|
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-
|
11
|
+
date: 2025-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ostruct
|