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/converters/factory.rb,
lib/money/parser/locale_aware.rb,
lib/money/converters/converter.rb,
lib/money/converters/stripe_converter.rb,
lib/money/converters/iso4217_converter.rb,
lib/money/rails/job_argument_serializer.rb,
lib/money/converters/legacy_dollars_converter.rb

Defined Under Namespace

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

Constant Summary collapse

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

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)


127
128
129
130
131
132
133
134
# File 'lib/money/money.rb', line 127

def initialize(value, currency)
  raise ArgumentError if value.nan?
  raise ArgumentError if value.infinite?

  @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



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

def config
  Money::Config.global
end

.configure(&block) ⇒ Object



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

def configure(&block)
  Money::Config.global.tap(&block)
end

.current_currencyObject



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

def current_currency
  Money::Config.current.currency
end

.current_currency=(value) ⇒ Object



60
61
62
# File 'lib/money/money.rb', line 60

def current_currency=(value)
  Money::Config.current.currency = value
end

.from_hash(hash) ⇒ Object



95
96
97
98
# File 'lib/money/money.rb', line 95

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

.from_json(string) ⇒ Object



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

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: nil) ⇒ Object



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

def from_subunits(subunits, currency_iso, format: nil)
  Converters.for(format).from_subunits(subunits, currency_iso)
end

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



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

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



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

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_config(**configs, &block) ⇒ Object



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

def with_config(**configs, &block)
  Money::Config.configure_current(**configs, &block)
end

.with_currency(currency, &block) ⇒ Object



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

def with_currency(currency, &block)
  if currency.nil?
    currency = current_currency
  end
  with_config(currency: currency, &block)
end

Instance Method Details

#*(other) ⇒ Object

Raises:

  • (ArgumentError)


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

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



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

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



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

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



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

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

#/(other) ⇒ Object



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

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

#<=>(other) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
# File 'lib/money/money.rb', line 160

def <=>(other)
  if other.is_a?(Numeric)
    return value <=> other
  end

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

#==(other) ⇒ Object



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

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

#absObject



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

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

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



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

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

#allocate_max_amounts(maximums) ⇒ Object



315
316
317
# File 'lib/money/money.rb', line 315

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

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



275
276
277
278
279
280
281
# File 'lib/money/money.rb', line 275

def as_json(options = nil)
  if (options.is_a?(Hash) && options[:legacy_format]) || Money::Config.current.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:



342
343
344
# File 'lib/money/money.rb', line 342

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)


354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/money/money.rb', line 354

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)


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

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



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

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

#encode_with(coder) ⇒ Object



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

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)


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

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

#floorObject



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

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

#fraction(rate) ⇒ Object

Raises:

  • (ArgumentError)


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

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



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

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

#inspectObject



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

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

#no_currency?Boolean

Returns:

  • (Boolean)


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

def no_currency?
  currency.is_a?(NullCurrency)
end

#round(ndigits = 0) ⇒ Object



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

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:



327
328
329
# File 'lib/money/money.rb', line 327

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

#subunits(format: nil) ⇒ Object



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

def subunits(format: nil)
  Converters.for(format).to_subunits(self)
end

#to_dObject



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

def to_d
  value
end

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



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

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



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

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

#to_money(new_currency = nil) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/money/money.rb', line 221

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