master
  1class Quantity
  2  attr_reader :amount, :unit
  3
  4  def initialize(amount, unit)
  5    @amount = amount
  6    @unit = UnitOfMeasure.for(unit)
  7  end
  8
  9  def to(target_unit)
 10    Quantity.new(
 11      UnitOfMeasure.for(target_unit).convert(amount, unit),
 12      target_unit
 13    )
 14  end
 15
 16  def to_f
 17    @amount.to_f
 18  end
 19
 20  def +(other)
 21    Quantity.new(amount + amount_from(other), unit)
 22  end
 23
 24  def -(other)
 25    Quantity.new(amount - amount_from(other), unit)
 26  end
 27
 28  def /(other)
 29    Quantity.new(amount / amount_from(other), unit)
 30  end
 31
 32  def *(other)
 33    Quantity.new(amount * amount_from(other), unit)
 34  end
 35
 36  def >(other)
 37    amount > amount_from(other)
 38  end
 39
 40  def >=(other)
 41    self.>(other) || eql?(other)
 42  end
 43
 44  def <(other)
 45    amount < amount_from(other)
 46  end
 47
 48  def coerce(other)
 49    [self, other]
 50  end
 51
 52  def hash
 53    amount.hash + unit.class.hash
 54  end
 55
 56  def eql?(other, delta = 0.1)
 57    (amount - amount_from(other)).abs <= delta
 58  end
 59
 60  def ==(other)
 61    eql?(other)
 62  end
 63
 64  def to_s
 65    to_f.to_s
 66  end
 67
 68  def pretty_print
 69    "#{to_f} #{unit}"
 70  end
 71
 72  def to_h
 73    { amount: amount, unit: unit.to_s }
 74  end
 75
 76  def to_hash
 77    to_h
 78  end
 79
 80  private
 81
 82  def amount_from(quantity)
 83    quantity.respond_to?(:to) ? quantity.to(unit).amount : quantity
 84  end
 85
 86  class UnitOfMeasure
 87    def self.for(unit)
 88      case unit
 89      when :lbs, :lb
 90        Pound.new
 91      when :kg, :kgs
 92        Kilogram.new
 93      else
 94        unit
 95      end
 96    end
 97  end
 98
 99  class Pound < UnitOfMeasure
100    def convert(amount, unit)
101      case unit
102      when Kilogram
103        amount * 2.20462
104      else
105        amount
106      end
107    end
108
109    def to_s
110      "lbs"
111    end
112  end
113
114  class Kilogram < UnitOfMeasure
115    def convert(amount, unit)
116      case unit
117      when Pound
118        amount * 0.453592
119      else
120        amount
121      end
122    end
123
124    def to_s
125      "kg"
126    end
127  end
128end