Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [Unreleased]

- Add Put.enum

## [0.2.0] - 2026-09-22

- **[Breaking]** Drop support for Ruby 2.6; require Ruby >= 3.1
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,31 @@ As you might be able to guess, `Put.nils_last` puts the nils last:
} # => [:every, :counts, nil]
```

### Put.enum(value, order:, nils_first: false)

When you're sorting by a discrete attribute — a role, a status, a tier — that
doesn't have a natural order of its own, `Put.enum` sorts a value by its
position in a list you provide, instead of you writing a `case`/`when`
translation table by hand:

```ruby
[:director, :staff, :manager].sort_by { |role|
[Put.enum(role, order: [:staff, :manager, :director])]
} # => [:staff, :manager, :director]
```

A value that isn't in `order` sorts after every value that is, rather than
raising:

```ruby
[:manager, :contractor, :staff].sort_by { |role|
[Put.enum(role, order: [:staff, :manager, :director])]
} # => [:staff, :manager, :contractor]
```

Like `Put.asc` and `Put.desc`, `Put.enum` also takes a `nils_first` keyword
argument (defaulting to `false`) for where `nil` values should sort.

### Put.debug(sorting_arrays)

If you see "comparison of Array with Array failed" and you don't have any idea
Expand Down
5 changes: 5 additions & 0 deletions lib/put.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
require_relative "put/puts_thing/first"
require_relative "put/puts_thing/last"
require_relative "put/puts_thing/in_order"
require_relative "put/puts_thing/enum"
require_relative "put/puts_thing/ascending"
require_relative "put/puts_thing/descending"
require_relative "put/puts_thing/nil_order"
Expand All @@ -29,6 +30,10 @@ def self.asc(value, nils_first: false)
PutsThing::Ascending.new(value, nils_first: nils_first)
end

def self.enum(value, order:, nils_first: false)
PutsThing::Enum.new(value, order: order, nils_first: nils_first)
end

def self.nils_first(value)
PutsThing::NilsFirst.new(value)
end
Expand Down
10 changes: 10 additions & 0 deletions lib/put/puts_thing/enum.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Put
class PutsThing
class Enum < InOrder
def initialize(value, order:, nils_first: false)
rank = value.nil? ? nil : (order.index(value) || Float::INFINITY)
super(rank, nils_first: nils_first)
end
end
end
end
78 changes: 78 additions & 0 deletions test/put/puts_thing/enum_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
require "test_helper"

module Put
class PutsThing
class EnumTest < Minitest::Test
def test_matched_values_sort_by_order_position
result = [:director, :staff, :manager].sort_by { |role|
[Put.enum(role, order: [:staff, :manager, :director])]
}

assert_equal [:staff, :manager, :director], result
end

def test_unmatched_value_sorts_after_every_matched_value_and_before_nil
result = [nil, :contractor, :director, :staff].sort_by { |role|
[Put.enum(role, order: [:staff, :director])]
}

assert_equal [:staff, :director, :contractor, nil], result
end

def test_nil_sorts_last_by_default
result = [nil, :staff, :director].sort_by { |role|
[Put.enum(role, order: [:staff, :director])]
}

assert_equal [:staff, :director, nil], result
end

def test_nil_sorts_first_when_nils_first_is_true
result = [:staff, nil, :director].sort_by { |role|
[Put.enum(role, order: [:staff, :director], nils_first: true)]
}

assert_equal [nil, :staff, :director], result
end

def test_nil_sorts_after_matched_and_unmatched_values_by_default
result = [nil, :contractor, :director, :staff].sort_by { |role|
[Put.enum(role, order: [:staff, :director])]
}

assert_equal [:staff, :director, :contractor, nil], result
end

def test_nil_sorts_before_matched_and_unmatched_values_when_nils_first_is_true
result = [:staff, :contractor, nil, :director].sort_by { |role|
[Put.enum(role, order: [:staff, :director], nils_first: true)]
}

assert_equal [nil, :staff, :director, :contractor], result
end

def test_repeated_order_entry_ranks_by_first_occurrence
result = [:director, :staff].sort_by { |role|
[Put.enum(role, order: [:staff, :director, :staff])]
}

assert_equal [:staff, :director], result
end

def test_nil_entry_in_order_has_no_effect_on_ranking
result = [nil, :director, :staff].sort_by { |role|
[Put.enum(role, order: [nil, :staff, :director])]
}

assert_equal [:staff, :director, nil], result
end

def test_same_rank_values_compare_as_equal
a = Put.enum(:staff, order: [:staff, :director])
b = Put.enum(:staff, order: [:staff, :director])

assert_equal 0, a <=> b
end
end
end
end
19 changes: 19 additions & 0 deletions test/put_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,25 @@ def test_put
assert_includes result.last(2), noah2
end

Employee = Struct.new(:name, :role, keyword_init: true)

def test_put_with_enum
employees = [
dana = Employee.new(name: "Dana", role: :director),
abe = Employee.new(name: "Abe", role: :staff),
cy = Employee.new(name: "Cy", role: :staff)
]

result = employees.sort_by { |employee|
[
Put.enum(employee.role, order: [:staff, :manager, :director]),
Put.asc(employee.name)
]
}

assert_equal [abe, cy, dana], result
end

Bot = Struct.new(:model, :age, keyword_init: true)

def test_debug
Expand Down
Loading