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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c2e4b452eb189b014f2807754a5be04f71419c60
4
- data.tar.gz: f94facfe0d9f541b0f3e1cae4cb94c1be7b9775d
3
+ metadata.gz: 8de2a6b8bf58ff5719f33d9db889ae722fa39b41
4
+ data.tar.gz: 713995a09dfac0042e598c153f3d7dffa54e06d7
5
5
  SHA512:
6
- metadata.gz: '008425091a080a8b13e96cb593863bb12bc7041de755686b27065de0d2b36830b2cbd5abd8c7e38424995511856a501d483557a3e4a20708669806917395fcfb'
7
- data.tar.gz: 6db8674c8037f772efc5ebfbdbaf680db6a9778d2a0f6fa9abe95b6d7802bb570efc4ae22b2b1d7f0c4385d9a35b38b6acf33f6b4d8e87d34cbbceeb5a4d8751
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.0
6
+ - 2.0
6
7
  - 2.1.10
7
- - 2.2.7
8
- - 2.3.4
9
- - 2.4.1
10
- - rbx-2
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.2.0
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-2
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
- # DB columns :from[String], :to[String], :rate[Float]
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": "B⃦",
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": "&#x20bf;",
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.new(from.fractional.to_s) / (
123
- BigDecimal.new(from.currency.subunit_to_unit.to_s) /
124
- BigDecimal.new(to_currency.subunit_to_unit.to_s)
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.new(rate.to_s)
129
+ ex = fractional * BigDecimal(rate.to_s)
130
130
  if block_given?
131
131
  yield ex
132
132
  elsif @rounding_method
@@ -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(@subunit_to_unit).round
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
- return self if other.zero?
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
- module Formatting
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 format(*rules)
202
- # support for old format parameters
203
- rules = normalize_formatting_rules(rules)
204
-
205
- rules = default_formatting_rules.merge(rules)
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
- if rules[:rounded_infinite_precision]
227
- formatted.gsub!(/#{decimal_mark}/, '.') unless '.' == decimal_mark
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
- sign = self.negative? ? '-' : ''
212
+ whole_part, decimal_part = extract_whole_and_decimal_parts
237
213
 
238
- if rules[:no_cents] || (rules[:no_cents_if_whole] && cents % currency.subunit_to_unit == 0)
239
- formatted = "#{formatted.to_i}"
240
- end
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
- # Inspiration: https://github.com/rails/rails/blob/16214d1108c31174c94503caced3855b0f6bad95/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb#L72-L79
243
- if rules[:drop_trailing_zeros]
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
- thousands_separator_value = thousands_separator
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
- # Apply thousands_separator
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
- i18n_format_for(:thousands_separator, :delimiter, ",")
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
- i18n_format_for(:decimal_mark, :separator, ".")
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 self.class.use_i18n
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
- # Cleans up formatting rules.
320
- #
321
- # @param [Hash] rules
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
- def localize_formatting_rules(rules)
383
- if currency.iso_code == "JPY" && I18n.locale == :ja
384
- rules[:symbol] = "円" unless rules[:symbol] == false
385
- rules[:symbol_position] = :after
386
- rules[:symbol_after_without_space] = true
387
- end
388
- rules
389
- end
390
-
391
- def symbol_value_from(rules)
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
- symbol
360
+ ""
398
361
  end
399
- elsif rules[:symbol]
400
- rules[:symbol]
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
- def symbol_position_from(rules)
414
- if rules.has_key?(:symbol_position)
415
- if [:before, :after].include?(rules[:symbol_position])
416
- return rules[:symbol_position]
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
- raise ArgumentError, ":symbol_position must be ':before' or ':after'"
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/formatting"
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.new('0.029')
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.new("1.00")
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.new("1.00")
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.new("1.00")
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 [0.50, 0.25, 0.25] to give 50% of the cash to party1, 25% to party2, and 25% to party3.
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([0.33, 0.33, 0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
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
- allocations = allocations_from_splits(splits)
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(0, rounding_mode), self.currency)
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.new(num.to_s.empty? ? 0 : num.to_s)
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
- cnt = currency.decimal_places
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(allocations, 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
@@ -1,3 +1,3 @@
1
1
  class Money
2
- VERSION = '6.10.1'
2
+ VERSION = '6.11.0'
3
3
  end
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.0']
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.8"
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($/)
@@ -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.new(0)
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.4"
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.4"
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.new('12.1'), "USD").format(:rounded_infinite_precision => true)).to eq "$0.12"
560
- expect(Money.new(BigDecimal.new('12.5'), "USD").format(:rounded_infinite_precision => true)).to eq "$0.13"
561
- expect(Money.new(BigDecimal.new('123.1'), "BHD").format(:rounded_infinite_precision => true)).to eq "ب.د0.123"
562
- expect(Money.new(BigDecimal.new('123.5'), "BHD").format(:rounded_infinite_precision => true)).to eq "ب.د0.124"
563
- expect(Money.new(BigDecimal.new('100.1'), "USD").format(:rounded_infinite_precision => true)).to eq "$1.00"
564
- expect(Money.new(BigDecimal.new('109.5'), "USD").format(:rounded_infinite_precision => true)).to eq "$1.10"
565
- expect(Money.new(BigDecimal.new('1'), "MGA").format(:rounded_infinite_precision => true)).to eq "Ar0.2"
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.new('12.1'), "USD").format(:rounded_infinite_precision => false)).to eq "$0.121"
570
- expect(Money.new(BigDecimal.new('12.5'), "USD").format(:rounded_infinite_precision => false)).to eq "$0.125"
571
- expect(Money.new(BigDecimal.new('123.1'), "BHD").format(:rounded_infinite_precision => false)).to eq "ب.د0.1231"
572
- expect(Money.new(BigDecimal.new('123.5'), "BHD").format(:rounded_infinite_precision => false)).to eq "ب.د0.1235"
573
- expect(Money.new(BigDecimal.new('100.1'), "USD").format(:rounded_infinite_precision => false)).to eq "$1.001"
574
- expect(Money.new(BigDecimal.new('109.5'), "USD").format(:rounded_infinite_precision => false)).to eq "$1.095"
575
- expect(Money.new(BigDecimal.new('1'), "MGA").format(:rounded_infinite_precision => false)).to eq "Ar0.1"
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.new('12.1'), "EUR").format(:rounded_infinite_precision => true)).to eq "€0,12"
589
- expect(Money.new(BigDecimal.new('12.5'), "EUR").format(:rounded_infinite_precision => true)).to eq "€0,13"
590
- expect(Money.new(BigDecimal.new('100.1'), "EUR").format(:rounded_infinite_precision => true)).to eq "€1,00"
591
- expect(Money.new(BigDecimal.new('109.5'), "EUR").format(:rounded_infinite_precision => true)).to eq "€1,10"
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.new('100012.1'), "EUR").format(:rounded_infinite_precision => true)).to eq "€1.000,12"
594
- expect(Money.new(BigDecimal.new('100012.5'), "EUR").format(:rounded_infinite_precision => true)).to eq "€1.000,13"
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.new('12.1'), "USD").format(:rounded_infinite_precision => true)).to eq "$0,12"
616
- expect(Money.new(BigDecimal.new('12.5'), "USD").format(:rounded_infinite_precision => true)).to eq "$0,13"
617
- expect(Money.new(BigDecimal.new('123.1'), "BHD").format(:rounded_infinite_precision => true)).to eq "ب.د0,123"
618
- expect(Money.new(BigDecimal.new('123.5'), "BHD").format(:rounded_infinite_precision => true)).to eq "ب.د0,124"
619
- expect(Money.new(BigDecimal.new('100.1'), "USD").format(:rounded_infinite_precision => true)).to eq "$1,00"
620
- expect(Money.new(BigDecimal.new('109.5'), "USD").format(:rounded_infinite_precision => true)).to eq "$1,10"
621
- expect(Money.new(BigDecimal.new('1'), "MGA").format(:rounded_infinite_precision => true)).to eq "Ar0,2"
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.new('0.25')]
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 -34
622
- expect(moneys[1].cents).to eq -33
623
- expect(moneys[2].cents).to eq -33
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
@@ -5,6 +5,8 @@ $LOAD_PATH.unshift File.dirname(__FILE__)
5
5
  require "rspec"
6
6
  require "money"
7
7
 
8
+ Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
9
+
8
10
  I18n.enforce_available_locales = false
9
11
 
10
12
  RSpec.configure do |c|
@@ -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.10.1
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: 2017-12-15 00:00:00.000000000 Z
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.0'
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.0'
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: '0.8'
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: '0.8'
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/formatting.rb
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