everythingrb 0.2.2 → 0.2.4
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 +17 -1
- data/README.md +218 -20
- data/flake.lock +3 -3
- data/flake.nix +8 -2
- data/lib/everythingrb/core/array.rb +22 -6
- data/lib/everythingrb/core/enumerable.rb +14 -4
- data/lib/everythingrb/core/hash.rb +61 -13
- data/lib/everythingrb/core/module.rb +17 -1
- data/lib/everythingrb/core/ostruct.rb +39 -8
- data/lib/everythingrb/core/string.rb +47 -7
- data/lib/everythingrb/core/symbol.rb +26 -0
- data/lib/everythingrb/version.rb +7 -1
- data/lib/everythingrb.rb +22 -0
- metadata +3 -3
- data/sig/everythingrb.rbs +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a31c21db03be7c59f60dacc8136cea0d28e0298f4b53cb7a89c0934d1d7617f0
|
4
|
+
data.tar.gz: 3ba88a496f226383284c234892c49fc976e7bc695489e1bef0ff3512bb7b2c16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa926d8fb5e356bb3ea0c6bf87ad2bbe847897a042075166a78d0dea39e27015cf35d49570a7db79fe489be2bb0dd324c5383412f5b1d60d06e1e60722c5e969
|
7
|
+
data.tar.gz: 04cea9d92d4f851c2eb3d0a928319c583cc55c2c5376177371c5da0d70ab41d5dbd9c8a7dc70e2c113f20e41ae216c46bcd74d5e6b56a51913d5aa1842177ee6
|
data/CHANGELOG.md
CHANGED
@@ -23,6 +23,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
23
23
|
|
24
24
|
### Removed
|
25
25
|
|
26
|
+
## [0.2.4] - 12025-03-20
|
27
|
+
|
28
|
+
### Changed
|
29
|
+
|
30
|
+
- Improved documentation
|
31
|
+
- Fixed an issue with `Hash#to_struct` on Ruby 3.2 would raise an exception if called on an empty Hash
|
32
|
+
|
33
|
+
## [0.2.3] - 12025-03-09
|
34
|
+
|
35
|
+
### Added
|
36
|
+
|
37
|
+
- Added `Symbol#with_quotes` and `Symbol#in_quotes`
|
38
|
+
|
39
|
+
|
26
40
|
## [0.2.2] - 12025-03-03
|
27
41
|
|
28
42
|
### Added
|
@@ -97,7 +111,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
97
111
|
|
98
112
|
- Added alias `each` to `each_pair` in OpenStruct for better enumerable compatibility
|
99
113
|
|
100
|
-
[unreleased]: https://github.com/itsthedevman/everythingrb/compare/v0.2.
|
114
|
+
[unreleased]: https://github.com/itsthedevman/everythingrb/compare/v0.2.4...HEAD
|
115
|
+
[0.2.3]: https://github.com/itsthedevman/everythingrb/compare/v0.2.3...v0.2.4
|
116
|
+
[0.2.3]: https://github.com/itsthedevman/everythingrb/compare/v0.2.2...v0.2.3
|
101
117
|
[0.2.2]: https://github.com/itsthedevman/everythingrb/compare/v0.2.1...v0.2.2
|
102
118
|
[0.2.1]: https://github.com/itsthedevman/everythingrb/compare/v0.2.0...v0.2.1
|
103
119
|
[0.2.0]: https://github.com/itsthedevman/everythingrb/compare/v0.1.2...v0.2.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
|
-
|
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.
|
8
8
|
|
9
9
|
## Looking for a Software Engineer?
|
10
10
|
|
@@ -14,34 +14,24 @@ I'm currently looking for opportunities where I can tackle meaningful problems a
|
|
14
14
|
|
15
15
|
# Table of Contents
|
16
16
|
|
17
|
+
- [Introduction](#introduction)
|
17
18
|
- [Compatibility](#compatibility)
|
18
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
|
+
- [Predicate Methods](#predicate-methods)
|
19
26
|
- [Core Extensions](#core-extensions)
|
20
27
|
- [Array](#array)
|
21
|
-
- [join_map](#join_map)
|
22
|
-
- [key_map](#key_map)
|
23
|
-
- [dig_map](#dig_map)
|
24
28
|
- [Enumerable](#enumerable)
|
25
|
-
- [join_map](#join_map-1)
|
26
29
|
- [Hash](#hash)
|
27
|
-
- [to_struct](#to_struct)
|
28
|
-
- [to_ostruct](#to_ostruct)
|
29
|
-
- [to_istruct](#to_istruct)
|
30
|
-
- [join_map](#join_map-2)
|
31
30
|
- [Module](#module)
|
32
|
-
- [attr_predicate](#attr_predicate)
|
33
31
|
- [OpenStruct](#openstruct)
|
34
|
-
- [each](#each)
|
35
|
-
- [map](#map)
|
36
|
-
- [filter_map](#filter_map)
|
37
|
-
- [join_map](#join_map-3)
|
38
32
|
- [String](#string)
|
39
|
-
|
40
|
-
|
41
|
-
- [to_ostruct](#to_ostruct-1)
|
42
|
-
- [to_struct](#to_struct-1)
|
43
|
-
- [to_deep_h](#to_deep_h)
|
44
|
-
- [with_quotes / in_quotes](#with_quotes--in_quotes)
|
33
|
+
- [Symbol](#symbol)
|
34
|
+
- [Advanced Usage](#advanced-usage)
|
45
35
|
- [Requirements](#requirements)
|
46
36
|
- [Contributing](#contributing)
|
47
37
|
- [License](#license)
|
@@ -50,6 +40,12 @@ I'm currently looking for opportunities where I can tackle meaningful problems a
|
|
50
40
|
|
51
41
|
Also see: [API Documentation](https://itsthedevman.com/docs/everythingrb)
|
52
42
|
|
43
|
+
## Introduction
|
44
|
+
|
45
|
+
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.
|
46
|
+
|
47
|
+
Whether you're transforming data, working with JSON, or building complex object structures, EverythingRB makes your code more readable and maintainable with minimal effort.
|
48
|
+
|
53
49
|
## Compatibility
|
54
50
|
|
55
51
|
Currently tested on:
|
@@ -76,6 +72,115 @@ Or install it yourself as:
|
|
76
72
|
$ gem install everythingrb
|
77
73
|
```
|
78
74
|
|
75
|
+
## Features
|
76
|
+
|
77
|
+
### Data Structure Conversions
|
78
|
+
|
79
|
+
Easily convert between different Ruby data structures:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
# Convert any hash to an OpenStruct, Struct, or Data (immutable) object
|
83
|
+
config = { server: { host: "example.com", port: 443 } }.to_ostruct
|
84
|
+
config.server.host # => "example.com"
|
85
|
+
|
86
|
+
# Parse JSON directly to your preferred structure
|
87
|
+
'{"user":{"name":"Alice"}}'.to_istruct.user.name # => "Alice"
|
88
|
+
'{"items":[1,2,3]}'.to_struct.items # => [1, 2, 3]
|
89
|
+
```
|
90
|
+
|
91
|
+
### Collection Processing
|
92
|
+
|
93
|
+
Process collections with elegant, chainable methods:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
# Extract specific data from arrays of hashes in one step
|
97
|
+
users = [{ name: "Alice", roles: ["admin"] }, { name: "Bob", roles: ["user"] }]
|
98
|
+
users.key_map(:name) # => ["Alice", "Bob"]
|
99
|
+
users.dig_map(:roles, 0) # => ["admin", "user"]
|
100
|
+
|
101
|
+
# Filter, map, and join in a single operation
|
102
|
+
[1, 2, nil, 3, 4].join_map(" | ") { |n| "Item #{n}" if n&.odd? }
|
103
|
+
# => "Item 1 | Item 3"
|
104
|
+
```
|
105
|
+
|
106
|
+
### JSON & String Handling
|
107
|
+
|
108
|
+
Work with JSON and strings more naturally:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
# Parse JSON with symbolized keys
|
112
|
+
'{"name": "Alice"}'.to_h # => { name: "Alice" }
|
113
|
+
|
114
|
+
# Recursively parse nested JSON strings
|
115
|
+
nested = '{"user":"{\"profile\":\"{\\\"name\\\":\\\"Bob\\\"}\"}"}'
|
116
|
+
nested.to_deep_h # => { user: { profile: { name: "Bob" } } }
|
117
|
+
|
118
|
+
# Format strings with quotes
|
119
|
+
"hello".with_quotes # => "\"hello\""
|
120
|
+
```
|
121
|
+
|
122
|
+
### Object Freezing
|
123
|
+
|
124
|
+
Freeze nested structures with a single call:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
config = {
|
128
|
+
api: {
|
129
|
+
key: "secret",
|
130
|
+
endpoints: ["v1", "v2"]
|
131
|
+
}
|
132
|
+
}.deep_freeze
|
133
|
+
|
134
|
+
# Everything is frozen!
|
135
|
+
config.frozen? # => true
|
136
|
+
config[:api].frozen? # => true
|
137
|
+
config[:api][:endpoints].frozen? # => true
|
138
|
+
config[:api][:endpoints][0].frozen? # => true
|
139
|
+
```
|
140
|
+
|
141
|
+
### Predicate Methods
|
142
|
+
|
143
|
+
Create boolean accessors with minimal code:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
class User
|
147
|
+
attr_accessor :admin, :verified
|
148
|
+
attr_predicate :admin, :verified
|
149
|
+
end
|
150
|
+
|
151
|
+
user = User.new
|
152
|
+
user.admin = true
|
153
|
+
user.admin? # => true
|
154
|
+
user.verified? # => false
|
155
|
+
|
156
|
+
# Works with Struct and Data objects too
|
157
|
+
Person = Struct.new(:active)
|
158
|
+
Person.attr_predicate(:active)
|
159
|
+
|
160
|
+
person = Person.new(true)
|
161
|
+
person.active? # => true
|
162
|
+
```
|
163
|
+
|
164
|
+
**ActiveSupport Integration:** When ActiveSupport is loaded, predicate methods automatically use `present?` instead of just checking truthiness:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
# With ActiveSupport loaded
|
168
|
+
class Product
|
169
|
+
attr_accessor :tags, :category
|
170
|
+
attr_predicate :tags, :category
|
171
|
+
end
|
172
|
+
|
173
|
+
product = Product.new
|
174
|
+
product.tags = []
|
175
|
+
product.tags? # => false (empty array is not "present")
|
176
|
+
|
177
|
+
product.tags = ["sale"]
|
178
|
+
product.tags? # => true (non-empty array is "present")
|
179
|
+
|
180
|
+
product.category = ""
|
181
|
+
product.category? # => false (blank string is not "present")
|
182
|
+
```
|
183
|
+
|
79
184
|
## Core Extensions
|
80
185
|
|
81
186
|
### Array
|
@@ -319,6 +424,99 @@ Wraps the string in double quotes
|
|
319
424
|
# => "\"Hello World\""
|
320
425
|
```
|
321
426
|
|
427
|
+
### Symbol
|
428
|
+
|
429
|
+
#### `with_quotes` / `in_quotes`
|
430
|
+
Wraps the symbol in double quotes
|
431
|
+
|
432
|
+
```ruby
|
433
|
+
:hello_world.with_quotes
|
434
|
+
# => :"\"hello_world\""
|
435
|
+
```
|
436
|
+
|
437
|
+
## Advanced Usage
|
438
|
+
|
439
|
+
See how EverythingRB transforms your code from verbose to elegant:
|
440
|
+
|
441
|
+
### Extracting Data from Nested JSON
|
442
|
+
|
443
|
+
**Before:**
|
444
|
+
```ruby
|
445
|
+
# Standard Ruby approach
|
446
|
+
json_data = '[{"user":{"name":"Alice","role":"admin"}},{"user":{"name":"Bob","role":"guest"}}]'
|
447
|
+
|
448
|
+
parsed_data = JSON.parse(json_data, symbolize_names: true)
|
449
|
+
names = parsed_data.map { |item| item[:user][:name] }
|
450
|
+
result = names.join(", ")
|
451
|
+
# => "Alice, Bob"
|
452
|
+
```
|
453
|
+
|
454
|
+
**After:**
|
455
|
+
```ruby
|
456
|
+
# With EverythingRB
|
457
|
+
json_data = '[{"user":{"name":"Alice","role":"admin"}},{"user":{"name":"Bob","role":"guest"}}]'
|
458
|
+
result = json_data.to_a.dig_map(:user, :name).join(", ")
|
459
|
+
# => "Alice, Bob"
|
460
|
+
```
|
461
|
+
|
462
|
+
### Freezing Nested Configurations
|
463
|
+
|
464
|
+
**Before:**
|
465
|
+
```ruby
|
466
|
+
# Standard Ruby approach
|
467
|
+
config_json = File.read("config.json")
|
468
|
+
config = JSON.parse(config_json, symbolize_names: true)
|
469
|
+
|
470
|
+
deep_freeze = lambda do |obj|
|
471
|
+
case obj
|
472
|
+
when Hash
|
473
|
+
obj.each_value { |v| deep_freeze.call(v) }
|
474
|
+
obj.freeze
|
475
|
+
when Array
|
476
|
+
obj.each { |v| deep_freeze.call(v) }
|
477
|
+
obj.freeze
|
478
|
+
else
|
479
|
+
obj.freeze
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
frozen_config = deep_freeze.call(config)
|
484
|
+
```
|
485
|
+
|
486
|
+
**After:**
|
487
|
+
```ruby
|
488
|
+
# With EverythingRB
|
489
|
+
config_json = File.read("config.json")
|
490
|
+
frozen_config = config_json.to_h.deep_freeze
|
491
|
+
```
|
492
|
+
|
493
|
+
### Filtering and Formatting Nested Collections
|
494
|
+
|
495
|
+
**Before:**
|
496
|
+
```ruby
|
497
|
+
# Standard Ruby approach
|
498
|
+
users_json = '[{"user":{"name":"Alice","admin":true,"active":true}},{"user":{"name":"Bob","admin":true,"active":false}}]'
|
499
|
+
|
500
|
+
users = JSON.parse(users_json, symbolize_names: true)
|
501
|
+
active_admins = users.map { |u| u[:user] }.select { |u| u[:admin] && u[:active] }
|
502
|
+
admin_names = active_admins.map { |u| u[:name] }.join(", ")
|
503
|
+
# => "Alice"
|
504
|
+
```
|
505
|
+
|
506
|
+
**After:**
|
507
|
+
```ruby
|
508
|
+
# With EverythingRB
|
509
|
+
users_json = '[{"user":{"name":"Alice","admin":true,"active":true}},{"user":{"name":"Bob","admin":true,"active":false}}]'
|
510
|
+
admin_names = users_json.to_a.key_map(:user).join_map(", ") do |user|
|
511
|
+
user[:name] if user[:admin] && user[:active]
|
512
|
+
end
|
513
|
+
# => "Alice"
|
514
|
+
```
|
515
|
+
|
516
|
+
## Requirements
|
517
|
+
|
518
|
+
- Ruby 3.2 or higher
|
519
|
+
|
322
520
|
## Contributing
|
323
521
|
|
324
522
|
1. Fork it
|
data/flake.lock
CHANGED
@@ -20,11 +20,11 @@
|
|
20
20
|
},
|
21
21
|
"nixpkgs": {
|
22
22
|
"locked": {
|
23
|
-
"lastModified":
|
24
|
-
"narHash": "sha256-
|
23
|
+
"lastModified": 1742422364,
|
24
|
+
"narHash": "sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ=",
|
25
25
|
"owner": "NixOS",
|
26
26
|
"repo": "nixpkgs",
|
27
|
-
"rev": "
|
27
|
+
"rev": "a84ebe20c6bc2ecbcfb000a50776219f48d134cc",
|
28
28
|
"type": "github"
|
29
29
|
},
|
30
30
|
"original": {
|
data/flake.nix
CHANGED
@@ -6,8 +6,14 @@
|
|
6
6
|
flake-utils.url = "github:numtide/flake-utils";
|
7
7
|
};
|
8
8
|
|
9
|
-
outputs =
|
10
|
-
|
9
|
+
outputs =
|
10
|
+
{
|
11
|
+
self,
|
12
|
+
nixpkgs,
|
13
|
+
flake-utils,
|
14
|
+
}:
|
15
|
+
flake-utils.lib.eachDefaultSystem (
|
16
|
+
system:
|
11
17
|
let
|
12
18
|
pkgs = nixpkgs.legacyPackages.${system};
|
13
19
|
in
|
@@ -1,5 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
#
|
4
|
+
# Extensions to Ruby's core Array class
|
5
|
+
#
|
6
|
+
# This module adds convenient mapping, joining, and freezing functionality
|
7
|
+
# to all Arrays in your application.
|
8
|
+
#
|
9
|
+
# @example Using the extensions
|
10
|
+
# numbers = [1, 2, nil, 3]
|
11
|
+
#
|
12
|
+
# # Filter out nils and format odd numbers
|
13
|
+
# numbers.join_map(", ") { |n| "odd: #{n}" if n&.odd? }
|
14
|
+
# # => "odd: 1, odd: 3"
|
15
|
+
#
|
16
|
+
# users = [{name: "Alice", role: "admin"}, {name: "Bob", role: "user"}]
|
17
|
+
# users.key_map(:name) # => ["Alice", "Bob"]
|
18
|
+
#
|
3
19
|
class Array
|
4
20
|
#
|
5
21
|
# Combines filter_map and join operations
|
@@ -40,11 +56,11 @@ class Array
|
|
40
56
|
#
|
41
57
|
# @param key [Symbol, String] The key to extract
|
42
58
|
#
|
43
|
-
# @return [Array] Array of values
|
59
|
+
# @return [Array] Array of values extracted from each hash
|
44
60
|
#
|
45
61
|
# @example
|
46
|
-
# [{name:
|
47
|
-
# # => [
|
62
|
+
# [{name: "Alice", age: 30}, {name: "Bob", age: 25}].key_map(:name)
|
63
|
+
# # => ["Alice", "Bob"]
|
48
64
|
#
|
49
65
|
def key_map(key)
|
50
66
|
map { |v| v[key] }
|
@@ -59,10 +75,10 @@ class Array
|
|
59
75
|
#
|
60
76
|
# @example
|
61
77
|
# [
|
62
|
-
# {user: {profile: {name:
|
63
|
-
# {user: {profile: {name:
|
78
|
+
# {user: {profile: {name: "Alice"}}},
|
79
|
+
# {user: {profile: {name: "Bob"}}}
|
64
80
|
# ].dig_map(:user, :profile, :name)
|
65
|
-
# # => [
|
81
|
+
# # => ["Alice", "Bob"]
|
66
82
|
#
|
67
83
|
def dig_map(*keys)
|
68
84
|
map { |v| v.dig(*keys) }
|
@@ -1,5 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
#
|
4
|
+
# Extensions to Ruby's core Enumerable module
|
5
|
+
#
|
6
|
+
# These additions make working with any enumerable collection more expressive
|
7
|
+
# by combining common operations into convenient methods.
|
8
|
+
#
|
9
|
+
# @example Using join_map with a Range
|
10
|
+
# (1..5).join_map(" | ") { |n| "item-#{n}" if n.even? }
|
11
|
+
# # => "item-2 | item-4"
|
12
|
+
#
|
3
13
|
module Enumerable
|
4
14
|
#
|
5
15
|
# Combines filter_map and join operations
|
@@ -7,7 +17,7 @@ module Enumerable
|
|
7
17
|
# @param join_with [String] The delimiter to join elements with (defaults to empty string)
|
8
18
|
# @param with_index [Boolean] Whether to include the index in the block (defaults to false)
|
9
19
|
#
|
10
|
-
# @yield [element, index] Block that filters and transforms
|
20
|
+
# @yield [element, index] Block that filters and transforms elements
|
11
21
|
# @yieldparam element [Object] The current element
|
12
22
|
# @yieldparam index [Integer] The index of the current element (only if with_index: true)
|
13
23
|
#
|
@@ -21,9 +31,9 @@ module Enumerable
|
|
21
31
|
# ["a", "b", "c"].join_map(", ", with_index: true) { |char, i| "#{i}:#{char}" }
|
22
32
|
# # => "0:a, 1:b, 2:c"
|
23
33
|
#
|
24
|
-
# @example
|
25
|
-
#
|
26
|
-
# # => "
|
34
|
+
# @example Using with other enumerables
|
35
|
+
# (1..10).join_map(" | ") { |n| "num#{n}" if n.even? }
|
36
|
+
# # => "num2 | num4 | num6 | num8 | num10"
|
27
37
|
#
|
28
38
|
def join_map(join_with = "", with_index: false, &block)
|
29
39
|
block = ->(i) { i } if block.nil?
|
@@ -1,24 +1,52 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
#
|
4
|
+
# Extensions to Ruby's core Hash class
|
5
|
+
#
|
6
|
+
# These additions make working with hashes more convenient by adding
|
7
|
+
# conversion methods to different data structures and string formatting helpers.
|
8
|
+
#
|
9
|
+
# @example Converting to different structures
|
10
|
+
# user = { name: "Alice", roles: ["admin"] }
|
11
|
+
# user.to_struct # => #<struct name="Alice", roles=["admin"]>
|
12
|
+
# user.to_ostruct # => #<OpenStruct name="Alice", roles=["admin"]>
|
13
|
+
#
|
14
|
+
# # Filtering and joining hash entries
|
15
|
+
# { a: 1, b: nil, c: 3 }.join_map(", ") { |k, v| "#{k}:#{v}" if v }
|
16
|
+
# # => "a:1, c:3"
|
17
|
+
#
|
3
18
|
class Hash
|
4
19
|
#
|
5
|
-
#
|
20
|
+
# A minimal empty struct for Ruby 3.2+ compatibility
|
21
|
+
#
|
22
|
+
# Ruby 3.2 enforces stricter argument handling for Struct. This means trying to create
|
23
|
+
# a Struct from an empty Hash will result in an ArgumentError being raised.
|
24
|
+
# This is trying to keep a consistent experience with that version and newer versions.
|
25
|
+
#
|
26
|
+
# @return [Struct] A struct with a single nil-valued field
|
27
|
+
#
|
28
|
+
# @api private
|
6
29
|
#
|
7
|
-
|
30
|
+
EMPTY_STRUCT = Struct.new(:_).new(nil)
|
31
|
+
|
32
|
+
#
|
33
|
+
# Combines filter_map and join operations
|
8
34
|
#
|
9
35
|
# @param join_with [String] The delimiter to join elements with (defaults to empty string)
|
10
36
|
#
|
11
|
-
# @yield [
|
37
|
+
# @yield [key, value] Block that filters and transforms hash entries
|
38
|
+
# @yieldparam key [Object] The current key
|
39
|
+
# @yieldparam value [Object] The current value
|
12
40
|
#
|
13
|
-
# @return [String] Joined string of filtered and transformed
|
41
|
+
# @return [String] Joined string of filtered and transformed entries
|
14
42
|
#
|
15
43
|
# @example
|
16
|
-
# { a: 1, b:
|
17
|
-
# # => "1 3"
|
44
|
+
# { a: 1, b: nil, c: 2, d: nil, e: 3 }.join_map(", ") { |k, v| "#{k}-#{v}" if v }
|
45
|
+
# # => "a-1, c-2, e-3"
|
18
46
|
#
|
19
|
-
# @example
|
20
|
-
# { a: 1, b:
|
21
|
-
# # => "a
|
47
|
+
# @example Without a block
|
48
|
+
# { a: 1, b: nil, c: 2 }.join_map(" ")
|
49
|
+
# # => "a 1 b c 2"
|
22
50
|
#
|
23
51
|
def join_map(join_with = "", &block)
|
24
52
|
block = ->(kv_pair) { kv_pair.compact } if block.nil?
|
@@ -29,7 +57,13 @@ class Hash
|
|
29
57
|
#
|
30
58
|
# Converts hash to an immutable Data structure
|
31
59
|
#
|
32
|
-
# @return [Data]
|
60
|
+
# @return [Data] An immutable Data object with the same structure
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# hash = { person: { name: "Bob", age: 30 } }
|
64
|
+
# data = hash.to_istruct
|
65
|
+
# data.person.name # => "Bob"
|
66
|
+
# data.class # => Data
|
33
67
|
#
|
34
68
|
def to_istruct
|
35
69
|
recurse = lambda do |input|
|
@@ -49,9 +83,18 @@ class Hash
|
|
49
83
|
#
|
50
84
|
# Converts hash to a Struct recursively
|
51
85
|
#
|
52
|
-
# @return [Struct]
|
86
|
+
# @return [Struct] A struct with methods matching hash keys
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# hash = { user: { name: "Alice", roles: ["admin"] } }
|
90
|
+
# struct = hash.to_struct
|
91
|
+
# struct.user.name # => "Alice"
|
92
|
+
# struct.class # => Struct
|
53
93
|
#
|
54
94
|
def to_struct
|
95
|
+
# For Ruby 3.2, it raises if you attempt to create a Struct with no keys
|
96
|
+
return EMPTY_STRUCT if RUBY_VERSION.start_with?("3.2") && empty?
|
97
|
+
|
55
98
|
recurse = lambda do |value|
|
56
99
|
case value
|
57
100
|
when Hash
|
@@ -69,7 +112,12 @@ class Hash
|
|
69
112
|
#
|
70
113
|
# Converts hash to an OpenStruct recursively
|
71
114
|
#
|
72
|
-
# @return [OpenStruct]
|
115
|
+
# @return [OpenStruct] An OpenStruct with methods matching hash keys
|
116
|
+
#
|
117
|
+
# @example
|
118
|
+
# hash = { config: { api_key: "secret" } }
|
119
|
+
# config = hash.to_ostruct
|
120
|
+
# config.config.api_key # => "secret"
|
73
121
|
#
|
74
122
|
def to_ostruct
|
75
123
|
recurse = lambda do |value|
|
@@ -91,7 +139,7 @@ class Hash
|
|
91
139
|
#
|
92
140
|
# @return [self] Returns the frozen hash
|
93
141
|
#
|
94
|
-
# @example
|
142
|
+
# @example
|
95
143
|
# { user: { name: "Alice", roles: ["admin"] } }.deep_freeze
|
96
144
|
# # => Hash and all nested structures are now frozen
|
97
145
|
#
|
@@ -1,5 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
#
|
4
|
+
# Extensions to Ruby's core Module class
|
5
|
+
#
|
6
|
+
# These additions provide a convenient way to create boolean-style accessor
|
7
|
+
# methods for any class.
|
8
|
+
#
|
9
|
+
# @example Creating predicate methods
|
10
|
+
# class User
|
11
|
+
# attr_accessor :admin
|
12
|
+
# attr_predicate :admin
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# user = User.new
|
16
|
+
# user.admin = true
|
17
|
+
# user.admin? # => true
|
18
|
+
#
|
3
19
|
class Module
|
4
20
|
#
|
5
21
|
# Creates predicate (boolean) methods that return true/false
|
@@ -8,7 +24,7 @@ class Module
|
|
8
24
|
#
|
9
25
|
# Note: If ActiveSupport is loaded, this will check if the value is present? instead of truthy
|
10
26
|
#
|
11
|
-
# @param
|
27
|
+
# @param attributes [Array<Symbol, String>] Attribute names
|
12
28
|
#
|
13
29
|
# @return [nil]
|
14
30
|
#
|
@@ -1,11 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
#
|
4
|
+
# Extensions to Ruby's OpenStruct class
|
5
|
+
#
|
6
|
+
# These additions make OpenStructs way more flexible with enumeration
|
7
|
+
# methods and ActiveSupport integration.
|
8
|
+
#
|
9
|
+
# @example Using enumeration methods
|
10
|
+
# person = OpenStruct.new(name: "Alice", age: 30)
|
11
|
+
# person.map { |k, v| "#{k} is #{v}" } # => ["name is Alice", "age is 30"]
|
12
|
+
#
|
3
13
|
class OpenStruct
|
14
|
+
# ActiveSupport integrations
|
4
15
|
if defined?(ActiveSupport)
|
5
16
|
#
|
6
17
|
# Checks if the OpenStruct has no attributes
|
7
18
|
#
|
8
|
-
# @return [Boolean]
|
19
|
+
# @return [Boolean] true if the OpenStruct has no attributes
|
9
20
|
#
|
10
21
|
def blank?
|
11
22
|
@table.blank?
|
@@ -14,7 +25,7 @@ class OpenStruct
|
|
14
25
|
#
|
15
26
|
# Checks if the OpenStruct has any attributes
|
16
27
|
#
|
17
|
-
# @return [Boolean]
|
28
|
+
# @return [Boolean] true if the OpenStruct has attributes
|
18
29
|
#
|
19
30
|
def present?
|
20
31
|
@table.present?
|
@@ -26,7 +37,15 @@ class OpenStruct
|
|
26
37
|
#
|
27
38
|
# Maps over OpenStruct entries and returns an array
|
28
39
|
#
|
29
|
-
# @
|
40
|
+
# @yield [key, value] Block that transforms each key-value pair
|
41
|
+
# @yieldparam key [Symbol] The attribute name
|
42
|
+
# @yieldparam value [Object] The attribute value
|
43
|
+
#
|
44
|
+
# @return [Array, Enumerator] Results of mapping or an Enumerator if no block given
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# struct = OpenStruct.new(a: 1, b: 2)
|
48
|
+
# struct.map { |key, value| [key, value * 2] } # => [[:a, 2], [:b, 4]]
|
30
49
|
#
|
31
50
|
def map(&)
|
32
51
|
@table.map(&)
|
@@ -35,7 +54,15 @@ class OpenStruct
|
|
35
54
|
#
|
36
55
|
# Maps over OpenStruct entries and returns an array without nil values
|
37
56
|
#
|
38
|
-
# @
|
57
|
+
# @yield [key, value] Block that transforms each key-value pair
|
58
|
+
# @yieldparam key [Symbol] The attribute name
|
59
|
+
# @yieldparam value [Object] The attribute value
|
60
|
+
#
|
61
|
+
# @return [Array, Enumerator] Non-nil results of mapping or an Enumerator if no block given
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# struct = OpenStruct.new(a: 1, b: nil, c: 2)
|
65
|
+
# struct.filter_map { |key, value| value * 2 if value } # => [2, 4]
|
39
66
|
#
|
40
67
|
def filter_map(&block)
|
41
68
|
return enum_for(:filter_map) unless block
|
@@ -48,16 +75,18 @@ class OpenStruct
|
|
48
75
|
#
|
49
76
|
# @param join_with [String] The delimiter to join elements with (defaults to empty string)
|
50
77
|
#
|
51
|
-
# @yield [
|
78
|
+
# @yield [key, value] Block that filters and transforms OpenStruct entries
|
79
|
+
# @yieldparam key [Symbol] The attribute name
|
80
|
+
# @yieldparam value [Object] The attribute value
|
52
81
|
#
|
53
|
-
# @return [String] Joined string of filtered and transformed
|
82
|
+
# @return [String] Joined string of filtered and transformed entries
|
54
83
|
#
|
55
84
|
# @example
|
56
85
|
# object = OpenStruct.new(a: 1, b: nil, c: 3)
|
57
86
|
# object.join_map(" ") { |k, v| "#{k}-#{v}" if v }
|
58
87
|
# # => "a-1 c-3"
|
59
88
|
#
|
60
|
-
# @example
|
89
|
+
# @example Default behavior without block
|
61
90
|
# object = OpenStruct.new(a: 1, b: nil, c: 3)
|
62
91
|
# object.join_map(", ")
|
63
92
|
# # => "a, 1, b, c, 3"
|
@@ -69,7 +98,9 @@ class OpenStruct
|
|
69
98
|
end
|
70
99
|
|
71
100
|
#
|
72
|
-
#
|
101
|
+
# Returns self (identity method for consistent interfaces)
|
102
|
+
#
|
103
|
+
# @return [self] Returns the OpenStruct
|
73
104
|
#
|
74
105
|
def to_ostruct
|
75
106
|
self
|
@@ -1,10 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
#
|
4
|
+
# Extensions to Ruby's core String class
|
5
|
+
#
|
6
|
+
# These additions make working with JSON strings easy by providing methods for conversion
|
7
|
+
# to various Ruby data structures. Plus some nice formatting helpers that saves tons of typing
|
8
|
+
#
|
9
|
+
# @example Converting JSON to different structures
|
10
|
+
# json = '{"user": {"name": "Alice", "admin": true}}'
|
11
|
+
# json.to_h # => {user: {name: "Alice", admin: true}}
|
12
|
+
# json.to_istruct.user.name # => "Alice"
|
13
|
+
# json.to_ostruct.user.name # => "Alice"
|
14
|
+
#
|
3
15
|
class String
|
4
16
|
#
|
5
17
|
# Converts JSON string to Hash, returning nil if it failed
|
6
18
|
#
|
7
|
-
# @return [Hash, nil] Parsed JSON as hash
|
19
|
+
# @return [Hash, nil] Parsed JSON as hash or nil if invalid JSON
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# '{"name": "Alice"}'.to_h # => {name: "Alice"}
|
23
|
+
# "invalid json".to_h # => nil
|
8
24
|
#
|
9
25
|
def to_h
|
10
26
|
JSON.parse(self, symbolize_names: true)
|
@@ -18,7 +34,14 @@ class String
|
|
18
34
|
# Deep parsing of nested JSON strings
|
19
35
|
# Recursively attempts to parse string values as JSON
|
20
36
|
#
|
21
|
-
# @return [Hash] Deeply parsed hash
|
37
|
+
# @return [Hash] Deeply parsed hash with all nested JSON strings converted
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# nested_json = '{
|
41
|
+
# "user": "{\"name\":\"Alice\",\"roles\":[\"admin\"]}"
|
42
|
+
# }'
|
43
|
+
# nested_json.to_deep_h
|
44
|
+
# # => {user: {name: "Alice", roles: ["admin"]}}
|
22
45
|
#
|
23
46
|
def to_deep_h
|
24
47
|
recursive_convert = lambda do |object|
|
@@ -48,7 +71,11 @@ class String
|
|
48
71
|
# Attempts to parse JSON and convert to Data struct.
|
49
72
|
# Returns nil if string does not contain valid JSON
|
50
73
|
#
|
51
|
-
# @return [
|
74
|
+
# @return [Data, nil] Immutable Data structure or nil if invalid JSON
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# '{"name": "Alice"}'.to_istruct # => #<data name="Alice">
|
78
|
+
# "not json".to_istruct # => nil
|
52
79
|
#
|
53
80
|
def to_istruct
|
54
81
|
to_h&.to_istruct
|
@@ -58,7 +85,12 @@ class String
|
|
58
85
|
# Attempts to parse JSON and convert to OpenStruct.
|
59
86
|
# Returns nil if string does not contain valid JSON
|
60
87
|
#
|
61
|
-
# @return [
|
88
|
+
# @return [OpenStruct, nil] OpenStruct or nil if invalid JSON
|
89
|
+
#
|
90
|
+
# @example
|
91
|
+
# '{"name": "Alice"}'.to_ostruct # => #<OpenStruct name="Alice">
|
92
|
+
# "not json".to_ostruct # => nil
|
93
|
+
#
|
62
94
|
def to_ostruct
|
63
95
|
to_h&.to_ostruct
|
64
96
|
end
|
@@ -67,16 +99,24 @@ class String
|
|
67
99
|
# Attempts to parse JSON and convert to Struct.
|
68
100
|
# Returns nil if string does not contain valid JSON
|
69
101
|
#
|
70
|
-
# @return [
|
102
|
+
# @return [Struct, nil] Struct or nil if invalid JSON
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# '{"name": "Alice"}'.to_struct # => #<struct name="Alice">
|
106
|
+
# "not json".to_struct # => nil
|
71
107
|
#
|
72
108
|
def to_struct
|
73
109
|
to_h&.to_struct
|
74
110
|
end
|
75
111
|
|
76
112
|
#
|
77
|
-
# Returns self wrapped in double quotes
|
113
|
+
# Returns self wrapped in double quotes
|
114
|
+
#
|
115
|
+
# @return [String] The string with surrounding double quotes
|
78
116
|
#
|
79
|
-
# @
|
117
|
+
# @example
|
118
|
+
# "Hello World".with_quotes # => "\"Hello World\""
|
119
|
+
# "Quote \"me\"".with_quotes # => "\"Quote \\\"me\\\"\""
|
80
120
|
#
|
81
121
|
def with_quotes
|
82
122
|
%("#{self}")
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Extensions to Ruby's core Symbol class
|
5
|
+
#
|
6
|
+
# These additions provide handy formatting helpers for symbols.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# :hello_world.with_quotes # => :"\"hello_world\""
|
10
|
+
#
|
11
|
+
class Symbol
|
12
|
+
#
|
13
|
+
# Returns self wrapped in double quotes
|
14
|
+
#
|
15
|
+
# @return [Symbol] The symbol with surrounding double quotes
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# :hello_world.with_quotes # => :"\"hello_world\""
|
19
|
+
# :hello_world.in_quotes # => :"\"hello_world\""
|
20
|
+
#
|
21
|
+
def with_quotes
|
22
|
+
:"\"#{self}\""
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :in_quotes, :with_quotes
|
26
|
+
end
|
data/lib/everythingrb/version.rb
CHANGED
data/lib/everythingrb.rb
CHANGED
@@ -10,6 +10,28 @@ require_relative "everythingrb/core/hash"
|
|
10
10
|
require_relative "everythingrb/core/module"
|
11
11
|
require_relative "everythingrb/core/ostruct"
|
12
12
|
require_relative "everythingrb/core/string"
|
13
|
+
require_relative "everythingrb/core/symbol"
|
13
14
|
|
15
|
+
#
|
16
|
+
# EverythingRB - Super handy extensions to Ruby's core classes
|
17
|
+
#
|
18
|
+
# This gem enhances Ruby's built-in classes with useful methods that make
|
19
|
+
# your code more expressive and fun to write. Just require "everythingrb"
|
20
|
+
# and all the extensions are automatically available!
|
21
|
+
#
|
22
|
+
# @author Bryan "itsthedevman"
|
23
|
+
# @since 0.1.0
|
24
|
+
#
|
25
|
+
# @example Basic usage
|
26
|
+
# # In your Gemfile
|
27
|
+
# gem "everythingrb"
|
28
|
+
#
|
29
|
+
# # In your code
|
30
|
+
# require "everythingrb"
|
31
|
+
#
|
32
|
+
# # Now you have access to all the extensions!
|
33
|
+
# users = [{name: "Alice"}, {name: "Bob"}]
|
34
|
+
# users.key_map(:name).join(", ") # => "Alice, Bob"
|
35
|
+
#
|
14
36
|
module Everythingrb
|
15
37
|
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.
|
4
|
+
version: 0.2.4
|
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-
|
11
|
+
date: 2025-03-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ostruct
|
@@ -63,8 +63,8 @@ files:
|
|
63
63
|
- lib/everythingrb/core/module.rb
|
64
64
|
- lib/everythingrb/core/ostruct.rb
|
65
65
|
- lib/everythingrb/core/string.rb
|
66
|
+
- lib/everythingrb/core/symbol.rb
|
66
67
|
- lib/everythingrb/version.rb
|
67
|
-
- sig/everythingrb.rbs
|
68
68
|
homepage: https://github.com/itsthedevman/everythingrb
|
69
69
|
licenses:
|
70
70
|
- MIT
|
data/sig/everythingrb.rbs
DELETED