sortsmith 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2145a4f96e357b7c580dd3bbd5c672120788c422d76737fcf7015539ace75c7
4
- data.tar.gz: 80376b95b0cb7cee7a0b741a69d90a91307384623007b63384a1551871fdb2d5
3
+ metadata.gz: 16b369873631fe1de4c59f32bbbf5fbdb383ee1b3924a864ccd11a2ac6c4f1d5
4
+ data.tar.gz: cb329eecad62b7cf05e13624258fc09dd4a21e0aab192e50774a8e2a6c6beed3
5
5
  SHA512:
6
- metadata.gz: 5de79e4da81b5dbf9096a9c0cbf21dd6c00500a89b8fcec679b3116b324864b3b70bf8d8e4759069412dc0979eeadbe5e3a05e8da28016ea2638de7ab8fbc54a
7
- data.tar.gz: 1e7270386a18f165954b41810143d89920f66195f311826a19f4465a60c8865cbf3dd9e0de29cc7408d9bd41cbac43f8803030aa5724bc64e0e63f96e077f456
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,45 @@ 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!`
15
55
 
16
56
  ## [0.9.0] - 12025-07-06
17
57
 
@@ -24,23 +64,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
24
64
  ### Added
25
65
 
26
66
  #### Core API Transformation
67
+
27
68
  - **Fluent API**: Direct extension of `Enumerable#sort_by` for natural Ruby integration
28
69
  - **Chainable Interface**: Method chaining that reads like English: `users.sort_by.dig(:name).downcase.desc.sort`
29
70
  - **Universal `dig` Method**: Single extraction method that works with hashes, objects, and nested structures
30
71
  - **Indifferent Key Access**: Handle mixed symbol/string hash keys with `dig(:name, indifferent: true)`
31
72
 
32
73
  #### New Extraction Methods
74
+
33
75
  - `dig(*identifiers, indifferent: false)` - Extract values from hashes, objects, or nested structures
34
76
  - Support for nested extraction: `dig(:user, :profile, :email)`
35
77
  - Automatic fallback to method calls for non-hash objects
36
78
 
37
79
  #### Enhanced Modifiers
80
+
38
81
  - `downcase` / `upcase` - Case transformations with automatic type checking
39
82
  - `insensitive` - Alias for `downcase` for semantic clarity
40
83
  - `asc` / `desc` - Explicit sort direction control
41
84
  - Smart modifier chaining that only affects compatible data types
42
85
 
43
86
  #### Multiple Terminators
87
+
44
88
  - `sort` - Returns new sorted array (non-mutating)
45
89
  - `sort!` - Mutates original array in place
46
90
  - `reverse` - Shorthand for `desc.sort`
@@ -48,6 +92,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
48
92
  - `to_a` - Alias for `sort` for semantic clarity
49
93
 
50
94
  #### Backward Compatibility
95
+
51
96
  - `sort_by` with block maintains original Ruby behavior
52
97
  - `sort_by` without block returns Sortsmith::Sorter instance
53
98
  - Zero breaking changes for existing Ruby code
@@ -55,16 +100,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
55
100
  ### Changed
56
101
 
57
102
  #### API Design Philosophy
103
+
58
104
  - **Before**: `Sortsmith::Sorter.new(collection).by_key(:name).case_insensitive.desc.sort`
59
105
  - **After**: `collection.sort_by.dig(:name).insensitive.desc.sort`
60
106
 
61
107
  #### Improved Ergonomics
108
+
62
109
  - No more explicit `Sorter.new()` instantiation required
63
110
  - Tab-completable method discovery
64
111
  - Natural language flow in method chains
65
112
  - Unified `dig` method replaces separate `by_key`/`by_method` methods
66
113
 
67
114
  #### Enhanced Ruby Version Support
115
+
68
116
  - **Restored Ruby 3.0 and 3.1 compatibility** - Previously removed in v0.2.0, now supported again
69
117
  - Full compatibility matrix: Ruby 3.0.7, 3.1.7, 3.2.8, 3.3.8, 3.4.4
70
118
  - Expanded from Ruby 3.2+ requirement back to Ruby 3.0+ for broader accessibility
@@ -72,6 +120,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
72
120
  ### Removed
73
121
 
74
122
  #### Legacy API (Breaking Changes)
123
+
75
124
  - `by_key` method (replaced by `dig`)
76
125
  - `by_method`/`by_attribute` methods (replaced by `dig`)
77
126
  - `case_insensitive` method (replaced by `insensitive`/`downcase`)
@@ -122,11 +171,13 @@ objects.sort_by.dig(:calculate_score).sort
122
171
  ## [0.1.1] - 12025-01-15
123
172
 
124
173
  ### Changed
174
+
125
175
  - Improved handling of non-string objects when sorting
126
176
 
127
177
  ## [0.1.0] - 12025-01-14
128
178
 
129
179
  ### Added
180
+
130
181
  - Initial implementation of `Sortsmith::Sorter`
131
182
  - Support for case-insensitive sorting
132
183
  - Support for sorting by hash keys and object methods
@@ -134,7 +185,8 @@ objects.sort_by.dig(:calculate_score).sort
134
185
  - Type checking with Steep/RBS
135
186
  - GitHub Actions workflow for automated testing and type checking
136
187
 
137
- [unreleased]: https://github.com/itsthedevman/sortsmith/compare/v0.9.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
138
190
  [0.9.0]: https://github.com/itsthedevman/sortsmith/compare/v0.2.0...v0.9.0
139
191
  [0.2.0]: https://github.com/itsthedevman/sortsmith/compare/v0.1.1...v0.2.0
140
192
  [0.1.1]: https://github.com/itsthedevman/sortsmith/compare/v0.1.0...v0.1.1
data/README.md CHANGED
@@ -1,7 +1,7 @@
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.0+-ruby)
4
+ ![Ruby Version](https://img.shields.io/badge/ruby-3.1+-ruby)
5
5
  [![Tests](https://github.com/itsthedevman/sortsmith/actions/workflows/main.yml/badge.svg)](https://github.com/itsthedevman/sortsmith/actions/workflows/main.yml)
6
6
 
7
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.
@@ -14,7 +14,7 @@ users.sort_by { |user| user[:name].downcase }.reverse
14
14
  users.sort_by.dig(:name).downcase.reverse
15
15
  ```
16
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.
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
18
 
19
19
  ## Table of Contents
20
20
 
@@ -55,12 +55,12 @@ users.sort_by.dig(:name, indifferent: true).insensitive.desc.sort
55
55
  ```
56
56
 
57
57
  **Features:**
58
+
58
59
  - **Fluent chaining** - Reads like English
59
60
  - **Universal extraction** - Works with hashes, objects, and nested data
60
61
  - **Indifferent key access** - Handles mixed symbol/string keys automatically
61
62
  - **Nil-safe** - Graceful handling of missing data
62
63
  - **Minimal overhead** - Extends existing Ruby methods without breaking compatibility
63
- - **Tab-completable** - Discoverable API through your editor
64
64
 
65
65
  ## Installation
66
66
 
@@ -84,17 +84,17 @@ $ gem install sortsmith
84
84
 
85
85
  ## Quick Start
86
86
 
87
- Sortsmith extends Ruby's `sort_by` method. When called without a block, it returns a chainable sorter:
87
+ Sortsmith extends Ruby's `sort_by` method with a fluent, chainable API. Use it with or without a block for maximum flexibility:
88
88
 
89
89
  ```ruby
90
90
  require "sortsmith"
91
91
 
92
- # Basic string sorting
92
+ # Direct syntax for simple cases (NEW!)
93
93
  names = ["charlie", "Alice", "bob"]
94
- names.sort_by.insensitive.sort
94
+ names.sort_by(:name).insensitive.sort
95
95
  # => ["Alice", "bob", "charlie"]
96
96
 
97
- # Hash sorting
97
+ # Or use the chainable API for complex scenarios
98
98
  users = [
99
99
  { name: "Charlie", age: 25 },
100
100
  { name: "Alice", age: 30 },
@@ -104,6 +104,10 @@ users = [
104
104
  users.sort_by.dig(:name).sort
105
105
  # => [{ name: "Alice", age: 30 }, { name: "Bob", age: 20 }, { name: "Charlie", age: 25 }]
106
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
+
107
111
  # The original sort_by with blocks still works exactly the same!
108
112
  users.sort_by { |u| u[:age] }
109
113
  # => [{ name: "Bob", age: 20 }, { name: "Charlie", age: 25 }, { name: "Alice", age: 30 }]
@@ -113,10 +117,10 @@ users.sort_by { |u| u[:age] }
113
117
 
114
118
  Sortsmith uses a simple pipeline concept where each step is **optional** except for the terminator:
115
119
 
116
- 1. **Extract** - Get the value to sort by (`dig`) - *optional*
117
- 2. **Transform** - Modify the value for comparison (`downcase`, `upcase`) - *optional*
118
- 3. **Order** - Choose sort direction (`asc`, `desc`) - *optional*
119
- 4. **Execute** - Run the sort (`sort`, `sort!`, `reverse`) - **required**
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**
120
124
 
121
125
  ```ruby
122
126
  collection.sort_by.dig(:field).downcase.desc.sort
@@ -126,6 +130,7 @@ collection.sort_by.dig(:field).downcase.desc.sort
126
130
  ```
127
131
 
128
132
  **Minimal example:**
133
+
129
134
  ```ruby
130
135
  # This works! (though not particularly useful)
131
136
  users.sort_by.sort # Same as users.sort
@@ -141,59 +146,29 @@ Each step builds on the previous ones, so you can mix and match based on what yo
141
146
 
142
147
  ## Usage Examples
143
148
 
144
- ### Array Sorting
149
+ ### Simple Direct Syntax
145
150
 
146
151
  ```ruby
147
- # Basic sorting
148
- words = ["banana", "Apple", "cherry"]
149
- words.sort_by.sort
150
- # => ["Apple", "banana", "cherry"]
151
-
152
- # Case-insensitive
153
- words.sort_by.insensitive.sort
154
- # => ["Apple", "banana", "cherry"]
155
-
156
- # Descending order
157
- words.sort_by.downcase.desc.sort
158
- # => ["cherry", "banana", "Apple"]
159
-
160
- # In-place mutation
161
- words.sort_by.insensitive.sort!
162
- # Modifies the original array
163
- ```
164
-
165
- ### Hash Collections
152
+ # Clean and direct for common operations
153
+ words = ["elephant", "cat", "butterfly"]
154
+ words.sort_by(:length).desc.sort
155
+ # => ["butterfly", "elephant", "cat"]
166
156
 
167
- ```ruby
157
+ # Works great with hashes
168
158
  users = [
169
- { name: "Charlie", score: 85, team: "red" },
170
- { name: "Alice", score: 92, team: "blue" },
171
- { name: "bob", score: 78, team: "red" }
172
- ]
173
-
174
- # Sort by name (case-sensitive)
175
- users.sort_by.dig(:name).sort
176
- # => [{ name: "Alice" }, { name: "Charlie" }, { name: "bob" }]
177
-
178
- # Sort by name (case-insensitive)
179
- users.sort_by.dig(:name).insensitive.sort
180
- # => [{ name: "Alice" }, { name: "bob" }, { name: "Charlie" }]
181
-
182
- # Sort by score (descending)
183
- users.sort_by.dig(:score).desc.sort
184
- # => [{ score: 92 }, { score: 85 }, { score: 78 }]
185
-
186
- # Multiple field extraction (nested digging)
187
- data = [
188
- { user: { profile: { name: "Charlie" } } },
189
- { user: { profile: { name: "Alice" } } }
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 },
190
164
  ]
191
165
 
192
- data.sort_by.dig(:user, :profile, :name).sort
193
- # => [{ user: { profile: { name: "Alice" } } }, ...]
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 }]
194
169
  ```
195
170
 
196
- ### Object Collections
171
+ ### Object Method Sorting
197
172
 
198
173
  ```ruby
199
174
  User = Struct.new(:name, :age, :city)
@@ -204,20 +179,59 @@ users = [
204
179
  User.new("bob", 20, "Chicago")
205
180
  ]
206
181
 
207
- # Sort by any method/attribute
208
- users.sort_by.dig(:name).insensitive.sort
182
+ # Sort by any method or attribute
183
+ users.sort_by.method(:name).insensitive.sort
209
184
  # => [User.new("Alice"), User.new("bob"), User.new("Charlie")]
210
185
 
211
- users.sort_by.dig(:age).reverse
212
- # => [User.new("Alice", 30), User.new("Charlie", 25), User.new("bob", 20)]
186
+ # Or use the semantic alias
187
+ users.sort_by.attribute(:age).desc.first
188
+ # => User.new("Alice", 30, "LA")
213
189
 
214
- # Works with any object that responds to the method
215
- class Score
216
- def calculate; rand(100); end
190
+ # Methods with arguments work too
191
+ class Product
192
+ def price_in(currency)
193
+ # calculation logic
194
+ end
217
195
  end
218
196
 
219
- scores = [Score.new, Score.new, Score.new]
220
- scores.sort_by.dig(:calculate).desc.sort
197
+ products.sort_by.method(:price_in, "USD").sort
198
+ ```
199
+
200
+ ### Hash Collections with Multiple Access Patterns
201
+
202
+ ```ruby
203
+ users = [
204
+ { name: "Charlie", score: 85, team: "red" },
205
+ { name: "Alice", score: 92, team: "blue" },
206
+ { name: "bob", score: 78, team: "red" }
207
+ ]
208
+
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
213
+
214
+ # Case handling with explicit naming
215
+ users.sort_by(:name).case_insensitive.reverse
216
+ # => [{ name: "bob" }, { name: "Charlie" }, { name: "Alice" }]
217
+ ```
218
+
219
+ ### Seamless Enumerable Integration
220
+
221
+ ```ruby
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
221
235
  ```
222
236
 
223
237
  ### Mixed Key Types
@@ -251,17 +265,26 @@ api_response.sort_by.dig(:name, indifferent: true).sort
251
265
 
252
266
  ## API Reference
253
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
+
254
274
  ### Extractors
255
275
 
256
276
  - **`dig(*identifiers, indifferent: false)`** - Extract values from hashes, objects, or nested structures
257
- - Works with hash keys, object methods, and nested paths
258
- - `indifferent: true` normalizes hash keys for consistent lookup across string/symbol keys
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!)
259
281
 
260
282
  ### Modifiers
261
283
 
262
284
  - **`downcase`** - Convert extracted strings to lowercase for comparison
263
285
  - **`upcase`** - Convert extracted strings to uppercase for comparison
264
286
  - **`insensitive`** - Alias for `downcase` (semantic clarity)
287
+ - **`case_insensitive`** - Alias for `downcase` (explicit case handling) (NEW!)
265
288
 
266
289
  ### Ordering
267
290
 
@@ -273,33 +296,28 @@ api_response.sort_by.dig(:name, indifferent: true).sort
273
296
  - **`sort`** - Execute sort and return new array
274
297
  - **`sort!`** - Execute sort and mutate original array
275
298
  - **`to_a`** - Alias for `sort`
299
+ - **`to_a!`** - Alias for `sort!` (NEW!)
276
300
  - **`reverse`** - Shorthand for `desc.sort`
277
301
  - **`reverse!`** - Shorthand for `desc.sort!`
278
302
 
279
- ## Migration from v0.2.x
280
-
281
- The v0.3.x API is more concise and intuitive:
282
-
283
- ```ruby
284
- # v0.2.x (OLD - no longer works)
285
- Sortsmith::Sorter.new(users).by_key(:name).case_insensitive.desc.sort
303
+ ### Delegated Enumerable Methods (NEW!)
286
304
 
287
- # v0.3.x (NEW)
288
- users.sort_by.dig(:name).insensitive.desc.sort
305
+ The following methods execute the sort and delegate to the resulting array:
289
306
 
290
- # v0.2.x (OLD)
291
- Sortsmith::Sorter.new(objects).by_method(:calculate_score).sort
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
292
312
 
293
- # v0.3.x (NEW)
294
- objects.sort_by.dig(:calculate_score).sort
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
295
319
  ```
296
320
 
297
- **Key Changes:**
298
- - `by_key` → `dig`
299
- - `by_method`/`by_attribute` → `dig`
300
- - `case_insensitive` → `insensitive` or `downcase`
301
- - No more manual `Sorter.new()` - just call `sort_by` without a block
302
-
303
321
  ## Development
304
322
 
305
323
  ### Prerequisites
@@ -310,11 +328,13 @@ objects.sort_by.dig(:calculate_score).sort
310
328
  ### Setting Up the Development Environment
311
329
 
312
330
  With Nix:
331
+
313
332
  ```bash
314
333
  direnv allow
315
334
  ```
316
335
 
317
336
  Without Nix:
337
+
318
338
  ```bash
319
339
  bundle install
320
340
  ```
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": {
@@ -33,43 +33,77 @@ module Enumerable
33
33
  alias_method :og_sort_by, :sort_by
34
34
 
35
35
  ##
36
- # Enhanced sort_by that supports both traditional block usage and chainable API.
36
+ # Enhanced sort_by that supports both traditional block usage and direct field extraction.
37
37
  #
38
- # When called with a block, behaves exactly like Ruby's original `sort_by`.
39
- # When called without a block, returns a {Sortsmith::Sorter} instance that
40
- # provides a chainable interface for complex sorting operations.
38
+ # This method extends Ruby's built-in `sort_by` to provide a fluent, chainable API
39
+ # for sorting operations. When called with a block, it behaves exactly like the
40
+ # original `sort_by`. When called without a block, it returns a {Sortsmith::Sorter}
41
+ # instance for method chaining.
41
42
  #
42
- # This dual behavior ensures complete backward compatibility while unlocking
43
- # powerful new sorting capabilities.
43
+ # The direct syntax (`sort_by(field)`) provides a concise way to sort by a specific
44
+ # field or method without verbose chaining, making simple sorting operations more
45
+ # readable and intuitive.
44
46
  #
47
+ # @param field [Symbol, String, nil] Optional field name for direct extraction
48
+ # @param positional [Array] Additional positional arguments for extraction
49
+ # @param keyword [Hash] Additional keyword arguments for extraction
45
50
  # @param block [Proc, nil] Optional block for traditional sort_by behavior
46
- # @return [Array, Sortsmith::Sorter] Array when block given, Sorter when block nil
47
51
  #
48
- # @example Traditional usage (unchanged)
52
+ # @return [Array, Sortsmith::Sorter] Array when block given, Sorter instance otherwise
53
+ #
54
+ # @example Traditional block usage (unchanged)
49
55
  # users.sort_by { |user| user.name.downcase }
50
56
  # # => sorted array
51
57
  #
52
- # @example Chainable usage (new)
53
- # users.sort_by.dig(:name).downcase.desc.sort
58
+ # @example Direct field syntax (new)
59
+ # users.sort_by(:name).insensitive.sort
54
60
  # # => sorted array via method chaining
55
61
  #
56
- # @example Mixed key types with indifferent access
62
+ # @example Direct syntax with immediate result
63
+ # users.sort_by(:score).desc.first(3)
64
+ # # => top 3 users by score
65
+ #
66
+ # @example Chainable interface without field
67
+ # users.sort_by.dig(:profile, :email).sort
68
+ # # => sorted by nested email field
69
+ #
70
+ # @example Mixed key types
57
71
  # mixed_data = [
58
72
  # { name: "Bob" }, # symbol key
59
73
  # { "name" => "Alice" } # string key
60
74
  # ]
61
- # mixed_data.sort_by.dig(:name, indifferent: true).sort
75
+ # mixed_data.sort_by(:name, indifferent: true).sort
62
76
  # # => handles both key types gracefully
63
77
  #
64
- # @see Sortsmith::Sorter#dig
65
- # @see Sortsmith::Sorter#sort
66
- # @see #og_sort_by Original sort_by behavior
78
+ # @example Object method sorting
79
+ # products.sort_by(:calculate_price).desc.sort
80
+ # # => sorted by calculated price method
81
+ #
82
+ # @example Dynamic field selection
83
+ # sort_field = params[:sort_by] # might be nil
84
+ # users.sort_by(sort_field).sort
85
+ # # => gracefully handles nil field
67
86
  #
68
- # @since 0.1.0
87
+ # @example Integration with enumerable methods
88
+ # users.sort_by(:created_at).desc.take(10)
89
+ # # => newest 10 users without breaking the chain
69
90
  #
70
- def sort_by(&block)
71
- return Sortsmith::Sorter.new(self) if block.nil?
91
+ # @raise [ArgumentError] When extraction results in incomparable types
92
+ #
93
+ # @note This method maintains full backward compatibility with Ruby's original sort_by
94
+ # @note When field is nil, returns a plain Sorter instance for manual chaining
95
+ #
96
+ # @see Sortsmith::Sorter The chainable sorting interface
97
+ # @see #extract Universal extraction method
98
+ # @see Enumerable#sort_by Original Ruby method (aliased as og_sort_by)
99
+ # @since 1.0.0
100
+ #
101
+ def sort_by(field = nil, *positional, **keyword, &block)
102
+ return og_sort_by(&block) if block
103
+
104
+ sorter = Sortsmith::Sorter.new(self)
105
+ return sorter if field.nil?
72
106
 
73
- og_sort_by(&block)
107
+ sorter.extract(field, *positional, **keyword)
74
108
  end
75
109
  end