require 'money/variable_exchange_bank'
# Represents an amount of money in a certain currency.
class Money
include Comparable
attr_reader :cents, :currency, :bank
class << self
# Each Money object is associated to a bank object, which is responsible
# for currency exchange. This property allows one to specify the default
# bank object.
#
# bank1 = MyBank.new
# bank2 = MyOtherBank.new
#
# Money.default_bank = bank1
# money1 = Money.new(10)
# money1.bank # => bank1
#
# Money.default_bank = bank2
# money2 = Money.new(10)
# money2.bank # => bank2
# money1.bank # => bank1
#
# The default value for this property is an instance if VariableExchangeBank.
# It allows one to specify custom exchange rates:
#
# Money.default_bank.add_rate("USD", "CAD", 1.24515)
# Money.default_bank.add_rate("CAD", "USD", 0.803115)
# Money.us_dollar(100).exchange_to("CAD") # => Money.ca_dollar(124)
# Money.ca_dollar(100).exchange_to("USD") # => Money.us_dollar(80)
attr_accessor :default_bank
# The default currency, which is used when Money.new is called
# without an explicit currency argument. The default value is "USD".
attr_accessor :default_currency
end
self.default_bank = VariableExchangeBank.instance
self.default_currency = "USD"
# Create a new money object with value 0.
def self.empty(currency = default_currency)
Money.new(0, currency)
end
# Creates a new Money object of the given value, using the Canadian dollar currency.
def self.ca_dollar(cents)
Money.new(cents, "CAD")
end
# Creates a new Money object of the given value, using the American dollar currency.
def self.us_dollar(cents)
Money.new(cents, "USD")
end
# Creates a new Money object of the given value, using the Euro currency.
def self.euro(cents)
Money.new(cents, "EUR")
end
def self.add_rate(from_currency, to_currency, rate)
Money.default_bank.add_rate(from_currency, to_currency, rate)
end
# Creates a new money object.
# Money.new(100)
#
# Alternativly you can use the convinience methods like
# Money.ca_dollar and Money.us_dollar
def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
@cents = cents.round
@currency = currency
@bank = bank
end
# Do two money objects equal? Only works if both objects are of the same currency
def ==(other_money)
cents == other_money.cents && bank.same_currency?(currency, other_money.currency)
end
def <=>(other_money)
if bank.same_currency?(currency, other_money.currency)
cents <=> other_money.cents
else
cents <=> other_money.exchange_to(currency).cents
end
end
def +(other_money)
if currency == other_money.currency
Money.new(cents + other_money.cents, other_money.currency)
else
Money.new(cents + other_money.exchange_to(currency).cents,currency)
end
end
def -(other_money)
if currency == other_money.currency
Money.new(cents - other_money.cents, other_money.currency)
else
Money.new(cents - other_money.exchange_to(currency).cents, currency)
end
end
# get the cents value of the object
def cents
@cents
end
# multiply money by fixnum
def *(fixnum)
Money.new(cents * fixnum, currency)
end
# divide money by fixnum
def /(fixnum)
Money.new(cents / fixnum, currency)
end
# Test if the money amount is zero
def zero?
cents == 0
end
# Format the price according to several rules
# Currently supported are :display_free, :with_currency, :no_cents and :html
#
# display_free:
#
# Money.us_dollar(0).format(:display_free) => "free"
# Money.us_dollar(0).format(:display_free => 'gratis') => "gratis"
# Money.us_dollar(0).format => "$0.00"
#
# with_currency:
#
# Money.ca_dollar(100).format => "$1.00"
# Money.ca_dollar(100).format(:with_currency) => "$1.00 CAD"
# Money.us_dollar(85).format(:with_currency) => "$0.85 USD"
#
# no_cents:
#
# Money.ca_dollar(100).format(:no_cents) => "$1"
# Money.ca_dollar(599).format(:no_cents) => "$5"
#
# Money.ca_dollar(570).format(:no_cents, :with_currency) => "$5 CAD"
# Money.ca_dollar(39000).format(:no_cents) => "$390"
#
# html:
#
# Money.ca_dollar(570).format(:html, :with_currency) => "$5.70 CAD"
def format(*rules)
rules = rules.flatten
if cents == 0
hash_option = rules.detect { |r| r.is_a?(Hash) && r[:display_free] }
return hash_option[:display_free] if hash_option
return 'free' if rules.include?(:display_free)
end
if rules.include?(:no_cents)
formatted = sprintf("$%d", cents.to_f / 100 )
else
formatted = sprintf("$%.2f", cents.to_f / 100 )
end
# Commify
formatted = formatted.gsub(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/,'\1,\2')
if rules.include?(:with_currency)
formatted << " "
formatted << '' if rules.include?(:html)
formatted << currency
formatted << '' if rules.include?(:html)
end
formatted
end
# Money.ca_dollar(100).to_s => "1.00"
def to_s
sprintf("%.2f", cents / 100.00)
end
# Recieve the amount of this money object in another currency.
def exchange_to(other_currency)
Money.new(@bank.exchange(self.cents, currency, other_currency), other_currency)
end
# Recieve a money object with the same amount as the current Money object
# in american dollar
def as_us_dollar
exchange_to("USD")
end
# Recieve a money object with the same amount as the current Money object
# in canadian dollar
def as_ca_dollar
exchange_to("CAD")
end
# Recieve a money object with the same amount as the current Money object
# in euro
def as_euro
exchange_to("EUR")
end
# Conversation to self
def to_money
self
end
end