money 6.10.1 → 6.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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