The equality-test operators is a set of members that consist of ==(equals), !=(not equal), > (greater than), <(less than), >=(greater or equal), and <= (less or equal), with the most common being the == operator.
We can see that with both Integer
and Float
which are subclasses of the Numeric
class. Objects of both classes can be compared using the equality-test operators because they include the Comparable module, and override the comparison method <=>
, also called the spaceship operator or spaceship method.
Let's imagine in an e-commerce sight we have Product
s. A product can have many different Price
per customer/conuntry. The modeling might look like this
class Price
attr_accessor :amount_cents
def initialize(amount_cents)
self.amount_cents = amount_cents
end
end
Let's imagine that we need to compare the price of two products(for whatever reason we need to). To compare the amount_cents
we might do something like
if price_1.amount_cents < price_2.amount_cents
puts "price_1 is less than price_2"
elsif price_1.amount_cents > price_2.amount_cents
puts "price_1 is greater than price_2"
else
puts "both prices are equal"
end
To compare the price objects we simply need to do so on the amount_cents
attribute, since it's an instance of Numeric
(either float or integer) it can be compared using the equality-test operators mentioned above.
But, we cannot do something like this
if price_1 < price_2
puts "price_1 is less than price_2"
elsif price_1 > price_2
puts "price_1 is greater than price_2"
else
puts "both prices are equal"
end
Because our class Price
does not include the Comparable
module, thus it does not support this. If we try to we get
undefined method `<' for #<Price:0x00007fbd98992de0 @amount_cents=10> (NoMethodError)
The ruby interpreter tells us that the class does not have support for the <
method. Because we did not define this.
Lo! and behold the Comparable
module in action.
Let's allow our Price
class to work nice with the equality-test operators. To do that we need to do
- include the Comparable module
- override the
<=>
method
Let's rewrite the Product
class to support that.
class Price
include Comparable
attr_accessor :amount_cents
def initialize(amount_cents)
self.amount_cents = amount_cents
end
def <=>(other_price)
if self.amount_cents < other_price.amount_cents
-1
elsif self.amount_cents > other_price.amount_cents
1
else
0
end
end
end
Notice the return value for the <=>
method.
- -1 means that it's less than the passed argument.
- 1 means that it's greater than the passed argument.
- 0 means both prices are equal.
Now, we can run this code
if price_1 < price_2
puts "price_1 is less than price_2"
elsif price_1 > price_2
puts "price_1 is greater than price_2"
else
puts "both prices are equal"
end
and get the output
price_1 is less than price_2
=> nil
Including the Comparable module provides us with more methods more than just the equality-test operators. For example, using the Price
class determine if a price is between two other price
price_1 = Price.new 100
price_2 = Price.new 200
price_3 = Price.new 150
price_3.between? price_1, price_2
The output would be true
We can also sort them!
[price_3, price_1, price_2].sort
[<Price:0x00007fa65e225d38 @amount_cents=100>,
<Price:0x00007fa65e0be9b8 @amount_cents=150>,
<Price:0x00007fa661b965e0 @amount_cents=200>]
Note
We can piggy-back the method call to the attribute itself. Since it's an instance of Numeric
. We can shorten the method to
class Price
include Comparable
attr_accessor :amount_cents
def initialize(amount_cents)
self.amount_cents = amount_cents
end
def <=>(other_price)
self.amount_cents <=> other_price.amount_cents
end
end
Conclusion
We can allow our classes to work well with the equality-test family members and save ourselves much time trying to access attributes for comparison. And as a side-effect our Ruby code becomes more natural and ruby-ish, and we type less :).
Thanks for reading, and Happy Coding!.
Top comments (5)
I would do
self.amount_cents <=> other.amount_cents
Damn. Yea you're right. We can piggy-back function to Ruby. Since it's a Numeric.
Just added it to the post!!. Thanks for pointing that out!
And yeah you should probably be using the money gem :)
Funny thing is, I am. Just couldn't think of a better example to illustrate the thingi!.