Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 297
Richard Stokes Avatar asked Nov 14 '11 15:11

Richard Stokes


3 Answers

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

def <=>(other)
  [self.attr1, self.attr2] <=> [other.attr1, other.attr2]
end
like image 83
steenslag Avatar answered Nov 02 '22 10:11

steenslag


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
like image 7
Gavin Miller Avatar answered Nov 02 '22 10:11

Gavin Miller


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
like image 2
notapatch Avatar answered Nov 02 '22 09:11

notapatch