Class: Money

Inherits:
Object
  • Object
show all
Extended by:
Constructors
Includes:
Comparable, Arithmetic
Defined in:
lib/money/money.rb,
lib/money/version.rb,
lib/money/currency.rb,
lib/money/bank/base.rb,
lib/money/currency/loader.rb,
lib/money/money/formatter.rb,
lib/money/money/allocation.rb,
lib/money/money/arithmetic.rb,
lib/money/money/constructors.rb,
lib/money/rates_store/memory.rb,
lib/money/currency/heuristics.rb,
lib/money/locale_backend/base.rb,
lib/money/locale_backend/i18n.rb,
lib/money/bank/single_currency.rb,
lib/money/money/locale_backend.rb,
lib/money/locale_backend/errors.rb,
lib/money/locale_backend/legacy.rb,
lib/money/bank/variable_exchange.rb,
lib/money/money/formatting_rules.rb,
lib/money/locale_backend/currency.rb

Overview

“Money is any object or record that is generally accepted as payment for goods and services and repayment of debts in a given socio-economic context or country.” -Wikipedia

An instance of Money represents an amount of a specific currency.

Money is a value object and should be treated as immutable.

Defined Under Namespace

Modules: Arithmetic, Bank, Constructors, LocaleBackend, RatesStore Classes: Allocation, Currency, Formatter, FormattingRules, UndefinedSmallestDenomination

Constant Summary collapse

VERSION =
'6.19.0'.freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Constructors

ca_dollar, empty, euro, pound_sterling, us_dollar

Methods included from Arithmetic

#%, #*, #+, #-, #-@, #/, #<=>, #==, #abs, #coerce, #div, #divmod, #eql?, #modulo, #negative?, #nonzero?, #positive?, #remainder, #zero?

Constructor Details

#initialize(obj, currency = Money.default_currency, options = {}) ⇒ Money

Creates a new Money object of value given in the fractional unit of the given currency.

Alternatively you can use the convenience methods like Money::Constructors#ca_dollar and Money::Constructors#us_dollar.

Examples:

Money.new(100)        #=> #<Money @fractional=100 @currency="USD">
Money.new(100, "USD") #=> #<Money @fractional=100 @currency="USD">
Money.new(100, "EUR") #=> #<Money @fractional=100 @currency="EUR">

Parameters:

  • obj (Object)

    Either the fractional value of the money, a Money object, or a currency. (If passed a currency as the first argument, a Money will be created in that currency with fractional value

    0.

  • currency (Currency, String, Symbol) (defaults to: Money.default_currency)

    The currency format.

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

    Optional settings for the new Money instance

  • [Money::Bank::*] (Hash)

    a customizable set of options

Raises:

  • (ArgumentError)


346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/money/money.rb', line 346

def initialize(obj, currency = Money.default_currency, options = {})
  # For backwards compatibility, if options is not a Hash, treat it as a bank parameter
  unless options.is_a?(Hash)
    options = { bank: options }
  end

  @fractional = as_d(obj.respond_to?(:fractional) ? obj.fractional : obj)
  @currency   = obj.respond_to?(:currency) ? obj.currency : Currency.wrap(currency)
  @currency ||= Money.default_currency
  @bank       = obj.respond_to?(:bank) ? obj.bank : options[:bank]
  @bank     ||= Money.default_bank

  # BigDecimal can be Infinity and NaN, money of that amount does not make sense
  raise ArgumentError, 'must be initialized with a finite value' unless @fractional.finite?
  raise Currency::NoCurrency, 'must provide a currency' if @currency.nil?
end

Class Attribute Details

.conversion_precisionInteger

Used to specify precision for converting Rational to BigDecimal

Returns:

  • (Integer)


133
# File 'lib/money/money.rb', line 133

attr_accessor :default_formatting_rules, :default_infinite_precision, :conversion_precision

.default_bankMoney::Bank::Base

Used to set a default bank for currency exchange.

Each Money object is associated with a bank object, which is responsible for currency exchange. This property allows you to specify the default bank object. The default value for this property is an instance of Bank::VariableExchange. It allows one to specify custom exchange rates.

Returns:



133
# File 'lib/money/money.rb', line 133

attr_accessor :default_formatting_rules, :default_infinite_precision, :conversion_precision

.default_formatting_rulesHash

Used to define a default hash of rules for every time Money#format is called. Rules provided on method call will be merged with the default ones. To overwrite a rule, just provide the intended value while calling format.

Examples:

Money.default_formatting_rules = { display_free: true }
Money.new(0, "USD").format                          # => "free"
Money.new(0, "USD").format(display_free: false)  # => "$0.00"

Returns:

  • (Hash)

See Also:



133
134
135
# File 'lib/money/money.rb', line 133

def default_formatting_rules
  @default_formatting_rules
end

.default_infinite_precisionBoolean

Returns Use this to enable infinite precision cents as the global default.

Returns:

  • (Boolean)

    Use this to enable infinite precision cents as the global default



133
# File 'lib/money/money.rb', line 133

attr_accessor :default_formatting_rules, :default_infinite_precision, :conversion_precision

.locale_backendObject

Returns the value of attribute locale_backend.



134
135
136
# File 'lib/money/money.rb', line 134

def locale_backend
  @locale_backend
end

.use_i18nBoolean

Used to disable i18n even if it’s used by other components of your app.

Returns:

  • (Boolean)


133
# File 'lib/money/money.rb', line 133

attr_accessor :default_formatting_rules, :default_infinite_precision, :conversion_precision

Instance Attribute Details

#bankObject (readonly)

Returns the value of attribute bank.



89
# File 'lib/money/money.rb', line 89

attr_reader :currency, :bank

#currencyCurrency (readonly)

Returns The money’s currency.

Returns:

  • (Currency)

    The money’s currency.



89
90
91
# File 'lib/money/money.rb', line 89

def currency
  @currency
end

#default_currencyMoney::Currency

Returns The default currency, which is used when Money.new is called without an explicit currency argument. The default value is Currency.new(“USD”). The value must be a valid Money::Currency instance.

Returns:

  • (Money::Currency)

    The default currency, which is used when Money.new is called without an explicit currency argument. The default value is Currency.new(“USD”). The value must be a valid Money::Currency instance.



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/money/money.rb', line 153

def self.default_currency
  if @using_deprecated_default_currency
    warn '[WARNING] The default currency will change from `USD` to `nil` in the next major release. Make ' \
         'sure to set it explicitly using `Money.default_currency=` to avoid potential issues'
    @using_deprecated_default_currency = false
  end

  if @default_currency.nil?
    nil
  elsif @default_currency.respond_to?(:call)
    Money::Currency.new(@default_currency.call)
  else
    Money::Currency.new(@default_currency)
  end
end

Class Method Details

.add_rate(from_currency, to_currency, rate) ⇒ Numeric

Adds a new exchange rate to the default bank and return the rate.

Examples:

Money.add_rate("USD", "CAD", 1.25) #=> 1.25

Parameters:

  • from_currency (Currency, String, Symbol)

    Currency to exchange from.

  • to_currency (Currency, String, Symbol)

    Currency to exchange to.

  • rate (Numeric)

    Rate to exchange with.

Returns:

  • (Numeric)


285
286
287
# File 'lib/money/money.rb', line 285

def self.add_rate(from_currency, to_currency, rate)
  Money.default_bank.add_rate(from_currency, to_currency, rate)
end

.default_currencyObject



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/money/money.rb', line 153

def self.default_currency
  if @using_deprecated_default_currency
    warn '[WARNING] The default currency will change from `USD` to `nil` in the next major release. Make ' \
         'sure to set it explicitly using `Money.default_currency=` to avoid potential issues'
    @using_deprecated_default_currency = false
  end

  if @default_currency.nil?
    nil
  elsif @default_currency.respond_to?(:call)
    Money::Currency.new(@default_currency.call)
  else
    Money::Currency.new(@default_currency)
  end
end

.default_currency=(currency) ⇒ Object



169
170
171
172
# File 'lib/money/money.rb', line 169

def self.default_currency=(currency)
  @using_deprecated_default_currency = false
  @default_currency = currency
end

.disallow_currency_conversion!Object

Sets the default bank to be a SingleCurrency bank that raises on currency exchange. Useful when apps operate in a single currency at a time.



291
292
293
# File 'lib/money/money.rb', line 291

def self.disallow_currency_conversion!
  self.default_bank = Bank::SingleCurrency.instance
end

.from_amount(amount, currency = default_currency, options = {}) ⇒ Money Also known as: from_dollars

Creates a new Money object of value given in the unit of the given currency.

Examples:

Money.from_amount(23.45, "USD") # => #<Money fractional:2345 currency:USD>
Money.from_amount(23.45, "JPY") # => #<Money fractional:23 currency:JPY>

Parameters:

  • amount (Numeric)

    The numerical value of the money.

  • currency (Currency, String, Symbol) (defaults to: default_currency)

    The currency format.

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

    Optional settings for the new Money instance

  • [Money::Bank::*] (Hash)

    a customizable set of options

Returns:

Raises:

  • (ArgumentError)

See Also:



310
311
312
313
314
315
316
317
318
# File 'lib/money/money.rb', line 310

def self.from_amount(amount, currency = default_currency, options = {})
  raise ArgumentError, "'amount' must be numeric" unless Numeric === amount

  currency = Currency.wrap(currency) || Money.default_currency
  raise Currency::NoCurrency, 'must provide a currency' if currency.nil?

  value = amount.to_d * currency.subunit_to_unit
  new(value, currency, options)
end

.infinite_precisionObject



137
138
139
140
# File 'lib/money/money.rb', line 137

def infinite_precision
  warn '[DEPRECATION] `Money.infinite_precision` is deprecated - use `Money.default_infinite_precision` instead'
  default_infinite_precision
end

.infinite_precision=(value) ⇒ Object



142
143
144
145
# File 'lib/money/money.rb', line 142

def infinite_precision=(value)
  warn '[DEPRECATION] `Money.infinite_precision=` is deprecated - use `Money.default_infinite_precision= ` instead'
  self.default_infinite_precision = value
end

.inherited(base) ⇒ Object



227
228
229
# File 'lib/money/money.rb', line 227

def self.inherited(base)
  base.setup_defaults
end

.rounding_mode(mode = nil) ⇒ BigDecimal::ROUND_MODE

Use this to return the rounding mode.

Parameters:

  • mode (BigDecimal::ROUND_MODE) (defaults to: nil)

Returns:

  • (BigDecimal::ROUND_MODE)

    rounding mode



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/money/money.rb', line 238

def self.rounding_mode(mode = nil)
  if mode
    warn "[DEPRECATION] calling `rounding_mode` with a block is deprecated. Please use `.with_rounding_mode` instead."
    return with_rounding_mode(mode) { yield }
  end

  return Thread.current[:money_rounding_mode] if Thread.current[:money_rounding_mode]

  if @using_deprecated_default_rounding_mode
    warn '[WARNING] The default rounding mode will change from `ROUND_HALF_EVEN` to `ROUND_HALF_UP` in the ' \
         'next major release. Set it explicitly using `Money.rounding_mode=` to avoid potential problems.'
    @using_deprecated_default_rounding_mode = false
  end

  @rounding_mode
end

.rounding_mode=(new_rounding_mode) ⇒ Object



187
188
189
190
# File 'lib/money/money.rb', line 187

def self.rounding_mode=(new_rounding_mode)
  @using_deprecated_default_rounding_mode = false
  @rounding_mode = new_rounding_mode
end

.setup_defaultsObject



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/money/money.rb', line 202

def self.setup_defaults
  # Set the default bank for creating new +Money+ objects.
  self.default_bank = Bank::VariableExchange.instance

  # Set the default currency for creating new +Money+ object.
  self.default_currency = Currency.new("USD")
  @using_deprecated_default_currency = true

  # Default to using i18n
  @use_i18n = true

  # Default to using legacy locale backend
  self.locale_backend = :legacy

  # Default to not using infinite precision cents
  self.default_infinite_precision = false

  # Default to bankers rounding
  self.rounding_mode = BigDecimal::ROUND_HALF_EVEN
  @using_deprecated_default_rounding_mode = true

  # Default the conversion of Rationals precision to 16
  self.conversion_precision = 16
end

.with_rounding_mode(mode) { ... } ⇒ Object

Temporarily changes the rounding mode in a given block.

Examples:

fee = Money.with_rounding_mode(BigDecimal::ROUND_HALF_UP) do
  Money.new(1200) * BigDecimal('0.029')
end

Parameters:

  • mode (BigDecimal::ROUND_MODE)

Yields:

  • The block within which rounding mode will be changed. Its return value will also be the return value of the whole method.

Returns:

  • (Object)

    block results



268
269
270
271
272
273
# File 'lib/money/money.rb', line 268

def self.with_rounding_mode(mode)
  Thread.current[:money_rounding_mode] = mode
  yield
ensure
  Thread.current[:money_rounding_mode] = nil
end

Instance Method Details

#allocate(parts) ⇒ Array<Money> Also known as: split

Splits a given amount in parts without losing pennies. The left-over pennies will be distributed round-robin amongst the parties. This means that parts listed first will likely receive more pennies than ones listed later.

Pass [2, 1, 1] as input to give twice as much to part1 as part2 or part3 which results in 50% of the cash to party1, 25% to part2, and 25% to part3. Passing a number instead of an array will split the amount evenly (without losing pennies when rounding).

Examples:

Money.new(5,   "USD").allocate([3, 7]) #=> [Money.new(2), Money.new(3)]
Money.new(100, "USD").allocate([1, 1, 1]) #=> [Money.new(34), Money.new(33), Money.new(33)]
Money.new(100, "USD").allocate(2) #=> [Money.new(50), Money.new(50)]
Money.new(100, "USD").allocate(3) #=> [Money.new(34), Money.new(33), Money.new(33)]

Parameters:

  • parts (Array<Numeric>, Numeric)

    how amount should be distributed to parts

Returns:



601
602
603
604
# File 'lib/money/money.rb', line 601

def allocate(parts)
  amounts = Money::Allocation.generate(fractional, parts, !Money.default_infinite_precision)
  amounts.map { |amount| dup_with(fractional: amount) }
end

#amountBigDecimal

Returns the numerical value of the money

Examples:

Money.new(1_00, "USD").amount    # => BigDecimal("1.00")

Returns:

  • (BigDecimal)

See Also:



392
393
394
# File 'lib/money/money.rb', line 392

def amount
  to_d
end

#as_ca_dollarMoney

Receive a money object with the same amount as the current Money object in Canadian dollar.

Examples:

n = Money.new(100, "USD").as_ca_dollar
n.currency #=> #<Money::Currency id: cad>

Returns:



567
568
569
# File 'lib/money/money.rb', line 567

def as_ca_dollar
  exchange_to("CAD")
end

#as_euroMoney

Receive a money object with the same amount as the current Money object in euro.

Examples:

n = Money.new(100, "USD").as_euro
n.currency #=> #<Money::Currency id: eur>

Returns:



579
580
581
# File 'lib/money/money.rb', line 579

def as_euro
  exchange_to("EUR")
end

#as_us_dollarMoney

Receive a money object with the same amount as the current Money object in United States dollar.

Examples:

n = Money.new(100, "CAD").as_us_dollar
n.currency #=> #<Money::Currency id: usd>

Returns:



555
556
557
# File 'lib/money/money.rb', line 555

def as_us_dollar
  exchange_to("USD")
end

#centsInteger, BigDecimal

Convenience method for fractional part of the amount. Synonym of #fractional

Returns:

  • (Integer)

    when infinite_precision is false

  • (BigDecimal)

    when infinite_precision is true

See Also:



34
35
36
# File 'lib/money/money.rb', line 34

def cents
  fractional
end

#currency_as_stringString

Return string representation of currency object

Examples:

Money.new(100, :USD).currency_as_string #=> "USD"

Returns:

  • (String)


402
403
404
405
# File 'lib/money/money.rb', line 402

def currency_as_string
  warn "[DEPRECATION] `currency_as_string` is deprecated. Please use `.currency.to_s` instead."
  currency.to_s
end

#currency_as_string=(val) ⇒ Money::Currency

Set currency object using a string

Examples:

Money.new(100).currency_as_string("CAD") #=> #<Money::Currency id: cad>

Parameters:

  • val (String)

    The currency string.

Returns:



415
416
417
418
419
# File 'lib/money/money.rb', line 415

def currency_as_string=(val)
  warn "[DEPRECATION] `currency_as_string=` is deprecated - Money instances are immutable." \
    " Please use `with_currency` instead."
  @currency = Currency.wrap(val)
end

#decimal_markString

Returns a decimal mark according to the locale

Returns:

  • (String)


650
651
652
653
# File 'lib/money/money.rb', line 650

def decimal_mark
  (locale_backend && locale_backend.lookup(:decimal_mark, currency)) ||
    Money::Formatter::DEFAULTS[:decimal_mark]
end

#dollarsBigDecimal

Assuming using a currency using dollars: Returns the value of the money in dollars, instead of in the fractional unit cents.

Synonym of #amount

Examples:

Money.new(1_00, "USD").dollars   # => BigDecimal("1.00")

Returns:

  • (BigDecimal)

See Also:



378
379
380
# File 'lib/money/money.rb', line 378

def dollars
  amount
end

#dup_with(options = {}) ⇒ Object



655
656
657
658
659
660
661
# File 'lib/money/money.rb', line 655

def dup_with(options = {})
  self.class.new(
    options[:fractional] || fractional,
    options[:currency] || currency,
    bank: options[:bank] || bank
  )
end

#exchange_to(other_currency) {|n| ... } ⇒ Money

Receive the amount of this money object in another Currency.

Examples:

Money.new(2000, "USD").exchange_to("EUR")
Money.new(2000, "USD").exchange_to("EUR") {|x| x.round}
Money.new(2000, "USD").exchange_to(Currency.new("EUR"))

Parameters:

  • other_currency (Currency, String, Symbol)

    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:



538
539
540
541
542
543
544
545
# File 'lib/money/money.rb', line 538

def exchange_to(other_currency, &rounding_method)
  other_currency = Currency.wrap(other_currency)
  if self.currency == other_currency
    self
  else
    @bank.exchange_with(self, other_currency, &rounding_method)
  end
end

#format(*rules) ⇒ String

Creates a formatted price string according to several rules.

Parameters:

Returns:

  • (String)


633
634
635
# File 'lib/money/money.rb', line 633

def format(*rules)
  Money::Formatter.new(self, *rules).to_s
end

#fractionalInteger, BigDecimal

The value of the monetary amount represented in the fractional or subunit of the currency.

For example, in the US dollar currency the fractional unit is cents, and there are 100 cents in one US dollar. So given the Money representation of one US dollar, the fractional interpretation is 100.

Another example is that of the Kuwaiti dinar. In this case the fractional unit is the fils and there 1000 fils to one Kuwaiti dinar. So given the Money representation of one Kuwaiti dinar, the fractional interpretation is 1000.

Returns:

  • (Integer)

    when infinite_precision is false

  • (BigDecimal)

    when infinite_precision is true

See Also:



54
55
56
57
58
59
60
# File 'lib/money/money.rb', line 54

def fractional
  # Ensure we have a BigDecimal. If the Money object is created
  # from YAML, @fractional can end up being set to a Float.
  fractional = as_d(@fractional)

  return_value(fractional)
end

#hashInteger

Returns a Integer hash value based on the fractional and currency attributes in order to use functions like & (intersection), group_by, etc.

Examples:

Money.new(100).hash #=> 908351

Returns:

  • (Integer)


428
429
430
# File 'lib/money/money.rb', line 428

def hash
  [fractional.hash, currency.hash].hash
end

#inspectString

Common inspect function

Returns:

  • (String)


445
446
447
# File 'lib/money/money.rb', line 445

def inspect
  "#<#{self.class.name} fractional:#{fractional} currency:#{currency}>"
end

#round(rounding_mode = self.class.rounding_mode, rounding_precision = 0) ⇒ Money

Note:

This method is only useful when operating with infinite_precision turned on. Without infinite_precision values are rounded to the smallest unit of coinage automatically.

Round the monetary amount to smallest unit of coinage.

Examples:

Money.new(10.1, 'USD').round #=> Money.new(10, 'USD')

Returns:

See Also:



622
623
624
625
# File 'lib/money/money.rb', line 622

def round(rounding_mode = self.class.rounding_mode, rounding_precision = 0)
  rounded_amount = as_d(@fractional).round(rounding_precision, rounding_mode)
  dup_with(fractional: rounded_amount)
end

#round_to_nearest_cash_valueInteger, BigDecimal

Round a given amount of money to the nearest possible amount in cash value. For example, in Swiss franc (CHF), the smallest possible amount of cash value is CHF 0.05. Therefore, this method rounds CHF 0.07 to CHF 0.05, and CHF 0.08 to CHF 0.10.

Returns:

  • (Integer)

    when infinite_precision is false

  • (BigDecimal)

    when infinite_precision is true

See Also:



71
72
73
74
75
76
77
78
79
80
81
# File 'lib/money/money.rb', line 71

def round_to_nearest_cash_value
  unless self.currency.smallest_denomination
    raise UndefinedSmallestDenomination, 'Smallest denomination of this currency is not defined'
  end

  fractional = as_d(@fractional)
  smallest_denomination = as_d(self.currency.smallest_denomination)
  rounded_value = (fractional / smallest_denomination).round(0, self.class.rounding_mode) * smallest_denomination

  return_value(rounded_value)
end

#symbolString

Uses Currency#symbol. If nil is returned, defaults to “¤”.

Examples:

Money.new(100, "USD").symbol #=> "$"

Returns:

  • (String)


438
439
440
# File 'lib/money/money.rb', line 438

def symbol
  currency.symbol || "¤"
end

#thousands_separatorString

Returns a thousands separator according to the locale

Returns:

  • (String)


641
642
643
644
# File 'lib/money/money.rb', line 641

def thousands_separator
  (locale_backend && locale_backend.lookup(:thousands_separator, currency)) ||
    Money::Formatter::DEFAULTS[:thousands_separator]
end

#to_dBigDecimal

Return the amount of money as a BigDecimal.

Examples:

Money.us_dollar(1_00).to_d #=> BigDecimal("1.00")

Returns:

  • (BigDecimal)


468
469
470
# File 'lib/money/money.rb', line 468

def to_d
  as_d(fractional) / as_d(currency.subunit_to_unit)
end

#to_fFloat

Return the amount of money as a float. Floating points cannot guarantee precision. Therefore, this function should only be used when you no longer need to represent currency or working with another system that requires floats.

Examples:

Money.us_dollar(100).to_f #=> 1.0

Returns:

  • (Float)


491
492
493
# File 'lib/money/money.rb', line 491

def to_f
  to_d.to_f
end

#to_iInteger

Return the amount of money as a Integer.

Examples:

Money.us_dollar(1_00).to_i #=> 1

Returns:

  • (Integer)


478
479
480
# File 'lib/money/money.rb', line 478

def to_i
  to_d.to_i
end

#to_money(given_currency = nil) ⇒ self

Conversion to self.

Returns:

  • (self)


513
514
515
516
517
518
519
520
# File 'lib/money/money.rb', line 513

def to_money(given_currency = nil)
  given_currency = Currency.wrap(given_currency)
  if given_currency.nil? || self.currency == given_currency
    self
  else
    exchange_to(given_currency)
  end
end

#to_sString

Returns the amount of money as a string.

Examples:

Money.ca_dollar(100).to_s #=> "1.00"

Returns:

  • (String)


455
456
457
458
459
460
# File 'lib/money/money.rb', line 455

def to_s
  format thousands_separator: '',
         no_cents_if_whole: currency.decimal_places == 0,
         symbol: false,
         ignore_defaults: true
end

#with_currency(new_currency) ⇒ self

Returns a new Money instance in a given currency leaving the amount intact and not performing currency conversion.

Parameters:

  • new_currency (Currency, String, Symbol)

    Currency of the new object.

Returns:

  • (self)


501
502
503
504
505
506
507
508
# File 'lib/money/money.rb', line 501

def with_currency(new_currency)
  new_currency = Currency.wrap(new_currency)
  if !new_currency || currency == new_currency
    self
  else
    dup_with(currency: new_currency)
  end
end