require 'get_set_method' class Invoice module Currency class Symbols @@map = {} def self.map @@map end end class Convertor @@records = {} def self.records @@records end def self.add(c1, c2, ratio) @@records["#{c1}:#{c2}"] = ratio @@records["#{c2}:#{c1}"] = 1/ratio.to_f end def self.get(c1, c2) raise 'Finding identity!' if c1 == c2 @@records["#{c1}:#{c2}"] end def initialize(amount) @amount = amount end def method_missing(sym, *args) # Workaround for rspec return super(sym, *args) if sym.to_s.match(/^should/) Amount.new(sym.to_s, @@records["#{@amount.name}:#{sym.to_s}"] * @amount.amount) end end class Amount < Numeric attr_reader :name, :amount def initialize(name, amt) @name = name @name += 's' if @name[-1..-1] != 's' @amount = amt.to_f end include GetSetMethod # A bit of a hack. We need this operation value so that we know in the container what sum was performed, # so we can calibrate the output. get_set_method :last_operation_value ['+','-'].each do |op| class_eval("def #{op}(rhs); ret = Amount.new(name, @amount #{op} find_amount(rhs)); ret.last_operation_value(rhs.to_f); ret; end") end ['*','/'].each do |op| class_eval("def #{op}(rhs); ret = Amount.new(name, @amount #{op} rhs.to_f); ret.last_operation_value(rhs.to_f); ret; end") end def coerce(numeric) return [Amount.new(name, numeric), self] end def to_s "%.2f" % @amount.to_s end def to_f @amount end def currency_symbol Symbols.map[@name] end def in return Convertor.new(self) end def ==(other) return false if other.class != Amount if @name == other.name return @amount == other.amount else return other.amount == Convertor.get(name, other.name) * @amount rescue false end end def |(other) Convertor.add(name, other.name, other.amount.to_f/amount.to_f) end alias :are_worth :| alias :is_worth :| def find_amount(rhs) return rhs if rhs.class != Amount return rhs.amount if rhs.name == name Convertor.get(rhs.name, name) * rhs.amount end end end end class Numeric def method_missing(sym, *args) # Workaround for rspec return super(sym, *args) if sym.to_s.match(/^should/) amt = Invoice::Currency::Amount.new(sym.to_s, self) # Add the 'symbol' if it exists Invoice::Currency::Symbols.map[amt.name] = args[0] if args.length > 0 amt end end