everythingrb 0.9.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -7
- data/README.md +78 -118
- data/Rakefile +24 -0
- data/benchmarks/array_benchmark.rb +108 -0
- data/benchmarks/benchmark_helper.rb +42 -0
- data/benchmarks/enumerable_benchmark.rb +80 -0
- data/benchmarks/hash_benchmark.rb +160 -0
- data/benchmarks/kernel_benchmark.rb +51 -0
- data/benchmarks/module_benchmark.rb +106 -0
- data/benchmarks/ostruct_benchmark.rb +84 -0
- data/benchmarks/quotable_benchmark.rb +111 -0
- data/benchmarks/run_all.rb +74 -0
- data/benchmarks/string_benchmark.rb +85 -0
- data/lib/everythingrb/array.rb +0 -45
- data/lib/everythingrb/data.rb +0 -19
- data/lib/everythingrb/date.rb +2 -0
- data/lib/everythingrb/hash.rb +7 -108
- data/lib/everythingrb/module.rb +79 -14
- data/lib/everythingrb/ostruct.rb +0 -20
- data/lib/everythingrb/string.rb +26 -52
- data/lib/everythingrb/struct.rb +0 -21
- data/lib/everythingrb/version.rb +1 -1
- metadata +12 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c1aaf48c681b67675cfd4a3e053086163c755a47fc3941507ac0b08bb33ddf09
|
|
4
|
+
data.tar.gz: 2e9389e279e225463b03d0ccaac1db55444aa3d5c99f4ec06c5d86e89a235efa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16cdf21b81457e94d393b246dd762c33400de2e39b01435be9334946cdb2b0639583db9064fdac7c2053e6bc533f3b699fcdd5fe7c119e3052f1b51e8cb911ae
|
|
7
|
+
data.tar.gz: fc9e95bbd7b0dd0fdbc5a8474f00ffccc0968e703b45c63a7aef1ddc8b6f0418123dfb7474813ce03e889f35244c6e7bf42cab368d31e5db4174551a93ee9f20
|
data/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
15
15
|
### Removed
|
|
16
16
|
-->
|
|
17
17
|
|
|
18
|
+
## [1.0.0] - 12026-03-21
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **Added `from:` option to `attr_predicate`** - Map a predicate to a differently-named source. Use `@` prefix for instance variables (e.g., `from: :@started_at`), omit for methods (e.g., `from: :error_messages`). This enables patterns like `attr_predicate :started, from: :@started_at` and `attr_predicate :errored, from: :error_messages`.
|
|
23
|
+
- **Added `private:` option to `attr_predicate`** - Define predicates as private methods with `attr_predicate :verified, private: true`.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **BREAKING: Renamed `Hash#value_where` to `Hash#find_value` and `Hash#values_where` to `Hash#select_values`** - The new names better align with Ruby conventions and clearly express the methods' purpose of finding values that match a condition.
|
|
28
|
+
- **BREAKING: Renamed `String#to_h` to `String#parse_json`** - This change improves compatibility with Ruby's type coercion expectations and avoids conflicts with methods like `Array(obj)` that rely on standard `to_a`/`to_h` behavior. The method parses JSON strings with symbolized keys by default and returns `nil` for invalid JSON.
|
|
29
|
+
- **Fixed Ruby 3.4+ compatibility** - Added `require "date"` to ensure Date and DateTime classes are properly loaded before extending them.
|
|
30
|
+
- **BREAKING: `attr_predicate` no longer falls back to method calls** - For performance reasons (~5x faster), `attr_predicate` now assumes direct instance variable access by default. The previous behavior checked `instance_variable_defined?` and `respond_to?` on every call, which was slow. Struct, OpenStruct, and Data classes are automatically detected and use method access. For other cases where you need method delegation, use the new `from:` option explicitly (e.g., `attr_predicate :errored, from: :error_messages`).
|
|
31
|
+
- **Improved `attr_predicate` empty value handling** - Without ActiveSupport, values responding to `empty?` (arrays, hashes, strings) now return `false` when empty. Previously, an empty array would return `true` since `!![]` is truthy. With ActiveSupport, behavior is unchanged (uses `present?`).
|
|
32
|
+
|
|
33
|
+
### Removed
|
|
34
|
+
|
|
35
|
+
- **BREAKING: Removed `Hash.new_nested_hash`** - Use Ruby's built-in `Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }` instead.
|
|
36
|
+
- **BREAKING: Removed `#to_deep_h` from all classes** - This method has been removed from String, Hash, Array, Struct, OpenStruct, and Data classes. If you need this functionality, implement it locally in your project.
|
|
37
|
+
- **Removed `String#to_a`** - This method was removed along with the JSON parsing refactor.
|
|
38
|
+
|
|
18
39
|
## [0.9.0] - 12025-08-01
|
|
19
40
|
|
|
20
41
|
### Added
|
|
@@ -206,10 +227,6 @@ This change aligns our method signatures with Ruby's conventions and matches our
|
|
|
206
227
|
- `#rename_keys!` - Same as `#rename_keys` but modifies the hash in place
|
|
207
228
|
- `#rename_key_unordered` - Renames a key without preserving element order (faster operation)
|
|
208
229
|
- `#rename_key_unordered!` - Same as `#rename_key_unordered` but modifies the hash in place
|
|
209
|
-
- Added `to_deep_h` to core Ruby classes for consistent deep hash conversion:
|
|
210
|
-
- `Struct#to_deep_h` - Recursively converts Struct objects and all nested values to hashes
|
|
211
|
-
- `OpenStruct#to_deep_h` - Recursively converts OpenStruct objects and all nested values to hashes
|
|
212
|
-
- `Data#to_deep_h` - Recursively converts Data objects and all nested values to hashes
|
|
213
230
|
- Added `depth` parameter to `Hash.new_nested_hash` to control nesting behaviors
|
|
214
231
|
|
|
215
232
|
### Changed
|
|
@@ -235,7 +252,6 @@ This change aligns our method signatures with Ruby's conventions and matches our
|
|
|
235
252
|
|
|
236
253
|
- Added `Array#to_or_sentence`, creates a sentence with "or" connector between items
|
|
237
254
|
- Added `#with_key` method to `Hash#transform_values` and `Hash#transform_values!`, grants access to both keys and values during transformations
|
|
238
|
-
- Added `Array#to_deep_h` and `Hash#to_deep_h`, recursively converts underlying values to hashes
|
|
239
255
|
- Added `Enumerable#group_by_key`, group an array of hashes by their keys
|
|
240
256
|
- Added `Hash#new_nested_hash`, creates a new Hash that automatically initializes the value to a hash
|
|
241
257
|
- Added `Hash#value_where` and `Hash#values_where`, easily find values in a hash based on key-value conditions
|
|
@@ -335,14 +351,14 @@ This change aligns our method signatures with Ruby's conventions and matches our
|
|
|
335
351
|
- `join_map` method consistent with Array/Hash implementations
|
|
336
352
|
- Enhanced `String` class with:
|
|
337
353
|
- `to_h` and `to_a` methods for JSON parsing with `nil` fallback on error
|
|
338
|
-
- `to_deep_h` for recursive JSON string parsing
|
|
339
354
|
- `to_istruct`, `to_ostruct`, and `to_struct` conversion methods
|
|
340
355
|
|
|
341
356
|
### Changed
|
|
342
357
|
|
|
343
358
|
- Added alias `each` to `each_pair` in OpenStruct for better enumerable compatibility
|
|
344
359
|
|
|
345
|
-
[unreleased]: https://github.com/itsthedevman/everythingrb/compare/
|
|
360
|
+
[unreleased]: https://github.com/itsthedevman/everythingrb/compare/v1.0.0...HEAD
|
|
361
|
+
[1.0.0]: https://github.com/itsthedevman/everythingrb/compare/v0.9.0...v1.0.0
|
|
346
362
|
[0.9.0]: https://github.com/itsthedevman/everythingrb/compare/v0.8.3...v0.9.0
|
|
347
363
|
[0.8.3]: https://github.com/itsthedevman/everythingrb/compare/v0.8.2...v0.8.3
|
|
348
364
|
[0.8.3]: https://github.com/itsthedevman/everythingrb/compare/v0.8.2...v0.8.3
|
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
|
+
Practical extensions to Ruby core classes that let your code say what it means.
|
|
8
8
|
|
|
9
9
|
## Express Your Intent, Not Your Logic
|
|
10
10
|
|
|
@@ -33,8 +33,6 @@ users.join_map(", ") { |u| u[:name] if u[:role] == "admin" }
|
|
|
33
33
|
|
|
34
34
|
_Methods used: [`join_map`](https://itsthedevman.com/docs/everythingrb/Array.html#join_map-instance_method)_
|
|
35
35
|
|
|
36
|
-
Because life's too short to write all that boilerplate!
|
|
37
|
-
|
|
38
36
|
## Installation
|
|
39
37
|
|
|
40
38
|
```ruby
|
|
@@ -68,7 +66,7 @@ config.server.port # => 443
|
|
|
68
66
|
|
|
69
67
|
#### Cherry-Pick Extensions
|
|
70
68
|
|
|
71
|
-
If you only need specific extensions
|
|
69
|
+
If you only need specific extensions:
|
|
72
70
|
|
|
73
71
|
```ruby
|
|
74
72
|
require "everythingrb/prelude" # Required base module
|
|
@@ -87,7 +85,7 @@ Available modules:
|
|
|
87
85
|
|
|
88
86
|
- `array`: Array extensions (join_map, key_map, etc.)
|
|
89
87
|
- `boolean`: Boolean extensions (in_quotes, with_quotes)
|
|
90
|
-
- `data`: Data extensions (
|
|
88
|
+
- `data`: Data extensions (in_quotes)
|
|
91
89
|
- `date`: Date and DateTime extensions (in_quotes)
|
|
92
90
|
- `enumerable`: Enumerable extensions (join_map, group_by_key)
|
|
93
91
|
- `hash`: Hash extensions (to_ostruct, transform_values(with_key: true), etc.)
|
|
@@ -98,14 +96,14 @@ Available modules:
|
|
|
98
96
|
- `ostruct`: OpenStruct extensions (map, join_map, etc.)
|
|
99
97
|
- `range`: Range extensions (in_quotes)
|
|
100
98
|
- `regexp`: Regexp extensions (in_quotes)
|
|
101
|
-
- `string`: String extensions (
|
|
102
|
-
- `struct`: Struct extensions (
|
|
99
|
+
- `string`: String extensions (parse_json, to_ostruct, to_camelcase, etc.)
|
|
100
|
+
- `struct`: Struct extensions (in_quotes)
|
|
103
101
|
- `symbol`: Symbol extensions (with_quotes)
|
|
104
102
|
- `time`: Time extensions (in_quotes)
|
|
105
103
|
|
|
106
104
|
### Rails Applications
|
|
107
105
|
|
|
108
|
-
EverythingRB works out of the box with Rails
|
|
106
|
+
EverythingRB works out of the box with Rails. Just add it to your Gemfile and you're all set.
|
|
109
107
|
|
|
110
108
|
If you only want specific extensions, configure them in an initializer:
|
|
111
109
|
|
|
@@ -122,8 +120,6 @@ By default (when `config.everythingrb.extensions` is not set), all extensions ar
|
|
|
122
120
|
|
|
123
121
|
### Data Structure Conversions
|
|
124
122
|
|
|
125
|
-
Stop writing complicated parsers and nested transformations:
|
|
126
|
-
|
|
127
123
|
```ruby
|
|
128
124
|
# BEFORE
|
|
129
125
|
json_string = '{"user":{"name":"Alice","roles":["admin"]}}'
|
|
@@ -137,8 +133,6 @@ result = OpenStruct.new(
|
|
|
137
133
|
result.user.name # => "Alice"
|
|
138
134
|
```
|
|
139
135
|
|
|
140
|
-
With EverythingRB, it's one elegant step:
|
|
141
|
-
|
|
142
136
|
```ruby
|
|
143
137
|
# AFTER
|
|
144
138
|
'{"user":{"name":"Alice","roles":["admin"]}}'.to_ostruct.user.name # => "Alice"
|
|
@@ -146,7 +140,7 @@ With EverythingRB, it's one elegant step:
|
|
|
146
140
|
|
|
147
141
|
_Methods used: [`to_ostruct`](https://itsthedevman.com/docs/everythingrb/String.html#to_ostruct-instance_method)_
|
|
148
142
|
|
|
149
|
-
Convert between data structures
|
|
143
|
+
Convert between data structures:
|
|
150
144
|
|
|
151
145
|
```ruby
|
|
152
146
|
# BEFORE
|
|
@@ -164,31 +158,11 @@ config.server.host # => "example.com"
|
|
|
164
158
|
|
|
165
159
|
_Methods used: [`to_struct`](https://itsthedevman.com/docs/everythingrb/Hash.html#to_struct-instance_method)_
|
|
166
160
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
```ruby
|
|
170
|
-
# BEFORE
|
|
171
|
-
data = OpenStruct.new(user: Data.define(:name).new(name: "Bob"))
|
|
172
|
-
result = {
|
|
173
|
-
user: {
|
|
174
|
-
name: data.user.name
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
```ruby
|
|
180
|
-
# AFTER
|
|
181
|
-
data = OpenStruct.new(user: Data.define(:name).new(name: "Bob"))
|
|
182
|
-
data.to_deep_h # => {user: {name: "Bob"}}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
_Methods used: [`to_deep_h`](https://itsthedevman.com/docs/everythingrb/OpenStruct.html#to_deep_h-instance_method)_
|
|
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)
|
|
161
|
+
**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), [`parse_json`](https://itsthedevman.com/docs/everythingrb/String.html#parse_json-instance_method)
|
|
188
162
|
|
|
189
163
|
### Collection Processing
|
|
190
164
|
|
|
191
|
-
Extract and transform data
|
|
165
|
+
Extract and transform data:
|
|
192
166
|
|
|
193
167
|
```ruby
|
|
194
168
|
# BEFORE
|
|
@@ -223,7 +197,7 @@ users.dig_map(:user, :profile, :name) # => ["Alice", "Bob"]
|
|
|
223
197
|
|
|
224
198
|
_Methods used: [`dig_map`](https://itsthedevman.com/docs/everythingrb/Array.html#dig_map-instance_method)_
|
|
225
199
|
|
|
226
|
-
Combine filter, map, and join
|
|
200
|
+
Combine filter, map, and join in one step:
|
|
227
201
|
|
|
228
202
|
```ruby
|
|
229
203
|
# BEFORE
|
|
@@ -240,7 +214,7 @@ result = data.compact.filter_map { |n| "Item #{n}" if n.odd? }.join(" | ")
|
|
|
240
214
|
|
|
241
215
|
_Methods used: [`join_map`](https://itsthedevman.com/docs/everythingrb/Array.html#join_map-instance_method)_
|
|
242
216
|
|
|
243
|
-
|
|
217
|
+
Both Array and Hash support `with_index` when you need position-aware processing:
|
|
244
218
|
|
|
245
219
|
```ruby
|
|
246
220
|
# BEFORE
|
|
@@ -257,7 +231,7 @@ users.join_map(", ", with_index: true) { |(k, v), i| "#{i + 1}. #{v}" }
|
|
|
257
231
|
|
|
258
232
|
_Methods used: [`join_map`](https://itsthedevman.com/docs/everythingrb/Hash.html#join_map-instance_method)_
|
|
259
233
|
|
|
260
|
-
Group
|
|
234
|
+
Group by a nested key path directly:
|
|
261
235
|
|
|
262
236
|
```ruby
|
|
263
237
|
# BEFORE
|
|
@@ -278,7 +252,7 @@ users.group_by_key(:department, :name)
|
|
|
278
252
|
|
|
279
253
|
_Methods used: [`group_by_key`](https://itsthedevman.com/docs/everythingrb/Enumerable.html#group_by_key-instance_method)_
|
|
280
254
|
|
|
281
|
-
|
|
255
|
+
Build 'or'-joined lists:
|
|
282
256
|
|
|
283
257
|
```ruby
|
|
284
258
|
# BEFORE
|
|
@@ -305,28 +279,11 @@ _Methods used: [`to_or_sentence`](https://itsthedevman.com/docs/everythingrb/Arr
|
|
|
305
279
|
|
|
306
280
|
**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)
|
|
307
281
|
|
|
308
|
-
|
|
282
|
+
Here's just that section with the fixes:
|
|
309
283
|
|
|
310
|
-
|
|
284
|
+
---
|
|
311
285
|
|
|
312
|
-
|
|
313
|
-
# BEFORE
|
|
314
|
-
stats = {}
|
|
315
|
-
stats[:server] ||= {}
|
|
316
|
-
stats[:server][:region] ||= {}
|
|
317
|
-
stats[:server][:region][:us_east] ||= {}
|
|
318
|
-
stats[:server][:region][:us_east][:errors] ||= []
|
|
319
|
-
stats[:server][:region][:us_east][:errors] << "Connection timeout"
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
```ruby
|
|
323
|
-
# AFTER
|
|
324
|
-
stats = Hash.new_nested_hash(depth: 3)
|
|
325
|
-
(stats[:server][:region][:us_east][:errors] ||= []) << "Connection timeout"
|
|
326
|
-
# No need to initialize each level first!
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
_Methods used: [`new_nested_hash`](https://itsthedevman.com/docs/everythingrb/Hash.html#new_nested_hash-class_method)_
|
|
286
|
+
### Hash Convenience
|
|
330
287
|
|
|
331
288
|
Transform values with access to their keys:
|
|
332
289
|
|
|
@@ -363,13 +320,13 @@ admins = users.select { |_k, v| v[:role] == "admin" }.values
|
|
|
363
320
|
|
|
364
321
|
```ruby
|
|
365
322
|
# AFTER
|
|
366
|
-
users.
|
|
323
|
+
users.select_values { |_k, v| v[:role] == "admin" }
|
|
367
324
|
# => [{name: "Alice", role: "admin"}, {name: "Charlie", role: "admin"}]
|
|
368
325
|
```
|
|
369
326
|
|
|
370
|
-
_Methods used: [`
|
|
327
|
+
_Methods used: [`select_values`](https://itsthedevman.com/docs/everythingrb/Hash.html#select_values-instance_method)_
|
|
371
328
|
|
|
372
|
-
Just want the first match?
|
|
329
|
+
Just want the first match?
|
|
373
330
|
|
|
374
331
|
```ruby
|
|
375
332
|
# BEFORE
|
|
@@ -379,11 +336,11 @@ users.find { |_k, v| v[:role] == "admin" }&.last
|
|
|
379
336
|
|
|
380
337
|
```ruby
|
|
381
338
|
# AFTER
|
|
382
|
-
users.
|
|
339
|
+
users.find_value { |_k, v| v[:role] == "admin" }
|
|
383
340
|
# => {name: "Alice", role: "admin"}
|
|
384
341
|
```
|
|
385
342
|
|
|
386
|
-
_Methods used: [`
|
|
343
|
+
_Methods used: [`find_value`](https://itsthedevman.com/docs/everythingrb/Hash.html#find_value-instance_method)_
|
|
387
344
|
|
|
388
345
|
Rename keys while preserving order:
|
|
389
346
|
|
|
@@ -393,14 +350,10 @@ config = {api_key: "secret", timeout: 30}
|
|
|
393
350
|
new_config = config.each_with_object({}) do |(key, value), hash|
|
|
394
351
|
new_key =
|
|
395
352
|
case key
|
|
396
|
-
when :api_key
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
:request_timeout
|
|
400
|
-
else
|
|
401
|
-
key
|
|
353
|
+
when :api_key then :key
|
|
354
|
+
when :timeout then :request_timeout
|
|
355
|
+
else key
|
|
402
356
|
end
|
|
403
|
-
|
|
404
357
|
hash[new_key] = value
|
|
405
358
|
end
|
|
406
359
|
# => {key: "secret", request_timeout: 30}
|
|
@@ -415,65 +368,45 @@ config.rename_keys(api_key: :key, timeout: :request_timeout)
|
|
|
415
368
|
|
|
416
369
|
_Methods used: [`rename_keys`](https://itsthedevman.com/docs/everythingrb/Hash.html#rename_keys-instance_method)_
|
|
417
370
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
```ruby
|
|
421
|
-
# BEFORE
|
|
422
|
-
user_params = {name: "Alice", role: "user"}
|
|
423
|
-
filtered = {verified: true, admin: true}.select { |k, v| v == true && k == :verified }
|
|
424
|
-
user_params.merge(filtered)
|
|
425
|
-
# => {name: "Alice", role: "user", verified: true}
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
```ruby
|
|
429
|
-
# AFTER
|
|
430
|
-
user_params = {name: "Alice", role: "user"}
|
|
431
|
-
user_params.merge_if(verified: true, admin: true) { |k, v| v == true && k == :verified }
|
|
432
|
-
# => {name: "Alice", role: "user", verified: true}
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
_Methods used: [`merge_if`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_if-instance_method)_
|
|
436
|
-
|
|
437
|
-
The nil-filtering pattern we've all written dozens of times:
|
|
371
|
+
Merge while dropping nils:
|
|
438
372
|
|
|
439
373
|
```ruby
|
|
440
374
|
# BEFORE
|
|
441
375
|
params = {sort: "created_at"}
|
|
442
|
-
|
|
443
|
-
params.merge(
|
|
376
|
+
search_params = {filter: "active", search: nil}.compact
|
|
377
|
+
params.merge(search_params)
|
|
444
378
|
# => {sort: "created_at", filter: "active"}
|
|
445
379
|
```
|
|
446
380
|
|
|
447
381
|
```ruby
|
|
448
382
|
# AFTER
|
|
449
|
-
|
|
450
|
-
params.compact_merge(
|
|
383
|
+
search_params = {filter: "active", search: nil}
|
|
384
|
+
params.compact_merge(search_params)
|
|
451
385
|
# => {sort: "created_at", filter: "active"}
|
|
452
386
|
```
|
|
453
387
|
|
|
454
388
|
_Methods used: [`compact_merge`](https://itsthedevman.com/docs/everythingrb/Hash.html#compact_merge-instance_method)_
|
|
455
389
|
|
|
456
|
-
|
|
390
|
+
Merge while dropping nils and blank values (requires ActiveSupport):
|
|
457
391
|
|
|
458
392
|
```ruby
|
|
459
393
|
# BEFORE
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
# => {api_key: "secret", timeout: 30, debug: true}
|
|
394
|
+
params = {sort: "created_at"}
|
|
395
|
+
search_params = {filter: "active", search: nil, query: ""}.reject { |_k, v| v.blank? }
|
|
396
|
+
params.merge(search_params)
|
|
397
|
+
# => {sort: "created_at", filter: "active"}
|
|
465
398
|
```
|
|
466
399
|
|
|
467
400
|
```ruby
|
|
468
401
|
# AFTER
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
# => {
|
|
402
|
+
search_params = {filter: "active", search: nil, query: ""}
|
|
403
|
+
params.compact_blank_merge(search_params)
|
|
404
|
+
# => {sort: "created_at", filter: "active"}
|
|
472
405
|
```
|
|
473
406
|
|
|
474
407
|
_Methods used: [`compact_blank_merge`](https://itsthedevman.com/docs/everythingrb/Hash.html#compact_blank_merge-instance_method)_
|
|
475
408
|
|
|
476
|
-
**Extensions:** [`
|
|
409
|
+
**Extensions:** [`transform_values(with_key: true)`](https://itsthedevman.com/docs/everythingrb/Hash.html#transform_values-instance_method), [`find_value`](https://itsthedevman.com/docs/everythingrb/Hash.html#find_value-instance_method), [`select_values`](https://itsthedevman.com/docs/everythingrb/Hash.html#select_values-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)
|
|
477
410
|
|
|
478
411
|
### Array Cleaning
|
|
479
412
|
|
|
@@ -493,7 +426,7 @@ data.drop_while(&:nil?).reverse.drop_while(&:nil?).reverse
|
|
|
493
426
|
|
|
494
427
|
_Methods used: [`trim_nils`](https://itsthedevman.com/docs/everythingrb/Array.html#trim_nils-instance_method)_
|
|
495
428
|
|
|
496
|
-
With ActiveSupport, remove blank values too:
|
|
429
|
+
With ActiveSupport, remove blank values from the edges too:
|
|
497
430
|
|
|
498
431
|
```ruby
|
|
499
432
|
# BEFORE
|
|
@@ -513,7 +446,7 @@ _Methods used: [`trim_blanks`](https://itsthedevman.com/docs/everythingrb/Array.
|
|
|
513
446
|
|
|
514
447
|
### String Formatting
|
|
515
448
|
|
|
516
|
-
Format
|
|
449
|
+
Format values consistently without a helper method:
|
|
517
450
|
|
|
518
451
|
```ruby
|
|
519
452
|
# BEFORE
|
|
@@ -552,7 +485,7 @@ message = "You selected #{selection.in_quotes}"
|
|
|
552
485
|
|
|
553
486
|
_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)_
|
|
554
487
|
|
|
555
|
-
Convert strings to camelCase
|
|
488
|
+
Convert strings to camelCase:
|
|
556
489
|
|
|
557
490
|
```ruby
|
|
558
491
|
# BEFORE
|
|
@@ -573,7 +506,7 @@ camel_case
|
|
|
573
506
|
"user_profile_settings".to_camelcase # => "UserProfileSettings"
|
|
574
507
|
"user_profile_settings".to_camelcase(:lower) # => "userProfileSettings"
|
|
575
508
|
|
|
576
|
-
# Handles
|
|
509
|
+
# Handles mixed input consistently
|
|
577
510
|
"please-WAIT while_loading...".to_camelcase # => "PleaseWaitWhileLoading"
|
|
578
511
|
```
|
|
579
512
|
|
|
@@ -583,7 +516,7 @@ _Methods used: [`to_camelcase`](https://itsthedevman.com/docs/everythingrb/Strin
|
|
|
583
516
|
|
|
584
517
|
### Boolean Methods
|
|
585
518
|
|
|
586
|
-
|
|
519
|
+
Define predicate methods from any attribute:
|
|
587
520
|
|
|
588
521
|
```ruby
|
|
589
522
|
# BEFORE
|
|
@@ -614,6 +547,39 @@ user.admin? # => true
|
|
|
614
547
|
|
|
615
548
|
_Methods used: [`attr_predicate`](https://itsthedevman.com/docs/everythingrb/Module.html#attr_predicate-instance_method)_
|
|
616
549
|
|
|
550
|
+
Map predicates to differently-named sources with `from:`:
|
|
551
|
+
|
|
552
|
+
```ruby
|
|
553
|
+
# BEFORE
|
|
554
|
+
class Task
|
|
555
|
+
attr_accessor :started_at, :stopped_at
|
|
556
|
+
|
|
557
|
+
def started?
|
|
558
|
+
!@started_at.nil?
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def finished?
|
|
562
|
+
!@stopped_at.nil?
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
```ruby
|
|
568
|
+
# AFTER
|
|
569
|
+
class Task
|
|
570
|
+
attr_accessor :started_at, :stopped_at
|
|
571
|
+
attr_predicate :started, from: :@started_at
|
|
572
|
+
attr_predicate :finished, from: :@stopped_at
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
task = Task.new
|
|
576
|
+
task.started? # => false
|
|
577
|
+
task.started_at = Time.now
|
|
578
|
+
task.started? # => true
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
_Methods used: [`attr_predicate`](https://itsthedevman.com/docs/everythingrb/Module.html#attr_predicate-instance_method)_
|
|
582
|
+
|
|
617
583
|
Works with Data objects too:
|
|
618
584
|
|
|
619
585
|
```ruby
|
|
@@ -638,7 +604,7 @@ _Methods used: [`attr_predicate`](https://itsthedevman.com/docs/everythingrb/Mod
|
|
|
638
604
|
|
|
639
605
|
### Value Transformation
|
|
640
606
|
|
|
641
|
-
|
|
607
|
+
An alias for `then`/`yield_self` that reads more naturally in transformation chains:
|
|
642
608
|
|
|
643
609
|
```ruby
|
|
644
610
|
# BEFORE
|
|
@@ -668,10 +634,4 @@ Bug reports and pull requests are welcome! This project is intended to be a safe
|
|
|
668
634
|
|
|
669
635
|
## License
|
|
670
636
|
|
|
671
|
-
[MIT License](LICENSE.txt)
|
|
672
|
-
|
|
673
|
-
## Looking for a Software Engineer?
|
|
674
|
-
|
|
675
|
-
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!
|
|
676
|
-
|
|
677
|
-
[bryan@itsthedevman.com](mailto:bryan@itsthedevman.com)
|
|
637
|
+
[MIT License](LICENSE.txt)
|
data/Rakefile
CHANGED
|
@@ -17,3 +17,27 @@ require "standard/rake"
|
|
|
17
17
|
|
|
18
18
|
task test: [:test_regular, :test_active_support]
|
|
19
19
|
task default: %i[test standard]
|
|
20
|
+
|
|
21
|
+
# Benchmark tasks
|
|
22
|
+
namespace :benchmark do
|
|
23
|
+
desc "Run all benchmarks"
|
|
24
|
+
task :all do
|
|
25
|
+
ruby "benchmarks/run_all.rb"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
desc "Run all benchmarks with ActiveSupport"
|
|
29
|
+
task :all_with_active_support do
|
|
30
|
+
ENV["LOAD_ACTIVE_SUPPORT"] = "true"
|
|
31
|
+
ruby "benchmarks/run_all.rb"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
%w[array hash string enumerable ostruct module kernel quotable].each do |name|
|
|
35
|
+
desc "Run #{name} benchmarks"
|
|
36
|
+
task name.to_sym do
|
|
37
|
+
ruby "benchmarks/#{name}_benchmark.rb"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
desc "Run all benchmarks"
|
|
43
|
+
task benchmark: "benchmark:all"
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "benchmark_helper"
|
|
4
|
+
|
|
5
|
+
BenchmarkHelper.header("Array Extension Benchmarks")
|
|
6
|
+
|
|
7
|
+
# Test data
|
|
8
|
+
small_array = (1..10).to_a
|
|
9
|
+
medium_array = (1..100).to_a
|
|
10
|
+
large_array = (1..1000).to_a
|
|
11
|
+
|
|
12
|
+
small_hashes = [{name: "Alice", age: 30}, {name: "Bob", age: 25}, {name: "Charlie", age: 35}]
|
|
13
|
+
medium_hashes = (1..100).map { |i| {name: "User#{i}", age: 20 + (i % 50)} }
|
|
14
|
+
large_hashes = (1..1000).map { |i| {name: "User#{i}", age: 20 + (i % 50)} }
|
|
15
|
+
|
|
16
|
+
nested_hashes = [
|
|
17
|
+
{user: {profile: {name: "Alice"}}},
|
|
18
|
+
{user: {profile: {name: "Bob"}}},
|
|
19
|
+
{user: {profile: {name: "Charlie"}}}
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
array_with_nils_prefix = [nil, nil, nil, 1, 2, 3, nil, 4, 5]
|
|
23
|
+
array_with_nils_suffix = [1, 2, 3, nil, 4, 5, nil, nil, nil]
|
|
24
|
+
array_with_nils_both = [nil, nil, 1, 2, nil, 3, 4, nil, nil]
|
|
25
|
+
|
|
26
|
+
# ============================================================================
|
|
27
|
+
# join_map benchmarks
|
|
28
|
+
# ============================================================================
|
|
29
|
+
|
|
30
|
+
BenchmarkHelper.run("join_map - small array (10 elements)") do |x|
|
|
31
|
+
x.report("join_map") { small_array.join_map(", ") { |n| n.to_s if n.even? } }
|
|
32
|
+
x.report("join_map(with_index)") { small_array.join_map(", ", with_index: true) { |n, i| "#{i}:#{n}" } }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
BenchmarkHelper.run("join_map - medium array (100 elements)") do |x|
|
|
36
|
+
x.report("join_map") { medium_array.join_map(", ") { |n| n.to_s if n.even? } }
|
|
37
|
+
x.report("join_map(with_index)") { medium_array.join_map(", ", with_index: true) { |n, i| "#{i}:#{n}" } }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
BenchmarkHelper.run("join_map - large array (1000 elements)") do |x|
|
|
41
|
+
x.report("join_map") { large_array.join_map(", ") { |n| n.to_s if n.even? } }
|
|
42
|
+
x.report("join_map(with_index)") { large_array.join_map(", ", with_index: true) { |n, i| "#{i}:#{n}" } }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# ============================================================================
|
|
46
|
+
# key_map benchmarks
|
|
47
|
+
# ============================================================================
|
|
48
|
+
|
|
49
|
+
BenchmarkHelper.run("key_map - small array of hashes (3 elements)") do |x|
|
|
50
|
+
x.report("key_map") { small_hashes.key_map(:name) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
BenchmarkHelper.run("key_map - medium array of hashes (100 elements)") do |x|
|
|
54
|
+
x.report("key_map") { medium_hashes.key_map(:name) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
BenchmarkHelper.run("key_map - large array of hashes (1000 elements)") do |x|
|
|
58
|
+
x.report("key_map") { large_hashes.key_map(:name) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# ============================================================================
|
|
62
|
+
# dig_map benchmarks
|
|
63
|
+
# ============================================================================
|
|
64
|
+
|
|
65
|
+
BenchmarkHelper.run("dig_map - nested hashes") do |x|
|
|
66
|
+
x.report("dig_map(:user, :profile, :name)") { nested_hashes.dig_map(:user, :profile, :name) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# ============================================================================
|
|
70
|
+
# compact_prefix / compact_suffix / trim_nils benchmarks
|
|
71
|
+
# ============================================================================
|
|
72
|
+
|
|
73
|
+
BenchmarkHelper.run("nil trimming operations") do |x|
|
|
74
|
+
x.report("compact_prefix") { array_with_nils_prefix.compact_prefix }
|
|
75
|
+
x.report("compact_suffix") { array_with_nils_suffix.compact_suffix }
|
|
76
|
+
x.report("trim_nils") { array_with_nils_both.trim_nils }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# ============================================================================
|
|
80
|
+
# in_quotes benchmarks
|
|
81
|
+
# ============================================================================
|
|
82
|
+
|
|
83
|
+
BenchmarkHelper.run("in_quotes / with_quotes") do |x|
|
|
84
|
+
x.report("small array in_quotes") { small_array.in_quotes }
|
|
85
|
+
x.report("medium array in_quotes") { medium_array.in_quotes }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# ============================================================================
|
|
89
|
+
# ActiveSupport-only benchmarks
|
|
90
|
+
# ============================================================================
|
|
91
|
+
|
|
92
|
+
if BenchmarkHelper.active_support?
|
|
93
|
+
array_with_blanks = ["", nil, "foo", "", "bar", nil, ""]
|
|
94
|
+
words = %w[red blue green]
|
|
95
|
+
|
|
96
|
+
BenchmarkHelper.run("compact_blank_prefix / compact_blank_suffix / trim_blanks") do |x|
|
|
97
|
+
x.report("compact_blank_prefix") { array_with_blanks.compact_blank_prefix }
|
|
98
|
+
x.report("compact_blank_suffix") { array_with_blanks.compact_blank_suffix }
|
|
99
|
+
x.report("trim_blanks") { array_with_blanks.trim_blanks }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
BenchmarkHelper.run("to_or_sentence") do |x|
|
|
103
|
+
x.report("to_or_sentence") { words.to_or_sentence }
|
|
104
|
+
x.report("to_or_sentence(custom)") { words.to_or_sentence(locale: :en) }
|
|
105
|
+
end
|
|
106
|
+
else
|
|
107
|
+
puts "\n[SKIPPED] ActiveSupport benchmarks - run with LOAD_ACTIVE_SUPPORT=true"
|
|
108
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "benchmark/ips"
|
|
5
|
+
|
|
6
|
+
# Load ActiveSupport first if requested
|
|
7
|
+
if ENV["LOAD_ACTIVE_SUPPORT"] == "true"
|
|
8
|
+
require "active_support"
|
|
9
|
+
require "active_support/core_ext"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require "everythingrb"
|
|
13
|
+
|
|
14
|
+
# Helper module for benchmark formatting and utilities
|
|
15
|
+
module BenchmarkHelper
|
|
16
|
+
class << self
|
|
17
|
+
def header(title)
|
|
18
|
+
puts "\n" + "=" * 70
|
|
19
|
+
puts title.center(70)
|
|
20
|
+
puts "=" * 70
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def section(title)
|
|
24
|
+
puts "\n#{"-" * 30}"
|
|
25
|
+
puts title
|
|
26
|
+
puts "-" * 30
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def run(title, warmup: 2, time: 5, &block)
|
|
30
|
+
section(title)
|
|
31
|
+
|
|
32
|
+
Benchmark.ips do |x|
|
|
33
|
+
x.config(warmup:, time:)
|
|
34
|
+
block.call(x)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def active_support?
|
|
39
|
+
defined?(ActiveSupport)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|