micro-lite-lib 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/micro-lite-lib.gemspec +11 -0
- data/money-7.0.2/CHANGELOG.md +843 -0
- data/money-7.0.2/LICENSE +23 -0
- data/money-7.0.2/README.md +626 -0
- data/money-7.0.2/config/currency_backwards_compatible.json +225 -0
- data/money-7.0.2/config/currency_iso.json +2750 -0
- data/money-7.0.2/config/currency_non_iso.json +146 -0
- data/money-7.0.2/lib/money/bank/base.rb +130 -0
- data/money-7.0.2/lib/money/bank/single_currency.rb +26 -0
- data/money-7.0.2/lib/money/bank/variable_exchange.rb +285 -0
- data/money-7.0.2/lib/money/currency/heuristics.rb +11 -0
- data/money-7.0.2/lib/money/currency/loader.rb +28 -0
- data/money-7.0.2/lib/money/currency.rb +477 -0
- data/money-7.0.2/lib/money/locale_backend/base.rb +9 -0
- data/money-7.0.2/lib/money/locale_backend/currency.rb +13 -0
- data/money-7.0.2/lib/money/locale_backend/errors.rb +8 -0
- data/money-7.0.2/lib/money/locale_backend/i18n.rb +28 -0
- data/money-7.0.2/lib/money/money/allocation.rb +87 -0
- data/money-7.0.2/lib/money/money/arithmetic.rb +346 -0
- data/money-7.0.2/lib/money/money/constructors.rb +86 -0
- data/money-7.0.2/lib/money/money/formatter.rb +361 -0
- data/money-7.0.2/lib/money/money/formatting_rules.rb +92 -0
- data/money-7.0.2/lib/money/money/locale_backend.rb +20 -0
- data/money-7.0.2/lib/money/money.rb +664 -0
- data/money-7.0.2/lib/money/rates_store/memory.rb +122 -0
- data/money-7.0.2/lib/money/version.rb +5 -0
- data/money-7.0.2/lib/money.rb +9 -0
- data/money-7.0.2/money.gemspec +32 -0
- metadata +68 -0
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "money/bank/variable_exchange"
|
|
4
|
+
require "money/bank/single_currency"
|
|
5
|
+
require "money/money/arithmetic"
|
|
6
|
+
require "money/money/constructors"
|
|
7
|
+
require "money/money/formatter"
|
|
8
|
+
require "money/money/allocation"
|
|
9
|
+
require "money/money/locale_backend"
|
|
10
|
+
|
|
11
|
+
# "Money is any object or record that is generally accepted as payment for
|
|
12
|
+
# goods and services and repayment of debts in a given socio-economic context
|
|
13
|
+
# or country." -Wikipedia
|
|
14
|
+
#
|
|
15
|
+
# An instance of Money represents an amount of a specific currency.
|
|
16
|
+
#
|
|
17
|
+
# Money is a value object and should be treated as immutable.
|
|
18
|
+
#
|
|
19
|
+
# @see https://en.wikipedia.org/wiki/Money
|
|
20
|
+
class Money
|
|
21
|
+
include Comparable
|
|
22
|
+
include Money::Arithmetic
|
|
23
|
+
extend Constructors
|
|
24
|
+
|
|
25
|
+
# Raised when smallest denomination of a currency is not defined
|
|
26
|
+
class UndefinedSmallestDenomination < StandardError; end
|
|
27
|
+
|
|
28
|
+
# Convenience method for fractional part of the amount. Synonym of #fractional
|
|
29
|
+
#
|
|
30
|
+
# @return [Integer] when infinite_precision is false
|
|
31
|
+
# @return [BigDecimal] when infinite_precision is true
|
|
32
|
+
#
|
|
33
|
+
# @see infinite_precision
|
|
34
|
+
def cents
|
|
35
|
+
fractional
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The value of the monetary amount represented in the fractional or subunit
|
|
39
|
+
# of the currency.
|
|
40
|
+
#
|
|
41
|
+
# For example, in the US dollar currency the fractional unit is cents, and
|
|
42
|
+
# there are 100 cents in one US dollar. So given the Money representation of
|
|
43
|
+
# one US dollar, the fractional interpretation is 100.
|
|
44
|
+
#
|
|
45
|
+
# Another example is that of the Kuwaiti dinar. In this case the fractional
|
|
46
|
+
# unit is the fils and there 1000 fils to one Kuwaiti dinar. So given the
|
|
47
|
+
# Money representation of one Kuwaiti dinar, the fractional interpretation is
|
|
48
|
+
# 1000.
|
|
49
|
+
#
|
|
50
|
+
# @return [Integer] when infinite_precision is false
|
|
51
|
+
# @return [BigDecimal] when infinite_precision is true
|
|
52
|
+
#
|
|
53
|
+
# @see infinite_precision
|
|
54
|
+
def fractional
|
|
55
|
+
# Ensure we have a BigDecimal. If the Money object is created
|
|
56
|
+
# from YAML, @fractional can end up being set to a Float.
|
|
57
|
+
fractional = as_d(@fractional)
|
|
58
|
+
|
|
59
|
+
return_value(fractional)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Round a given amount of money to the nearest possible amount in cash value. For
|
|
63
|
+
# example, in Swiss franc (CHF), the smallest possible amount of cash value is
|
|
64
|
+
# CHF 0.05. Therefore, this method rounds CHF 0.07 to CHF 0.05, and CHF 0.08 to
|
|
65
|
+
# CHF 0.10.
|
|
66
|
+
#
|
|
67
|
+
# @return [Integer] when infinite_precision is false
|
|
68
|
+
# @return [BigDecimal] when infinite_precision is true
|
|
69
|
+
#
|
|
70
|
+
# @see infinite_precision
|
|
71
|
+
def round_to_nearest_cash_value
|
|
72
|
+
warn "[DEPRECATION] `round_to_nearest_cash_value` is deprecated - use " \
|
|
73
|
+
"`to_nearest_cash_value.fractional` instead"
|
|
74
|
+
|
|
75
|
+
to_nearest_cash_value.fractional
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Round a given amount of money to the nearest possible money in cash value.
|
|
79
|
+
# For example, in Swiss franc (CHF), the smallest possible amount of cash
|
|
80
|
+
# value is CHF 0.05. Therefore, this method rounds CHF 0.07 to CHF 0.05, and
|
|
81
|
+
# CHF 0.08 to CHF 0.10.
|
|
82
|
+
#
|
|
83
|
+
# @return [Money]
|
|
84
|
+
def to_nearest_cash_value
|
|
85
|
+
unless self.currency.smallest_denomination
|
|
86
|
+
raise UndefinedSmallestDenomination,
|
|
87
|
+
"Smallest denomination of this currency is not defined"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
fractional = as_d(@fractional)
|
|
91
|
+
smallest_denomination = as_d(self.currency.smallest_denomination)
|
|
92
|
+
rounded_value =
|
|
93
|
+
(fractional / smallest_denomination)
|
|
94
|
+
.round(0, self.class.rounding_mode) * smallest_denomination
|
|
95
|
+
|
|
96
|
+
dup_with(fractional: return_value(rounded_value))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @!attribute [r] currency
|
|
100
|
+
# @return [Currency] The money's currency.
|
|
101
|
+
# @!attribute [r] bank
|
|
102
|
+
# @return [Money::Bank::Base] The +Money::Bank+-based object which currency
|
|
103
|
+
# exchanges are performed with.
|
|
104
|
+
|
|
105
|
+
attr_reader :currency, :bank
|
|
106
|
+
|
|
107
|
+
# Class Methods
|
|
108
|
+
class << self
|
|
109
|
+
|
|
110
|
+
# @!attribute [rw] default_bank
|
|
111
|
+
# Used to set a default bank for currency exchange.
|
|
112
|
+
#
|
|
113
|
+
# Each Money object is associated with a bank
|
|
114
|
+
# object, which is responsible for currency exchange. This property
|
|
115
|
+
# allows you to specify the default bank object. The default value for
|
|
116
|
+
# this property is an instance of +Bank::VariableExchange.+ It allows
|
|
117
|
+
# one to specify custom exchange rates.
|
|
118
|
+
#
|
|
119
|
+
# @return [Money::Bank::Base]
|
|
120
|
+
#
|
|
121
|
+
# @!attribute default_formatting_rules
|
|
122
|
+
# Used to define a default hash of rules for every time
|
|
123
|
+
# +Money#format+ is called. Rules provided on method call will be
|
|
124
|
+
# merged with the default ones. To overwrite a rule, just provide the
|
|
125
|
+
# intended value while calling +format+.
|
|
126
|
+
#
|
|
127
|
+
# @see Money::Formatter#initialize Money::Formatter for more details
|
|
128
|
+
#
|
|
129
|
+
# @example
|
|
130
|
+
# Money.default_formatting_rules = { display_free: true }
|
|
131
|
+
# Money.new(0, "USD").format # => "free"
|
|
132
|
+
# Money.new(0, "USD").format(display_free: false) # => "$0.00"
|
|
133
|
+
#
|
|
134
|
+
# @return [Hash]
|
|
135
|
+
#
|
|
136
|
+
# @!attribute [rw] default_infinite_precision
|
|
137
|
+
# @return [Boolean] Use this to enable infinite precision cents as the
|
|
138
|
+
# global default
|
|
139
|
+
#
|
|
140
|
+
# @!attribute [rw] conversion_precision
|
|
141
|
+
# Used to specify precision for converting Rational to BigDecimal
|
|
142
|
+
#
|
|
143
|
+
# @return [Integer]
|
|
144
|
+
#
|
|
145
|
+
# @!attribute [rw] strict_eql_compare
|
|
146
|
+
# Use this to specify how +Money#eql?+ behaves. Opt-in to the new
|
|
147
|
+
# behavior by setting this to +true+ and disable warnings when comparing
|
|
148
|
+
# zero amounts with different currencies.
|
|
149
|
+
#
|
|
150
|
+
# @example
|
|
151
|
+
# Money.strict_eql_compare = false # (default)
|
|
152
|
+
# Money.new(0, "USD").eql?(Money.new(0, "EUR")) # => true
|
|
153
|
+
# # => [DEPRECATION] warning
|
|
154
|
+
#
|
|
155
|
+
# Money.strict_eql_compare = true
|
|
156
|
+
# Money.new(0, "USD").eql?(Money.new(0, "EUR")) # => false
|
|
157
|
+
#
|
|
158
|
+
# @return [Boolean]
|
|
159
|
+
#
|
|
160
|
+
# @see Money#eql
|
|
161
|
+
attr_accessor :default_formatting_rules,
|
|
162
|
+
:default_infinite_precision,
|
|
163
|
+
:conversion_precision,
|
|
164
|
+
:strict_eql_compare
|
|
165
|
+
attr_reader :locale_backend
|
|
166
|
+
attr_writer :default_bank
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# @!attribute default_currency
|
|
170
|
+
# @return [Money::Currency] The default currency, which is used when
|
|
171
|
+
# +Money.new+ is called without an explicit currency argument.
|
|
172
|
+
def self.default_currency
|
|
173
|
+
if @default_currency.nil?
|
|
174
|
+
nil
|
|
175
|
+
elsif @default_currency.respond_to?(:call)
|
|
176
|
+
Money::Currency.new(@default_currency.call)
|
|
177
|
+
else
|
|
178
|
+
Money::Currency.new(@default_currency)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def self.default_currency=(currency)
|
|
183
|
+
@default_currency = currency
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Modified to support thread-local bank override
|
|
187
|
+
def self.default_bank
|
|
188
|
+
# Check for thread-local bank first, then fall back to global default
|
|
189
|
+
return Thread.current[:money_bank] if Thread.current[:money_bank]
|
|
190
|
+
|
|
191
|
+
if @default_bank.respond_to?(:call)
|
|
192
|
+
@default_bank.call
|
|
193
|
+
else
|
|
194
|
+
@default_bank
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Thread-safe bank switching method
|
|
199
|
+
# Temporarily changes the default bank in the current thread only
|
|
200
|
+
#
|
|
201
|
+
# @param [Money::Bank::Base] bank The bank to use within the block
|
|
202
|
+
# @yield The block within which the bank will be changed
|
|
203
|
+
# @return [Object] block results
|
|
204
|
+
#
|
|
205
|
+
# @example
|
|
206
|
+
# Money.with_bank(european_bank) do
|
|
207
|
+
# Money.new(100, "USD").exchange_to("EUR")
|
|
208
|
+
# end
|
|
209
|
+
def self.with_bank(bank)
|
|
210
|
+
original_bank = Thread.current[:money_bank]
|
|
211
|
+
Thread.current[:money_bank] = bank
|
|
212
|
+
yield
|
|
213
|
+
ensure
|
|
214
|
+
Thread.current[:money_bank] = original_bank
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def self.locale_backend=(value)
|
|
218
|
+
@locale_backend = value ? LocaleBackend.find(value) : nil
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# @attr_writer rounding_mode Use this to specify the rounding mode
|
|
222
|
+
def self.rounding_mode=(new_rounding_mode)
|
|
223
|
+
@rounding_mode = new_rounding_mode
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def self.setup_defaults
|
|
227
|
+
# Set the default bank for creating new +Money+ objects.
|
|
228
|
+
self.default_bank = Bank::VariableExchange.instance
|
|
229
|
+
|
|
230
|
+
# Default to using currency backend
|
|
231
|
+
self.locale_backend = :currency
|
|
232
|
+
|
|
233
|
+
# Default to not using infinite precision cents
|
|
234
|
+
self.default_infinite_precision = false
|
|
235
|
+
|
|
236
|
+
# Default rounding mode toward the nearest neighbor; if the neighbors are equidistant, round away from zero
|
|
237
|
+
self.rounding_mode = BigDecimal::ROUND_HALF_UP
|
|
238
|
+
|
|
239
|
+
# Default the conversion of Rationals precision to 16
|
|
240
|
+
self.conversion_precision = 16
|
|
241
|
+
|
|
242
|
+
# Defaults to the deprecated behavior where
|
|
243
|
+
# `Money.new(0, "USD").eql?(Money.new(0, "EUR"))` is true.
|
|
244
|
+
self.strict_eql_compare = false
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def self.inherited(base)
|
|
248
|
+
base.setup_defaults
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
setup_defaults
|
|
252
|
+
|
|
253
|
+
# Use this to return the rounding mode.
|
|
254
|
+
#
|
|
255
|
+
# @return [BigDecimal::ROUND_MODE] rounding mode
|
|
256
|
+
def self.rounding_mode
|
|
257
|
+
return Thread.current[:money_rounding_mode] if Thread.current[:money_rounding_mode]
|
|
258
|
+
|
|
259
|
+
@rounding_mode
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Temporarily changes the rounding mode in a given block.
|
|
263
|
+
#
|
|
264
|
+
# @param [BigDecimal::ROUND_MODE] mode
|
|
265
|
+
#
|
|
266
|
+
# @yield The block within which rounding mode will be changed. Its return
|
|
267
|
+
# value will also be the return value of the whole method.
|
|
268
|
+
#
|
|
269
|
+
# @return [Object] block results
|
|
270
|
+
#
|
|
271
|
+
# @example
|
|
272
|
+
# fee = Money.with_rounding_mode(BigDecimal::ROUND_HALF_DOWN) do
|
|
273
|
+
# Money.new(1200) * BigDecimal('0.029')
|
|
274
|
+
# end
|
|
275
|
+
def self.with_rounding_mode(mode)
|
|
276
|
+
original_mode = Thread.current[:money_rounding_mode]
|
|
277
|
+
Thread.current[:money_rounding_mode] = mode
|
|
278
|
+
yield
|
|
279
|
+
ensure
|
|
280
|
+
Thread.current[:money_rounding_mode] = original_mode
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Adds a new exchange rate to the default bank and return the rate.
|
|
284
|
+
#
|
|
285
|
+
# @param [Currency, String, Symbol] from_currency Currency to exchange from.
|
|
286
|
+
# @param [Currency, String, Symbol] to_currency Currency to exchange to.
|
|
287
|
+
# @param [Numeric] rate Rate to exchange with.
|
|
288
|
+
#
|
|
289
|
+
# @return [Numeric]
|
|
290
|
+
#
|
|
291
|
+
# @example
|
|
292
|
+
# Money.add_rate("USD", "CAD", 1.25) #=> 1.25
|
|
293
|
+
def self.add_rate(from_currency, to_currency, rate)
|
|
294
|
+
Money.default_bank.add_rate(from_currency, to_currency, rate)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Sets the default bank to be a SingleCurrency bank that raises on
|
|
298
|
+
# currency exchange. Useful when apps operate in a single currency at a time.
|
|
299
|
+
def self.disallow_currency_conversion!
|
|
300
|
+
self.default_bank = Bank::SingleCurrency.instance
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Creates a new Money object of value given in the +unit+ of the given
|
|
304
|
+
# +currency+.
|
|
305
|
+
#
|
|
306
|
+
# @param [Numeric] amount The numerical value of the money.
|
|
307
|
+
# @param [Currency, String, Symbol] currency The currency format.
|
|
308
|
+
# @param [Hash] options Optional settings for the new Money instance
|
|
309
|
+
# @option [Money::Bank::*] :bank The exchange bank to use.
|
|
310
|
+
#
|
|
311
|
+
# @example
|
|
312
|
+
# Money.from_amount(23.45, "USD") # => #<Money fractional:2345 currency:USD>
|
|
313
|
+
# Money.from_amount(23.45, "JPY") # => #<Money fractional:23 currency:JPY>
|
|
314
|
+
#
|
|
315
|
+
# @return [Money]
|
|
316
|
+
#
|
|
317
|
+
# @see #initialize
|
|
318
|
+
def self.from_amount(amount, currency = default_currency, options = {})
|
|
319
|
+
raise ArgumentError, "'amount' must be numeric" unless Numeric === amount
|
|
320
|
+
|
|
321
|
+
currency = Currency.wrap(currency) || Money.default_currency
|
|
322
|
+
raise Currency::NoCurrency, 'must provide a currency' if currency.nil?
|
|
323
|
+
|
|
324
|
+
value = amount.to_d * currency.subunit_to_unit
|
|
325
|
+
new(value, currency, options)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# DEPRECATED.
|
|
329
|
+
#
|
|
330
|
+
# @see Money.from_amount
|
|
331
|
+
def self.from_dollars(amount, currency = default_currency, options = {})
|
|
332
|
+
warn "[DEPRECATION] `Money.from_dollars` is deprecated in favor of " \
|
|
333
|
+
"`Money.from_amount`."
|
|
334
|
+
|
|
335
|
+
from_amount(amount, currency, options)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
class << self
|
|
339
|
+
alias_method :from_cents, :new
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Creates a new Money object of value given in the
|
|
343
|
+
# +fractional unit+ of the given +currency+.
|
|
344
|
+
#
|
|
345
|
+
# Alternatively you can use the convenience
|
|
346
|
+
# methods like {Money.ca_dollar} and {Money.us_dollar}.
|
|
347
|
+
#
|
|
348
|
+
# @param [Object] obj Either the fractional value of the money,
|
|
349
|
+
# a Money object, or a currency. (If passed a currency as the first
|
|
350
|
+
# argument, a Money will be created in that currency with fractional value
|
|
351
|
+
# = 0.
|
|
352
|
+
# @param [Currency, String, Symbol] currency The currency format.
|
|
353
|
+
# @param [Hash] options Optional settings for the new Money instance
|
|
354
|
+
# @option [Money::Bank::*] :bank The exchange bank to use.
|
|
355
|
+
#
|
|
356
|
+
# @return [Money]
|
|
357
|
+
#
|
|
358
|
+
# @example
|
|
359
|
+
# Money.new(100) #=> #<Money @fractional=100 @currency="USD">
|
|
360
|
+
# Money.new(100, "USD") #=> #<Money @fractional=100 @currency="USD">
|
|
361
|
+
# Money.new(100, "EUR") #=> #<Money @fractional=100 @currency="EUR">
|
|
362
|
+
#
|
|
363
|
+
def initialize(obj, currency = nil, options = {})
|
|
364
|
+
# For backwards compatibility, if options is not a Hash, treat it as a bank parameter
|
|
365
|
+
unless options.is_a?(Hash)
|
|
366
|
+
options = { bank: options }
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
@fractional = as_d(obj.respond_to?(:fractional) ? obj.fractional : obj)
|
|
370
|
+
@currency = obj.respond_to?(:currency) ? obj.currency : Currency.wrap(currency)
|
|
371
|
+
@currency ||= Money.default_currency
|
|
372
|
+
@bank = obj.respond_to?(:bank) ? obj.bank : options[:bank]
|
|
373
|
+
@bank ||= Money.default_bank
|
|
374
|
+
|
|
375
|
+
# BigDecimal can be Infinity and NaN, money of that amount does not make sense
|
|
376
|
+
raise ArgumentError, 'must be initialized with a finite value' unless @fractional.finite?
|
|
377
|
+
raise Currency::NoCurrency, 'must provide a currency' if @currency.nil?
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# DEPRECATED.
|
|
381
|
+
#
|
|
382
|
+
# @see #amount
|
|
383
|
+
def dollars
|
|
384
|
+
warn "[DEPRECATION] `Money#dollars` is deprecated in favor of " \
|
|
385
|
+
"`Money#amount`."
|
|
386
|
+
|
|
387
|
+
amount
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Returns the numerical value of the money.
|
|
391
|
+
#
|
|
392
|
+
# @return [BigDecimal]
|
|
393
|
+
#
|
|
394
|
+
# @example
|
|
395
|
+
# Money.new(1_00, "USD").amount # => BigDecimal("1.00")
|
|
396
|
+
#
|
|
397
|
+
# @see #to_d
|
|
398
|
+
# @see #fractional
|
|
399
|
+
def amount
|
|
400
|
+
to_d
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Returns a Integer hash value based on the +fractional+ and +currency+ attributes
|
|
404
|
+
# in order to use functions like & (intersection), group_by, etc.
|
|
405
|
+
#
|
|
406
|
+
# @return [Integer]
|
|
407
|
+
#
|
|
408
|
+
# @example
|
|
409
|
+
# Money.new(100).hash #=> 908351
|
|
410
|
+
def hash
|
|
411
|
+
[fractional.hash, currency.hash].hash
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Uses +Currency#symbol+. If +nil+ is returned, defaults to "¤".
|
|
415
|
+
#
|
|
416
|
+
# @return [String]
|
|
417
|
+
#
|
|
418
|
+
# @example
|
|
419
|
+
# Money.new(100, "USD").symbol #=> "$"
|
|
420
|
+
def symbol
|
|
421
|
+
currency.symbol || "¤"
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Common inspect function
|
|
425
|
+
#
|
|
426
|
+
# @return [String]
|
|
427
|
+
def inspect
|
|
428
|
+
"#<#{self.class.name} fractional:#{fractional} currency:#{currency}>"
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# Returns the amount of money as a string.
|
|
432
|
+
#
|
|
433
|
+
# @return [String]
|
|
434
|
+
#
|
|
435
|
+
# @example
|
|
436
|
+
# Money.ca_dollar(100).to_s #=> "1.00"
|
|
437
|
+
def to_s
|
|
438
|
+
format thousands_separator: '',
|
|
439
|
+
no_cents_if_whole: currency.decimal_places == 0,
|
|
440
|
+
symbol: false,
|
|
441
|
+
ignore_defaults: true
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
# Return the amount of money as a BigDecimal.
|
|
445
|
+
#
|
|
446
|
+
# @return [BigDecimal]
|
|
447
|
+
#
|
|
448
|
+
# @example
|
|
449
|
+
# Money.us_dollar(1_00).to_d #=> BigDecimal("1.00")
|
|
450
|
+
def to_d
|
|
451
|
+
as_d(fractional) / as_d(currency.subunit_to_unit)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# Return the amount of money as a Integer.
|
|
455
|
+
#
|
|
456
|
+
# @return [Integer]
|
|
457
|
+
#
|
|
458
|
+
# @example
|
|
459
|
+
# Money.us_dollar(1_00).to_i #=> 1
|
|
460
|
+
def to_i
|
|
461
|
+
to_d.to_i
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Return the amount of money as a float. Floating points cannot guarantee
|
|
465
|
+
# precision. Therefore, this function should only be used when you no longer
|
|
466
|
+
# need to represent currency or working with another system that requires
|
|
467
|
+
# floats.
|
|
468
|
+
#
|
|
469
|
+
# @return [Float]
|
|
470
|
+
#
|
|
471
|
+
# @example
|
|
472
|
+
# Money.us_dollar(100).to_f #=> 1.0
|
|
473
|
+
def to_f
|
|
474
|
+
to_d.to_f
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# Returns a new Money instance in a given currency leaving the amount intact
|
|
478
|
+
# and not performing currency conversion.
|
|
479
|
+
#
|
|
480
|
+
# @param [Currency, String, Symbol] new_currency Currency of the new object.
|
|
481
|
+
#
|
|
482
|
+
# @return [self]
|
|
483
|
+
def with_currency(new_currency)
|
|
484
|
+
new_currency = Currency.wrap(new_currency)
|
|
485
|
+
if !new_currency || currency == new_currency
|
|
486
|
+
self
|
|
487
|
+
else
|
|
488
|
+
dup_with(currency: new_currency)
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Conversion to +self+.
|
|
493
|
+
#
|
|
494
|
+
# @return [self]
|
|
495
|
+
def to_money(given_currency = nil)
|
|
496
|
+
given_currency = Currency.wrap(given_currency)
|
|
497
|
+
if given_currency.nil? || self.currency == given_currency
|
|
498
|
+
self
|
|
499
|
+
else
|
|
500
|
+
exchange_to(given_currency)
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Receive the amount of this money object in another Currency.
|
|
505
|
+
#
|
|
506
|
+
# @param [Currency, String, Symbol] other_currency Currency to exchange to.
|
|
507
|
+
#
|
|
508
|
+
# @yield [n] Optional block to use when rounding after exchanging one currency
|
|
509
|
+
# for another.
|
|
510
|
+
# @yieldparam [Float] n The resulting float after exchanging one currency for
|
|
511
|
+
# another.
|
|
512
|
+
# @yieldreturn [Integer]
|
|
513
|
+
#
|
|
514
|
+
# @return [Money]
|
|
515
|
+
#
|
|
516
|
+
# @example
|
|
517
|
+
# Money.new(2000, "USD").exchange_to("EUR")
|
|
518
|
+
# Money.new(2000, "USD").exchange_to("EUR") {|x| x.round}
|
|
519
|
+
# Money.new(2000, "USD").exchange_to(Currency.new("EUR"))
|
|
520
|
+
def exchange_to(other_currency, &rounding_method)
|
|
521
|
+
other_currency = Currency.wrap(other_currency)
|
|
522
|
+
if self.currency == other_currency
|
|
523
|
+
self
|
|
524
|
+
else
|
|
525
|
+
@bank.exchange_with(self, other_currency, &rounding_method)
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# Receive a money object with the same amount as the current Money object
|
|
530
|
+
# in United States dollar.
|
|
531
|
+
#
|
|
532
|
+
# @return [Money]
|
|
533
|
+
#
|
|
534
|
+
# @example
|
|
535
|
+
# n = Money.new(100, "CAD").as_us_dollar
|
|
536
|
+
# n.currency #=> #<Money::Currency id: usd>
|
|
537
|
+
def as_us_dollar
|
|
538
|
+
exchange_to("USD")
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
# Receive a money object with the same amount as the current Money object
|
|
542
|
+
# in Canadian dollar.
|
|
543
|
+
#
|
|
544
|
+
# @return [Money]
|
|
545
|
+
#
|
|
546
|
+
# @example
|
|
547
|
+
# n = Money.new(100, "USD").as_ca_dollar
|
|
548
|
+
# n.currency #=> #<Money::Currency id: cad>
|
|
549
|
+
def as_ca_dollar
|
|
550
|
+
exchange_to("CAD")
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# Receive a money object with the same amount as the current Money object
|
|
554
|
+
# in euro.
|
|
555
|
+
#
|
|
556
|
+
# @return [Money]
|
|
557
|
+
#
|
|
558
|
+
# @example
|
|
559
|
+
# n = Money.new(100, "USD").as_euro
|
|
560
|
+
# n.currency #=> #<Money::Currency id: eur>
|
|
561
|
+
def as_euro
|
|
562
|
+
exchange_to("EUR")
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
# Splits a given amount in parts without losing pennies. The left-over pennies will be
|
|
566
|
+
# distributed round-robin amongst the parties. This means that parts listed first will likely
|
|
567
|
+
# receive more pennies than ones listed later.
|
|
568
|
+
#
|
|
569
|
+
# Pass [2, 1, 1] as input to give twice as much to part1 as part2 or
|
|
570
|
+
# part3 which results in 50% of the cash to party1, 25% to part2, and 25% to part3. Passing a
|
|
571
|
+
# number instead of an array will split the amount evenly (without losing pennies when rounding).
|
|
572
|
+
#
|
|
573
|
+
# @param [Array<Numeric>, Numeric] parts how amount should be distributed to parts
|
|
574
|
+
#
|
|
575
|
+
# @return [Array<Money>]
|
|
576
|
+
#
|
|
577
|
+
# @example
|
|
578
|
+
# Money.new(5, "USD").allocate([3, 7]) #=> [Money.new(2), Money.new(3)]
|
|
579
|
+
# Money.new(100, "USD").allocate([1, 1, 1]) #=> [Money.new(34), Money.new(33), Money.new(33)]
|
|
580
|
+
# Money.new(100, "USD").allocate(2) #=> [Money.new(50), Money.new(50)]
|
|
581
|
+
# Money.new(100, "USD").allocate(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
|
|
582
|
+
#
|
|
583
|
+
def allocate(parts)
|
|
584
|
+
amounts = Money::Allocation.generate(fractional, parts, !Money.default_infinite_precision)
|
|
585
|
+
amounts.map { |amount| dup_with(fractional: amount) }
|
|
586
|
+
end
|
|
587
|
+
alias_method :split, :allocate
|
|
588
|
+
|
|
589
|
+
# Round the monetary amount to smallest unit of coinage.
|
|
590
|
+
#
|
|
591
|
+
# @note
|
|
592
|
+
# This method is only useful when operating with infinite_precision turned
|
|
593
|
+
# on. Without infinite_precision values are rounded to the smallest unit of
|
|
594
|
+
# coinage automatically.
|
|
595
|
+
#
|
|
596
|
+
# @return [Money]
|
|
597
|
+
#
|
|
598
|
+
# @example
|
|
599
|
+
# Money.new(10.1, 'USD').round #=> Money.new(10, 'USD')
|
|
600
|
+
#
|
|
601
|
+
# @see Money.default_infinite_precision
|
|
602
|
+
def round(rounding_mode = self.class.rounding_mode, rounding_precision = 0)
|
|
603
|
+
rounded_amount = as_d(@fractional).round(rounding_precision, rounding_mode)
|
|
604
|
+
dup_with(fractional: rounded_amount)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# Creates a formatted price string according to several rules.
|
|
608
|
+
#
|
|
609
|
+
# @param [Hash] rules See {Money::Formatter Money::Formatter} for the list of formatting options
|
|
610
|
+
#
|
|
611
|
+
# @return [String]
|
|
612
|
+
#
|
|
613
|
+
def format(*rules)
|
|
614
|
+
Money::Formatter.new(self, *rules).to_s
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# Returns a thousands separator according to the locale
|
|
618
|
+
#
|
|
619
|
+
# @return [String]
|
|
620
|
+
#
|
|
621
|
+
def thousands_separator
|
|
622
|
+
(locale_backend && locale_backend.lookup(:thousands_separator, currency)) ||
|
|
623
|
+
Money::Formatter::DEFAULTS[:thousands_separator]
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
# Returns a decimal mark according to the locale
|
|
627
|
+
#
|
|
628
|
+
# @return [String]
|
|
629
|
+
#
|
|
630
|
+
def decimal_mark
|
|
631
|
+
(locale_backend && locale_backend.lookup(:decimal_mark, currency)) ||
|
|
632
|
+
Money::Formatter::DEFAULTS[:decimal_mark]
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
def dup_with(options = {})
|
|
636
|
+
self.class.new(
|
|
637
|
+
options[:fractional] || fractional,
|
|
638
|
+
options[:currency] || currency,
|
|
639
|
+
bank: options[:bank] || bank
|
|
640
|
+
)
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
private
|
|
644
|
+
|
|
645
|
+
def as_d(num)
|
|
646
|
+
if num.respond_to?(:to_d)
|
|
647
|
+
num.is_a?(Rational) ? num.to_d(self.class.conversion_precision) : num.to_d
|
|
648
|
+
else
|
|
649
|
+
BigDecimal(num.to_s.empty? ? 0 : num.to_s)
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
def return_value(value)
|
|
654
|
+
if self.class.default_infinite_precision
|
|
655
|
+
value
|
|
656
|
+
else
|
|
657
|
+
value.round(0, self.class.rounding_mode).to_i
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
def locale_backend
|
|
662
|
+
self.class.locale_backend
|
|
663
|
+
end
|
|
664
|
+
end
|