memo money

# -*- encoding: UTF-8 -*-
describe "Money" do
  it "3000.yen で Moneyオブジェクトが生成できること" do
    3000.yen.should == Money.create(3000, :yen)
  end
  
  describe "比較演算" do
    it " == できること " do
      (1000.yen == 1000.yen).should be_true
      (1000.yen == 1000.dollar).should be_false
      (1000.yen == 1001.yen).should be_false
    end
        
    it "!= できること" do
      (1000.yen != 1000.yen).should be_false
      (1000.yen != 1000.dollar).should be_true
      (1000.yen != 1001.yen).should be_true
    end
    
    it "> できること" do
      (1000.yen > 1001.yen).should be_false
      (1000.yen > 1000.yen).should be_false
      (1000.yen > 999.yen).should be_true
    end

    it "< できること" do
      (1000.yen < 1001.yen).should be_true
      (1000.yen < 1000.yen).should be_false
      (1000.yen < 999.yen).should be_false
    end

    it ">= できること" do
      (1000.yen >= 1001.yen).should be_false
      (1000.yen >= 1000.yen).should be_true
      (1000.yen >= 999.yen).should be_true
    end

    it "<= できること" do
      (1000.yen <= 1001.yen).should be_true
      (1000.yen <= 1000.yen).should be_true
      (1000.yen <= 999.yen).should be_false
    end
    
    ["<", ">", ">=", "<="].each do |op|
      it "異なる単位は比較できないこと #{op}" do
        eval %Q{
        expect do
          3000.yen #{op} 2000.dollar
        end.to raise_error
        }
      end
    end
  end
  
  describe "足し算と引き算" do
    it "引き算が計算できること" do
      (3000.yen - 2000.yen).should == 1000.yen      
      (3000.yen - 2000.yen).should_not == 0.yen
    end
    
    it "足し算が計算できること" do
      (3000.yen + 999.yen).should == 3999.yen
      (3000.yen + 999.yen).should_not == 3998.yen
    end
    
    it "異なる単位は計算できないこと" do
      expect do 
        3000.yen - 2000.dollar
      end.to raise_error
      expect do 
        3000.yen + 2000.dollar
      end.to raise_error
    end
  end
  
  describe "ソート" do
    it "ソートできること" do
      [200.yen, 100.yen, 300.yen].sort.should == [100.yen, 200.yen, 300.yen]
      [200.yen, 100.yen, 300.yen].sort.reverse.should == [300.yen, 200.yen, 100.yen]
    end
    
    it "ソートできないこと。異なる単位が混ざっている場合" do
      expect do
        [100.yen, 300.yen, 400.dollar, 200.dollar, 500.yen].sort
      end.to raise_error
    end
  end
end

class Money
  # @@money = {}
  def self.create amount, currency = :yen
    # @@money["#{amount}/#{currency}"] || Money.new(amount, currency)
    Money.new(amount, currency)
  end

  attr_reader :amount, :currency

  def initialize(amount, currency)
    @amount, @currency = amount, currency
    # @@money["#{amount}/#{currency}"] = self
  end
  
  # 比較
  def ==(other)
    self.class == other.class && self.amount == other.amount && self.currency == other.currency
  end
  
  def <=> other
    assert_same_currency other
    self.amount <=> other.amount
  end
  
  def < other
    (self <=> other) == -1
  end

  def > other
    (self <=> other) == 1
  end

  def >= other
    (self <=> other) >= 0
  end

  def <= other
    (self <=> other) <= 0
  end
   
  # 演算
  def +(other)
    assert_same_currency other
    Money.create(self.amount + other.amount, self.currency)
  end

  def -(other)
    assert_same_currency other
    Money.create(self.amount - other.amount, self.currency)
  end
  # TODO 割り算、 かけ算
    
  def assert_same_currency other
    raise "単位が一致しませんでした。 #{self.currency}/#{other.currency}" if self.currency != other.currency     
  end
end

class Fixnum
  def yen
    Money.create(self, :yen)
  end
  
  def dollar
    Money.create(self, :dollar)
  end
end

hashはオーバライドすべきなのか。かけ算、割り算が残っている.
itの文字列テンプレート化、二次元配列、 evalを使えば、重複を削れそうなんだが、読みにくかったのでやめた。
RSpecは、Data Driven Testの文法を用意してたっけ。よく欲しくなる