HELLO, I’M SERHAT AND THIS IS MY FANCY TITLE.

Comparing Ruby Objects (Class Instances) and Equity in Ruby

Recently I needed to compare two different Ruby objects (or Class instances) in a project. I just Googled around a little bit, but found no clue. Then I just continued looking around and came across with a great article written by Alan Skorkin (Skorks).

Here is my sample Money::Cash class that I'm going to be using during this article:

module Money
  class Cash
    attr_accessor :amount, :currency

    def initialize(amount, currency)
      @amount = amount.round(2)
      @currency = currency.upcase
    end

    def convert_to(rate)
      rate = rate.upcase
      current_rate = Money::Rate.all.find { |r| r.code == @currency }

      if current_rate.nil?
        raise TypeError, "#{rate} has not defined for Money::Cash object yet."
      elsif rate == @currency
        return self
      else
        rate_value = current_rate.rates[rate.to_sym]
        return Money::Cash.new(amount * rate_value, rate)
      end
    end
  end
end

As you might guess, there are two different classes in this project, Cash and Rate. Rate is holding exchange rates for the Cash object, and Cash is holding the amount and the currency of the money. I just followed some basic OOP principles. We already have a convert_to method available for money conversation which makes money conversations based on defined Rates. Let's create some new Cash instances first:

dollar = Money::Cash.new(50, 'USD')
euro = Money::Cash.new(10, 'EUR')

The question is, based on the amount values - how can we compare two different Cash objects in Ruby? Ruby has an amazing library named as Comparable just for these kind of comparisons. First we have to include it to our class:

module Money
  class Cash
    include Comparable
    ....
  end
end

Then we will define <=> method, which makes <, >, ==, !=, >= and <= comparisons available for us:

module Money
  class Cash
    include Comparable
    attr_accessor :amount, :currency

    def initialize(amount, currency)
      @amount = amount.round(2)
      @currency = currency.upcase
    end

    def convert_to(rate)
      rate = rate.upcase
      current_rate = Money::Rate.all.find { |r| r.code == @currency }

      if current_rate.nil?
        raise TypeError, "#{rate} has not defined for Money::Cash object yet."
      elsif rate == @currency
        return self
      else
        rate_value = current_rate.rates[rate.to_sym]
        return Money::Cash.new(amount * rate_value, rate)
      end
    end

    def <=>(other)
      raise TypeError, "Can't compare #{other.class} with Cash" unless other.is_a?(Money::Cash)
      other = other.convert_to(currency.upcase) if other.currency != currency
      if amount < other.amount
        -1
      elsif amount > other.amount
        1
      else
        0
      end
    end
  end
end

Now, we are fully able to do any type of comparisons between Ruby objects. Lets remember our variables:

dollar = Money::Cash.new(50, 'USD')
euro = Money::Cash.new(10, 'EUR')

We can do any comparisons on these objects:

dollar < euro # false
dollar <= euro # false
dollar == euro # false
dollar != euro # true
dollar > euro # true
dollar >= euro # true

Good luck!


Share this post!


Blog Comments powered by Disqus.