Richard Stokes
Richard Stokes

Reputation: 3552

Ruby - Using Comparable mixin to compare objects on two different attributes

Is there an easy way (i.e. using the spaceship operator) to define comparison in Ruby based on two different attributes? I.e. If I have a class that contains two attributes, attr1 and attr2, is there a Rubyesque way of comparing two instances of this class on attr1, and if they're equal then compare them on attr2?

Upvotes: 6

Views: 3881

Answers (3)

notapatch
notapatch

Reputation: 7173

Spaceship with 'not comparable'

Spaceship requires:

a <=> b :=
  if a < b then return -1
  if a = b then return  0
  if a > b then return  1
  if a and b are not comparable then return nil  <= I wanted this

Steenslag's solution wasn't giving me the nil when 'not comparable'.

Impementation

Added extra line for coping with 'not comparable':

def <=> other
  return nil unless other.is_a?(self.class)
  [self.attr1, self.attr2] <=> [other.attr1, other.attr2]
end

Upvotes: 2

steenslag
steenslag

Reputation: 80075

This is an easily extendible (to more attributes) way:

def <=>(other)
  [self.attr1, self.attr2] <=> [other.attr1, other.attr2]
end

Upvotes: 16

Gavin Miller
Gavin Miller

Reputation: 43835

The whole point of the comparable mixin is to provide a definition for the spaceship (comparison) operator. So if you want to do a comparison across two attributes, then do it. Here's an overly verbose example:

def <=>(obj)
  comparison = self.attr1 <=> obj.attr1

  if comparison == 0
    return self.attr2 <=> obj.attr2
  else
    return comparison
  end
end

Obviously the above assumes attr1, and attr2 both have definitions for the spaceship operator. As well you'll need to determine what constitutes greater than, and less than, which is likely a bit difficult across two attributes. Which suggests that comparable may not be the proper code for your scenario.


A more succinct and idiomatic way of writing this would be:

def <=>(obj)
  self.attr1 <=> obj.attr1 == 0 ? self.attr2 <=> obj.attr2 : self.attr1 <=> obj.attr1
end

Upvotes: 7

Related Questions