Comparable and Enumerable Modules
Comparable and Enumerable are two of Ruby's most powerful built-in mixins, and they embody a core Ruby design philosophy: implement one small, well-defined method, and inherit a rich, battle-tested API for free. Comparable grants ordering operators (<, >, <=, >=, ==, between?, clamp) to any class that defines <=>. Enumerable grants dozens of traversal, search, and transformation methods (map, select, reduce, sort, find, min, max, include?, and more) to any class that defines each. Both are includable modules — include Comparable or include Enumerable — not superclasses, so they can be mixed into any class hierarchy.
Cricket analogy: Just as a franchise only needs to register one player database schema to unlock ranking, filtering, and search across an entire league portal, a class only needs to define <=> or each to unlock Ruby's whole toolkit for free.
Comparable: order from a single method
To make a class sortable and comparable, define <=> (the spaceship operator), which must return -1, 0, or 1 depending on whether self is less than, equal to, or greater than the argument (or nil if the objects are not comparable). Once <=> exists and Comparable is included, the class automatically gains <, <=, ==, >=, >, between?, and clamp.
Cricket analogy: The spaceship operator works like an umpire's single lbw decision, out, not out, or referred, that then automatically determines the scoreboard, the review outcome, and the match result, one -1/0/1 call cascades into everything else.
class Temperature
include Comparable
attr_reader :degrees
def initialize(degrees)
@degrees = degrees
end
def <=>(other)
degrees <=> other.degrees
end
end
freezing = Temperature.new(32)
boiling = Temperature.new(212)
freezing < boiling #=> true
[boiling, freezing].sort.map(&:degrees) #=> [32, 212]
freezing.clamp(Temperature.new(50), boiling).degrees #=> 50Enumerable: iteration from a single method
To make a custom collection class fully enumerable, define an each method that yields elements one at a time, then include Enumerable. This single method unlocks map, select, reject, reduce, sort_by, group_by, min_by, max_by, count, first, take, all?, any?, none?, and dozens more — all implemented in terms of each internally.
Cricket analogy: A scorer who commits to reading out deliveries one ball at a time (each) lets analysts derive strike rate (map), boundary count (select), and best batting spell (max_by) without the scorer doing that work themselves.
class Playlist
include Enumerable
def initialize(songs)
@songs = songs
end
def each
return enum_for(:each) unless block_given?
@songs.each { |song| yield song }
end
end
playlist = Playlist.new(['Song A', 'Song B', 'Song C'])
playlist.map(&:upcase) #=> ['SONG A', 'SONG B', 'SONG C']
playlist.select { |s| s.include?('B') } #=> ['Song B']
playlist.count #=> 3This 'one method unlocks many' pattern is a form of the Template Method design pattern: the module defines high-level algorithms (like sort, min_by) in terms of a primitive operation (each or <=>) that subclasses/includers supply. It's a hallmark of Ruby's mixin-driven design, contrasting with languages that require explicitly implementing every interface method (e.g. Java's Comparable<T> still only needs compareTo, but collection interfaces like Iterable require more boilerplate).
Forgetting return enum_for(:each) unless block_given? inside each means calling playlist.each without a block (common when Enumerable methods call each internally to build a lazy Enumerator) will raise an error or behave incorrectly instead of returning an Enumerator.
- Comparable requires only
<=>and grants<,>,<=,>=,==,between?, andclamp. - Enumerable requires only
eachand grantsmap,select,reduce,sort_by,min_by,count, and dozens more. - Both are mixins included via
include ModuleName, usable in any class. <=>must return -1, 0, 1, or nil for incomparable objects.- Guard
eachwithreturn enum_for(:each) unless block_given?to support Enumerator chaining. - This pattern reflects Ruby's philosophy of maximizing behavior from a minimal required interface.
Practice what you learned
1. What single method must a class define to gain full Comparable functionality?
2. What single method must a class define to gain full Enumerable functionality?
3. What values should the `<=>` method return?
4. Why should a custom `each` method include `return enum_for(:each) unless block_given?`?
5. Are Comparable and Enumerable classes or modules?
Was this page helpful?
You May Also Like
Operator Overloading
Learn how Ruby implements operators as ordinary methods, letting you define custom `+`, `==`, `<<`, and other operators on your own classes for expressive, idiomatic APIs.
each, map, and select
Master the three most commonly used Enumerable methods — each for side effects, map for transformation, and select for filtering — and when to reach for which.
reduce and inject
Understand Ruby's reduce/inject method — how it folds a collection into a single accumulated value, and how to use it for sums, grouping, and building complex results.
Enumerable Deep Dive
Go beyond map and select to explore Enumerable's full toolkit — sort_by, group_by, partition, each_with_object, lazy enumerators, and more — for expressive collection processing.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics