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 +4 -4
- data/CHANGELOG.md +53 -1
- data/README.md +106 -86
- data/flake.lock +3 -3
- data/lib/sortsmith/core_ext/enumerable.rb +53 -19
- data/lib/sortsmith/sorter.rb +536 -82
- data/lib/sortsmith/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16b369873631fe1de4c59f32bbbf5fbdb383ee1b3924a864ccd11a2ac6c4f1d5
|
4
|
+
data.tar.gz: cb329eecad62b7cf05e13624258fc09dd4a21e0aab192e50774a8e2a6c6beed3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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/
|
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
|
[](https://badge.fury.io/rb/sortsmith)
|
4
|
-

|
5
5
|
[](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
|
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.
|
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
|
-
#
|
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
|
-
#
|
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`) -
|
117
|
-
2. **Transform** - Modify the value for comparison (`downcase`, `upcase
|
118
|
-
3. **Order** - Choose sort direction (`asc`, `desc`) -
|
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
|
-
###
|
149
|
+
### Simple Direct Syntax
|
145
150
|
|
146
151
|
```ruby
|
147
|
-
#
|
148
|
-
words = ["
|
149
|
-
words.sort_by.sort
|
150
|
-
# => ["
|
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
|
-
|
157
|
+
# Works great with hashes
|
168
158
|
users = [
|
169
|
-
{ name: "
|
170
|
-
{ name: "
|
171
|
-
{ name: "
|
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
|
-
|
193
|
-
|
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
|
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
|
208
|
-
users.sort_by.
|
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
|
-
|
212
|
-
|
186
|
+
# Or use the semantic alias
|
187
|
+
users.sort_by.attribute(:age).desc.first
|
188
|
+
# => User.new("Alice", 30, "LA")
|
213
189
|
|
214
|
-
#
|
215
|
-
class
|
216
|
-
def
|
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
|
-
|
220
|
-
|
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
|
-
|
258
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
291
|
-
|
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
|
-
|
294
|
-
|
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":
|
24
|
-
"narHash": "sha256-
|
23
|
+
"lastModified": 1751792365,
|
24
|
+
"narHash": "sha256-J1kI6oAj25IG4EdVlg2hQz8NZTBNYvIS0l4wpr9KcUo=",
|
25
25
|
"owner": "NixOS",
|
26
26
|
"repo": "nixpkgs",
|
27
|
-
"rev": "
|
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
|
36
|
+
# Enhanced sort_by that supports both traditional block usage and direct field extraction.
|
37
37
|
#
|
38
|
-
#
|
39
|
-
# When called
|
40
|
-
#
|
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
|
-
#
|
43
|
-
#
|
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
|
-
# @
|
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
|
53
|
-
# users.sort_by
|
58
|
+
# @example Direct field syntax (new)
|
59
|
+
# users.sort_by(:name).insensitive.sort
|
54
60
|
# # => sorted array via method chaining
|
55
61
|
#
|
56
|
-
# @example
|
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
|
75
|
+
# mixed_data.sort_by(:name, indifferent: true).sort
|
62
76
|
# # => handles both key types gracefully
|
63
77
|
#
|
64
|
-
# @
|
65
|
-
#
|
66
|
-
#
|
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
|
-
# @
|
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
|
-
|
71
|
-
|
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
|
-
|
107
|
+
sorter.extract(field, *positional, **keyword)
|
74
108
|
end
|
75
109
|
end
|