Class: Money::Bank::VariableExchange

Inherits:
Base
  • Object
show all
Defined in:
lib/money/bank/variable_exchange.rb

Overview

Class for aiding in exchanging money between different currencies. By default, the Money class uses an object of this class (accessible through Money#bank) for performing currency exchanges.

By default, Money::Bank::VariableExchange has no knowledge about conversion rates. One must manually specify them with add_rate, after which one can perform exchanges with #exchange_with.

Exchange rates are stored in memory using Money::RatesStore::Memory by default. Pass custom rates stores for other types of storage (file, database, etc)

Examples:

bank = Money::Bank::VariableExchange.new
bank.add_rate("USD", "CAD", 1.24515)
bank.add_rate("CAD", "USD", 0.803115)

c1 = Money.new(100_00, "USD")
c2 = Money.new(100_00, "CAD")

# Exchange 100 USD to CAD:
bank.exchange_with(c1, "CAD") #=> #<Money fractional:12451 currency:CAD>

# Exchange 100 CAD to USD:
bank.exchange_with(c2, "USD") #=> #<Money fractional:8031 currency:USD>

# With custom exchange rates storage
redis_store = MyCustomRedisStore.new(host: 'localhost:6379')
bank = Money::Bank::VariableExchange.new(redis_store)
# Store rates in redis
bank.add_rate 'USD', 'CAD', 0.98
# Get rate from redis
bank.get_rate 'USD', 'CAD'

Constant Summary collapse

RATE_FORMATS =

Available formats for importing/exporting rates.

[:json, :ruby, :yaml].freeze
SERIALIZER_SEPARATOR =
'_TO_'.freeze
FORMAT_SERIALIZERS =
{json: JSON, ruby: Marshal, yaml: YAML}.freeze

Instance Attribute Summary collapse

Attributes inherited from Base

#rounding_method

Instance Method Summary collapse

Methods inherited from Base

instance, #same_currency?, #setup

Constructor Details

#initialize(st = Money::RatesStore::Memory.new) {|n| ... } ⇒ VariableExchange

Initializes a new Money::Bank::VariableExchange object. It defaults to using an in-memory, thread safe store instance for storing exchange rates.

Parameters:

  • st (RateStore) (defaults to: Money::RatesStore::Memory.new)

    An exchange rate store, used to persist exchange rate pairs.

Yields:

  • (n)

    Optional block to use when rounding after exchanging one currency for another. See Money::bank::base



59
60
61
62
# File 'lib/money/bank/variable_exchange.rb', line 59

def initialize(st = Money::RatesStore::Memory.new, &block)
  @store = st
  super(&block)
end

Instance Attribute Details

#mutexObject (readonly)

Returns the value of attribute mutex.



45
46
47
# File 'lib/money/bank/variable_exchange.rb', line 45

def mutex
  @mutex
end

Instance Method Details

#add_rate(from, to, rate) ⇒ Numeric

Registers a conversion rate and returns it (uses #set_rate). Delegates to Money::RatesStore::Memory

Examples:

bank = Money::Bank::VariableExchange.new
bank.add_rate("USD", "CAD", 1.24515)
bank.add_rate("CAD", "USD", 0.803115)

Parameters:

  • from (Currency, String, Symbol)

    Currency to exchange from.

  • to (Currency, String, Symbol)

    Currency to exchange to.

  • rate (Numeric)

    Rate to use when exchanging currencies.

Returns:

  • (Numeric)


158
159
160
# File 'lib/money/bank/variable_exchange.rb', line 158

def add_rate(from, to, rate)
  set_rate(from, to, rate)
end

#calculate_fractional(from, to_currency) ⇒ Object



127
128
129
130
131
132
# File 'lib/money/bank/variable_exchange.rb', line 127

def calculate_fractional(from, to_currency)
  BigDecimal(from.fractional.to_s) / (
    BigDecimal(from.currency.subunit_to_unit.to_s) /
    BigDecimal(to_currency.subunit_to_unit.to_s)
  )
end

#exchange(fractional, rate, &block) ⇒ Object



134
135
136
137
138
139
140
141
142
143
# File 'lib/money/bank/variable_exchange.rb', line 134

def exchange(fractional, rate, &block)
  ex = fractional * BigDecimal(rate.to_s)
  if block_given?
    yield ex
  elsif @rounding_method
    @rounding_method.call(ex)
  else
    ex
  end
end

#exchange_with(from, to_currency) {|n| ... } ⇒ Money

Exchanges the given Money object to a new Money object in to_currency.

Examples:

bank = Money::Bank::VariableExchange.new
bank.add_rate("USD", "CAD", 1.24515)
bank.add_rate("CAD", "USD", 0.803115)

c1 = Money.new(100_00, "USD")
c2 = Money.new(100_00, "CAD")

# Exchange 100 USD to CAD:
bank.exchange_with(c1, "CAD") #=> #<Money fractional:12451 currency:CAD>

# Exchange 100 CAD to USD:
bank.exchange_with(c2, "USD") #=> #<Money fractional:8031 currency:USD>

Parameters:

  • from (Money)

    The Money object to exchange.

  • to_currency (Currency, String, Symbol)

    The currency to exchange to.

Yields:

  • (n)

    Optional block to use when rounding after exchanging one currency for another.

Yield Parameters:

  • n (Float)

    The resulting float after exchanging one currency for another.

Yield Returns:

  • (Integer)

Returns:

Raises:

  • Money::Bank::UnknownRate if the conversion rate is unknown.



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/money/bank/variable_exchange.rb', line 109

def exchange_with(from, to_currency, &block)
  to_currency = Currency.wrap(to_currency)
  if from.currency == to_currency
    from
  else
    if rate = get_rate(from.currency, to_currency)
      fractional = calculate_fractional(from, to_currency)
      from.dup_with(
        fractional: exchange(fractional, rate, &block),
        currency: to_currency,
        bank: self
      )
    else
      raise UnknownRate, "No conversion rate known for '#{from.currency.iso_code}' -> '#{to_currency}'"
    end
  end
end

#export_rates(format, file = nil, opts = {}) ⇒ String

Return the known rates as a string in the format specified. If file is given will also write the string out to the file specified. Available formats are :json, :ruby and :yaml.

Examples:

bank = Money::Bank::VariableExchange.new
bank.set_rate("USD", "CAD", 1.24515)
bank.set_rate("CAD", "USD", 0.803115)

s = bank.export_rates(:json)
s #=> "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"

Parameters:

  • format (Symbol)

    Request format for the resulting string.

  • file (String) (defaults to: nil)

    Optional file location to write the rates to.

  • opts (Hash) (defaults to: {})

    Options hash to set special parameters. Backwards compatibility only.

Returns:

  • (String)

Raises:

  • Money::Bank::UnknownRateFormat if format is unknown.



221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/money/bank/variable_exchange.rb', line 221

def export_rates(format, file = nil, opts = {})
  raise Money::Bank::UnknownRateFormat unless RATE_FORMATS.include?(format)

  store.transaction do
    s = FORMAT_SERIALIZERS[format].dump(rates)

    unless file.nil?
      File.open(file, "w") {|f| f.write(s) }
    end

    s
  end
end

#get_rate(from, to, opts = {}) ⇒ Numeric

Retrieve the rate for the given currencies. data access. Delegates to Money::RatesStore::Memory

Examples:

bank = Money::Bank::VariableExchange.new
bank.set_rate("USD", "CAD", 1.24515)
bank.set_rate("CAD", "USD", 0.803115)

bank.get_rate("USD", "CAD") #=> 1.24515
bank.get_rate("CAD", "USD") #=> 0.803115

Parameters:

  • from (Currency, String, Symbol)

    Currency to exchange from.

  • to (Currency, String, Symbol)

    Currency to exchange to.

  • opts (Hash) (defaults to: {})

    Options hash to set special parameters. Backwards compatibility only.

Returns:

  • (Numeric)


198
199
200
# File 'lib/money/bank/variable_exchange.rb', line 198

def get_rate(from, to, opts = {})
  store.get_rate(Currency.wrap(from).iso_code, Currency.wrap(to).iso_code)
end

#import_rates(format, s, opts = {}) ⇒ self

Loads rates provided in s given the specified format. Available formats are :json, :ruby and :yaml. Delegates to Money::RatesStore::Memory

Examples:

s = "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"
bank = Money::Bank::VariableExchange.new
bank.import_rates(:json, s)

bank.get_rate("USD", "CAD") #=> 1.24515
bank.get_rate("CAD", "USD") #=> 0.803115

Parameters:

  • format (Symbol)

    The format of s.

  • s (String)

    The rates string.

  • opts (Hash) (defaults to: {})

    Options hash to set special parameters. Backwards compatibility only.

Returns:

  • (self)

Raises:

  • Money::Bank::UnknownRateFormat if format is unknown.



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/money/bank/variable_exchange.rb', line 261

def import_rates(format, s, opts = {})
  raise Money::Bank::UnknownRateFormat unless RATE_FORMATS.include?(format)

  if format == :ruby
    warn '[WARNING] Using :ruby format when importing rates is potentially unsafe and ' \
      'might lead to remote code execution via Marshal.load deserializer. Consider using ' \
      'safe alternatives such as :json and :yaml.'
  end

  store.transaction do
    data = FORMAT_SERIALIZERS[format].load(s)

    data.each do |key, rate|
      from, to = key.split(SERIALIZER_SEPARATOR)
      store.add_rate from, to, rate
    end
  end

  self
end

#marshal_dumpObject



68
69
70
# File 'lib/money/bank/variable_exchange.rb', line 68

def marshal_dump
  [store.marshal_dump, @rounding_method]
end

#marshal_load(arr) ⇒ Object



72
73
74
75
76
# File 'lib/money/bank/variable_exchange.rb', line 72

def marshal_load(arr)
  store_info = arr[0]
  @store = store_info.shift.new(*store_info)
  @rounding_method = arr[1]
end

#ratesObject

This should be deprecated.



236
237
238
239
240
# File 'lib/money/bank/variable_exchange.rb', line 236

def rates
  store.each_rate.each_with_object({}) do |(from,to,rate),hash|
    hash[[from, to].join(SERIALIZER_SEPARATOR)] = rate
  end
end

#set_rate(from, to, rate, opts = {}) ⇒ Numeric

Set the rate for the given currencies. access. Delegates to Money::RatesStore::Memory

Examples:

bank = Money::Bank::VariableExchange.new
bank.set_rate("USD", "CAD", 1.24515)
bank.set_rate("CAD", "USD", 0.803115)

Parameters:

  • from (Currency, String, Symbol)

    Currency to exchange from.

  • to (Currency, String, Symbol)

    Currency to exchange to.

  • rate (Numeric)

    Rate to use when exchanging currencies.

  • opts (Hash) (defaults to: {})

    Options hash to set special parameters. Backwards compatibility only.

Returns:

  • (Numeric)


177
178
179
# File 'lib/money/bank/variable_exchange.rb', line 177

def set_rate(from, to, rate, opts = {})
  store.add_rate(Currency.wrap(from).iso_code, Currency.wrap(to).iso_code, rate)
end

#storeObject



64
65
66
# File 'lib/money/bank/variable_exchange.rb', line 64

def store
  @store.is_a?(String) ? Object.const_get(@store) : @store
end