Class: Money::Bank::VariableExchange

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

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.

Returns:

  • (Array[Symbol])
[:json, :ruby, :yaml].freeze
SERIALIZER_SEPARATOR =

Returns:

  • (string)
"_TO_"
FORMAT_SERIALIZERS =

Returns:

  • (Hash[Symbol, (JSON | Marshal | YAML)])
{ 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(store = 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)

    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

Yield Returns:

  • (Object)


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

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

Instance Attribute Details

#mutexObject (readonly)

Returns the value of attribute mutex.

Returns:

  • (Object)


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

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)


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

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

#calculate_fractional(from, to_currency) ⇒ Object

Parameters:

  • from (Object)
  • to_currency (Object)

Returns:

  • (Object)


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

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) ⇒ void

This method returns an undefined value.

Parameters:

  • fractional (Object)
  • rate (Object)


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

def exchange(fractional, rate, &)
  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.



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

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

    Options hash to set special parameters. Backwards compatibility only.

Returns:

  • (String)

Raises:

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



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

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.write(file, 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)

    Options hash to set special parameters. Backwards compatibility only.

Returns:

  • (Numeric)


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

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

#import_rates(format, string, _opts = {}) ⇒ self

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

Examples:

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

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

Parameters:

  • format (Symbol)

    The format of string.

  • string (String)

    The rates string.

  • opts (Hash)

    Options hash to set special parameters. Backwards compatibility only.

Returns:

  • (self)

Raises:

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



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

def import_rates(format, string, _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(string)

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

  self
end

#marshal_dump::Array[untyped]

Returns:

  • (::Array[untyped])


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

def marshal_dump
  [store.marshal_dump, @rounding_method]
end

#marshal_load(arr) ⇒ Object

Parameters:

  • arr (Object)

Returns:

  • (Object)


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

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.

Returns:

  • (Object)


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

def rates
  store.each_rate.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)

    Options hash to set special parameters. Backwards compatibility only.

Returns:

  • (Numeric)


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

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

#storeObject

Returns:

  • (Object)


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

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