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

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



282
283
284
285
286
# File 'lib/money/money.rb', line 282

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

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



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

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

#allocate_max_amounts(maximums) ⇒ Object



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

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

#as_json(options = nil) ⇒ Object



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

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:



340
341
342
# File 'lib/money/money.rb', line 340

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)


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

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

#convert_currency(exchange_rate, new_currency) ⇒ Object



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

def convert_currency(exchange_rate, new_currency)
  Money.new(value * exchange_rate, new_currency)
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



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

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

#fraction(rate) ⇒ Object

Raises:

  • (ArgumentError)


300
301
302
303
304
305
# File 'lib/money/money.rb', line 300

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



294
295
296
297
298
# File 'lib/money/money.rb', line 294

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:



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

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



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

def to_d
  value
end

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



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

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



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

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



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

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