sortsmith 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb5b3aee0492a9a38de3999f07444c05e0e7ad00c86c3c3f6a87be19151e5b23
4
- data.tar.gz: e3a1f4497ffc436f56ee881e58086e23dc0009357e1bfe3cd9393dc4642162de
3
+ metadata.gz: 16b369873631fe1de4c59f32bbbf5fbdb383ee1b3924a864ccd11a2ac6c4f1d5
4
+ data.tar.gz: cb329eecad62b7cf05e13624258fc09dd4a21e0aab192e50774a8e2a6c6beed3
5
5
  SHA512:
6
- metadata.gz: 64d378b0764c272de8ba01a1230ef7457ea36627712c5c38f1531b9d57e1ca44e2627bb880aeede760f0bfc168cfdffccea8d1b3577e07daa8fe64181e3cb8cd
7
- data.tar.gz: 373dea1996fbe61a6c2e0cf3095c761b4b388f52932e78e8e43f1d3e7cac696d949ecf2233cd4f8d89c77cd1133c9aa6b2e84d40fc0e9c70d7e5795b5e65b84a
6
+ metadata.gz: e3ada898d22d88aabe5c314dc67ac35dced713ec5f7bced55ec4ca87d12f10cd62b9fe218d6acb45831d9ec62734917d72c5343dce56b15bace088b23188bc95
7
+ data.tar.gz: e362605cde0d51b9eb63ef435e69cf13d91651ff2a9ba22dfe07a8cdd1e33c9c8bb0ce695c0914bfb3579226a3306c2d4d03f029752c25f8b17dbff22133fb1d
data/CHANGELOG.md CHANGED
@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ <!--
8
9
  ## [Unreleased]
9
10
 
10
11
  ### Added
@@ -12,6 +13,146 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
12
13
  ### Changed
13
14
 
14
15
  ### Removed
16
+ -->
17
+
18
+ ## [1.0.0] - 12025-08-03
19
+
20
+ ### 🎉 API Stability Milestone
21
+
22
+ Sortsmith has reached 1.0.0! After evolving through several major API redesigns and proving itself in real projects, the core interface is now stable and ready for broader adoption.
23
+
24
+ **What 1.0.0 means:**
25
+ - **Stable API**: Method signatures and behavior are locked for semver compatibility
26
+ - **Complete Vision**: From the verbose early days to today's clean `collection.sort_by(:name).insensitive.desc.sort`, the API finally feels right
27
+ - **Battle Tested**: Handles the weird edge cases and mixed data types you actually encounter in real Ruby apps
28
+
29
+ This represents the sorting library I always wished Ruby had built-in. Simple things are simple, complex things are possible, and it all reads like English.
30
+
31
+ ### Added
32
+
33
+ #### Universal Field Extraction
34
+
35
+ - **Direct syntax**: `sort_by(field, **opts)` - Sort by any field/method without explicit chaining
36
+ - **Object method extraction**: `method(*args, **kwargs)` - Call methods on objects with full argument support
37
+ - **Mixed key handling**: Enhanced `indifferent: true` support across all extractors
38
+
39
+ #### Semantic Aliases for Better Readability
40
+
41
+ - `key` - Alias for `dig` (emphasizes hash key extraction)
42
+ - `field` - Alias for `dig` (emphasizes object field access)
43
+ - `attribute` - Alias for `method` (emphasizes object attribute access)
44
+ - `case_insensitive` - Alias for `insensitive`/`downcase` (explicit case handling)
45
+
46
+ #### Seamless Enumerable Integration
47
+
48
+ - **Delegated methods**: `first`, `last`, `take`, `drop`, `each`, `map`, `select`, `[]`, `size`, `count`, `length`
49
+ - **Fluent chaining**: Sort operations flow directly into array operations without breaking
50
+ - **Full argument support**: All delegated methods support blocks, arguments, and return appropriate types
51
+
52
+ #### Enhanced Terminators
53
+
54
+ - `to_a!` - Mutating version of `to_a` for consistency with `sort!`
55
+
56
+ ## [0.9.0] - 12025-07-06
57
+
58
+ ### 🎉 MAJOR REWRITE: Fluent Chainable API
59
+
60
+ **BREAKING CHANGES**: Complete API redesign introducing a fluent, chainable interface. See migration guide below.
61
+
62
+ **Pre-1.0 Notice**: This release represents our new stable API design, but we're seeking community feedback before locking in 1.0.0 compatibility guarantees.
63
+
64
+ ### Added
65
+
66
+ #### Core API Transformation
67
+
68
+ - **Fluent API**: Direct extension of `Enumerable#sort_by` for natural Ruby integration
69
+ - **Chainable Interface**: Method chaining that reads like English: `users.sort_by.dig(:name).downcase.desc.sort`
70
+ - **Universal `dig` Method**: Single extraction method that works with hashes, objects, and nested structures
71
+ - **Indifferent Key Access**: Handle mixed symbol/string hash keys with `dig(:name, indifferent: true)`
72
+
73
+ #### New Extraction Methods
74
+
75
+ - `dig(*identifiers, indifferent: false)` - Extract values from hashes, objects, or nested structures
76
+ - Support for nested extraction: `dig(:user, :profile, :email)`
77
+ - Automatic fallback to method calls for non-hash objects
78
+
79
+ #### Enhanced Modifiers
80
+
81
+ - `downcase` / `upcase` - Case transformations with automatic type checking
82
+ - `insensitive` - Alias for `downcase` for semantic clarity
83
+ - `asc` / `desc` - Explicit sort direction control
84
+ - Smart modifier chaining that only affects compatible data types
85
+
86
+ #### Multiple Terminators
87
+
88
+ - `sort` - Returns new sorted array (non-mutating)
89
+ - `sort!` - Mutates original array in place
90
+ - `reverse` - Shorthand for `desc.sort`
91
+ - `reverse!` - Shorthand for `desc.sort!`
92
+ - `to_a` - Alias for `sort` for semantic clarity
93
+
94
+ #### Backward Compatibility
95
+
96
+ - `sort_by` with block maintains original Ruby behavior
97
+ - `sort_by` without block returns Sortsmith::Sorter instance
98
+ - Zero breaking changes for existing Ruby code
99
+
100
+ ### Changed
101
+
102
+ #### API Design Philosophy
103
+
104
+ - **Before**: `Sortsmith::Sorter.new(collection).by_key(:name).case_insensitive.desc.sort`
105
+ - **After**: `collection.sort_by.dig(:name).insensitive.desc.sort`
106
+
107
+ #### Improved Ergonomics
108
+
109
+ - No more explicit `Sorter.new()` instantiation required
110
+ - Tab-completable method discovery
111
+ - Natural language flow in method chains
112
+ - Unified `dig` method replaces separate `by_key`/`by_method` methods
113
+
114
+ #### Enhanced Ruby Version Support
115
+
116
+ - **Restored Ruby 3.0 and 3.1 compatibility** - Previously removed in v0.2.0, now supported again
117
+ - Full compatibility matrix: Ruby 3.0.7, 3.1.7, 3.2.8, 3.3.8, 3.4.4
118
+ - Expanded from Ruby 3.2+ requirement back to Ruby 3.0+ for broader accessibility
119
+
120
+ ### Removed
121
+
122
+ #### Legacy API (Breaking Changes)
123
+
124
+ - `by_key` method (replaced by `dig`)
125
+ - `by_method`/`by_attribute` methods (replaced by `dig`)
126
+ - `case_insensitive` method (replaced by `insensitive`/`downcase`)
127
+ - `Sortsmith::Step` class (internal restructure)
128
+ - Manual `Sorter.new()` instantiation requirement
129
+ - `rbs` and `steep` type checking
130
+
131
+ ### Migration Guide
132
+
133
+ The new API is more concise and intuitive, but requires updating existing code:
134
+
135
+ ```ruby
136
+ # v0.2.x (OLD)
137
+ Sortsmith::Sorter.new(users).by_key(:name).case_insensitive.desc.sort
138
+
139
+ # v0.9.x (NEW)
140
+ users.sort_by.dig(:name).insensitive.desc.sort
141
+
142
+ # v0.2.x (OLD)
143
+ Sortsmith::Sorter.new(objects).by_method(:calculate_score).sort
144
+
145
+ # v0.9.x (NEW)
146
+ objects.sort_by.dig(:calculate_score).sort
147
+ ```
148
+
149
+ ### Technical Improvements
150
+
151
+ - Complete test suite rewrite with comprehensive edge case coverage
152
+ - Enhanced error handling for mixed data types
153
+ - Improved performance through reduced object allocations
154
+ - Cleaner internal architecture with separation of concerns
155
+ - Better documentation with extensive API examples
15
156
 
16
157
  ## [0.2.0] - 12025-02-17
17
158
 
@@ -30,11 +171,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
30
171
  ## [0.1.1] - 12025-01-15
31
172
 
32
173
  ### Changed
174
+
33
175
  - Improved handling of non-string objects when sorting
34
176
 
35
177
  ## [0.1.0] - 12025-01-14
36
178
 
37
179
  ### Added
180
+
38
181
  - Initial implementation of `Sortsmith::Sorter`
39
182
  - Support for case-insensitive sorting
40
183
  - Support for sorting by hash keys and object methods
@@ -42,7 +185,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
42
185
  - Type checking with Steep/RBS
43
186
  - GitHub Actions workflow for automated testing and type checking
44
187
 
45
- [unreleased]: https://github.com/itsthedevman/sortsmith/compare/v0.2.0...HEAD
188
+ [unreleased]: https://github.com/itsthedevman/sortsmith/compare/v1.0.0...HEAD
189
+ [1.0.0]: https://github.com/itsthedevman/sortsmith/compare/v0.9.0...v1.0.0
190
+ [0.9.0]: https://github.com/itsthedevman/sortsmith/compare/v0.2.0...v0.9.0
46
191
  [0.2.0]: https://github.com/itsthedevman/sortsmith/compare/v0.1.1...v0.2.0
47
192
  [0.1.1]: https://github.com/itsthedevman/sortsmith/compare/v0.1.0...v0.1.1
48
193
  [0.1.0]: https://github.com/itsthedevman/sortsmith/compare/ac357965a1bc641d187333a5b032c5a423020ae9...v0.1.0
data/README.md CHANGED
@@ -1,17 +1,66 @@
1
1
  # Sortsmith
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/sortsmith.svg)](https://badge.fury.io/rb/sortsmith)
4
+ ![Ruby Version](https://img.shields.io/badge/ruby-3.1+-ruby)
4
5
  [![Tests](https://github.com/itsthedevman/sortsmith/actions/workflows/main.yml/badge.svg)](https://github.com/itsthedevman/sortsmith/actions/workflows/main.yml)
5
- ![Ruby Version](https://img.shields.io/badge/ruby-3.3.6-ruby)
6
6
 
7
- Sortsmith is a flexible sorting library for Ruby that makes complex sorting operations simple and composable. It makes handling common sorting patterns like case-insensitive sorting of hashes and objects easy, while remaining extensible for custom sorting needs.
7
+ **Sortsmith** makes sorting Ruby collections feel natural and fun. Instead of wrestling with verbose blocks or complex comparisons, just chain together what you want in plain English.
8
8
 
9
- ## Features
9
+ ```ruby
10
+ # Instead of this...
11
+ users.sort_by { |user| user[:name].downcase }.reverse
12
+
13
+ # Write this!
14
+ users.sort_by.dig(:name).downcase.reverse
15
+ ```
16
+
17
+ Sortsmith extends Ruby's built-in `sort_by` method with a fluent, chainable API that handles the messy details so you can focus on expressing _what_ you want sorted, not _how_ to sort it.
18
+
19
+ ## Table of Contents
20
+
21
+ - [Why Sortsmith?](#why-sortsmith)
22
+ - [Installation](#installation)
23
+ - [Quick Start](#quick-start)
24
+ - [Core Concepts](#core-concepts)
25
+ - [Usage Examples](#usage-examples)
26
+ - [Array Sorting](#array-sorting)
27
+ - [Hash Collections](#hash-collections)
28
+ - [Object Collections](#object-collections)
29
+ - [Mixed Key Types](#mixed-key-types)
30
+ - [API Reference](#api-reference)
31
+ - [Migration from v0.2.x](#migration-from-v02x)
32
+ - [Development](#development)
33
+ - [Contributing](#contributing)
34
+ - [License](#license)
10
35
 
11
- - Builder pattern for chainable sorting configuration
12
- - Built-in support for case-insensitive sorting
13
- - Hash key and method/attribute sorting
14
- - Flexible transformation pipeline
36
+ ## Why Sortsmith?
37
+
38
+ Ruby's `sort_by` is powerful, but real-world sorting often gets messy:
39
+
40
+ ```ruby
41
+ # Sorting users by name, case-insensitive, descending
42
+ users.sort_by { |u| u[:name].to_s.downcase }.reverse
43
+
44
+ # What if some names are nil?
45
+ users.sort_by { |u| (u[:name] || "").downcase }.reverse
46
+
47
+ # What about mixed string/symbol keys?
48
+ users.sort_by { |u| (u[:name] || u["name"] || "").downcase }.reverse
49
+ ```
50
+
51
+ Sortsmith handles all the edge cases and gives you a clean, readable API:
52
+
53
+ ```ruby
54
+ users.sort_by.dig(:name, indifferent: true).insensitive.desc.sort
55
+ ```
56
+
57
+ **Features:**
58
+
59
+ - **Fluent chaining** - Reads like English
60
+ - **Universal extraction** - Works with hashes, objects, and nested data
61
+ - **Indifferent key access** - Handles mixed symbol/string keys automatically
62
+ - **Nil-safe** - Graceful handling of missing data
63
+ - **Minimal overhead** - Extends existing Ruby methods without breaking compatibility
15
64
 
16
65
  ## Installation
17
66
 
@@ -33,99 +82,259 @@ Or install it yourself as:
33
82
  $ gem install sortsmith
34
83
  ```
35
84
 
36
- ## Usage
85
+ ## Quick Start
37
86
 
38
- ### Basic Sorting
87
+ Sortsmith extends Ruby's `sort_by` method with a fluent, chainable API. Use it with or without a block for maximum flexibility:
39
88
 
40
89
  ```ruby
41
- # Sort an array of Strings
42
- users = ["Bob", "Alice", "Carol"]
90
+ require "sortsmith"
91
+
92
+ # Direct syntax for simple cases (NEW!)
93
+ names = ["charlie", "Alice", "bob"]
94
+ names.sort_by(:name).insensitive.sort
95
+ # => ["Alice", "bob", "charlie"]
43
96
 
44
- sorted_users = Sortsmith::Sorter.new(users).sort
97
+ # Or use the chainable API for complex scenarios
98
+ users = [
99
+ { name: "Charlie", age: 25 },
100
+ { name: "Alice", age: 30 },
101
+ { name: "Bob", age: 20 }
102
+ ]
45
103
 
46
- # Result: ["Alice", "Bob", "Carol"]
104
+ users.sort_by.dig(:name).sort
105
+ # => [{ name: "Alice", age: 30 }, { name: "Bob", age: 20 }, { name: "Charlie", age: 25 }]
106
+
107
+ # Seamless integration with enumerable methods (NEW!)
108
+ users.sort_by(:age).desc.first(2)
109
+ # => [{ name: "Alice", age: 30 }, { name: "Charlie", age: 25 }]
110
+
111
+ # The original sort_by with blocks still works exactly the same!
112
+ users.sort_by { |u| u[:age] }
113
+ # => [{ name: "Bob", age: 20 }, { name: "Charlie", age: 25 }, { name: "Alice", age: 30 }]
114
+ ```
115
+
116
+ ## Core Concepts
117
+
118
+ Sortsmith uses a simple pipeline concept where each step is **optional** except for the terminator:
119
+
120
+ 1. **Extract** - Get the value to sort by (`dig`, `method`, etc.) - _optional_
121
+ 2. **Transform** - Modify the value for comparison (`downcase`, `upcase`, etc.) - _optional_
122
+ 3. **Order** - Choose sort direction (`asc`, `desc`) - _optional_
123
+ 4. **Execute** - Run the sort (`sort`, `sort!`, `reverse`, `to_a`, etc.) - **required**
124
+
125
+ ```ruby
126
+ collection.sort_by.dig(:field).downcase.desc.sort
127
+ # ↑ ↑ ↑ ↑ ↑
128
+ # | extract transform order execute
129
+ # chainable (opt) (opt) (opt) (required)
130
+ ```
131
+
132
+ **Minimal example:**
133
+
134
+ ```ruby
135
+ # This works! (though not particularly useful)
136
+ users.sort_by.sort # Same as users.sort
137
+
138
+ # More practical examples
139
+ users.sort_by.dig(:name).sort # Extract only
140
+ users.sort_by.downcase.sort # Transform only
141
+ users.sort_by.desc.sort # Order only
142
+ users.sort_by.dig(:name).desc.sort # Extract + order
47
143
  ```
48
144
 
49
- ### Hash Sorting
145
+ Each step builds on the previous ones, so you can mix and match based on what your data needs. The only requirement is ending with a terminator to actually execute the sort.
146
+
147
+ ## Usage Examples
148
+
149
+ ### Simple Direct Syntax
50
150
 
51
151
  ```ruby
52
- # Sort an array of hashes by a key
152
+ # Clean and direct for common operations
153
+ words = ["elephant", "cat", "butterfly"]
154
+ words.sort_by(:length).desc.sort
155
+ # => ["butterfly", "elephant", "cat"]
156
+
157
+ # Works great with hashes
53
158
  users = [
54
- { name: "Carol", age: 35 },
55
- { name: "Bob", age: 25 },
56
- { name: "Alice", age: 30 }
159
+ { name: "Cat", score: 99 },
160
+ { name: "Charlie", score: 85 },
161
+ { name: "karen", score: 50 },
162
+ { name: "Alice", score: 92 },
163
+ { name: "bob", score: 78 },
57
164
  ]
58
165
 
59
- sorted_users = Sortsmith::Sorter.new(users)
60
- .by_key(:name)
61
- .sort
62
-
63
- # Result: [{ name: "Alice" }, { name: "Bob" }, { name: "Carol" }]
166
+ # Get top 3 by score
167
+ users.sort_by(:score).desc.first(3)
168
+ # => [{ name: "Cat", score: 99 }, { name: "Alice", score: 92 }, { name: "Charlie", score: 85 }]
64
169
  ```
65
170
 
66
- ### Object Sorting
171
+ ### Object Method Sorting
67
172
 
68
173
  ```ruby
69
- # Sort objects by method/attribute
70
- class User < Data.define(:name)
71
- end
174
+ User = Struct.new(:name, :age, :city)
175
+
176
+ users = [
177
+ User.new("Charlie", 25, "NYC"),
178
+ User.new("Alice", 30, "LA"),
179
+ User.new("bob", 20, "Chicago")
180
+ ]
72
181
 
73
- users = [User.new(name: "Bob"), User.new(name: "Carol"), User.new(name: "Alice")]
182
+ # Sort by any method or attribute
183
+ users.sort_by.method(:name).insensitive.sort
184
+ # => [User.new("Alice"), User.new("bob"), User.new("Charlie")]
74
185
 
75
- sorted_users = Sortsmith::Sorter.new(users)
76
- .by_method(:name)
77
- .sort
186
+ # Or use the semantic alias
187
+ users.sort_by.attribute(:age).desc.first
188
+ # => User.new("Alice", 30, "LA")
78
189
 
79
- # Result: [#<data User name="Alice">, #<data User name="Bob">, #<data User name="Carol">]
190
+ # Methods with arguments work too
191
+ class Product
192
+ def price_in(currency)
193
+ # calculation logic
194
+ end
195
+ end
196
+
197
+ products.sort_by.method(:price_in, "USD").sort
80
198
  ```
81
199
 
82
- ### Case Insensitive Sorting
200
+ ### Hash Collections with Multiple Access Patterns
83
201
 
84
202
  ```ruby
85
203
  users = [
86
- {"name" => "bob"},
87
- {"name" => "Billy"},
88
- {"name" => "Alice"},
89
- {"name" => "carol"},
90
- {"name" => "Cassidy"},
91
- {"name" => "alex"}
204
+ { name: "Charlie", score: 85, team: "red" },
205
+ { name: "Alice", score: 92, team: "blue" },
206
+ { name: "bob", score: 78, team: "red" }
92
207
  ]
93
208
 
94
- # Order of methods does not matter
95
- # However, the hash's key type does
96
- sorted_users = Sortsmith::Sorter.new(users)
97
- .case_insensitive
98
- .by_key("name")
99
- .sort
209
+ # Multiple semantic ways to express extraction
210
+ users.sort_by.key(:name).insensitive.sort # Emphasizes hash keys
211
+ users.sort_by.field(:score).desc.sort # Emphasizes data fields
212
+ users.sort_by.dig(:team, :name).sort # Nested access
100
213
 
101
- # Result: [{"name"=>"Alice"}, {"name"=>"alex"}, {"name"=>"Billy"}, {"name"=>"bob"}, {"name"=>"Cassidy"}, {"name"=>"carol"}]
214
+ # Case handling with explicit naming
215
+ users.sort_by(:name).case_insensitive.reverse
216
+ # => [{ name: "bob" }, { name: "Charlie" }, { name: "Alice" }]
102
217
  ```
103
218
 
104
- ### Reverse Sorting
219
+ ### Seamless Enumerable Integration
105
220
 
106
221
  ```ruby
107
- # Sort in descending order
108
- sorted_desc = Sortsmith::Sorter.new(array)
109
- .by_attribute(:name)
110
- .desc
111
- .sort
222
+ # Chain directly into enumerable methods - no .to_a needed!
223
+ users.sort_by(:score).desc.first(2) # Top 2 performers
224
+ users.sort_by(:name).each { |u| puts u } # Iterate in order
225
+ users.sort_by(:team).map(&:name) # Transform sorted results
226
+ users.sort_by(:score).select { |u| u[:active] } # Filter sorted results
227
+
228
+ # Array access works too
229
+ users.sort_by(:score).desc[0] # Best performer
230
+ users.sort_by(:name)[1..3] # Users 2-4 alphabetically
231
+
232
+ # Quick stats
233
+ users.sort_by(:score).count # Total count
234
+ users.sort_by(:team).count { |u| u[:active] } # Conditional count
235
+ ```
236
+
237
+ ### Mixed Key Types
238
+
239
+ Real-world data often has inconsistent key types. Sortsmith handles this gracefully:
240
+
241
+ ```ruby
242
+ mixed_users = [
243
+ { name: "Charlie" }, # symbol key
244
+ { "name" => "Alice" }, # string key
245
+ { :name => "Bob" }, # symbol key again
246
+ { "name" => "Diana" } # string key again
247
+ ]
248
+
249
+ # The indifferent option handles both key types
250
+ mixed_users.sort_by.dig(:name, indifferent: true).sort
251
+ # => [{ "name" => "Alice" }, { :name => "Bob" }, { name: "Charlie" }, { "name" => "Diana" }]
252
+
253
+ # Without indifferent access, you'd get sorting failures or unexpected results
254
+ ```
255
+
256
+ **Performance Note**: Indifferent key access adds modest overhead (~2x slower depending on the machine) but operates in microseconds and is typically worth the convenience for mixed-key scenarios.
257
+
258
+ ```ruby
259
+ # Rails users can also normalize keys upfront for better performance
260
+ mixed_users.map(&:symbolize_keys).sort_by.dig(:name).sort
261
+
262
+ # But indifferent access is handy when you can't control the data source
263
+ api_response.sort_by.dig(:name, indifferent: true).sort
264
+ ```
265
+
266
+ ## API Reference
267
+
268
+ ### Universal Extraction
269
+
270
+ - **`sort_by(field, **opts)`\*\* - Direct field extraction (NEW!)
271
+ - Works with hashes, objects, and any method name
272
+ - Supports all the same options as `dig` and `method`
273
+
274
+ ### Extractors
275
+
276
+ - **`dig(*identifiers, indifferent: false)`** - Extract values from hashes, objects, or nested structures
277
+ - **`method(method_name, \*args, **kwargs)`\*\* - Call methods on objects with arguments (NEW!)
278
+ - **`key(\*identifiers, **opts)`** - Alias for `dig` (semantic clarity for hash keys) (NEW!)
279
+ - **`field(\*identifiers, **opts)`** - Alias for `dig` (semantic clarity for object fields) (NEW!)
280
+ - **`attribute(method_name, \*args, **kwargs)`** - Alias for `method` (semantic clarity) (NEW!)
281
+
282
+ ### Modifiers
283
+
284
+ - **`downcase`** - Convert extracted strings to lowercase for comparison
285
+ - **`upcase`** - Convert extracted strings to uppercase for comparison
286
+ - **`insensitive`** - Alias for `downcase` (semantic clarity)
287
+ - **`case_insensitive`** - Alias for `downcase` (explicit case handling) (NEW!)
288
+
289
+ ### Ordering
290
+
291
+ - **`asc`** - Sort in ascending order (default)
292
+ - **`desc`** - Sort in descending order
293
+
294
+ ### Terminators
295
+
296
+ - **`sort`** - Execute sort and return new array
297
+ - **`sort!`** - Execute sort and mutate original array
298
+ - **`to_a`** - Alias for `sort`
299
+ - **`to_a!`** - Alias for `sort!` (NEW!)
300
+ - **`reverse`** - Shorthand for `desc.sort`
301
+ - **`reverse!`** - Shorthand for `desc.sort!`
302
+
303
+ ### Delegated Enumerable Methods (NEW!)
304
+
305
+ The following methods execute the sort and delegate to the resulting array:
306
+
307
+ - **`first(n=1)`**, **`last(n=1)`** - Get first/last n elements
308
+ - **`take(n)`**, **`drop(n)`** - Take/drop n elements
309
+ - **`each`**, **`map`**, **`select`** - Standard enumerable operations
310
+ - **`[](index)`** - Array access by index or range
311
+ - **`size`**, **`count`**, **`length`** - Size information
312
+
313
+ ```ruby
314
+ # All of these execute the sort first, then apply the operation
315
+ users.sort_by(:score).desc.first(3) # Get top 3
316
+ users.sort_by(:name).take(5) # Take first 5 alphabetically
317
+ users.sort_by(:team)[0] # First by team name
318
+ users.sort_by(:score).size # Total size after sorting
112
319
  ```
113
320
 
114
321
  ## Development
115
322
 
116
323
  ### Prerequisites
117
324
 
118
- - Ruby 3.3.6
325
+ - Ruby 3.0+
119
326
  - Nix with Direnv (optional, but recommended)
120
327
 
121
328
  ### Setting Up the Development Environment
122
329
 
123
330
  With Nix:
331
+
124
332
  ```bash
125
333
  direnv allow
126
334
  ```
127
335
 
128
336
  Without Nix:
337
+
129
338
  ```bash
130
339
  bundle install
131
340
  ```
@@ -136,12 +345,6 @@ bundle install
136
345
  bundle exec rake test
137
346
  ```
138
347
 
139
- ### Type Checking
140
-
141
- ```bash
142
- bundle exec steep check
143
- ```
144
-
145
348
  ### Code Style
146
349
 
147
350
  This project uses StandardRB. To check your code:
@@ -168,7 +371,7 @@ Please note that this project is released with a [Contributor Code of Conduct](C
168
371
 
169
372
  ## License
170
373
 
171
- The gem is available as open source under the terms of the [MIT License](LICENSE.md).
374
+ The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
172
375
 
173
376
  ## Changelog
174
377
 
@@ -177,3 +380,9 @@ See [CHANGELOG.md](CHANGELOG.md) for a list of changes.
177
380
  ## Credits
178
381
 
179
382
  - Author: Bryan "itsthedevman"
383
+
384
+ ## Looking for a Software Engineer?
385
+
386
+ 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!
387
+
388
+ [bryan@itsthedevman.com](mailto:bryan@itsthedevman.com)
data/flake.lock CHANGED
@@ -20,11 +20,11 @@
20
20
  },
21
21
  "nixpkgs": {
22
22
  "locked": {
23
- "lastModified": 1736798957,
24
- "narHash": "sha256-qwpCtZhSsSNQtK4xYGzMiyEDhkNzOCz/Vfu4oL2ETsQ=",
23
+ "lastModified": 1751792365,
24
+ "narHash": "sha256-J1kI6oAj25IG4EdVlg2hQz8NZTBNYvIS0l4wpr9KcUo=",
25
25
  "owner": "NixOS",
26
26
  "repo": "nixpkgs",
27
- "rev": "9abb87b552b7f55ac8916b6fc9e5cb486656a2f3",
27
+ "rev": "1fd8bada0b6117e6c7eb54aad5813023eed37ccb",
28
28
  "type": "github"
29
29
  },
30
30
  "original": {
data/flake.nix CHANGED
@@ -1,21 +1,27 @@
1
1
  {
2
- description = "Ruby 3.4 development environment";
2
+ description = "Ruby 3.2 development environment";
3
3
 
4
4
  inputs = {
5
5
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6
6
  flake-utils.url = "github:numtide/flake-utils";
7
7
  };
8
8
 
9
- outputs = { self, nixpkgs, flake-utils }:
10
- flake-utils.lib.eachDefaultSystem (system:
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
14
20
  {
15
21
  devShells.default = pkgs.mkShell {
16
22
  buildInputs = with pkgs; [
17
- (ruby_3_4.override {
18
- jemallocSupport = true;
23
+ (ruby_3_2.override {
24
+ jemallocSupport = false;
19
25
  docSupport = false;
20
26
  })
21
27