Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
49: Comparable Module and Custom Sorting
I see this happen all the time when developers first encounter the Comparable module: they think that simply adding include Comparable to the top of their class is like flipping a magic switch that suddenly makes their objects "sortable." They expect Ruby to somehow intuit how two instances of a custom class should be compared.
Including Comparable Isn't a Magic Switch
Let's look at why that doesn't work. Imagine we're building a system to track software versions. You might try something like this:
class SoftwareVersion
include Comparable
attr_reader :major, :minor
def initialize(major, minor)
@major = major
@minor = minor
end
end
v1 = SoftwareVersion.new(1, 2)
v2 = SoftwareVersion.new(2, 0)
v1 < v2 # This will raise a NoMethodError or return an error
If you run this, Ruby will complain. Even though we included Comparable, we haven't actually told Ruby how to compare two versions. Comparable is a "mixin" that provides the logic for <, >, <=, >=, and between?, but it does so by relying entirely on one single method: the spaceship operator <=>.
The Spaceship Operator is the Engine
To make Comparable work, you have to implement <=>. I call it the engine because it powers everything else. The spaceship operator is designed to return one of three things: -1 if the current object is smaller, 0 if they are equal, and 1 if the current object is larger.
Here is how we actually implement that for our software versions. I'll use a little Ruby shorthand here to keep it clean:
class SoftwareVersion
include Comparable
attr_reader :major, :minor
def initialize(major, minor)
@major = major
@minor = minor
end
def <=>(other)
return nil unless other.is_a?(SoftwareVersion)
if @major != other.major
@major <=> other.major
else
@minor <=> other.minor
end
end
end
v1 = SoftwareVersion.new(1, 10)
v2 = SoftwareVersion.new(2, 0)
v1 < v2 # => true
v1 == v2 # => false
Notice that inside the <=> method, I'm actually calling <=> on the integers themselves. Since Integers already implement the spaceship operator, we can just delegate the hard work to them. It's a clean, recursive way of thinking about comparison.
Choosing Between sort and sort_by
Once your class is Comparable, calling Array#sort on a list of those objects just works. But as you get into larger datasets, you'll realize there's a performance difference between sort and sort_by.
sort uses the <=> method repeatedly. If your comparison logic is expensive (like hitting a database or doing complex regex), sort will be slow because it calls that logic every time it compares two elements.
sort_by is different. It uses something called a Schwartzian Transform. It calculates the "sort key" for every element exactly once, stores it, and then sorts those keys. I usually reach for sort_by whenever I'm sorting by a specific attribute rather than the object's inherent value.
# This calls <=> many times
versions.sort
# This calls the block once per element, then sorts the results
versions.sort_by { |v| [v.major, v.minor] }
My rule of thumb: if you've implemented Comparable for a natural, inherent order, use sort. If you're doing a one-off sort based on a specific property, use sort_by.
📋 Practical Task
Implementing a Tournament Player Ranking System
You are building a tournament system. Each Player has a score and a wins count. You need to implement the Comparable module so that players can be ranked automatically.
- The primary ranking factor is
score(higher is better). - If two players have the same
score, the one with morewinsis ranked higher. - If both
scoreandwinsare identical, they are considered equal.
Requirements:
- Create a
Playerclass that includesComparable. - Implement the
<=>method to handle the logic described above. - Create an array of at least four
Playerobjects (including at least two with the same score but different win counts). - Use the
.sortmethod to display the players from lowest rank to highest rank.
There are no comments for now.