Tim's written a short guide to the Comparable module found in Ruby.
One of my favourite Ruby features is the Comparable module. By mixing this module into your classes, you can compare your objects using the
>= operators, as well as the
clamp methods. This allows you to quickly add some useful functionality with the minimum of coding effort. Hopefully, the following simple example will demonstrate some of the power of the Comparable module.
Let’s assume we are writing an application which deals with a football league consisting of a number of football teams. Each team’s placing in the league is determined by the number of points they have been awarded, say three points for a win and a single point for a draw. In this situation, it would be very easy to compare two team’s relative placings in the league. For example, your code might look like the following:
def FootballTeam attr_accessor :points def initialize points @points = points end end
The relative placings of two teams can be determined using their points values. Let’s create a couple of teams and assign them some points:
chelsea = FootballTeam.new(10) arsenal = FootballTeam.new(12)
In this case,
chelsea.points > arsenal.points would evaluate to false, while
arsenal.points <= chelsea.points would evaluate to true.
This is all very well, but let’s assume that if two teams have the same number of points, their relative placings in the league are determined by their goal difference - the number of goals the team has scored minus the number of goals the team has conceded. Our code might change to the following:
def FootballTeam attr_accessor :points, :goals_scored, :goals_conceded def initialize points, goals_scored, goals_conceded @points = points @goals_scored = goals_scored @goals_conceded = goals_conceded end def goal_difference @goals_scored - @goals_conceded end end
We’ve added code to allow the number of goals scored and the number of goals conceded to be set when the team object is created, and a method to allow the goal difference to be calculated. However, we can no longer determine two team’s relative placings simply by comparing their points - we need to take goal difference into account as well. One approach would be to override each of the comparison operators, so that for example we would add:
def > other_team if self.points == other_team.points self.goal_difference > other_team.goal_difference else self.points > other_team.points end end
Similar methods would be required for
However, this is where the Comparable module can help us. To use the Comparable module, we simply need to add the line include Comparable in our class definition, and add a method called <=> (sometimes known as the spaceship operator). The module will handle everything else, and will provide the other comparison methods. This sounds a little too good to be true, so let’s update the code, and see the Comparison module in action:
def FootballTeam include Comparable attr_accessor :points, :goals_scored, :goals_conceded def initialize points, goals_scored, goals_conceded @points = points @goals_scored = goals_scored @goals_conceded = goals_conceded end def goal_difference @goals_scored - @goals_conceded end def <=> other_team if self.points < other_team.points -1 elsif self.points > other_team.points 1 else if self.goal_difference < other_team.goal_difference -1 elsif self.goal_difference > other_team.goal_difference 1 else 0 end end end end
(We’ll refactor this in a moment.)
Now we can set up a few teams and compare them:
chelsea = FootballTeam.new(10, 11, 13) arsenal = FootballTeam.new(12, 12, 9) liverpool = FootballTeam.new(12, 12, 10)
chelsea > arsenal will evaluate to false, while
arsenal >= chelsea will evaluate to true. Additionally,
liverpool < arsenal will evaluate to true, and
liverpool.between?(chelsea, arsenal) will also evaluate to true.
We’ve gained quite a lot of useful functionality by defining a single function.
We can now refine our code using the spaceship operator inside our
def <=> other_team result = self.points <=> other_team.points if result == 0 result = self.goal_difference <=> other_team.goal_difference end result end
Hopefully, this post has given you an insight into why I’m a big fan of the Comparable module, and given you some ideas of how you can make use of it in your own code. I’d love to hear about the uses other coders have put it to - why not leave a comment?
We would love to hear from you so let's get in touch!