Class: Money

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Comparable
Defined in:
lib/money/money.rb,
lib/money/config.rb,
lib/money/errors.rb,
lib/money/helpers.rb,
lib/money/railtie.rb,
lib/money/version.rb,
lib/money/currency.rb,
lib/money/splitter.rb,
lib/money/allocator.rb,
lib/money/parser/fuzzy.rb,
lib/money/null_currency.rb,
lib/money/parser/simple.rb,
lib/money/currency/loader.rb,
lib/money/parser/accounting.rb,
lib/money/parser/locale_aware.rb,
lib/money/rails/job_argument_serializer.rb

Defined Under Namespace

Modules: Helpers, Parser, Rails Classes: Allocator, Config, Currency, Error, IncompatibleCurrencyError, NullCurrency, Railtie, ReverseOperationProxy, Splitter

Constant Summary collapse

NULL_CURRENCY =
NullCurrency.new.freeze
VERSION =
"3.0.2"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value, currency) ⇒ Money

Returns a new instance of Money.

Raises:

  • (ArgumentError)


150
151
152
153
154
155
# File 'lib/money/money.rb', line 150

def initialize(value, currency)
  raise ArgumentError if value.nan?
  @currency = Helpers.value_to_currency(currency)
  @value = BigDecimal(value.round(@currency.minor_units))
  freeze
end

Instance Attribute Details

#currencyObject (readonly)

Returns the value of attribute currency.



12
13
14
# File 'lib/money/money.rb', line 12

def currency
  @currency
end

#valueObject (readonly)

Returns the value of attribute value.



12
13
14
# File 'lib/money/money.rb', line 12

def value
  @value
end

Class Method Details

.configObject



44
45
46
# File 'lib/money/money.rb', line 44

def config
  Thread.current[:shopify_money__config] ||= @config.dup
end

.config=(config) ⇒ Object



48
49
50
# File 'lib/money/money.rb', line 48

def config=(config)
  Thread.current[:shopify_money__config] = config
end

.configure {|@config| ... } ⇒ Object

Yields:



52
53
54
55
# File 'lib/money/money.rb', line 52

def configure
  @config ||= Config.new
  yield(@config) if block_given?
end

.current_currencyObject



104
105
106
# File 'lib/money/money.rb', line 104

def current_currency
  Thread.current[:money_currency]
end

.current_currency=(currency) ⇒ Object



108
109
110
# File 'lib/money/money.rb', line 108

def current_currency=(currency)
  Thread.current[:money_currency] = currency
end

.from_hash(hash) ⇒ Object



92
93
94
95
# File 'lib/money/money.rb', line 92

def from_hash(hash)
  hash = hash.transform_keys(&:to_sym)
  Money.new(hash.fetch(:value), hash.fetch(:currency))
end

.from_json(string) ⇒ Object



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

def from_json(string)
  hash = JSON.parse(string, symbolize_names: true)
  Money.new(hash.fetch(:value), hash.fetch(:currency))
end

.from_subunits(subunits, currency_iso, format: :iso4217) ⇒ Object



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

def from_subunits(subunits, currency_iso, format: :iso4217)
  currency = Helpers.value_to_currency(currency_iso)

  subunit_to_unit_value = if format == :iso4217
    currency.subunit_to_unit
  elsif format == :stripe
    Helpers::STRIPE_SUBUNIT_OVERRIDE.fetch(currency.iso_code, currency.subunit_to_unit)
  else
    raise ArgumentError, "unknown format #{format}"
  end

  value = Helpers.value_to_decimal(subunits) / subunit_to_unit_value
  new(value, currency)
end

.new(value = 0, currency = nil) ⇒ Object Also known as: from_amount



57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/money/money.rb', line 57

def new(value = 0, currency = nil)
  return new_from_money(value, currency) if value.is_a?(Money)

  value = Helpers.value_to_decimal(value)
  currency = Helpers.value_to_currency(currency)

  if value.zero?
    @@zero_money ||= {}
    @@zero_money[currency.iso_code] ||= super(Helpers::DECIMAL_ZERO, currency)
  else
    super(value, currency)
  end
end

.rational(money1, money2) ⇒ Object



97
98
99
100
101
102
# File 'lib/money/money.rb', line 97

def rational(money1, money2)
  money1.send(:arithmetic, money2) do
    factor = money1.currency.subunit_to_unit * money2.currency.subunit_to_unit
    Rational((money1.value * factor).to_i, (money2.value * factor).to_i)
  end
end

.with_currency(new_currency) ⇒ Object

Set Money.default_currency inside the supplied block, resets it to the previous value when done to prevent leaking state. Similar to I18n.with_locale and ActiveSupport’s Time.use_zone. This won’t affect instances being created with explicitly set currency.



116
117
118
119
120
121
122
# File 'lib/money/money.rb', line 116

def with_currency(new_currency)
  old_currency = Money.current_currency
  Money.current_currency = new_currency
  yield
ensure
  Money.current_currency = old_currency
end

Instance Method Details

#*(other) ⇒ Object

Raises:

  • (ArgumentError)


207
208
209
210
211
212
# File 'lib/money/money.rb', line 207

def *(other)
  raise ArgumentError, "Money objects can only be multiplied by a Numeric" unless other.is_a?(Numeric)

  return self if other == 1
  Money.new(value.to_r * other, currency)
end

#+(other) ⇒ Object



193
194
195
196
197
198
# File 'lib/money/money.rb', line 193

def +(other)
  arithmetic(other) do |money|
    return self if money.value.zero? && !no_currency?
    Money.new(value + money.value, calculated_currency(money.currency))
  end
end

#-(other) ⇒ Object



200
201
202
203
204
205
# File 'lib/money/money.rb', line 200

def -(other)
  arithmetic(other) do |money|
    return self if money.value.zero? && !no_currency?
    Money.new(value - money.value, calculated_currency(money.currency))
  end
end

#-@Object



182
183
184
# File 'lib/money/money.rb', line 182

def -@
  Money.new(-value, currency)
end

#/(other) ⇒ Object



214
215
216
# File 'lib/money/money.rb', line 214

def /(other)
  raise "[Money] Dividing money objects can lose pennies. Use #split instead"
end

#<=>(other) ⇒ Object



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

def <=>(other)
  return unless other.respond_to?(:to_money)
  arithmetic(other) do |money|
    value <=> money.value
  end
end

#==(other) ⇒ Object



222
223
224
# File 'lib/money/money.rb', line 222

def ==(other)
  eql?(other)
end

#absObject



305
306
307
308
309
# File 'lib/money/money.rb', line 305

def abs
  abs = value.abs
  return self if value == abs
  Money.new(abs, currency)
end

#allocate(splits, strategy = :roundrobin) ⇒ Object



331
332
333
# File 'lib/money/money.rb', line 331

def allocate(splits, strategy = :roundrobin)
  Money::Allocator.new(self).allocate(splits, strategy)
end

#allocate_max_amounts(maximums) ⇒ Object



336
337
338
# File 'lib/money/money.rb', line 336

def allocate_max_amounts(maximums)
  Money::Allocator.new(self).allocate_max_amounts(maximums)
end

#as_json(options = nil) ⇒ Object Also known as: to_h



296
297
298
299
300
301
302
# File 'lib/money/money.rb', line 296

def as_json(options = nil)
  if (options.is_a?(Hash) && options[:legacy_format]) || Money.config.legacy_json_format
    to_s
  else
    { value: to_s(:amount), currency: currency.to_s }
  end
end

#calculate_splits(num) ⇒ Hash<Money, Integer>

Calculate the splits evenly without losing pennies. Returns the number of high and low splits and the value of the high and low splits. Where high represents the Money value with the extra penny and low a Money without the extra penny.

Examples:

Money.new(100, "USD").calculate_splits(3) #=> {Money.new(34) => 1, Money.new(33) => 2}

Parameters:

  • number (2)

    of parties.

Returns:



363
364
365
# File 'lib/money/money.rb', line 363

def calculate_splits(num)
  Splitter.new(self, num).split.dup
end

#clamp(min, max) ⇒ Object

Clamps the value to be within the specified minimum and maximum. Returns self if the value is within bounds, otherwise a new Money object with the closest min or max value.

Examples:

Money.new(50, "CAD").clamp(1, 100) #=> Money.new(50, "CAD")

Money.new(120, "CAD").clamp(0, 100) #=> Money.new(100, "CAD")

Raises:

  • (ArgumentError)


375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/money/money.rb', line 375

def clamp(min, max)
  raise ArgumentError, 'min cannot be greater than max' if min > max

  clamped_value = min if value < min
  clamped_value = max if value > max

  if clamped_value.nil?
    self
  else
    Money.new(clamped_value, currency)
  end
end

#coerce(other) ⇒ Object

Raises:

  • (TypeError)


233
234
235
236
# File 'lib/money/money.rb', line 233

def coerce(other)
  raise TypeError, "Money can't be coerced into #{other.class}" unless other.is_a?(Numeric)
  [ReverseOperationProxy.new(other), self]
end

#convert_currency(exchange_rate, new_currency) ⇒ Object



238
239
240
# File 'lib/money/money.rb', line 238

def convert_currency(exchange_rate, new_currency)
  Money.new(value * exchange_rate, new_currency)
end

#encode_with(coder) ⇒ Object



161
162
163
164
# File 'lib/money/money.rb', line 161

def encode_with(coder)
  coder['value'] = @value.to_s('F')
  coder['currency'] = @currency.iso_code
end

#eql?(other) ⇒ Boolean

TODO: Remove once cross-currency mathematical operations are no longer allowed

Returns:

  • (Boolean)


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

def eql?(other)
  return false unless other.is_a?(Money)
  return false unless currency.compatible?(other.currency)
  value == other.value
end

#floorObject



311
312
313
314
315
# File 'lib/money/money.rb', line 311

def floor
  floor = value.floor
  return self if floor == value
  Money.new(floor, currency)
end

#fraction(rate) ⇒ Object

Raises:

  • (ArgumentError)


323
324
325
326
327
328
# File 'lib/money/money.rb', line 323

def fraction(rate)
  raise ArgumentError, "rate should be positive" if rate < 0

  result = value / (1 + rate)
  Money.new(result, currency)
end

#init_with(coder) ⇒ Object



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

def init_with(coder)
  initialize(Helpers.value_to_decimal(coder['value']), coder['currency'])
end

#inspectObject



218
219
220
# File 'lib/money/money.rb', line 218

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

#no_currency?Boolean

Returns:

  • (Boolean)


178
179
180
# File 'lib/money/money.rb', line 178

def no_currency?
  currency.is_a?(NullCurrency)
end

#round(ndigits = 0) ⇒ Object



317
318
319
320
321
# File 'lib/money/money.rb', line 317

def round(ndigits = 0)
  round = value.round(ndigits)
  return self if round == value
  Money.new(round, currency)
end

#split(num) ⇒ Enumerable<Money, Money, Money>

Split money amongst parties evenly without losing pennies.

Examples:

Money.new(100, "USD").split(3) #=> Enumerable[Money.new(34), Money.new(33), Money.new(33)]

Parameters:

  • number (2)

    of parties.

Returns:



348
349
350
# File 'lib/money/money.rb', line 348

def split(num)
  Splitter.new(self, num)
end

#subunits(format: :iso4217) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
# File 'lib/money/money.rb', line 166

def subunits(format: :iso4217)
  subunit_to_unit_value = if format == :iso4217
    @currency.subunit_to_unit
  elsif format == :stripe
    Helpers::STRIPE_SUBUNIT_OVERRIDE.fetch(@currency.iso_code, @currency.subunit_to_unit)
  else
    raise ArgumentError, "unknown format #{format}"
  end

  (@value * subunit_to_unit_value).to_i
end

#to_dObject



259
260
261
# File 'lib/money/money.rb', line 259

def to_d
  value
end

#to_fs(style = nil) ⇒ Object Also known as: to_s, to_formatted_s



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/money/money.rb', line 263

def to_fs(style = nil)
  units = case style
  when :legacy_dollars
    2
  when :amount, nil
    currency.minor_units
  else
    raise ArgumentError, "Unexpected format: #{style}"
  end

  rounded_value = value.round(units)
  if units == 0
    format("%d", rounded_value)
  else
    formatted = rounded_value.to_s("F")
    decimal_digits = formatted.size - formatted.index(".") - 1
    (units - decimal_digits).times do
      formatted << '0'
    end
    formatted
  end
end

#to_json(options = nil) ⇒ Object



288
289
290
291
292
293
294
# File 'lib/money/money.rb', line 288

def to_json(options = nil)
  if (options.is_a?(Hash) && options[:legacy_format]) || Money.config.legacy_json_format
    to_s
  else
    as_json(options).to_json
  end
end

#to_money(new_currency = nil) ⇒ Object



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/money/money.rb', line 242

def to_money(new_currency = nil)
  if new_currency.nil?
    return self
  end

  if no_currency?
    return Money.new(value, new_currency)
  end

  ensure_compatible_currency(
    Helpers.value_to_currency(new_currency),
    "to_money is attempting to change currency of an existing money object from #{currency} to #{new_currency}",
  )

  self
end