everythingrb 0.9.1 → 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 +19 -10
- data/README.md +79 -100
- 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 -113
- 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,11 +15,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
15
15
|
### Removed
|
|
16
16
|
-->
|
|
17
17
|
|
|
18
|
-
## [0.
|
|
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`.
|
|
19
24
|
|
|
20
25
|
### Changed
|
|
21
26
|
|
|
22
|
-
- **
|
|
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.
|
|
23
38
|
|
|
24
39
|
## [0.9.0] - 12025-08-01
|
|
25
40
|
|
|
@@ -212,10 +227,6 @@ This change aligns our method signatures with Ruby's conventions and matches our
|
|
|
212
227
|
- `#rename_keys!` - Same as `#rename_keys` but modifies the hash in place
|
|
213
228
|
- `#rename_key_unordered` - Renames a key without preserving element order (faster operation)
|
|
214
229
|
- `#rename_key_unordered!` - Same as `#rename_key_unordered` but modifies the hash in place
|
|
215
|
-
- Added `to_deep_h` to core Ruby classes for consistent deep hash conversion:
|
|
216
|
-
- `Struct#to_deep_h` - Recursively converts Struct objects and all nested values to hashes
|
|
217
|
-
- `OpenStruct#to_deep_h` - Recursively converts OpenStruct objects and all nested values to hashes
|
|
218
|
-
- `Data#to_deep_h` - Recursively converts Data objects and all nested values to hashes
|
|
219
230
|
- Added `depth` parameter to `Hash.new_nested_hash` to control nesting behaviors
|
|
220
231
|
|
|
221
232
|
### Changed
|
|
@@ -241,7 +252,6 @@ This change aligns our method signatures with Ruby's conventions and matches our
|
|
|
241
252
|
|
|
242
253
|
- Added `Array#to_or_sentence`, creates a sentence with "or" connector between items
|
|
243
254
|
- Added `#with_key` method to `Hash#transform_values` and `Hash#transform_values!`, grants access to both keys and values during transformations
|
|
244
|
-
- Added `Array#to_deep_h` and `Hash#to_deep_h`, recursively converts underlying values to hashes
|
|
245
255
|
- Added `Enumerable#group_by_key`, group an array of hashes by their keys
|
|
246
256
|
- Added `Hash#new_nested_hash`, creates a new Hash that automatically initializes the value to a hash
|
|
247
257
|
- Added `Hash#value_where` and `Hash#values_where`, easily find values in a hash based on key-value conditions
|
|
@@ -341,15 +351,14 @@ This change aligns our method signatures with Ruby's conventions and matches our
|
|
|
341
351
|
- `join_map` method consistent with Array/Hash implementations
|
|
342
352
|
- Enhanced `String` class with:
|
|
343
353
|
- `to_h` and `to_a` methods for JSON parsing with `nil` fallback on error
|
|
344
|
-
- `to_deep_h` for recursive JSON string parsing
|
|
345
354
|
- `to_istruct`, `to_ostruct`, and `to_struct` conversion methods
|
|
346
355
|
|
|
347
356
|
### Changed
|
|
348
357
|
|
|
349
358
|
- Added alias `each` to `each_pair` in OpenStruct for better enumerable compatibility
|
|
350
359
|
|
|
351
|
-
[unreleased]: https://github.com/itsthedevman/everythingrb/compare/
|
|
352
|
-
[0.
|
|
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
|
|
353
362
|
[0.9.0]: https://github.com/itsthedevman/everythingrb/compare/v0.8.3...v0.9.0
|
|
354
363
|
[0.8.3]: https://github.com/itsthedevman/everythingrb/compare/v0.8.2...v0.8.3
|
|
355
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,9 +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:
|
|
283
|
+
|
|
284
|
+
---
|
|
309
285
|
|
|
310
|
-
|
|
286
|
+
### Hash Convenience
|
|
311
287
|
|
|
312
288
|
Transform values with access to their keys:
|
|
313
289
|
|
|
@@ -344,13 +320,13 @@ admins = users.select { |_k, v| v[:role] == "admin" }.values
|
|
|
344
320
|
|
|
345
321
|
```ruby
|
|
346
322
|
# AFTER
|
|
347
|
-
users.
|
|
323
|
+
users.select_values { |_k, v| v[:role] == "admin" }
|
|
348
324
|
# => [{name: "Alice", role: "admin"}, {name: "Charlie", role: "admin"}]
|
|
349
325
|
```
|
|
350
326
|
|
|
351
|
-
_Methods used: [`
|
|
327
|
+
_Methods used: [`select_values`](https://itsthedevman.com/docs/everythingrb/Hash.html#select_values-instance_method)_
|
|
352
328
|
|
|
353
|
-
Just want the first match?
|
|
329
|
+
Just want the first match?
|
|
354
330
|
|
|
355
331
|
```ruby
|
|
356
332
|
# BEFORE
|
|
@@ -360,11 +336,11 @@ users.find { |_k, v| v[:role] == "admin" }&.last
|
|
|
360
336
|
|
|
361
337
|
```ruby
|
|
362
338
|
# AFTER
|
|
363
|
-
users.
|
|
339
|
+
users.find_value { |_k, v| v[:role] == "admin" }
|
|
364
340
|
# => {name: "Alice", role: "admin"}
|
|
365
341
|
```
|
|
366
342
|
|
|
367
|
-
_Methods used: [`
|
|
343
|
+
_Methods used: [`find_value`](https://itsthedevman.com/docs/everythingrb/Hash.html#find_value-instance_method)_
|
|
368
344
|
|
|
369
345
|
Rename keys while preserving order:
|
|
370
346
|
|
|
@@ -374,14 +350,10 @@ config = {api_key: "secret", timeout: 30}
|
|
|
374
350
|
new_config = config.each_with_object({}) do |(key, value), hash|
|
|
375
351
|
new_key =
|
|
376
352
|
case key
|
|
377
|
-
when :api_key
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
:request_timeout
|
|
381
|
-
else
|
|
382
|
-
key
|
|
353
|
+
when :api_key then :key
|
|
354
|
+
when :timeout then :request_timeout
|
|
355
|
+
else key
|
|
383
356
|
end
|
|
384
|
-
|
|
385
357
|
hash[new_key] = value
|
|
386
358
|
end
|
|
387
359
|
# => {key: "secret", request_timeout: 30}
|
|
@@ -396,65 +368,45 @@ config.rename_keys(api_key: :key, timeout: :request_timeout)
|
|
|
396
368
|
|
|
397
369
|
_Methods used: [`rename_keys`](https://itsthedevman.com/docs/everythingrb/Hash.html#rename_keys-instance_method)_
|
|
398
370
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
```ruby
|
|
402
|
-
# BEFORE
|
|
403
|
-
user_params = {name: "Alice", role: "user"}
|
|
404
|
-
filtered = {verified: true, admin: true}.select { |k, v| v == true && k == :verified }
|
|
405
|
-
user_params.merge(filtered)
|
|
406
|
-
# => {name: "Alice", role: "user", verified: true}
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
```ruby
|
|
410
|
-
# AFTER
|
|
411
|
-
user_params = {name: "Alice", role: "user"}
|
|
412
|
-
user_params.merge_if(verified: true, admin: true) { |k, v| v == true && k == :verified }
|
|
413
|
-
# => {name: "Alice", role: "user", verified: true}
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
_Methods used: [`merge_if`](https://itsthedevman.com/docs/everythingrb/Hash.html#merge_if-instance_method)_
|
|
417
|
-
|
|
418
|
-
The nil-filtering pattern we've all written dozens of times:
|
|
371
|
+
Merge while dropping nils:
|
|
419
372
|
|
|
420
373
|
```ruby
|
|
421
374
|
# BEFORE
|
|
422
375
|
params = {sort: "created_at"}
|
|
423
|
-
|
|
424
|
-
params.merge(
|
|
376
|
+
search_params = {filter: "active", search: nil}.compact
|
|
377
|
+
params.merge(search_params)
|
|
425
378
|
# => {sort: "created_at", filter: "active"}
|
|
426
379
|
```
|
|
427
380
|
|
|
428
381
|
```ruby
|
|
429
382
|
# AFTER
|
|
430
|
-
|
|
431
|
-
params.compact_merge(
|
|
383
|
+
search_params = {filter: "active", search: nil}
|
|
384
|
+
params.compact_merge(search_params)
|
|
432
385
|
# => {sort: "created_at", filter: "active"}
|
|
433
386
|
```
|
|
434
387
|
|
|
435
388
|
_Methods used: [`compact_merge`](https://itsthedevman.com/docs/everythingrb/Hash.html#compact_merge-instance_method)_
|
|
436
389
|
|
|
437
|
-
|
|
390
|
+
Merge while dropping nils and blank values (requires ActiveSupport):
|
|
438
391
|
|
|
439
392
|
```ruby
|
|
440
393
|
# BEFORE
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
# => {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"}
|
|
446
398
|
```
|
|
447
399
|
|
|
448
400
|
```ruby
|
|
449
401
|
# AFTER
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
# => {
|
|
402
|
+
search_params = {filter: "active", search: nil, query: ""}
|
|
403
|
+
params.compact_blank_merge(search_params)
|
|
404
|
+
# => {sort: "created_at", filter: "active"}
|
|
453
405
|
```
|
|
454
406
|
|
|
455
407
|
_Methods used: [`compact_blank_merge`](https://itsthedevman.com/docs/everythingrb/Hash.html#compact_blank_merge-instance_method)_
|
|
456
408
|
|
|
457
|
-
**Extensions:** [`transform_values(with_key: true)`](https://itsthedevman.com/docs/everythingrb/Hash.html#transform_values-instance_method), [`
|
|
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)
|
|
458
410
|
|
|
459
411
|
### Array Cleaning
|
|
460
412
|
|
|
@@ -474,7 +426,7 @@ data.drop_while(&:nil?).reverse.drop_while(&:nil?).reverse
|
|
|
474
426
|
|
|
475
427
|
_Methods used: [`trim_nils`](https://itsthedevman.com/docs/everythingrb/Array.html#trim_nils-instance_method)_
|
|
476
428
|
|
|
477
|
-
With ActiveSupport, remove blank values too:
|
|
429
|
+
With ActiveSupport, remove blank values from the edges too:
|
|
478
430
|
|
|
479
431
|
```ruby
|
|
480
432
|
# BEFORE
|
|
@@ -494,7 +446,7 @@ _Methods used: [`trim_blanks`](https://itsthedevman.com/docs/everythingrb/Array.
|
|
|
494
446
|
|
|
495
447
|
### String Formatting
|
|
496
448
|
|
|
497
|
-
Format
|
|
449
|
+
Format values consistently without a helper method:
|
|
498
450
|
|
|
499
451
|
```ruby
|
|
500
452
|
# BEFORE
|
|
@@ -533,7 +485,7 @@ message = "You selected #{selection.in_quotes}"
|
|
|
533
485
|
|
|
534
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)_
|
|
535
487
|
|
|
536
|
-
Convert strings to camelCase
|
|
488
|
+
Convert strings to camelCase:
|
|
537
489
|
|
|
538
490
|
```ruby
|
|
539
491
|
# BEFORE
|
|
@@ -554,7 +506,7 @@ camel_case
|
|
|
554
506
|
"user_profile_settings".to_camelcase # => "UserProfileSettings"
|
|
555
507
|
"user_profile_settings".to_camelcase(:lower) # => "userProfileSettings"
|
|
556
508
|
|
|
557
|
-
# Handles
|
|
509
|
+
# Handles mixed input consistently
|
|
558
510
|
"please-WAIT while_loading...".to_camelcase # => "PleaseWaitWhileLoading"
|
|
559
511
|
```
|
|
560
512
|
|
|
@@ -564,7 +516,7 @@ _Methods used: [`to_camelcase`](https://itsthedevman.com/docs/everythingrb/Strin
|
|
|
564
516
|
|
|
565
517
|
### Boolean Methods
|
|
566
518
|
|
|
567
|
-
|
|
519
|
+
Define predicate methods from any attribute:
|
|
568
520
|
|
|
569
521
|
```ruby
|
|
570
522
|
# BEFORE
|
|
@@ -595,6 +547,39 @@ user.admin? # => true
|
|
|
595
547
|
|
|
596
548
|
_Methods used: [`attr_predicate`](https://itsthedevman.com/docs/everythingrb/Module.html#attr_predicate-instance_method)_
|
|
597
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
|
+
|
|
598
583
|
Works with Data objects too:
|
|
599
584
|
|
|
600
585
|
```ruby
|
|
@@ -619,7 +604,7 @@ _Methods used: [`attr_predicate`](https://itsthedevman.com/docs/everythingrb/Mod
|
|
|
619
604
|
|
|
620
605
|
### Value Transformation
|
|
621
606
|
|
|
622
|
-
|
|
607
|
+
An alias for `then`/`yield_self` that reads more naturally in transformation chains:
|
|
623
608
|
|
|
624
609
|
```ruby
|
|
625
610
|
# BEFORE
|
|
@@ -649,10 +634,4 @@ Bug reports and pull requests are welcome! This project is intended to be a safe
|
|
|
649
634
|
|
|
650
635
|
## License
|
|
651
636
|
|
|
652
|
-
[MIT License](LICENSE.txt)
|
|
653
|
-
|
|
654
|
-
## Looking for a Software Engineer?
|
|
655
|
-
|
|
656
|
-
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!
|
|
657
|
-
|
|
658
|
-
[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
|