money 6.10.1 → 6.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|