Class: Money

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Comparable
Defined in:
lib/money/money.rb,
lib/money/helpers.rb,
lib/money/version.rb,
lib/money/currency.rb,
lib/money/null_currency.rb,
lib/money/currency/loader.rb

Defined Under Namespace

Modules: Helpers Classes: Currency, NullCurrency, ReverseOperationProxy

Constant Summary collapse

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

Class Attribute Summary collapse

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)


81
82
83
84
85
86
# File 'lib/money/money.rb', line 81

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

Class Attribute Details

.default_currencyObject

Returns the value of attribute default_currency.



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

def default_currency
  @default_currency
end

.parserObject

Returns the value of attribute parser.



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

def parser
  @parser
end

Instance Attribute Details

#currencyObject (readonly)

Returns the value of attribute currency.



7
8
9
# File 'lib/money/money.rb', line 7

def currency
  @currency
end

#valueObject (readonly)

Returns the value of attribute value.



7
8
9
# File 'lib/money/money.rb', line 7

def value
  @value
end

Class Method Details

.current_currencyObject



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

def current_currency
  Thread.current[:money_currency]
end

.current_currency=(currency) ⇒ Object



56
57
58
# File 'lib/money/money.rb', line 56

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

.default_settingsObject



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

def default_settings
  self.parser = MoneyParser
  self.default_currency = Money::NULL_CURRENCY
end

.from_cents(cents, currency = nil) ⇒ Object



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

def from_cents(cents, currency = nil)
  new(cents.round.to_f / 100, currency)
end

.from_subunits(subunits, currency_iso) ⇒ Object



39
40
41
42
43
# File 'lib/money/money.rb', line 39

def from_subunits(subunits, currency_iso)
  currency = Helpers.value_to_currency(currency_iso)
  value = Helpers.value_to_decimal(subunits) / currency.subunit_to_unit
  new(value, currency)
end

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



13
14
15
16
17
18
19
20
21
22
23
# File 'lib/money/money.rb', line 13

def new(value = 0, currency = nil)
  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

.parse(*args) ⇒ Object



31
32
33
# File 'lib/money/money.rb', line 31

def parse(*args)
  parser.parse(*args)
end

.rational(money1, money2) ⇒ Object



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

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.



64
65
66
67
68
69
70
71
72
# File 'lib/money/money.rb', line 64

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

.zero(currency = NULL_CURRENCY) ⇒ Object Also known as: empty



26
27
28
# File 'lib/money/money.rb', line 26

def zero(currency = NULL_CURRENCY)
  new(0, currency)
end

Instance Method Details

#*(numeric) ⇒ Object



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

def *(numeric)
  unless numeric.is_a?(Numeric)
    Money.deprecate("Multiplying Money with #{numeric.class.name} is deprecated and will be removed in the next major release.")
  end
  Money.new(value.to_r * numeric, currency)
end

#+(other) ⇒ Object



120
121
122
123
124
# File 'lib/money/money.rb', line 120

def +(other)
  arithmetic(other) do |money|
    Money.new(value + money.value, calculated_currency(money.currency))
  end
end

#-(other) ⇒ Object



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

def -(other)
  arithmetic(other) do |money|
    Money.new(value - money.value, calculated_currency(money.currency))
  end
end

#-@Object



110
111
112
# File 'lib/money/money.rb', line 110

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

#/(numeric) ⇒ Object



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

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

#<=>(other) ⇒ Object



114
115
116
117
118
# File 'lib/money/money.rb', line 114

def <=>(other)
  arithmetic(other) do |money|
    value <=> money.value
  end
end

#==(other) ⇒ Object



147
148
149
# File 'lib/money/money.rb', line 147

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

#absObject



224
225
226
# File 'lib/money/money.rb', line 224

def abs
  Money.new(value.abs, currency)
end

#allocate(splits) ⇒ Object

Examples:

left over cents distributed to first party due to rounding, and two solutions for a more natural distribution

Money.new(30, "USD").allocate([0.667, 0.333])
  #=> [#<Money value:20.01 currency:USD>, #<Money value:9.99 currency:USD>]
Money.new(30, "USD").allocate([0.333, 0.667])
  #=> [#<Money value:20.00 currency:USD>, #<Money value:10.00 currency:USD>]
Money.new(30, "USD").allocate([Rational(2, 3), Rational(1, 3)])
  #=> [#<Money value:20.00 currency:USD>, #<Money value:10.00 currency:USD>]


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

def allocate(splits)
  if all_rational?(splits)
    allocations = splits.inject(0) { |sum, n| sum + n }
  else
    allocations = splits.inject(0) { |sum, n| sum + Helpers.value_to_decimal(n) }
  end

  if (allocations - BigDecimal("1")) > Float::EPSILON
    raise ArgumentError, "splits add to more than 100%"
  end

  amounts, left_over = amounts_from_splits(allocations, splits)

  left_over.to_i.times { |i| amounts[i % amounts.length] += 1 }

  amounts.collect { |subunits| Money.from_subunits(subunits, currency) }
end

#allocate_max_amounts(maximums) ⇒ Object

Allocates money between different parties up to the maximum amounts specified. Left over pennies will be assigned round-robin up to the maximum specified. Pennies are dropped when the maximums are attained.

Examples:

Money.new(30.75).allocate_max_amounts([Money.new(26), Money.new(4.75)])
  #=> [Money.new(26), Money.new(4.75)]

Money.new(30.75).allocate_max_amounts([Money.new(26), Money.new(4.74)]
  #=> [Money.new(26), Money.new(4.74)]

Money.new(30).allocate_max_amounts([Money.new(15), Money.new(15)]
  #=> [Money.new(15), Money.new(15)]

Money.new(1).allocate_max_amounts([Money.new(33), Money.new(33), Money.new(33)])
  #=> [Money.new(0.34), Money.new(0.33), Money.new(0.33)]

Money.new(100).allocate_max_amounts([Money.new(5), Money.new(2)])
  #=> [Money.new(5), Money.new(2)]


303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/money/money.rb', line 303

def allocate_max_amounts(maximums)
  allocation_currency = extract_currency(maximums + [self])
  maximums = maximums.map { |max| max.to_money(allocation_currency) }
  maximums_total = maximums.reduce(Money.new(0, allocation_currency), :+)

  splits = maximums.map do |max_amount|
    next(0) if maximums_total.zero?
    Money.rational(max_amount, maximums_total)
  end

  total_allocatable = [
    value * allocation_currency.subunit_to_unit,
    maximums_total.value * allocation_currency.subunit_to_unit
  ].min

  subunits_amounts, left_over = amounts_from_splits(1, splits, total_allocatable)

  subunits_amounts.each_with_index do |amount, index|
    break unless left_over > 0

    max_amount = maximums[index].value * allocation_currency.subunit_to_unit
    next unless amount < max_amount

    left_over -= 1
    subunits_amounts[index] += 1
  end

  subunits_amounts.map { |cents| Money.from_subunits(cents, allocation_currency) }
end

#as_json(*args) ⇒ Object



220
221
222
# File 'lib/money/money.rb', line 220

def as_json(*args)
  to_s
end

#centsObject



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

def cents
  # Money.deprecate('`money.cents` is deprecated and will be removed in the next major release. Please use `money.subunits` instead. Keep in mind, subunits are currency aware.')
  (value * 100).to_i
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)


365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/money/money.rb', line 365

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

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

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

#coerce(other) ⇒ Object

Raises:

  • (TypeError)


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

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

#encode_with(coder) ⇒ Object



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

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

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#floorObject



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

def floor
  Money.new(value.floor, currency)
end

#fraction(rate) ⇒ Object

Raises:

  • (ArgumentError)


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

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



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

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

#inspectObject



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

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

#no_currency?Boolean

Returns:

  • (Boolean)


106
107
108
# File 'lib/money/money.rb', line 106

def no_currency?
  currency.is_a?(NullCurrency)
end

#round(ndigits = 0) ⇒ Object



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

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

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

Split money amongst parties evenly without losing pennies.

Examples:

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

Parameters:

  • number (2)

    of parties.

Returns:

Raises:

  • (ArgumentError)


341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/money/money.rb', line 341

def split(num)
  raise ArgumentError, "need at least one party" if num < 1
  subunits = self.subunits
  low = Money.from_subunits(subunits / num, currency)
  high = Money.from_subunits(low.subunits + 1, currency)

  remainder = subunits % num
  result = []

  num.times do |index|
    result[index] = index < remainder ? high : low
  end

  return result
end

#subunitsObject



102
103
104
# File 'lib/money/money.rb', line 102

def subunits
  (@value * @currency.subunit_to_unit).to_i
end

#to_dObject



199
200
201
# File 'lib/money/money.rb', line 199

def to_d
  value
end

#to_json(options = {}) ⇒ Object



216
217
218
# File 'lib/money/money.rb', line 216

def to_json(options = {})
  to_s
end

#to_liquidObject



212
213
214
# File 'lib/money/money.rb', line 212

def to_liquid
  cents
end

#to_money(curr = nil) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/money/money.rb', line 186

def to_money(curr = nil)
  if !curr.nil? && no_currency?
    return Money.new(value, curr)
  end

  curr = Helpers.value_to_currency(curr)
  unless currency.compatible?(curr)
    Money.deprecate("mathematical operation not permitted for Money objects with different currencies #{curr} and #{currency}.")
  end

  self
end

#to_s(style = nil) ⇒ Object



203
204
205
206
207
208
209
210
# File 'lib/money/money.rb', line 203

def to_s(style = nil)
  case style
  when :legacy_dollars
    sprintf("%.2f", value)
  when :amount, nil
    sprintf("%.#{currency.minor_units}f", value)
  end
end