money 6.10.1 → 6.11.0
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 +4 -4
- data/.travis.yml +9 -7
- data/AUTHORS +3 -0
- data/CHANGELOG.md +13 -0
- data/README.md +2 -1
- data/config/currency_non_iso.json +2 -2
- data/lib/money/bank/variable_exchange.rb +4 -4
- data/lib/money/currency.rb +11 -28
- data/lib/money/money/arithmetic.rb +11 -9
- data/lib/money/money/{formatting.rb → formatter.rb} +111 -152
- data/lib/money/money/formatting_rules.rb +71 -0
- data/lib/money/money.rb +43 -28
- data/lib/money/version.rb +1 -1
- data/money.gemspec +2 -2
- data/spec/currency_spec.rb +26 -0
- data/spec/money/arithmetic_spec.rb +28 -1
- data/spec/money/formatting_spec.rb +34 -29
- data/spec/money_spec.rb +25 -8
- data/spec/spec_helper.rb +2 -0
- data/spec/support/shared_examples/money_examples.rb +14 -0
- metadata +10 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8de2a6b8bf58ff5719f33d9db889ae722fa39b41
|
4
|
+
data.tar.gz: 713995a09dfac0042e598c153f3d7dffa54e06d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86cd80fd6278138aa30cdf281e92bfc369035fe7122d98f78ebdd3662810fe7ead3a252e7dea8f40f6600df6c23134f2038e8f8a685fd3065306ef06b81a8f6b
|
7
|
+
data.tar.gz: e8ed3c0961bbb92ab3cb5523620f08fab61c1c9741819bf2f892c039fd8d5b550843113a71b984dc249ebdb5aa9cbf2f0291b813085c940648f647cb6764f404
|
data/.travis.yml
CHANGED
@@ -1,22 +1,24 @@
|
|
1
|
+
---
|
1
2
|
language: ruby
|
2
3
|
sudo: false
|
3
4
|
rvm:
|
4
5
|
- 1.9.3
|
5
|
-
- 2.0
|
6
|
+
- 2.0
|
6
7
|
- 2.1.10
|
7
|
-
- 2.2.
|
8
|
-
- 2.3.
|
9
|
-
- 2.4.
|
10
|
-
-
|
8
|
+
- 2.2.10
|
9
|
+
- 2.3.7
|
10
|
+
- 2.4.4
|
11
|
+
- 2.5.1
|
12
|
+
- rbx-3
|
11
13
|
- jruby-9.0.5.0
|
12
|
-
- jruby-9.1.
|
14
|
+
- jruby-9.1.16.0
|
13
15
|
- ruby-head
|
14
16
|
- jruby-head
|
15
17
|
matrix:
|
16
18
|
allow_failures:
|
17
19
|
- rvm: ruby-head
|
18
20
|
- rvm: jruby-head
|
19
|
-
- rvm: rbx-
|
21
|
+
- rvm: rbx-3
|
20
22
|
fast_finish: true
|
21
23
|
before_install:
|
22
24
|
- gem update bundler
|
data/AUTHORS
CHANGED
@@ -43,6 +43,7 @@ George Millo
|
|
43
43
|
Hakan Ensari
|
44
44
|
Hongli Lai
|
45
45
|
Ilia Lobsanov
|
46
|
+
Ivan Shamatov
|
46
47
|
Ingo Wichmann
|
47
48
|
Jacob Atzen
|
48
49
|
James Cotterill
|
@@ -112,6 +113,7 @@ Thomas Weymuth
|
|
112
113
|
Ticean Bennett
|
113
114
|
Tien Nguyen
|
114
115
|
Tim Hart
|
116
|
+
Tim Krins
|
115
117
|
Tobias Luetke
|
116
118
|
Tobias Schmidt
|
117
119
|
Tom Lianza
|
@@ -125,3 +127,4 @@ Yuri Sidorov
|
|
125
127
|
Yuusuke Takizawa
|
126
128
|
Zubin Henner
|
127
129
|
Бродяной Александр
|
130
|
+
Nicolay Hvidsten
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 6.11.0
|
4
|
+
- Support i18n 1.0
|
5
|
+
- Update yard dependency to 0.9.11
|
6
|
+
- Support for ruby 2.5.0
|
7
|
+
- Add inheritance for currency definitions
|
8
|
+
- Added new symbol for bitcoin denomination
|
9
|
+
- Specify custom rounding precision when using `infinite_precision`
|
10
|
+
- Allow splits with sums greater than 1
|
11
|
+
- Prevent arithmetic methods from loosing reference to the bank
|
12
|
+
- Fix coerced zero numeric subtraction
|
13
|
+
- Fix south asian formatting to support whole numbers
|
14
|
+
- Refactor formatting logic
|
15
|
+
|
3
16
|
## 6.10.1
|
4
17
|
- Fix an issue with Money.empty memoization
|
5
18
|
|
data/README.md
CHANGED
@@ -312,8 +312,9 @@ def marshal_dump; end
|
|
312
312
|
The following example implements an `ActiveRecord` store to save exchange rates to a database.
|
313
313
|
|
314
314
|
```ruby
|
315
|
-
#
|
315
|
+
# rails g model exchange_rate from:string to:string rate:float
|
316
316
|
|
317
|
+
# for Rails 5 replace ActiveRecord::Base with ApplicationRecord
|
317
318
|
class ExchangeRate < ActiveRecord::Base
|
318
319
|
def self.get_rate(from_iso_code, to_iso_code)
|
319
320
|
rate = find_by(:from => from_iso_code, :to => to_iso_code)
|
@@ -3,12 +3,12 @@
|
|
3
3
|
"priority": 100,
|
4
4
|
"iso_code": "BTC",
|
5
5
|
"name": "Bitcoin",
|
6
|
-
"symbol": "
|
6
|
+
"symbol": "₿",
|
7
7
|
"alternate_symbols": [],
|
8
8
|
"subunit": "Satoshi",
|
9
9
|
"subunit_to_unit": 100000000,
|
10
10
|
"symbol_first": true,
|
11
|
-
"html_entity": "",
|
11
|
+
"html_entity": "₿",
|
12
12
|
"decimal_mark": ".",
|
13
13
|
"thousands_separator": ",",
|
14
14
|
"iso_numeric": "",
|
@@ -119,14 +119,14 @@ class Money
|
|
119
119
|
end
|
120
120
|
|
121
121
|
def calculate_fractional(from, to_currency)
|
122
|
-
BigDecimal
|
123
|
-
BigDecimal
|
124
|
-
BigDecimal
|
122
|
+
BigDecimal(from.fractional.to_s) / (
|
123
|
+
BigDecimal(from.currency.subunit_to_unit.to_s) /
|
124
|
+
BigDecimal(to_currency.subunit_to_unit.to_s)
|
125
125
|
)
|
126
126
|
end
|
127
127
|
|
128
128
|
def exchange(fractional, rate, &block)
|
129
|
-
ex = fractional * BigDecimal
|
129
|
+
ex = fractional * BigDecimal(rate.to_s)
|
130
130
|
if block_given?
|
131
131
|
yield ex
|
132
132
|
elsif @rounding_method
|
data/lib/money/currency.rb
CHANGED
@@ -173,6 +173,15 @@ class Money
|
|
173
173
|
@stringified_keys = stringify_keys
|
174
174
|
end
|
175
175
|
|
176
|
+
# Inherit a new currency from existing one
|
177
|
+
#
|
178
|
+
# @param parent_iso_code [String] the international 3-letter code as defined
|
179
|
+
# @param curr [Hash] See {register} method for hash structure
|
180
|
+
def inherit(parent_iso_code, curr)
|
181
|
+
parent_iso_code = parent_iso_code.downcase.to_sym
|
182
|
+
curr = @table.fetch(parent_iso_code, {}).merge(curr)
|
183
|
+
register(curr)
|
184
|
+
end
|
176
185
|
|
177
186
|
# Unregister a currency.
|
178
187
|
#
|
@@ -399,38 +408,12 @@ class Money
|
|
399
408
|
#
|
400
409
|
# @return [Integer]
|
401
410
|
def exponent
|
402
|
-
Math.log10(
|
403
|
-
end
|
404
|
-
|
405
|
-
# Cache decimal places for subunit_to_unit values. Common ones pre-cached.
|
406
|
-
def self.decimal_places_cache
|
407
|
-
@decimal_places_cache ||= {1 => 0, 10 => 1, 100 => 2, 1000 => 3}
|
408
|
-
end
|
409
|
-
|
410
|
-
# The number of decimal places needed.
|
411
|
-
#
|
412
|
-
# @return [Integer]
|
413
|
-
def decimal_places
|
414
|
-
cache[subunit_to_unit] ||= calculate_decimal_places(subunit_to_unit)
|
411
|
+
Math.log10(subunit_to_unit).round
|
415
412
|
end
|
413
|
+
alias decimal_places exponent
|
416
414
|
|
417
415
|
private
|
418
416
|
|
419
|
-
def cache
|
420
|
-
self.class.decimal_places_cache
|
421
|
-
end
|
422
|
-
|
423
|
-
# If we need to figure out how many decimal places we need we
|
424
|
-
# use repeated integer division.
|
425
|
-
def calculate_decimal_places(num)
|
426
|
-
i = 1
|
427
|
-
while num >= 10
|
428
|
-
num /= 10
|
429
|
-
i += 1 if num >= 10
|
430
|
-
end
|
431
|
-
i
|
432
|
-
end
|
433
|
-
|
434
417
|
def initialize_data!
|
435
418
|
data = self.class.table[@id]
|
436
419
|
@alternate_symbols = data[:alternate_symbols]
|
@@ -16,7 +16,7 @@ class Money
|
|
16
16
|
# @example
|
17
17
|
# - Money.new(100) #=> #<Money @fractional=-100>
|
18
18
|
def -@
|
19
|
-
self.class.new(-fractional, currency)
|
19
|
+
self.class.new(-fractional, currency, bank)
|
20
20
|
end
|
21
21
|
|
22
22
|
# Checks whether two Money objects have the same currency and the same
|
@@ -125,11 +125,13 @@ class Money
|
|
125
125
|
[:+, :-].each do |op|
|
126
126
|
define_method(op) do |other|
|
127
127
|
unless other.is_a?(Money)
|
128
|
-
|
128
|
+
if other.zero?
|
129
|
+
return other.is_a?(CoercedNumeric) ? Money.empty.public_send(op, self) : self
|
130
|
+
end
|
129
131
|
raise TypeError
|
130
132
|
end
|
131
133
|
other = other.exchange_to(currency)
|
132
|
-
self.class.new(fractional.public_send(op, other.fractional), currency)
|
134
|
+
self.class.new(fractional.public_send(op, other.fractional), currency, bank)
|
133
135
|
end
|
134
136
|
end
|
135
137
|
|
@@ -150,7 +152,7 @@ class Money
|
|
150
152
|
def *(value)
|
151
153
|
value = value.value if value.is_a?(CoercedNumeric)
|
152
154
|
if value.is_a? Numeric
|
153
|
-
self.class.new(fractional * value, currency)
|
155
|
+
self.class.new(fractional * value, currency, bank)
|
154
156
|
else
|
155
157
|
raise TypeError, "Can't multiply a #{self.class.name} by a #{value.class.name}'s value"
|
156
158
|
end
|
@@ -176,7 +178,7 @@ class Money
|
|
176
178
|
fractional / as_d(value.exchange_to(currency).fractional).to_f
|
177
179
|
else
|
178
180
|
raise TypeError, 'Can not divide by Money' if value.is_a?(CoercedNumeric)
|
179
|
-
self.class.new(fractional / as_d(value), currency)
|
181
|
+
self.class.new(fractional / as_d(value), currency, bank)
|
180
182
|
end
|
181
183
|
end
|
182
184
|
|
@@ -214,13 +216,13 @@ class Money
|
|
214
216
|
def divmod_money(val)
|
215
217
|
cents = val.exchange_to(currency).cents
|
216
218
|
quotient, remainder = fractional.divmod(cents)
|
217
|
-
[quotient, self.class.new(remainder, currency)]
|
219
|
+
[quotient, self.class.new(remainder, currency, bank)]
|
218
220
|
end
|
219
221
|
private :divmod_money
|
220
222
|
|
221
223
|
def divmod_other(val)
|
222
224
|
quotient, remainder = fractional.divmod(as_d(val))
|
223
|
-
[self.class.new(quotient, currency), self.class.new(remainder, currency)]
|
225
|
+
[self.class.new(quotient, currency, bank), self.class.new(remainder, currency, bank)]
|
224
226
|
end
|
225
227
|
private :divmod_other
|
226
228
|
|
@@ -264,7 +266,7 @@ class Money
|
|
264
266
|
if (fractional < 0 && val < 0) || (fractional > 0 && val > 0)
|
265
267
|
self.modulo(val)
|
266
268
|
else
|
267
|
-
self.modulo(val) - (val.is_a?(Money) ? val : self.class.new(val, currency))
|
269
|
+
self.modulo(val) - (val.is_a?(Money) ? val : self.class.new(val, currency, bank))
|
268
270
|
end
|
269
271
|
end
|
270
272
|
|
@@ -275,7 +277,7 @@ class Money
|
|
275
277
|
# @example
|
276
278
|
# Money.new(-100).abs #=> #<Money @fractional=100>
|
277
279
|
def abs
|
278
|
-
self.class.new(fractional.abs, currency)
|
280
|
+
self.class.new(fractional.abs, currency, bank)
|
279
281
|
end
|
280
282
|
|
281
283
|
# Test if the money amount is zero.
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
require 'money/money/formatting_rules'
|
3
|
+
|
2
4
|
class Money
|
3
|
-
|
5
|
+
class Formatter
|
4
6
|
# Creates a formatted price string according to several rules.
|
5
7
|
#
|
6
8
|
# @param [Hash] rules The options used to format the string.
|
@@ -198,66 +200,27 @@ class Money
|
|
198
200
|
# Note that the default rules can be defined through {Money.default_formatting_rules} hash.
|
199
201
|
#
|
200
202
|
# @see Money.default_formatting_rules Money.default_formatting_rules for more information.
|
201
|
-
def
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
rules = localize_formatting_rules(rules)
|
207
|
-
rules = translate_formatting_rules(rules) if rules[:translate]
|
208
|
-
|
209
|
-
thousands_separator = self.thousands_separator
|
210
|
-
decimal_mark = self.decimal_mark
|
211
|
-
|
212
|
-
escaped_decimal_mark = Regexp.escape(decimal_mark)
|
213
|
-
|
214
|
-
if fractional == 0
|
215
|
-
if rules[:display_free].respond_to?(:to_str)
|
216
|
-
return rules[:display_free]
|
217
|
-
elsif rules[:display_free]
|
218
|
-
return "free"
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
symbol_value = symbol_value_from(rules)
|
223
|
-
|
224
|
-
formatted = self.abs.to_s
|
203
|
+
def initialize(money, *rules)
|
204
|
+
@money = money
|
205
|
+
@currency = money.currency
|
206
|
+
@rules = FormattingRules.new(@currency, *rules)
|
207
|
+
end
|
225
208
|
|
226
|
-
|
227
|
-
|
228
|
-
formatted = ((BigDecimal(formatted) * currency.subunit_to_unit).round / BigDecimal(currency.subunit_to_unit.to_s)).to_s("F")
|
229
|
-
formatted.gsub!(/\..*/) do |decimal_part|
|
230
|
-
decimal_part << '0' while decimal_part.length < (currency.decimal_places + 1)
|
231
|
-
decimal_part
|
232
|
-
end
|
233
|
-
formatted.gsub!(/\./, decimal_mark) unless '.' == decimal_mark
|
234
|
-
end
|
209
|
+
def to_s
|
210
|
+
return free_text if show_free_text?
|
235
211
|
|
236
|
-
|
212
|
+
whole_part, decimal_part = extract_whole_and_decimal_parts
|
237
213
|
|
238
|
-
|
239
|
-
|
240
|
-
|
214
|
+
# Format whole and decimal parts separately
|
215
|
+
decimal_part = format_decimal_part(decimal_part)
|
216
|
+
whole_part = format_whole_part(whole_part)
|
241
217
|
|
242
|
-
#
|
243
|
-
|
244
|
-
formatted = formatted.sub(/(#{escaped_decimal_mark})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_decimal_mark}\z/, '')
|
245
|
-
end
|
246
|
-
has_decimal_value = !!(formatted =~ /#{escaped_decimal_mark}/)
|
218
|
+
# Assemble the final formatted amount
|
219
|
+
formatted = [whole_part, decimal_part].compact.join(decimal_mark)
|
247
220
|
|
248
|
-
|
249
|
-
# Determine thousands_separator
|
250
|
-
if rules.has_key?(:thousands_separator)
|
251
|
-
thousands_separator_value = rules[:thousands_separator] || ''
|
252
|
-
end
|
221
|
+
sign = money.negative? ? '-' : ''
|
253
222
|
|
254
|
-
|
255
|
-
formatted.gsub!(regexp_format(formatted, rules, decimal_mark, symbol_value),
|
256
|
-
"\\1#{thousands_separator_value}")
|
257
|
-
|
258
|
-
symbol_position = symbol_position_from(rules)
|
259
|
-
|
260
|
-
if rules[:sign_positive] == true && self.positive?
|
223
|
+
if rules[:sign_positive] == true && money.positive?
|
261
224
|
sign = '+'
|
262
225
|
end
|
263
226
|
|
@@ -266,8 +229,11 @@ class Money
|
|
266
229
|
sign = ''
|
267
230
|
end
|
268
231
|
|
232
|
+
symbol_value = symbol_value_from(rules)
|
233
|
+
|
269
234
|
if symbol_value && !symbol_value.empty?
|
270
235
|
symbol_value = "<span class=\"currency_symbol\">#{symbol_value}</span>" if rules[:html_wrap_symbol]
|
236
|
+
symbol_position = symbol_position_from(rules)
|
271
237
|
|
272
238
|
formatted = if symbol_position == :before
|
273
239
|
symbol_space = rules[:symbol_before_without_space] === false ? " " : ""
|
@@ -280,8 +246,6 @@ class Money
|
|
280
246
|
formatted="#{sign_before}#{sign}#{formatted}"
|
281
247
|
end
|
282
248
|
|
283
|
-
apply_decimal_mark_from_rules(formatted, rules) if has_decimal_value
|
284
|
-
|
285
249
|
if rules[:with_currency]
|
286
250
|
formatted << " "
|
287
251
|
formatted << '<span class="currency">' if rules[:html]
|
@@ -292,11 +256,19 @@ class Money
|
|
292
256
|
end
|
293
257
|
|
294
258
|
def thousands_separator
|
295
|
-
|
259
|
+
if rules.has_key?(:thousands_separator)
|
260
|
+
rules[:thousands_separator] || ''
|
261
|
+
else
|
262
|
+
i18n_format_for(:thousands_separator, :delimiter, ',')
|
263
|
+
end
|
296
264
|
end
|
297
265
|
|
298
266
|
def decimal_mark
|
299
|
-
|
267
|
+
if rules.has_key?(:decimal_mark)
|
268
|
+
rules[:decimal_mark] || '.'
|
269
|
+
else
|
270
|
+
i18n_format_for(:decimal_mark, :separator, '.')
|
271
|
+
end
|
300
272
|
end
|
301
273
|
|
302
274
|
alias_method :delimiter, :thousands_separator
|
@@ -304,8 +276,57 @@ class Money
|
|
304
276
|
|
305
277
|
private
|
306
278
|
|
279
|
+
attr_reader :money, :currency, :rules
|
280
|
+
|
281
|
+
def show_free_text?
|
282
|
+
money.zero? && rules[:display_free]
|
283
|
+
end
|
284
|
+
|
285
|
+
def free_text
|
286
|
+
rules[:display_free].respond_to?(:to_str) ? rules[:display_free] : 'free'
|
287
|
+
end
|
288
|
+
|
289
|
+
def format_whole_part(value)
|
290
|
+
# Determine thousands_separator
|
291
|
+
thousands_separator_value = if rules.has_key?(:thousands_separator)
|
292
|
+
rules[:thousands_separator] || ''
|
293
|
+
else
|
294
|
+
thousands_separator
|
295
|
+
end
|
296
|
+
|
297
|
+
# Apply thousands_separator
|
298
|
+
value.gsub regexp_format, "\\1#{thousands_separator_value}"
|
299
|
+
end
|
300
|
+
|
301
|
+
def extract_whole_and_decimal_parts
|
302
|
+
fractional = money.fractional.abs
|
303
|
+
|
304
|
+
# Round the infinite precision part if needed
|
305
|
+
fractional = fractional.round if rules[:rounded_infinite_precision]
|
306
|
+
|
307
|
+
# Translate subunits into units
|
308
|
+
fractional_units = BigDecimal(fractional) / currency.subunit_to_unit
|
309
|
+
|
310
|
+
# Split the result and return whole and decimal parts separately
|
311
|
+
fractional_units.to_s('F').split('.')
|
312
|
+
end
|
313
|
+
|
314
|
+
def format_decimal_part(value)
|
315
|
+
return nil if currency.decimal_places == 0
|
316
|
+
return nil if rules[:no_cents]
|
317
|
+
return nil if rules[:no_cents_if_whole] && value.to_i == 0
|
318
|
+
|
319
|
+
# Pad value, making up for missing zeroes at the end
|
320
|
+
value = value.ljust(currency.decimal_places, '0')
|
321
|
+
|
322
|
+
# Drop trailing zeros if needed
|
323
|
+
value.gsub!(/0*$/, '') if rules[:drop_trailing_zeros]
|
324
|
+
|
325
|
+
value.empty? ? nil : value
|
326
|
+
end
|
327
|
+
|
307
328
|
def i18n_format_for(method, name, character)
|
308
|
-
if
|
329
|
+
if Money.use_i18n
|
309
330
|
begin
|
310
331
|
I18n.t name, :scope => "number.currency.format", :raise => true
|
311
332
|
rescue I18n::MissingTranslationData
|
@@ -316,111 +337,49 @@ class Money
|
|
316
337
|
end
|
317
338
|
end
|
318
339
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
# @return [Hash]
|
324
|
-
def normalize_formatting_rules(rules)
|
325
|
-
if rules.size == 0
|
326
|
-
rules = {}
|
327
|
-
elsif rules.size == 1
|
328
|
-
rules = rules.pop
|
329
|
-
rules = { rules => true } if rules.is_a?(Symbol)
|
330
|
-
end
|
331
|
-
if !rules.include?(:decimal_mark) && rules.include?(:separator)
|
332
|
-
rules[:decimal_mark] = rules[:separator]
|
333
|
-
end
|
334
|
-
if !rules.include?(:thousands_separator) && rules.include?(:delimiter)
|
335
|
-
rules[:thousands_separator] = rules[:delimiter]
|
336
|
-
end
|
337
|
-
rules
|
338
|
-
end
|
339
|
-
|
340
|
-
# Applies decimal mark from rules to formatted
|
341
|
-
#
|
342
|
-
# @param [String] formatted
|
343
|
-
# @param [Hash] rules
|
344
|
-
def apply_decimal_mark_from_rules(formatted, rules)
|
345
|
-
if rules.has_key?(:decimal_mark) && rules[:decimal_mark] &&
|
346
|
-
rules[:decimal_mark] != decimal_mark
|
347
|
-
|
348
|
-
regexp_decimal = Regexp.escape(decimal_mark)
|
349
|
-
formatted.sub!(/(.*)(#{regexp_decimal})(.*)\Z/,
|
350
|
-
"\\1#{rules[:decimal_mark]}\\3")
|
351
|
-
end
|
352
|
-
end
|
353
|
-
end
|
354
|
-
|
355
|
-
def default_formatting_rules
|
356
|
-
self.class.default_formatting_rules || {}
|
357
|
-
end
|
358
|
-
|
359
|
-
def regexp_format(formatted, rules, decimal_mark, symbol_value)
|
360
|
-
regexp_decimal = Regexp.escape(decimal_mark)
|
361
|
-
if rules[:south_asian_number_formatting]
|
362
|
-
/(\d+?)(?=(\d\d)+(\d)(?:\.))/
|
363
|
-
else
|
364
|
-
# Symbols may contain decimal marks (E.g "դր.")
|
365
|
-
if formatted.sub(symbol_value.to_s, "") =~ /#{regexp_decimal}/
|
366
|
-
/(\d)(?=(?:\d{3})+(?:#{regexp_decimal}))/
|
340
|
+
def regexp_format
|
341
|
+
if rules[:south_asian_number_formatting]
|
342
|
+
# from http://blog.revathskumar.com/2014/11/regex-comma-seperated-indian-currency-format.html
|
343
|
+
/(\d+?)(?=(\d\d)+(\d)(?!\d))(\.\d+)?/
|
367
344
|
else
|
368
345
|
/(\d)(?=(?:\d{3})+(?:[^\d]{1}|$))/
|
369
346
|
end
|
370
347
|
end
|
371
|
-
end
|
372
|
-
|
373
|
-
def translate_formatting_rules(rules)
|
374
|
-
begin
|
375
|
-
rules[:symbol] = I18n.t currency.iso_code, :scope => "number.currency.symbol", :raise => true
|
376
|
-
rescue I18n::MissingTranslationData
|
377
|
-
# Do nothing
|
378
|
-
end
|
379
|
-
rules
|
380
|
-
end
|
381
348
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
if rules.has_key?(:symbol)
|
393
|
-
if rules[:symbol] === true
|
394
|
-
if rules[:disambiguate] && currency.disambiguate_symbol
|
395
|
-
currency.disambiguate_symbol
|
349
|
+
def symbol_value_from(rules)
|
350
|
+
if rules.has_key?(:symbol)
|
351
|
+
if rules[:symbol] === true
|
352
|
+
if rules[:disambiguate] && currency.disambiguate_symbol
|
353
|
+
currency.disambiguate_symbol
|
354
|
+
else
|
355
|
+
money.symbol
|
356
|
+
end
|
357
|
+
elsif rules[:symbol]
|
358
|
+
rules[:symbol]
|
396
359
|
else
|
397
|
-
|
360
|
+
""
|
398
361
|
end
|
399
|
-
elsif rules[:
|
400
|
-
|
362
|
+
elsif rules[:html]
|
363
|
+
currency.html_entity == '' ? currency.symbol : currency.html_entity
|
364
|
+
elsif rules[:disambiguate] && currency.disambiguate_symbol
|
365
|
+
currency.disambiguate_symbol
|
401
366
|
else
|
402
|
-
|
367
|
+
money.symbol
|
403
368
|
end
|
404
|
-
elsif rules[:html]
|
405
|
-
currency.html_entity == '' ? currency.symbol : currency.html_entity
|
406
|
-
elsif rules[:disambiguate] && currency.disambiguate_symbol
|
407
|
-
currency.disambiguate_symbol
|
408
|
-
else
|
409
|
-
symbol
|
410
369
|
end
|
411
|
-
end
|
412
370
|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
371
|
+
def symbol_position_from(rules)
|
372
|
+
if rules.has_key?(:symbol_position)
|
373
|
+
if [:before, :after].include?(rules[:symbol_position])
|
374
|
+
return rules[:symbol_position]
|
375
|
+
else
|
376
|
+
raise ArgumentError, ":symbol_position must be ':before' or ':after'"
|
377
|
+
end
|
378
|
+
elsif currency.symbol_first?
|
379
|
+
:before
|
417
380
|
else
|
418
|
-
|
381
|
+
:after
|
419
382
|
end
|
420
|
-
elsif currency.symbol_first?
|
421
|
-
:before
|
422
|
-
else
|
423
|
-
:after
|
424
383
|
end
|
425
384
|
end
|
426
385
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
class Money
|
4
|
+
class FormattingRules
|
5
|
+
def initialize(currency, *raw_rules)
|
6
|
+
@currency = currency
|
7
|
+
|
8
|
+
# support for old format parameters
|
9
|
+
@rules = normalize_formatting_rules(raw_rules)
|
10
|
+
|
11
|
+
@rules = default_formatting_rules.merge(@rules)
|
12
|
+
@rules = localize_formatting_rules(@rules)
|
13
|
+
@rules = translate_formatting_rules(@rules) if @rules[:translate]
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
@rules[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
def has_key?(key)
|
21
|
+
@rules.has_key? key
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :currency
|
27
|
+
|
28
|
+
# Cleans up formatting rules.
|
29
|
+
#
|
30
|
+
# @param [Hash] rules
|
31
|
+
#
|
32
|
+
# @return [Hash]
|
33
|
+
def normalize_formatting_rules(rules)
|
34
|
+
if rules.size == 0
|
35
|
+
rules = {}
|
36
|
+
elsif rules.size == 1
|
37
|
+
rules = rules.pop
|
38
|
+
rules = { rules => true } if rules.is_a?(Symbol)
|
39
|
+
end
|
40
|
+
if !rules.include?(:decimal_mark) && rules.include?(:separator)
|
41
|
+
rules[:decimal_mark] = rules[:separator]
|
42
|
+
end
|
43
|
+
if !rules.include?(:thousands_separator) && rules.include?(:delimiter)
|
44
|
+
rules[:thousands_separator] = rules[:delimiter]
|
45
|
+
end
|
46
|
+
rules
|
47
|
+
end
|
48
|
+
|
49
|
+
def default_formatting_rules
|
50
|
+
Money.default_formatting_rules || {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def translate_formatting_rules(rules)
|
54
|
+
begin
|
55
|
+
rules[:symbol] = I18n.t currency.iso_code, :scope => "number.currency.symbol", :raise => true
|
56
|
+
rescue I18n::MissingTranslationData
|
57
|
+
# Do nothing
|
58
|
+
end
|
59
|
+
rules
|
60
|
+
end
|
61
|
+
|
62
|
+
def localize_formatting_rules(rules)
|
63
|
+
if currency.iso_code == "JPY" && I18n.locale == :ja
|
64
|
+
rules[:symbol] = "円" unless rules[:symbol] == false
|
65
|
+
rules[:symbol_position] = :after
|
66
|
+
rules[:symbol_after_without_space] = true
|
67
|
+
end
|
68
|
+
rules
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/money/money.rb
CHANGED
@@ -3,7 +3,7 @@ require "money/bank/variable_exchange"
|
|
3
3
|
require "money/bank/single_currency"
|
4
4
|
require "money/money/arithmetic"
|
5
5
|
require "money/money/constructors"
|
6
|
-
require "money/money/
|
6
|
+
require "money/money/formatter"
|
7
7
|
|
8
8
|
# "Money is any object or record that is generally accepted as payment for
|
9
9
|
# goods and services and repayment of debts in a given socio-economic context
|
@@ -17,7 +17,6 @@ require "money/money/formatting"
|
|
17
17
|
class Money
|
18
18
|
include Comparable
|
19
19
|
include Money::Arithmetic
|
20
|
-
include Money::Formatting
|
21
20
|
extend Constructors
|
22
21
|
|
23
22
|
# Raised when smallest denomination of a currency is not defined
|
@@ -177,7 +176,7 @@ class Money
|
|
177
176
|
#
|
178
177
|
# @example
|
179
178
|
# fee = Money.rounding_mode(BigDecimal::ROUND_HALF_UP) do
|
180
|
-
# Money.new(1200) * BigDecimal
|
179
|
+
# Money.new(1200) * BigDecimal('0.029')
|
181
180
|
# end
|
182
181
|
def self.rounding_mode(mode=nil)
|
183
182
|
if mode.nil?
|
@@ -271,7 +270,7 @@ class Money
|
|
271
270
|
# @return [BigDecimal]
|
272
271
|
#
|
273
272
|
# @example
|
274
|
-
# Money.new(1_00, "USD").dollars # => BigDecimal
|
273
|
+
# Money.new(1_00, "USD").dollars # => BigDecimal("1.00")
|
275
274
|
#
|
276
275
|
# @see #amount
|
277
276
|
# @see #to_d
|
@@ -286,7 +285,7 @@ class Money
|
|
286
285
|
# @return [BigDecimal]
|
287
286
|
#
|
288
287
|
# @example
|
289
|
-
# Money.new(1_00, "USD").amount # => BigDecimal
|
288
|
+
# Money.new(1_00, "USD").amount # => BigDecimal("1.00")
|
290
289
|
#
|
291
290
|
# @see #to_d
|
292
291
|
# @see #fractional
|
@@ -358,10 +357,10 @@ class Money
|
|
358
357
|
if fraction == ""
|
359
358
|
unit
|
360
359
|
else
|
361
|
-
"#{unit}#{decimal_mark}#{fraction}"
|
360
|
+
"#{unit}#{currency.decimal_mark}#{fraction}"
|
362
361
|
end
|
363
362
|
else
|
364
|
-
"#{unit}#{decimal_mark}#{pad_subunit(subunit)}#{fraction}"
|
363
|
+
"#{unit}#{currency.decimal_mark}#{pad_subunit(subunit)}#{fraction}"
|
365
364
|
end
|
366
365
|
|
367
366
|
fractional < 0 ? "-#{str}" : str
|
@@ -372,7 +371,7 @@ class Money
|
|
372
371
|
# @return [BigDecimal]
|
373
372
|
#
|
374
373
|
# @example
|
375
|
-
# Money.us_dollar(1_00).to_d #=> BigDecimal
|
374
|
+
# Money.us_dollar(1_00).to_d #=> BigDecimal("1.00")
|
376
375
|
def to_d
|
377
376
|
as_d(fractional) / as_d(currency.subunit_to_unit)
|
378
377
|
end
|
@@ -478,22 +477,17 @@ class Money
|
|
478
477
|
# be distributed round-robin amongst the parties. This means that parties
|
479
478
|
# listed first will likely receive more pennies than ones that are listed later
|
480
479
|
#
|
481
|
-
# @param [Array<Numeric>] splits [
|
480
|
+
# @param [Array<Numeric>] splits [2, 1, 1] to give twice as much to party1 as party2 or party3
|
481
|
+
# which results in 50% of the cash to party1, 25% to party2, and 25% to party3.
|
482
482
|
#
|
483
483
|
# @return [Array<Money>]
|
484
484
|
#
|
485
485
|
# @example
|
486
486
|
# Money.new(5, "USD").allocate([0.3, 0.7]) #=> [Money.new(2), Money.new(3)]
|
487
|
-
# Money.new(100, "USD").allocate([
|
487
|
+
# Money.new(100, "USD").allocate([1, 1, 1]) #=> [Money.new(34), Money.new(33), Money.new(33)]
|
488
488
|
#
|
489
489
|
def allocate(splits)
|
490
|
-
|
491
|
-
|
492
|
-
if (allocations - BigDecimal("1")) > Float::EPSILON
|
493
|
-
raise ArgumentError, "splits add to more then 100%"
|
494
|
-
end
|
495
|
-
|
496
|
-
amounts, left_over = amounts_from_splits(allocations, splits)
|
490
|
+
amounts, left_over = amounts_from_splits(splits)
|
497
491
|
|
498
492
|
unless self.class.infinite_precision
|
499
493
|
delta = left_over > 0 ? 1 : -1
|
@@ -537,21 +531,47 @@ class Money
|
|
537
531
|
# @see
|
538
532
|
# Money.infinite_precision
|
539
533
|
#
|
540
|
-
def round(rounding_mode = self.class.rounding_mode)
|
534
|
+
def round(rounding_mode = self.class.rounding_mode, rounding_precision = 0)
|
541
535
|
if self.class.infinite_precision
|
542
|
-
self.class.new(fractional.round(
|
536
|
+
self.class.new(fractional.round(rounding_precision, rounding_mode), self.currency)
|
543
537
|
else
|
544
538
|
self
|
545
539
|
end
|
546
540
|
end
|
547
541
|
|
542
|
+
# Creates a formatted price string according to several rules.
|
543
|
+
#
|
544
|
+
# @param [Hash] See Money::Formatter for the list of formatting options
|
545
|
+
#
|
546
|
+
# @return [String]
|
547
|
+
#
|
548
|
+
def format(*rules)
|
549
|
+
Money::Formatter.new(self, *rules).to_s
|
550
|
+
end
|
551
|
+
|
552
|
+
# Returns a thousands separator according to the locale
|
553
|
+
#
|
554
|
+
# @return [String]
|
555
|
+
#
|
556
|
+
def thousands_separator
|
557
|
+
Money::Formatter.new(self, {}).thousands_separator
|
558
|
+
end
|
559
|
+
|
560
|
+
# Returns a decimal mark according to the locale
|
561
|
+
#
|
562
|
+
# @return [String]
|
563
|
+
#
|
564
|
+
def decimal_mark
|
565
|
+
Money::Formatter.new(self, {}).decimal_mark
|
566
|
+
end
|
567
|
+
|
548
568
|
private
|
549
569
|
|
550
570
|
def as_d(num)
|
551
571
|
if num.respond_to?(:to_d)
|
552
572
|
num.is_a?(Rational) ? num.to_d(self.class.conversion_precision) : num.to_d
|
553
573
|
else
|
554
|
-
BigDecimal
|
574
|
+
BigDecimal(num.to_s.empty? ? 0 : num.to_s)
|
555
575
|
end
|
556
576
|
end
|
557
577
|
|
@@ -578,16 +598,11 @@ class Money
|
|
578
598
|
end
|
579
599
|
|
580
600
|
def pad_subunit(subunit)
|
581
|
-
|
582
|
-
padding = "0" * cnt
|
583
|
-
"#{padding}#{subunit}"[-1 * cnt, cnt]
|
584
|
-
end
|
585
|
-
|
586
|
-
def allocations_from_splits(splits)
|
587
|
-
splits.inject(0) { |sum, n| sum + n }
|
601
|
+
subunit.rjust(currency.decimal_places, '0')
|
588
602
|
end
|
589
603
|
|
590
|
-
def amounts_from_splits(
|
604
|
+
def amounts_from_splits(splits)
|
605
|
+
allocations = splits.inject(0, :+)
|
591
606
|
left_over = fractional
|
592
607
|
|
593
608
|
amounts = splits.map do |ratio|
|
data/lib/money/version.rb
CHANGED
data/money.gemspec
CHANGED
@@ -14,12 +14,12 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.description = "A Ruby Library for dealing with money and currency conversion."
|
15
15
|
s.license = "MIT"
|
16
16
|
|
17
|
-
s.add_dependency 'i18n', [">= 0.6.4", '< 1.
|
17
|
+
s.add_dependency 'i18n', [">= 0.6.4", '< 1.1']
|
18
18
|
|
19
19
|
s.add_development_dependency "bundler", "~> 1.3"
|
20
20
|
s.add_development_dependency "rake"
|
21
21
|
s.add_development_dependency "rspec", "~> 3.4.0"
|
22
|
-
s.add_development_dependency "yard", "~> 0.
|
22
|
+
s.add_development_dependency "yard", "~> 0.9.11"
|
23
23
|
s.add_development_dependency "kramdown", "~> 1.1"
|
24
24
|
|
25
25
|
s.files = `git ls-files`.split($/)
|
data/spec/currency_spec.rb
CHANGED
@@ -115,6 +115,32 @@ class Money
|
|
115
115
|
end
|
116
116
|
|
117
117
|
|
118
|
+
describe ".inherit" do
|
119
|
+
after do
|
120
|
+
Currency.unregister(iso_code: "XXX") if Currency.find("XXX")
|
121
|
+
Currency.unregister(iso_code: "YYY") if Currency.find("YYY")
|
122
|
+
end
|
123
|
+
|
124
|
+
it "inherit a new currency" do
|
125
|
+
Currency.register(
|
126
|
+
iso_code: "XXX",
|
127
|
+
name: "Golden Doubloon",
|
128
|
+
symbol: "%",
|
129
|
+
subunit_to_unit: 100
|
130
|
+
)
|
131
|
+
Currency.inherit("XXX",
|
132
|
+
iso_code: "YYY",
|
133
|
+
symbol: "@"
|
134
|
+
)
|
135
|
+
new_currency = Currency.find("YYY")
|
136
|
+
expect(new_currency).not_to be_nil
|
137
|
+
expect(new_currency.name).to eq "Golden Doubloon"
|
138
|
+
expect(new_currency.symbol).to eq "@"
|
139
|
+
expect(new_currency.subunit_to_unit).to eq 100
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
118
144
|
describe ".unregister" do
|
119
145
|
it "unregisters a currency" do
|
120
146
|
Currency.register(iso_code: "XXX")
|
@@ -12,6 +12,8 @@ describe Money do
|
|
12
12
|
special_money_class = Class.new(Money)
|
13
13
|
expect(- special_money_class.new(10_00)).to be_a special_money_class
|
14
14
|
end
|
15
|
+
|
16
|
+
it_behaves_like 'instance with custom bank', :-@
|
15
17
|
end
|
16
18
|
|
17
19
|
describe "#==" do
|
@@ -49,7 +51,7 @@ describe Money do
|
|
49
51
|
it 'allows comparison with zero' do
|
50
52
|
expect(Money.new(0, :usd)).to eq 0
|
51
53
|
expect(Money.new(0, :usd)).to eq 0.0
|
52
|
-
expect(Money.new(0, :usd)).to eq BigDecimal
|
54
|
+
expect(Money.new(0, :usd)).to eq BigDecimal(0)
|
53
55
|
expect(Money.new(1, :usd)).to_not eq 0
|
54
56
|
end
|
55
57
|
|
@@ -215,6 +217,8 @@ describe Money do
|
|
215
217
|
special_money_class = Class.new(Money)
|
216
218
|
expect(special_money_class.new(10_00, "USD") + Money.new(90, "USD")).to be_a special_money_class
|
217
219
|
end
|
220
|
+
|
221
|
+
it_behaves_like 'instance with custom bank', :+, Money.new(1)
|
218
222
|
end
|
219
223
|
|
220
224
|
describe "#-" do
|
@@ -236,6 +240,8 @@ describe Money do
|
|
236
240
|
special_money_class = Class.new(Money)
|
237
241
|
expect(special_money_class.new(10_00, "USD") - Money.new(90, "USD")).to be_a special_money_class
|
238
242
|
end
|
243
|
+
|
244
|
+
it_behaves_like 'instance with custom bank', :-, Money.new(1)
|
239
245
|
end
|
240
246
|
|
241
247
|
describe "#*" do
|
@@ -267,6 +273,8 @@ describe Money do
|
|
267
273
|
special_money_class = Class.new(Money)
|
268
274
|
expect(special_money_class.new(10_00, "USD") * 2).to be_a special_money_class
|
269
275
|
end
|
276
|
+
|
277
|
+
it_behaves_like 'instance with custom bank', :*, 1
|
270
278
|
end
|
271
279
|
|
272
280
|
describe "#/" do
|
@@ -363,6 +371,8 @@ describe Money do
|
|
363
371
|
end
|
364
372
|
end
|
365
373
|
end
|
374
|
+
|
375
|
+
it_behaves_like 'instance with custom bank', :/, 1
|
366
376
|
end
|
367
377
|
|
368
378
|
describe "#div" do
|
@@ -479,6 +489,9 @@ describe Money do
|
|
479
489
|
special_money_class = Class.new(Money)
|
480
490
|
expect(special_money_class.new(10_00, "USD").divmod(special_money_class.new(4_00)).last).to be_a special_money_class
|
481
491
|
end
|
492
|
+
|
493
|
+
it_behaves_like 'instance with custom bank', :divmod, Money.new(1)
|
494
|
+
it_behaves_like 'instance with custom bank', :divmod, 1
|
482
495
|
end
|
483
496
|
|
484
497
|
describe "#modulo" do
|
@@ -571,6 +584,8 @@ describe Money do
|
|
571
584
|
expect(t[:a].remainder(t[:b])).to eq t[:c]
|
572
585
|
end
|
573
586
|
end
|
587
|
+
|
588
|
+
it_behaves_like 'instance with custom bank', :remainder, -1
|
574
589
|
end
|
575
590
|
|
576
591
|
describe "#abs" do
|
@@ -584,6 +599,8 @@ describe Money do
|
|
584
599
|
special_money_class = Class.new(Money)
|
585
600
|
expect(special_money_class.new(-1).abs).to be_a special_money_class
|
586
601
|
end
|
602
|
+
|
603
|
+
it_behaves_like 'instance with custom bank', :abs
|
587
604
|
end
|
588
605
|
|
589
606
|
describe "#zero?" do
|
@@ -637,6 +654,16 @@ describe Money do
|
|
637
654
|
}.to raise_exception(TypeError)
|
638
655
|
end
|
639
656
|
|
657
|
+
it "allows subtraction from numeric zero" do
|
658
|
+
result = 0 - Money.new(4, 'USD')
|
659
|
+
expect(result).to eq Money.new(-4, 'USD')
|
660
|
+
end
|
661
|
+
|
662
|
+
it "allows addition from numeric zero" do
|
663
|
+
result = 0 + Money.new(4, 'USD')
|
664
|
+
expect(result).to eq Money.new(4, 'USD')
|
665
|
+
end
|
666
|
+
|
640
667
|
it "treats multiplication as commutative" do
|
641
668
|
expect {
|
642
669
|
2 * Money.new(2, 'USD')
|
@@ -265,7 +265,7 @@ describe Money, "formatting" do
|
|
265
265
|
expect(Money.new(10000, "VUV").format(:no_cents_if_whole => true, :symbol => false)).to eq "10,000"
|
266
266
|
expect(Money.new(10034, "VUV").format(:no_cents_if_whole => true, :symbol => false)).to eq "10,034"
|
267
267
|
expect(Money.new(10000, "MGA").format(:no_cents_if_whole => true, :symbol => false)).to eq "2,000"
|
268
|
-
expect(Money.new(10034, "MGA").format(:no_cents_if_whole => true, :symbol => false)).to eq "2,006.
|
268
|
+
expect(Money.new(10034, "MGA").format(:no_cents_if_whole => true, :symbol => false)).to eq "2,006.8"
|
269
269
|
expect(Money.new(10000, "VND").format(:no_cents_if_whole => true, :symbol => false)).to eq "10.000"
|
270
270
|
expect(Money.new(10034, "VND").format(:no_cents_if_whole => true, :symbol => false)).to eq "10.034"
|
271
271
|
expect(Money.new(10000, "USD").format(:no_cents_if_whole => true, :symbol => false)).to eq "100"
|
@@ -278,7 +278,7 @@ describe Money, "formatting" do
|
|
278
278
|
expect(Money.new(10000, "VUV").format(:no_cents_if_whole => false, :symbol => false)).to eq "10,000"
|
279
279
|
expect(Money.new(10034, "VUV").format(:no_cents_if_whole => false, :symbol => false)).to eq "10,034"
|
280
280
|
expect(Money.new(10000, "MGA").format(:no_cents_if_whole => false, :symbol => false)).to eq "2,000.0"
|
281
|
-
expect(Money.new(10034, "MGA").format(:no_cents_if_whole => false, :symbol => false)).to eq "2,006.
|
281
|
+
expect(Money.new(10034, "MGA").format(:no_cents_if_whole => false, :symbol => false)).to eq "2,006.8"
|
282
282
|
expect(Money.new(10000, "VND").format(:no_cents_if_whole => false, :symbol => false)).to eq "10.000"
|
283
283
|
expect(Money.new(10034, "VND").format(:no_cents_if_whole => false, :symbol => false)).to eq "10.034"
|
284
284
|
expect(Money.new(10000, "USD").format(:no_cents_if_whole => false, :symbol => false)).to eq "100.00"
|
@@ -405,6 +405,11 @@ describe Money, "formatting" do
|
|
405
405
|
expect(Money.new(1000000000, 'INDIAN_BAR').format(:south_asian_number_formatting => true, :symbol => false)).to eq "1,00,000.0000"
|
406
406
|
expect(Money.new(10000000).format(:south_asian_number_formatting => true)).to eq "$1,00,000.00"
|
407
407
|
end
|
408
|
+
|
409
|
+
specify "(:south_asian_number_formatting => true and no_cents_if_whole => true) works as documented" do
|
410
|
+
expect(Money.new(10000000, 'INR').format(:south_asian_number_formatting => true, :symbol => false, :no_cents_if_whole => true)).to eq "1,00,000"
|
411
|
+
expect(Money.new(1000000000, 'INDIAN_BAR').format(:south_asian_number_formatting => true, :symbol => false, :no_cents_if_whole => true)).to eq "1,00,000"
|
412
|
+
end
|
408
413
|
end
|
409
414
|
|
410
415
|
describe ":thousands_separator option" do
|
@@ -556,23 +561,23 @@ describe Money, "formatting" do
|
|
556
561
|
|
557
562
|
describe ":rounded_infinite_precision option", :infinite_precision do
|
558
563
|
it "does round fractional when set to true" do
|
559
|
-
expect(Money.new(BigDecimal
|
560
|
-
expect(Money.new(BigDecimal
|
561
|
-
expect(Money.new(BigDecimal
|
562
|
-
expect(Money.new(BigDecimal
|
563
|
-
expect(Money.new(BigDecimal
|
564
|
-
expect(Money.new(BigDecimal
|
565
|
-
expect(Money.new(BigDecimal
|
564
|
+
expect(Money.new(BigDecimal('12.1'), "USD").format(:rounded_infinite_precision => true)).to eq "$0.12"
|
565
|
+
expect(Money.new(BigDecimal('12.5'), "USD").format(:rounded_infinite_precision => true)).to eq "$0.13"
|
566
|
+
expect(Money.new(BigDecimal('123.1'), "BHD").format(:rounded_infinite_precision => true)).to eq "ب.د0.123"
|
567
|
+
expect(Money.new(BigDecimal('123.5'), "BHD").format(:rounded_infinite_precision => true)).to eq "ب.د0.124"
|
568
|
+
expect(Money.new(BigDecimal('100.1'), "USD").format(:rounded_infinite_precision => true)).to eq "$1.00"
|
569
|
+
expect(Money.new(BigDecimal('109.5'), "USD").format(:rounded_infinite_precision => true)).to eq "$1.10"
|
570
|
+
expect(Money.new(BigDecimal('1.7'), "MGA").format(:rounded_infinite_precision => true)).to eq "Ar0.4"
|
566
571
|
end
|
567
572
|
|
568
573
|
it "does not round fractional when set to false" do
|
569
|
-
expect(Money.new(BigDecimal
|
570
|
-
expect(Money.new(BigDecimal
|
571
|
-
expect(Money.new(BigDecimal
|
572
|
-
expect(Money.new(BigDecimal
|
573
|
-
expect(Money.new(BigDecimal
|
574
|
-
expect(Money.new(BigDecimal
|
575
|
-
expect(Money.new(BigDecimal
|
574
|
+
expect(Money.new(BigDecimal('12.1'), "USD").format(:rounded_infinite_precision => false)).to eq "$0.121"
|
575
|
+
expect(Money.new(BigDecimal('12.5'), "USD").format(:rounded_infinite_precision => false)).to eq "$0.125"
|
576
|
+
expect(Money.new(BigDecimal('123.1'), "BHD").format(:rounded_infinite_precision => false)).to eq "ب.د0.1231"
|
577
|
+
expect(Money.new(BigDecimal('123.5'), "BHD").format(:rounded_infinite_precision => false)).to eq "ب.د0.1235"
|
578
|
+
expect(Money.new(BigDecimal('100.1'), "USD").format(:rounded_infinite_precision => false)).to eq "$1.001"
|
579
|
+
expect(Money.new(BigDecimal('109.5'), "USD").format(:rounded_infinite_precision => false)).to eq "$1.095"
|
580
|
+
expect(Money.new(BigDecimal('1.7'), "MGA").format(:rounded_infinite_precision => false)).to eq "Ar0.34"
|
576
581
|
end
|
577
582
|
|
578
583
|
describe "with i18n = false" do
|
@@ -585,13 +590,13 @@ describe Money, "formatting" do
|
|
585
590
|
end
|
586
591
|
|
587
592
|
it 'does round fractional when set to true' do
|
588
|
-
expect(Money.new(BigDecimal
|
589
|
-
expect(Money.new(BigDecimal
|
590
|
-
expect(Money.new(BigDecimal
|
591
|
-
expect(Money.new(BigDecimal
|
593
|
+
expect(Money.new(BigDecimal('12.1'), "EUR").format(:rounded_infinite_precision => true)).to eq "€0,12"
|
594
|
+
expect(Money.new(BigDecimal('12.5'), "EUR").format(:rounded_infinite_precision => true)).to eq "€0,13"
|
595
|
+
expect(Money.new(BigDecimal('100.1'), "EUR").format(:rounded_infinite_precision => true)).to eq "€1,00"
|
596
|
+
expect(Money.new(BigDecimal('109.5'), "EUR").format(:rounded_infinite_precision => true)).to eq "€1,10"
|
592
597
|
|
593
|
-
expect(Money.new(BigDecimal
|
594
|
-
expect(Money.new(BigDecimal
|
598
|
+
expect(Money.new(BigDecimal('100012.1'), "EUR").format(:rounded_infinite_precision => true)).to eq "€1.000,12"
|
599
|
+
expect(Money.new(BigDecimal('100012.5'), "EUR").format(:rounded_infinite_precision => true)).to eq "€1.000,13"
|
595
600
|
end
|
596
601
|
end
|
597
602
|
|
@@ -612,13 +617,13 @@ describe Money, "formatting" do
|
|
612
617
|
end
|
613
618
|
|
614
619
|
it 'does round fractional when set to true' do
|
615
|
-
expect(Money.new(BigDecimal
|
616
|
-
expect(Money.new(BigDecimal
|
617
|
-
expect(Money.new(BigDecimal
|
618
|
-
expect(Money.new(BigDecimal
|
619
|
-
expect(Money.new(BigDecimal
|
620
|
-
expect(Money.new(BigDecimal
|
621
|
-
expect(Money.new(BigDecimal
|
620
|
+
expect(Money.new(BigDecimal('12.1'), "USD").format(:rounded_infinite_precision => true)).to eq "$0,12"
|
621
|
+
expect(Money.new(BigDecimal('12.5'), "USD").format(:rounded_infinite_precision => true)).to eq "$0,13"
|
622
|
+
expect(Money.new(BigDecimal('123.1'), "BHD").format(:rounded_infinite_precision => true)).to eq "ب.د0,123"
|
623
|
+
expect(Money.new(BigDecimal('123.5'), "BHD").format(:rounded_infinite_precision => true)).to eq "ب.د0,124"
|
624
|
+
expect(Money.new(BigDecimal('100.1'), "USD").format(:rounded_infinite_precision => true)).to eq "$1,00"
|
625
|
+
expect(Money.new(BigDecimal('109.5'), "USD").format(:rounded_infinite_precision => true)).to eq "$1,10"
|
626
|
+
expect(Money.new(BigDecimal('1'), "MGA").format(:rounded_infinite_precision => true)).to eq "Ar0,2"
|
622
627
|
end
|
623
628
|
end
|
624
629
|
end
|
data/spec/money_spec.rb
CHANGED
@@ -591,6 +591,18 @@ YAML
|
|
591
591
|
expect(moneys[1]).to eq Money.us_dollar(3)
|
592
592
|
end
|
593
593
|
|
594
|
+
it "handles small splits" do
|
595
|
+
moneys = Money.us_dollar(5).allocate([0.03, 0.07])
|
596
|
+
expect(moneys[0]).to eq Money.us_dollar(2)
|
597
|
+
expect(moneys[1]).to eq Money.us_dollar(3)
|
598
|
+
end
|
599
|
+
|
600
|
+
it "handles large splits" do
|
601
|
+
moneys = Money.us_dollar(5).allocate([3, 7])
|
602
|
+
expect(moneys[0]).to eq Money.us_dollar(2)
|
603
|
+
expect(moneys[1]).to eq Money.us_dollar(3)
|
604
|
+
end
|
605
|
+
|
594
606
|
it "does not lose pennies" do
|
595
607
|
moneys = Money.us_dollar(100).allocate([0.333, 0.333, 0.333])
|
596
608
|
expect(moneys[0].cents).to eq 34
|
@@ -607,7 +619,7 @@ YAML
|
|
607
619
|
end
|
608
620
|
|
609
621
|
it "handles mixed split types" do
|
610
|
-
splits = [Rational(1, 4), 0.25, 0.25, BigDecimal
|
622
|
+
splits = [Rational(1, 4), 0.25, 0.25, BigDecimal('0.25')]
|
611
623
|
moneys = Money.us_dollar(100).allocate(splits)
|
612
624
|
moneys.each do |money|
|
613
625
|
expect(money.cents).to eq 25
|
@@ -618,9 +630,9 @@ YAML
|
|
618
630
|
it "does not lose pennies" do
|
619
631
|
moneys = Money.us_dollar(-100).allocate([0.333, 0.333, 0.333])
|
620
632
|
|
621
|
-
expect(moneys[0].cents).to eq
|
622
|
-
expect(moneys[1].cents).to eq
|
623
|
-
expect(moneys[2].cents).to eq
|
633
|
+
expect(moneys[0].cents).to eq(-34)
|
634
|
+
expect(moneys[1].cents).to eq(-33)
|
635
|
+
expect(moneys[2].cents).to eq(-33)
|
624
636
|
end
|
625
637
|
|
626
638
|
it "allocates the same way as positive amounts" do
|
@@ -631,10 +643,6 @@ YAML
|
|
631
643
|
end
|
632
644
|
end
|
633
645
|
|
634
|
-
it "requires total to be less then 1" do
|
635
|
-
expect { Money.us_dollar(0.05).allocate([0.5, 0.6]) }.to raise_error(ArgumentError)
|
636
|
-
end
|
637
|
-
|
638
646
|
it "keeps subclasses intact" do
|
639
647
|
special_money_class = Class.new(Money)
|
640
648
|
expect(special_money_class.new(005).allocate([1]).first).to be_a special_money_class
|
@@ -733,6 +741,15 @@ YAML
|
|
733
741
|
expect(rounded).to be_a special_money_class
|
734
742
|
end
|
735
743
|
end
|
744
|
+
|
745
|
+
context "when using a specific rounding precision" do
|
746
|
+
let(:money) { Money.new(15.7526, 'NZD') }
|
747
|
+
|
748
|
+
it "uses the provided rounding precision" do
|
749
|
+
rounded = money.round(BigDecimal::ROUND_DOWN, 3)
|
750
|
+
expect(rounded.fractional).to eq 15.752
|
751
|
+
end
|
752
|
+
end
|
736
753
|
end
|
737
754
|
end
|
738
755
|
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
RSpec.shared_examples 'instance with custom bank' do |operation, value|
|
2
|
+
let(:custom_bank) { Money::Bank::VariableExchange.new }
|
3
|
+
let(:instance) { Money.new(1, :usd, custom_bank) }
|
4
|
+
|
5
|
+
subject { value ? instance.send(operation, value) : instance.send(operation) }
|
6
|
+
|
7
|
+
it "returns custom bank from new instance" do
|
8
|
+
new_money_instances = Array(subject).select { |el| el.is_a?(Money) }
|
9
|
+
|
10
|
+
new_money_instances.each do |money_instance|
|
11
|
+
expect(money_instance.bank).to eq(custom_bank)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: money
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.
|
4
|
+
version: 6.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shane Emmons
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-04-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: i18n
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: 0.6.4
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '1.
|
22
|
+
version: '1.1'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +29,7 @@ dependencies:
|
|
29
29
|
version: 0.6.4
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '1.
|
32
|
+
version: '1.1'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: bundler
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -78,14 +78,14 @@ dependencies:
|
|
78
78
|
requirements:
|
79
79
|
- - "~>"
|
80
80
|
- !ruby/object:Gem::Version
|
81
|
-
version:
|
81
|
+
version: 0.9.11
|
82
82
|
type: :development
|
83
83
|
prerelease: false
|
84
84
|
version_requirements: !ruby/object:Gem::Requirement
|
85
85
|
requirements:
|
86
86
|
- - "~>"
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version:
|
88
|
+
version: 0.9.11
|
89
89
|
- !ruby/object:Gem::Dependency
|
90
90
|
name: kramdown
|
91
91
|
requirement: !ruby/object:Gem::Requirement
|
@@ -131,7 +131,8 @@ files:
|
|
131
131
|
- lib/money/money.rb
|
132
132
|
- lib/money/money/arithmetic.rb
|
133
133
|
- lib/money/money/constructors.rb
|
134
|
-
- lib/money/money/
|
134
|
+
- lib/money/money/formatter.rb
|
135
|
+
- lib/money/money/formatting_rules.rb
|
135
136
|
- lib/money/rates_store/memory.rb
|
136
137
|
- lib/money/version.rb
|
137
138
|
- money.gemspec
|
@@ -147,6 +148,7 @@ files:
|
|
147
148
|
- spec/money_spec.rb
|
148
149
|
- spec/rates_store/memory_spec.rb
|
149
150
|
- spec/spec_helper.rb
|
151
|
+
- spec/support/shared_examples/money_examples.rb
|
150
152
|
homepage: https://rubymoney.github.io/money
|
151
153
|
licenses:
|
152
154
|
- MIT
|
@@ -184,3 +186,4 @@ test_files:
|
|
184
186
|
- spec/money_spec.rb
|
185
187
|
- spec/rates_store/memory_spec.rb
|
186
188
|
- spec/spec_helper.rb
|
189
|
+
- spec/support/shared_examples/money_examples.rb
|