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.1"

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)


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

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.new
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
# File 'lib/money/money.rb', line 52

def configure
  yield(config) if block_given?
end

.current_currencyObject



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

def current_currency
  Thread.current[:money_currency]
end

.current_currency=(currency) ⇒ Object



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

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

.from_hash(hash) ⇒ Object



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

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

.from_json(string) ⇒ Object



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

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



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

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



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

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



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

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.



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

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)


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

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



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

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



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

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



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

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

#/(other) ⇒ Object



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

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

#<=>(other) ⇒ Object



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

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

#==(other) ⇒ Object



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

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

#absObject



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

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

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



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

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

#allocate_max_amounts(maximums) ⇒ Object



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

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

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



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

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:



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

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)


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

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)


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

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



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

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

#encode_with(coder) ⇒ Object



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

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)


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

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

#floorObject



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

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

#fraction(rate) ⇒ Object

Raises:

  • (ArgumentError)


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

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



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

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

#inspectObject



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

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

#no_currency?Boolean

Returns:

  • (Boolean)


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

def no_currency?
  currency.is_a?(NullCurrency)
end

#round(ndigits = 0) ⇒ Object



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

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:



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

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

#subunits(format: :iso4217) ⇒ Object



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

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



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

def to_d
  value
end

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



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

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



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

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



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

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