everythingrb 0.2.5 → 0.3.1

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: 330f80c66964fd22cd85850c251963ea372957a2b3190f4abcb5d0f3346989f7
4
- data.tar.gz: f28ebe4ab45e0bb7f1e68f2553af302d6ab2dfaa0509bc3d73615a84330b6d1e
3
+ metadata.gz: 747441fc1a0c8be071a4465edb190dead79eea7195b66b262976e8ae91b6bc0f
4
+ data.tar.gz: 491c3bc6c22bc02495425fecfb51166c8982d86ad0eede1b10980e2bf040b705
5
5
  SHA512:
6
- metadata.gz: ae50b585bb625724ca3e7ae917074fef6ceb25278818c88965aa10ccfae42e067b2716fc36b2f4da0d2cd4b4228b764166c12a43c212c7b44b3874ebe00aa071
7
- data.tar.gz: 5558f7dfe032d20489d7accf298eb889ffb22cd4a215509e4d2fd90fa35c23eebefa95ca2170695d4b82a637eb2583daa5a3b11b1660b9a9ccb1fa3b9dc5dcf9
6
+ metadata.gz: f6605c462aeeb634fe3342a3befb424fe3a67f31059f486d4c835a3670192962a44f6237f51947629cbe1a27d7a21579c03a48f9c98e22f155821cdb62f1e8c1
7
+ data.tar.gz: e6d2ce8fca0b90b513c686dfc44f8010cfb2aca9cc3be84cdbd08c6fccbbac49727ffbe42805f5a0789496df7b3f68ace624b7c201b30f5ee5305be16af485fa
data/CHANGELOG.md CHANGED
@@ -23,6 +23,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
23
23
 
24
24
  ### Removed
25
25
 
26
+ ## [0.3.1] - 12025-04-09
27
+
28
+ ### Added
29
+
30
+ ### Changed
31
+
32
+ - Fixed `Hash#value_where` error when nothing is found
33
+
34
+ ### Removed
35
+
36
+ ## [0.3.0] - 12025-04-09
37
+
38
+ ### Added
39
+
40
+ - Added `Array#to_or_sentence`, creates a sentence with "or" connector between items
41
+ - Added `#with_key` method to `Hash#transform_values` and `Hash#transform_values!`, grants access to both keys and values during transformations
42
+ - Added `Array#to_deep_h` and `Hash#to_deep_h`, recursively converts underlying values to hashes
43
+ - Added `Enumerable#group_by_key`, group an array of hashes by their keys
44
+ - Added `Hash#new_nested_hash`, creates a new Hash that automatically initializes the value to a hash
45
+ - Added `Hash#value_where` and `Hash#values_where`, easily find values in a hash based on key-value conditions
46
+
47
+ ### Changed
48
+
49
+ ### Removed
50
+
26
51
  ## [0.2.5] - 12025-03-29
27
52
 
28
53
  ### Added
@@ -124,7 +149,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
124
149
 
125
150
  - Added alias `each` to `each_pair` in OpenStruct for better enumerable compatibility
126
151
 
127
- [unreleased]: https://github.com/itsthedevman/everythingrb/compare/v0.2.5...HEAD
152
+ [unreleased]: https://github.com/itsthedevman/everythingrb/compare/v0.3.1...HEAD
153
+ [0.3.1]: https://github.com/itsthedevman/everythingrb/compare/v0.3.0...v0.3.1
154
+ [0.3.0]: https://github.com/itsthedevman/everythingrb/compare/v0.2.5...v0.3.0
128
155
  [0.2.5]: https://github.com/itsthedevman/everythingrb/compare/v0.2.4...v0.2.5
129
156
  [0.2.4]: https://github.com/itsthedevman/everythingrb/compare/v0.2.3...v0.2.4
130
157
  [0.2.3]: https://github.com/itsthedevman/everythingrb/compare/v0.2.2...v0.2.3
data/README.md CHANGED
@@ -4,97 +4,52 @@
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 you never knew you needed until now. Write more expressive, readable, and maintainable code with less boilerplate.
7
+ Super handy extensions to Ruby core classes that make your code more expressive, readable, and fun to write.
8
8
 
9
- ## Looking for a Software Engineer?
10
-
11
- I'm currently looking for opportunities where I can tackle meaningful problems and help build reliable software while mentoring the next generation of developers. If you're looking for a senior engineer with full-stack Rails expertise and a passion for clean, maintainable code, let's talk!
12
-
13
- [bryan@itsthedevman.com](mailto:bryan@itsthedevman.com)
9
+ ```ruby
10
+ # Instead of this:
11
+ users = [{ name: "Alice", role: "admin" }, { name: "Bob", role: "user" }]
12
+ admin_names = users.select { |u| u[:role] == "admin" }.map { |u| u[:name] }.join(", ")
14
13
 
15
- # Table of Contents
16
-
17
- - [Introduction](#introduction)
18
- - [Compatibility](#compatibility)
19
- - [Installation](#installation)
20
- - [Features](#features)
21
- - [Data Structure Conversions](#data-structure-conversions)
22
- - [Collection Processing](#collection-processing)
23
- - [JSON & String Handling](#json--string-handling)
24
- - [Object Freezing](#object-freezing)
25
- - [Array Trimming](#array-trimming)
26
- - [Predicate Methods](#predicate-methods)
27
- - [Core Extensions](#core-extensions)
28
- - [Array](#array)
29
- - [Enumerable](#enumerable)
30
- - [Hash](#hash)
31
- - [Module](#module)
32
- - [OpenStruct](#openstruct)
33
- - [String](#string)
34
- - [Symbol](#symbol)
35
- - [Advanced Usage](#advanced-usage)
36
- - [Requirements](#requirements)
37
- - [Contributing](#contributing)
38
- - [License](#license)
39
- - [Changelog](#changelog)
40
- - [Credits](#credits)
41
-
42
- Also see: [API Documentation](https://itsthedevman.com/docs/everythingrb)
43
-
44
- ## Introduction
45
-
46
- EverythingRB adds powerful, intuitive extensions to Ruby's core classes that help you write cleaner, more expressive code. It focuses on common patterns that typically require multiple method calls or temporary variables, turning them into single fluid operations.
47
-
48
- Whether you're transforming data, working with JSON, or building complex object structures, EverythingRB makes your code more readable and maintainable with minimal effort.
49
-
50
- ## Compatibility
51
-
52
- Currently tested on:
53
- - MRI Ruby 3.2+
54
- - NixOS (see `flake.nix` for details)
14
+ # Write this:
15
+ users.join_map(", ") { |u| u[:name] if u[:role] == "admin" }
16
+ ```
55
17
 
56
18
  ## Installation
57
19
 
58
- Add this line to your application's Gemfile:
59
-
60
20
  ```ruby
21
+ # In your Gemfile
61
22
  gem "everythingrb"
62
- ```
63
23
 
64
- And then execute:
65
-
66
- ```bash
67
- $ bundle install
24
+ # Or install manually
25
+ gem install everythingrb
68
26
  ```
69
27
 
70
- Or install it yourself as:
71
-
72
- ```bash
73
- $ gem install everythingrb
74
- ```
28
+ ## What's Included
75
29
 
76
- ## Features
30
+ EverythingRB extends Ruby's core classes with intuitive methods that simplify common patterns.
77
31
 
78
32
  ### Data Structure Conversions
79
33
 
80
- Easily convert between different Ruby data structures:
34
+ Convert between data structures with ease:
81
35
 
82
36
  ```ruby
83
- # Convert any hash to an OpenStruct, Struct, or Data (immutable) object
37
+ # Convert hashes to more convenient structures
84
38
  config = { server: { host: "example.com", port: 443 } }.to_ostruct
85
39
  config.server.host # => "example.com"
86
40
 
87
41
  # Parse JSON directly to your preferred structure
88
42
  '{"user":{"name":"Alice"}}'.to_istruct.user.name # => "Alice"
89
- '{"items":[1,2,3]}'.to_struct.items # => [1, 2, 3]
90
43
  ```
91
44
 
45
+ **Extensions:** `to_struct`, `to_ostruct`, `to_istruct`, `to_h`, `to_deep_h`
46
+
92
47
  ### Collection Processing
93
48
 
94
- Process collections with elegant, chainable methods:
49
+ Extract and transform data elegantly:
95
50
 
96
51
  ```ruby
97
- # Extract specific data from arrays of hashes in one step
52
+ # Extract data from arrays of hashes in one step
98
53
  users = [{ name: "Alice", roles: ["admin"] }, { name: "Bob", roles: ["user"] }]
99
54
  users.key_map(:name) # => ["Alice", "Bob"]
100
55
  users.dig_map(:roles, 0) # => ["admin", "user"]
@@ -102,27 +57,16 @@ users.dig_map(:roles, 0) # => ["admin", "user"]
102
57
  # Filter, map, and join in a single operation
103
58
  [1, 2, nil, 3, 4].join_map(" | ") { |n| "Item #{n}" if n&.odd? }
104
59
  # => "Item 1 | Item 3"
105
- ```
106
-
107
- ### JSON & String Handling
108
-
109
- Work with JSON and strings more naturally:
110
-
111
- ```ruby
112
- # Parse JSON with symbolized keys
113
- '{"name": "Alice"}'.to_h # => { name: "Alice" }
114
60
 
115
- # Recursively parse nested JSON strings
116
- nested = '{"user":"{\"profile\":\"{\\\"name\\\":\\\"Bob\\\"}\"}"}'
117
- nested.to_deep_h # => { user: { profile: { name: "Bob" } } }
118
-
119
- # Format strings with quotes
120
- "hello".with_quotes # => "\"hello\""
61
+ # With ActiveSupport loaded, join arrays in natural language with "or"
62
+ ["red", "blue", "green"].to_or_sentence # => "red, blue, or green"
121
63
  ```
122
64
 
123
- ### Object Freezing
65
+ **Extensions:** `join_map`, `key_map`, `dig_map`, `to_or_sentence`, `group_by_key`
66
+
67
+ ### Object Protection
124
68
 
125
- Freeze nested structures with a single call:
69
+ Prevent unwanted modifications with a single call:
126
70
 
127
71
  ```ruby
128
72
  config = {
@@ -134,429 +78,71 @@ config = {
134
78
 
135
79
  # Everything is frozen!
136
80
  config.frozen? # => true
137
- config[:api].frozen? # => true
138
- config[:api][:endpoints].frozen? # => true
139
81
  config[:api][:endpoints][0].frozen? # => true
140
82
  ```
141
83
 
142
- ### Array Trimming
84
+ **Extensions:** `deep_freeze`
143
85
 
144
- Clean up array boundaries without losing internal structure:
86
+ ### Hash Convenience
145
87
 
146
- ```ruby
147
- # Remove nil values from the beginning or end
148
- [nil, nil, 1, nil, 2, nil, nil].trim_nils # => [1, nil, 2]
149
-
150
- # With ActiveSupport, remove any blank values (nil, "", etc.)
151
- [nil, "", 1, "", 2, nil, ""].trim_blanks # => [1, "", 2]
152
-
153
- # Only trim from one end if needed
154
- [nil, nil, 1, 2, 3].compact_prefix # => [1, 2, 3]
155
- [1, 2, 3, nil, nil].compact_suffix # => [1, 2, 3]
156
- ```
157
-
158
- ### Predicate Methods
159
-
160
- Create boolean accessors with minimal code:
161
-
162
- ```ruby
163
- class User
164
- attr_accessor :admin, :verified
165
- attr_predicate :admin, :verified
166
- end
167
-
168
- user = User.new
169
- user.admin = true
170
- user.admin? # => true
171
- user.verified? # => false
172
-
173
- # Works with Struct and Data objects too
174
- Person = Struct.new(:active)
175
- Person.attr_predicate(:active)
176
-
177
- person = Person.new(true)
178
- person.active? # => true
179
- ```
180
-
181
- **ActiveSupport Integration:** When ActiveSupport is loaded, predicate methods automatically use `present?` instead of just checking truthiness:
88
+ Work with hashes more intuitively:
182
89
 
183
90
  ```ruby
184
- # With ActiveSupport loaded
185
- class Product
186
- attr_accessor :tags, :category
187
- attr_predicate :tags, :category
188
- end
189
-
190
- product = Product.new
191
- product.tags = []
192
- product.tags? # => false (empty array is not "present")
91
+ # Create deeply nested structures without initialization
92
+ stats = Hash.new_nested_hash
93
+ stats[:server][:region][:us_east][:errors] << "Connection timeout"
94
+ # No need to initialize each level first!
193
95
 
194
- product.tags = ["sale"]
195
- product.tags? # => true (non-empty array is "present")
96
+ # Transform values with access to keys
97
+ users.transform_values.with_key { |k, v| "User #{k}: #{v[:name]}" }
196
98
 
197
- product.category = ""
198
- product.category? # => false (blank string is not "present")
99
+ # Find values based on conditions
100
+ users.values_where { |k, v| v[:role] == "admin" }
199
101
  ```
200
102
 
201
- ## Core Extensions
103
+ **Extensions:** `new_nested_hash`, `with_key`, `value_where`, `values_where`
202
104
 
203
- ### Array
105
+ ### Array Cleaning
204
106
 
205
- #### `join_map`
206
- Combines `filter_map` and `join` operations in one convenient method. Optionally provides the index to the block.
107
+ Clean up array boundaries while preserving internal structure:
207
108
 
208
109
  ```ruby
209
- # Without index
210
- [1, 2, nil, 3].join_map(" ") { |n| n&.to_s if n&.odd? }
211
- # => "1 3"
212
-
213
- # With index
214
- ["a", "b", "c"].join_map(", ", with_index: true) { |char, i| "#{i}:#{char}" }
215
- # => "0:a, 1:b, 2:c"
216
-
217
- # Default behavior without block
218
- [1, 2, nil, 3].join_map(", ")
219
- # => "1, 2, 3"
220
- ```
221
-
222
- #### `key_map`
223
- Extracts a specific key from each hash in an array.
224
-
225
- ```ruby
226
- users = [
227
- { name: "Alice", age: 30 },
228
- { name: "Bob", age: 25 }
229
- ]
230
-
231
- users.key_map(:name)
232
- # => ["Alice", "Bob"]
233
- ```
234
-
235
- #### `dig_map`
236
- Extracts nested values from each hash in an array using the `dig` method.
237
-
238
- ```ruby
239
- data = [
240
- { user: { profile: { name: "Alice" } } },
241
- { user: { profile: { name: "Bob" } } }
242
- ]
243
-
244
- data.dig_map(:user, :profile, :name)
245
- # => ["Alice", "Bob"]
246
- ```
247
-
248
- #### `compact_prefix` / `compact_suffix` / `trim_nils`
249
- Remove nil values from the beginning, end, or both ends of an array without touching interior nil values.
250
-
251
- ```ruby
252
- [nil, nil, 1, nil, 2, nil, nil].compact_prefix
253
- # => [1, nil, 2, nil, nil]
254
-
255
- [1, nil, 2, nil, nil].compact_suffix
256
- # => [1, nil, 2]
257
-
258
- [nil, nil, 1, nil, 2, nil, nil].trim_nils
259
- # => [1, nil, 2]
260
- ```
261
-
262
- #### `compact_blank_prefix` / `compact_blank_suffix` / `trim_blanks` (with ActiveSupport)
263
- Remove blank values (nil, empty strings, etc.) from the beginning, end, or both ends of an array.
264
-
265
- ```ruby
266
- [nil, "", 1, "", 2, nil, ""].compact_blank_prefix
267
- # => [1, "", 2, nil, ""]
268
-
269
- [nil, "", 1, "", 2, nil, ""].compact_blank_suffix
270
- # => [nil, "", 1, "", 2]
271
-
272
- [nil, "", 1, "", 2, nil, ""].trim_blanks
273
- # => [1, "", 2]
274
- ```
275
-
276
- #### `deep_freeze`
277
- Recursively freezes an array and all of its nested elements.
278
-
279
- ```ruby
280
- array = ["hello", { name: "Alice" }, [1, 2, 3]]
281
- array.deep_freeze
282
- # => All elements and nested structures are now frozen
283
- ```
284
-
285
- ### Enumerable
286
-
287
- #### `join_map`
288
- Combines filtering, mapping and joining operations into one convenient method.
289
-
290
- ```ruby
291
- # Basic usage with arrays
292
- [1, 2, nil, 3].join_map(", ") { |n| n&.to_s if n&.odd? }
293
- # => "1, 3"
294
-
295
- # Works with any Enumerable
296
- (1..10).join_map(" | ") { |n| "num#{n}" if n.even? }
297
- # => "num2 | num4 | num6 | num8 | num10"
298
-
299
- # Supports with_index option
300
- %w[a b c].join_map("-", with_index: true) { |char, i| "#{i}:#{char}" }
301
- # => "0:a-1:b-2:c"
302
- ```
303
-
304
- ### Hash
305
-
306
- #### `to_struct`
307
- Recursively converts a hash into a Struct, including nested hashes and arrays.
308
-
309
- ```ruby
310
- hash = { user: { name: "Alice", roles: ["admin", "user"] } }
311
- struct = hash.to_struct
312
- struct.class # => Struct
313
- struct.user.name # => "Alice"
314
- struct.user.roles # => ["admin", "user"]
315
- ```
316
-
317
- #### `to_ostruct`
318
- Recursively converts a hash into an OpenStruct, including nested hashes and arrays.
319
-
320
- ```ruby
321
- hash = { config: { api_key: "secret", endpoints: ["v1", "v2"] } }
322
- config = hash.to_ostruct
323
- config.class # => OpenStruct
324
- config.config.api_key # => "secret"
325
- ```
326
-
327
- #### `to_istruct`
328
- Recursively converts a hash into an immutable Data structure.
329
-
330
- ```ruby
331
- hash = { person: { name: "Bob", age: 30 } }
332
- data = hash.to_istruct
333
- data.class # => Data
334
- data.person.name # => "Bob"
335
- ```
336
-
337
- #### `join_map`
338
- Similar to Array#join_map but operates on hash values.
110
+ # Remove nil values from the beginning/end
111
+ [nil, nil, 1, nil, 2, nil, nil].trim_nils # => [1, nil, 2]
339
112
 
340
- ```ruby
341
- { a: 1, b: 2, c: nil, d: 3 }.join_map(" ") { |k, v| [k, v] if v }
342
- # => "a 1 b 2 d 3"
113
+ # With ActiveSupport, remove blank values
114
+ [nil, "", 1, "", 2, nil, ""].trim_blanks # => [1, "", 2]
343
115
  ```
344
116
 
345
- #### `deep_freeze`
346
- Recursively freezes a hash and all of its nested values.
117
+ **Extensions:** `trim_nils`, `compact_prefix`, `compact_suffix`, `trim_blanks` (with ActiveSupport)
347
118
 
348
- ```ruby
349
- hash = { user: { name: "Alice", roles: ["admin", "user"] } }
350
- hash.deep_freeze
351
- # => Hash and all nested structures are now frozen
352
- ```
119
+ ### Boolean Methods
353
120
 
354
- ### Module
355
-
356
- #### `attr_predicate`
357
- Creates predicate (boolean) methods for instance variables.
121
+ Create predicate methods with minimal code:
358
122
 
359
123
  ```ruby
360
124
  class User
361
- attr_writer :admin
362
- attr_predicate :admin, :active
125
+ attr_accessor :admin
126
+ attr_predicate :admin
363
127
  end
364
128
 
365
129
  user = User.new
366
- user.active? # => false
367
130
  user.admin = true
368
- user.admin? # => true
369
- ```
370
-
371
- ### OpenStruct
372
-
373
- #### `each`
374
- Alias for `each_pair`.
375
-
376
- ```ruby
377
- struct = OpenStruct.new(a: 1, b: 2)
378
- struct.each { |key, value| puts "#{key}: #{value}" }
379
- ```
380
-
381
- #### `map`
382
- Maps over OpenStruct entries.
383
-
384
- ```ruby
385
- struct = OpenStruct.new(a: 1, b: 2)
386
- struct.map { |key, value| [key, value * 2] }
387
- # => [[:a, 2], [:b, 4]]
388
- ```
389
-
390
- #### `filter_map`
391
- Combines `map` and `compact` operations in one convenient method.
392
-
393
- ```ruby
394
- struct = OpenStruct.new(a: 1, b: nil, c: 2)
395
- struct.filter_map { |key, value| value * 2 if value }
396
- # => [2, 4]
397
- ```
398
-
399
- #### `join_map`
400
- Combines `filter_map` and `join` operations in one convenient method.
401
-
402
- ```ruby
403
- config = OpenStruct.new(
404
- alice: {roles: ["admin"]},
405
- bob: {roles: ["user"]},
406
- carol: {roles: ["admin"]}
407
- )
408
-
409
- config.join_map(", ") { |key, value| key if value[:roles].include?("admin") }
410
- # => "alice, carol"
411
- ```
412
-
413
- ### String
414
-
415
- #### `to_h` / `to_a`
416
- Parses JSON string into a Ruby Hash or Array.
417
-
418
- ```ruby
419
- '{"name": "Alice"}'.to_h
420
- # => {name: "Alice"}
421
-
422
- '["Alice"]'.to_h # Or you can use #to_a for different readability
423
- # => ["Alice"]
424
- ```
425
-
426
- #### `to_istruct`
427
- Parses JSON string into an immutable Data structure.
428
-
429
- ```ruby
430
- '{"user": {"name": "Alice"}}'.to_istruct
431
- # => #<data user=#<data name="Alice">>
432
- ```
433
-
434
- #### `to_ostruct`
435
- Parses JSON string into an OpenStruct.
436
-
437
- ```ruby
438
- '{"user": {"name": "Alice"}}'.to_ostruct
439
- # => #<OpenStruct user=#<OpenStruct name="Alice">>
440
- ```
441
-
442
- #### `to_struct`
443
- Parses JSON string into a Struct.
444
-
445
- ```ruby
446
- '{"user": {"name": "Alice"}}'.to_struct
447
- # => #<struct user=#<struct name="Alice">>
448
- ```
449
-
450
- #### `to_deep_h`
451
- Recursively parses nested JSON strings within a structure.
452
-
453
- ```ruby
454
- nested_json = {
455
- users: [
456
- {name: "Alice", roles: ["admin", "user"]}.to_json,
457
- ]
458
- }.to_json
459
-
460
- nested_json.to_deep_h
461
- # => {users: [{name: "Alice", roles: ["admin", "user"]}]}
462
- ```
463
-
464
- #### `with_quotes` / `in_quotes`
465
- Wraps the string in double quotes
466
-
467
- ```ruby
468
- "Hello World".with_quotes
469
- # => "\"Hello World\""
470
- ```
471
-
472
- ### Symbol
473
-
474
- #### `with_quotes` / `in_quotes`
475
- Wraps the symbol in double quotes
476
-
477
- ```ruby
478
- :hello_world.with_quotes
479
- # => :"\"hello_world\""
480
- ```
481
-
482
- ## Advanced Usage
483
-
484
- See how EverythingRB transforms your code from verbose to elegant:
485
-
486
- ### Extracting Data from Nested JSON
487
-
488
- **Before:**
489
- ```ruby
490
- # Standard Ruby approach
491
- json_data = '[{"user":{"name":"Alice","role":"admin"}},{"user":{"name":"Bob","role":"guest"}}]'
492
-
493
- parsed_data = JSON.parse(json_data, symbolize_names: true)
494
- names = parsed_data.map { |item| item[:user][:name] }
495
- result = names.join(", ")
496
- # => "Alice, Bob"
497
- ```
498
-
499
- **After:**
500
- ```ruby
501
- # With EverythingRB
502
- json_data = '[{"user":{"name":"Alice","role":"admin"}},{"user":{"name":"Bob","role":"guest"}}]'
503
- result = json_data.to_a.dig_map(:user, :name).join(", ")
504
- # => "Alice, Bob"
505
- ```
506
-
507
- ### Freezing Nested Configurations
508
-
509
- **Before:**
510
- ```ruby
511
- # Standard Ruby approach
512
- config_json = File.read("config.json")
513
- config = JSON.parse(config_json, symbolize_names: true)
514
-
515
- deep_freeze = lambda do |obj|
516
- case obj
517
- when Hash
518
- obj.each_value { |v| deep_freeze.call(v) }
519
- obj.freeze
520
- when Array
521
- obj.each { |v| deep_freeze.call(v) }
522
- obj.freeze
523
- else
524
- obj.freeze
525
- end
526
- end
131
+ user.admin? # => true
527
132
 
528
- frozen_config = deep_freeze.call(config)
529
- ```
133
+ # Works with Data objects too!
134
+ Person = Data.define(:active)
135
+ Person.attr_predicate(:active)
530
136
 
531
- **After:**
532
- ```ruby
533
- # With EverythingRB
534
- config_json = File.read("config.json")
535
- frozen_config = config_json.to_h.deep_freeze
137
+ person = Person.new(active: false)
138
+ person.active? # => false
536
139
  ```
537
140
 
538
- ### Filtering and Formatting Nested Collections
141
+ **Extensions:** `attr_predicate`
539
142
 
540
- **Before:**
541
- ```ruby
542
- # Standard Ruby approach
543
- users_json = '[{"user":{"name":"Alice","admin":true,"active":true}},{"user":{"name":"Bob","admin":true,"active":false}}]'
544
-
545
- users = JSON.parse(users_json, symbolize_names: true)
546
- active_admins = users.map { |u| u[:user] }.select { |u| u[:admin] && u[:active] }
547
- admin_names = active_admins.map { |u| u[:name] }.join(", ")
548
- # => "Alice"
549
- ```
143
+ ## Full Documentation
550
144
 
551
- **After:**
552
- ```ruby
553
- # With EverythingRB
554
- users_json = '[{"user":{"name":"Alice","admin":true,"active":true}},{"user":{"name":"Bob","admin":true,"active":false}}]'
555
- admin_names = users_json.to_a.key_map(:user).join_map(", ") do |user|
556
- user[:name] if user[:admin] && user[:active]
557
- end
558
- # => "Alice"
559
- ```
145
+ For complete method listings, examples, and detailed usage, see the [API Documentation](https://itsthedevman.com/docs/everythingrb).
560
146
 
561
147
  ## Requirements
562
148
 
@@ -564,22 +150,14 @@ end
564
150
 
565
151
  ## Contributing
566
152
 
567
- 1. Fork it
568
- 2. Create your feature branch (`git checkout -b feature/my-new-feature`)
569
- 3. Commit your changes (`git commit -am 'Add some feature'`)
570
- 4. Push to the branch (`git push origin feature/my-new-feature`)
571
- 5. Create new Pull Request
572
-
573
- Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
153
+ Bug reports and pull requests are welcome! This project is intended to be a safe, welcoming space for collaboration.
574
154
 
575
155
  ## License
576
156
 
577
- The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
578
-
579
- ## Changelog
157
+ [MIT License](LICENSE.txt)
580
158
 
581
- See [CHANGELOG.md](CHANGELOG.md) for a list of changes.
159
+ ## Looking for a Software Engineer?
582
160
 
583
- ## Credits
161
+ I'm currently looking for opportunities where I can tackle meaningful problems and help build reliable software while mentoring the next generation of developers. If you're looking for a senior engineer with full-stack Rails expertise and a passion for clean, maintainable code, let's talk!
584
162
 
585
- - Author: Bryan "itsthedevman"
163
+ [bryan@itsthedevman.com](mailto:bryan@itsthedevman.com)
@@ -93,6 +93,10 @@ class Array
93
93
  # ["hello", { name: "Alice" }, [1, 2, 3]].deep_freeze
94
94
  # # => All elements and nested structures are now frozen
95
95
  #
96
+ # @note CAUTION: Be careful when freezing collections that contain class objects
97
+ # or singleton instances - this will freeze those classes/objects globally!
98
+ # Only use deep_freeze on pure data structures you want to make immutable.
99
+ #
96
100
  def deep_freeze
97
101
  each { |v| v.respond_to?(:deep_freeze) ? v.deep_freeze : v.freeze }
98
102
  freeze
@@ -177,5 +181,73 @@ class Array
177
181
  def trim_blanks
178
182
  compact_blank_prefix.compact_blank_suffix
179
183
  end
184
+
185
+ #
186
+ # Joins array elements into a sentence with "or" connector
187
+ #
188
+ # Similar to ActiveSupport's #to_sentence but uses "or" instead of "and"
189
+ # as the joining word between elements.
190
+ #
191
+ # @param options [Hash] Options to pass to #to_sentence
192
+ #
193
+ # @return [String] Array elements joined in sentence form with "or" connector
194
+ #
195
+ # @example Basic usage
196
+ # ["red", "blue", "green"].to_or_sentence
197
+ # # => "red, blue, or green"
198
+ #
199
+ # @example With only two elements
200
+ # ["yes", "no"].to_or_sentence
201
+ # # => "yes or no"
202
+ #
203
+ def to_or_sentence(options = {})
204
+ options = options.reverse_merge(last_word_connector: ", or ", two_words_connector: " or ")
205
+ to_sentence(options)
206
+ end
207
+ end
208
+
209
+ #
210
+ # Recursively converts all elements that respond to #to_h
211
+ #
212
+ # Maps over the array and calls #to_deep_h on any Hash/String elements,
213
+ # #to_h on any objects that respond to it, and handles nested arrays.
214
+ #
215
+ # @return [Array] A new array with all convertible elements deeply converted
216
+ #
217
+ # @example Converting arrays with mixed object types
218
+ # users = [
219
+ # {name: "Alice", roles: ["admin"]},
220
+ # OpenStruct.new(name: "Bob", active: true),
221
+ # Data.define(:name).new(name: "Carol")
222
+ # ]
223
+ # users.to_deep_h
224
+ # # => [
225
+ # # {name: "Alice", roles: ["admin"]},
226
+ # # {name: "Bob", active: true},
227
+ # # {name: "Carol"}
228
+ # # ]
229
+ #
230
+ # @example With nested arrays and JSON strings
231
+ # data = [
232
+ # {profile: '{"level":"expert"}'},
233
+ # [OpenStruct.new(id: 1), OpenStruct.new(id: 2)]
234
+ # ]
235
+ # data.to_deep_h
236
+ # # => [{profile: {level: "expert"}}, [{id: 1}, {id: 2}]]
237
+ #
238
+ def to_deep_h
239
+ map do |value|
240
+ case value
241
+ when Hash
242
+ value.to_deep_h
243
+ when Array
244
+ value.to_deep_h
245
+ when String
246
+ # If the string is not valid JSON, #to_deep_h will return `nil`
247
+ value.to_deep_h || value
248
+ else
249
+ value.respond_to?(:to_h) ? value.to_h : value
250
+ end
251
+ end
180
252
  end
181
253
  end
@@ -44,4 +44,49 @@ module Enumerable
44
44
  filter_map(&block).join(join_with)
45
45
  end
46
46
  end
47
+
48
+ #
49
+ # Groups elements by a given key or nested keys
50
+ #
51
+ # @param keys [Array<Symbol, String>] The key(s) to group by
52
+ #
53
+ # @yield [value] Optional block to transform the key value before grouping
54
+ # @yieldparam value [Object] The value at the specified key(s)
55
+ # @yieldreturn [Object] The transformed value to use as the group key
56
+ #
57
+ # @return [Hash] A hash where keys are the grouped values and values are arrays of elements
58
+ #
59
+ # @example Group by a single key
60
+ # users = [
61
+ # {name: "Alice", role: "admin"},
62
+ # {name: "Bob", role: "user"},
63
+ # {name: "Charlie", role: "admin"}
64
+ # ]
65
+ # users.group_by_key(:role)
66
+ # # => {"admin"=>[{name: "Alice", role: "admin"}, {name: "Charlie", role: "admin"}],
67
+ # # "user"=>[{name: "Bob", role: "user"}]}
68
+ #
69
+ # @example Group by nested keys
70
+ # data = [
71
+ # {department: {name: "Sales"}, employee: "Alice"},
72
+ # {department: {name: "IT"}, employee: "Bob"},
73
+ # {department: {name: "Sales"}, employee: "Charlie"}
74
+ # ]
75
+ # data.group_by_key(:department, :name)
76
+ # # => {"Sales"=>[{department: {name: "Sales"}, employee: "Alice"},
77
+ # # {department: {name: "Sales"}, employee: "Charlie"}],
78
+ # # "IT"=>[{department: {name: "IT"}, employee: "Bob"}]}
79
+ #
80
+ # @example With transformation block
81
+ # users.group_by_key(:role) { |role| role.upcase }
82
+ # # => {"ADMIN"=>[{name: "Alice", role: "admin"}, {name: "Charlie", role: "admin"}],
83
+ # # "USER"=>[{name: "Bob", role: "user"}]}
84
+ #
85
+ def group_by_key(*keys, &block)
86
+ group_by do |value|
87
+ result = value.respond_to?(:dig) ? value.dig(*keys) : nil
88
+ result = yield(result) if block
89
+ result
90
+ end
91
+ end
47
92
  end
@@ -29,6 +29,32 @@ class Hash
29
29
  #
30
30
  EMPTY_STRUCT = Struct.new(:_).new(nil)
31
31
 
32
+ #
33
+ # Creates a new Hash that automatically initializes missing keys with nested hashes
34
+ #
35
+ # This method creates a hash where any missing key access will automatically
36
+ # create another nested hash with the same behavior, allowing for unlimited
37
+ # nesting depth without explicit initialization.
38
+ #
39
+ # @return [Hash] A hash that recursively creates nested hashes for missing keys
40
+ #
41
+ # @example Basic usage with two levels
42
+ # users = Hash.new_nested_hash
43
+ # users[:john][:role] = "admin" # No need to initialize users[:john] first
44
+ # users # => {john: {role: "admin"}}
45
+ #
46
+ # @example Deep nesting without initialization
47
+ # stats = Hash.new_nested_hash
48
+ # (stats[:server][:region][:us_east][:errors] = []) << "Some Error"
49
+ # stats # => {server: {region: {us_east: {errors: ["Some Error"]}}}}
50
+ #
51
+ # @note While extremely convenient, be cautious with very deep structures
52
+ # as this creates new hashes on demand for any key access
53
+ #
54
+ def self.new_nested_hash
55
+ new { |hash, key| hash[key] = new_nested_hash }
56
+ end
57
+
32
58
  #
33
59
  # Combines filter_map and join operations
34
60
  #
@@ -54,6 +80,53 @@ class Hash
54
80
  filter_map(&block).join(join_with)
55
81
  end
56
82
 
83
+ #
84
+ # Recursively converts all values that respond to #to_h
85
+ #
86
+ # Similar to #to_h but recursively traverses the Hash structure
87
+ # and calls #to_h on any object that responds to it. Useful for
88
+ # normalizing nested data structures and parsing nested JSON.
89
+ #
90
+ # @return [Hash] A deeply converted hash with all nested objects converted
91
+ #
92
+ # @example Converting nested Data objects
93
+ # user = { name: "Alice", metadata: Data.define(:source).new(source: "API") }
94
+ # user.to_deep_h # => {name: "Alice", metadata: {source: "API"}}
95
+ #
96
+ # @example Parsing nested JSON strings
97
+ # nested = { profile: '{"role":"admin"}' }
98
+ # nested.to_deep_h # => {profile: {role: "admin"}}
99
+ #
100
+ # @example Mixed nested structures
101
+ # data = {
102
+ # config: OpenStruct.new(api_key: "secret"),
103
+ # users: [
104
+ # Data.define(:name).new(name: "Bob"),
105
+ # {role: "admin"}
106
+ # ]
107
+ # }
108
+ # data.to_deep_h
109
+ # # => {
110
+ # # config: {api_key: "secret"},
111
+ # # users: [{name: "Bob"}, {role: "admin"}]
112
+ # # }
113
+ #
114
+ def to_deep_h
115
+ transform_values do |value|
116
+ case value
117
+ when Hash
118
+ value.to_deep_h
119
+ when Array
120
+ value.to_deep_h
121
+ when String
122
+ # If the string is not valid JSON, #to_deep_h will return `nil`
123
+ value.to_deep_h || value
124
+ else
125
+ value.respond_to?(:to_h) ? value.to_h : value
126
+ end
127
+ end
128
+ end
129
+
57
130
  #
58
131
  # Converts hash to an immutable Data structure
59
132
  #
@@ -143,8 +216,162 @@ class Hash
143
216
  # { user: { name: "Alice", roles: ["admin"] } }.deep_freeze
144
217
  # # => Hash and all nested structures are now frozen
145
218
  #
219
+ # @note CAUTION: Be careful when freezing collections that contain class objects
220
+ # or singleton instances - this will freeze those classes/objects globally!
221
+ # Only use deep_freeze on pure data structures you want to make immutable.
222
+ #
146
223
  def deep_freeze
147
224
  each_value { |v| v.respond_to?(:deep_freeze) ? v.deep_freeze : v.freeze }
148
225
  freeze
149
226
  end
227
+
228
+ # Allows calling original method. See below
229
+ alias_method :og_transform_values, :transform_values
230
+
231
+ # Allows calling original method. See below
232
+ alias_method :og_transform_values!, :transform_values!
233
+
234
+ #
235
+ # Transforms hash values while allowing access to keys via the chainable with_key method
236
+ #
237
+ # This method either performs a standard transform_values operation if a block is given,
238
+ # or returns an enumerator with a with_key method that passes both the key and value
239
+ # to the block.
240
+ #
241
+ # @yield [value] Block to transform each value (standard behavior)
242
+ # @yieldparam value [Object] The value to transform
243
+ # @yieldreturn [Object] The transformed value
244
+ #
245
+ # @return [Hash, Enumerator] Result hash or Enumerator with with_key method
246
+ #
247
+ # @example Standard transform_values
248
+ # {a: 1, b: 2}.transform_values { |v| v * 2 }
249
+ # # => {a: 2, b: 4}
250
+ #
251
+ # @example Using with_key to access keys during transformation
252
+ # {a: 1, b: 2}.transform_values.with_key { |k, v| "#{k}_#{v}" }
253
+ # # => {a: "a_1", b: "b_2"}
254
+ #
255
+ def transform_values(&block)
256
+ return transform_values_enumerator if block.nil?
257
+
258
+ og_transform_values(&block)
259
+ end
260
+
261
+ #
262
+ # Transforms hash values in place while allowing access to keys via the chainable with_key method
263
+ #
264
+ # This method either performs a standard transform_values! operation if a block is given,
265
+ # or returns an enumerator with a with_key method that passes both the key and value
266
+ # to the block, updating the hash in place.
267
+ #
268
+ # @yield [value] Block to transform each value (standard behavior)
269
+ # @yieldparam value [Object] The value to transform
270
+ # @yieldreturn [Object] The transformed value
271
+ #
272
+ # @return [self, Enumerator]
273
+ # Original hash with transformed values or Enumerator with with_key method
274
+ #
275
+ # @example Standard transform_values!
276
+ # hash = {a: 1, b: 2}
277
+ # hash.transform_values! { |v| v * 2 }
278
+ # # => {a: 2, b: 4}
279
+ #
280
+ # @example Using with_key to access keys during in-place transformation
281
+ # hash = {a: 1, b: 2}
282
+ # hash.transform_values!.with_key { |k, v| "#{k}_#{v}" }
283
+ # # => {a: "a_1", b: "b_2"}
284
+ #
285
+ def transform_values!(&block)
286
+ return transform_values_bang_enumerator if block.nil?
287
+
288
+ og_transform_values!(&block)
289
+ end
290
+
291
+ #
292
+ # Returns the first value where the key-value pair satisfies the given condition
293
+ #
294
+ # @yield [key, value] Block that determines whether to include the value
295
+ # @yieldparam key [Object] The current key
296
+ # @yieldparam value [Object] The current value
297
+ # @yieldreturn [Boolean] Whether to include this value
298
+ #
299
+ # @return [Object, nil] The first matching value or nil if none found
300
+ # @return [Enumerator] If no block is given
301
+ #
302
+ # @example Find first admin user by role
303
+ # users = {
304
+ # alice: {name: "Alice", role: "admin"},
305
+ # bob: {name: "Bob", role: "user"},
306
+ # charlie: {name: "Charlie", role: "admin"}
307
+ # }
308
+ # users.value_where { |k, v| v[:role] == "admin" } # => {name: "Alice", role: "admin"}
309
+ #
310
+ def value_where(&block)
311
+ return to_enum(:value_where) if block.nil?
312
+
313
+ find(&block)&.last
314
+ end
315
+
316
+ #
317
+ # Returns all values where the key-value pairs satisfy the given condition
318
+ #
319
+ # @yield [key, value] Block that determines whether to include the value
320
+ # @yieldparam key [Object] The current key
321
+ # @yieldparam value [Object] The current value
322
+ # @yieldreturn [Boolean] Whether to include this value
323
+ #
324
+ # @return [Array] All matching values
325
+ # @return [Enumerator] If no block is given
326
+ #
327
+ # @example Find all admin users by role
328
+ # users = {
329
+ # alice: {name: "Alice", role: "admin"},
330
+ # bob: {name: "Bob", role: "user"},
331
+ # charlie: {name: "Charlie", role: "admin"}
332
+ # }
333
+ # users.values_where { |k, v| v[:role] == "admin" }
334
+ # # => [{name: "Alice", role: "admin"}, {name: "Charlie", role: "admin"}]
335
+ #
336
+ def values_where(&block)
337
+ return to_enum(:values_where) if block.nil?
338
+
339
+ select(&block).values
340
+ end
341
+
342
+ private
343
+
344
+ def transform_values_enumerator
345
+ original_hash = self
346
+ enum = to_enum(:transform_values)
347
+
348
+ # Add the with_key method directly to the enum
349
+ enum.define_singleton_method(:with_key) do |&block|
350
+ raise ArgumentError, "Missing block for Hash#transform_values.with_key" if block.nil?
351
+
352
+ original_hash.each_pair.with_object({}) do |(key, value), output|
353
+ output[key] = block.call(key, value)
354
+ end
355
+ end
356
+
357
+ enum
358
+ end
359
+
360
+ def transform_values_bang_enumerator
361
+ original_hash = self
362
+ enum = to_enum(:transform_values!)
363
+
364
+ # Add the with_key method directly to the enum
365
+ enum.define_singleton_method(:with_key) do |&block|
366
+ raise ArgumentError, "Missing block for Hash#transform_values!.with_key" if block.nil?
367
+
368
+ original_hash.each_pair do |key, value|
369
+ original_hash[key] = block.call(key, value)
370
+ end
371
+
372
+ original_hash
373
+ end
374
+
375
+ enum
376
+ end
150
377
  end
@@ -7,5 +7,5 @@
7
7
  #
8
8
  module Everythingrb
9
9
  # Current version of the everythingrb gem
10
- VERSION = "0.2.5"
10
+ VERSION = "0.3.1"
11
11
  end
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.2.5
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-30 00:00:00.000000000 Z
11
+ date: 2025-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ostruct