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 =
"2.2.2"

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)


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

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

Class Attribute Details

.configObject

Returns the value of attribute config.



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

def config
  @config
end

Instance Attribute Details

#currencyObject (readonly)

Returns the value of attribute currency.



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

def currency
  @currency
end

#valueObject (readonly)

Returns the value of attribute value.



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

def value
  @value
end

Class Method Details

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

Yields:



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

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

.current_currencyObject



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

def current_currency
  Thread.current[:money_currency]
end

.current_currency=(currency) ⇒ Object



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

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

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



62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/money/money.rb', line 62

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



47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/money/money.rb', line 47

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



77
78
79
80
81
82
# File 'lib/money/money.rb', line 77

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.



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

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

Instance Method Details

#*(numeric) ⇒ Object

Raises:

  • (ArgumentError)


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

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

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

#+(other) ⇒ Object



173
174
175
176
177
178
# File 'lib/money/money.rb', line 173

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



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

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



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

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

#/(numeric) ⇒ Object



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

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

#<=>(other) ⇒ Object



166
167
168
169
170
171
# File 'lib/money/money.rb', line 166

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

#==(other) ⇒ Object



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

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

#absObject



278
279
280
281
282
# File 'lib/money/money.rb', line 278

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

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



304
305
306
# File 'lib/money/money.rb', line 304

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

#allocate_max_amounts(maximums) ⇒ Object



309
310
311
# File 'lib/money/money.rb', line 309

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

#as_json(options = nil) ⇒ Object



270
271
272
273
274
275
276
# File 'lib/money/money.rb', line 270

def as_json(options = nil)
  if (options.is_a?(Hash) && options.delete(: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:



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

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)


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

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)


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

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



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

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)


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

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

#floorObject



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

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

#fraction(rate) ⇒ Object

Raises:

  • (ArgumentError)


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

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



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

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

#inspectObject



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

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

#no_currency?Boolean

Returns:

  • (Boolean)


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

def no_currency?
  currency.is_a?(NullCurrency)
end

#round(ndigits = 0) ⇒ Object



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

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:



321
322
323
# File 'lib/money/money.rb', line 321

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

#subunits(format: :iso4217) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
# File 'lib/money/money.rb', line 146

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



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

def to_d
  value
end

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



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

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
    sprintf("%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



262
263
264
265
266
267
268
# File 'lib/money/money.rb', line 262

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

#to_money(new_currency = nil) ⇒ Object



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

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