money 6.13.8 → 6.16.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,6 +9,7 @@
9
9
  "subunit": "Satoshi",
10
10
  "subunit_to_unit": 100000000,
11
11
  "symbol_first": false,
12
+ "format": "%n %u",
12
13
  "html_entity": "₿",
13
14
  "decimal_mark": ".",
14
15
  "thousands_separator": ",",
@@ -113,8 +113,10 @@ class Money
113
113
  else
114
114
  if rate = get_rate(from.currency, to_currency)
115
115
  fractional = calculate_fractional(from, to_currency)
116
- from.class.new(
117
- exchange(fractional, rate, &block), to_currency, self
116
+ from.dup_with(
117
+ fractional: exchange(fractional, rate, &block),
118
+ currency: to_currency,
119
+ bank: self
118
120
  )
119
121
  else
120
122
  raise UnknownRate, "No conversion rate known for '#{from.currency.iso_code}' -> '#{to_currency}'"
@@ -128,7 +128,7 @@ class Money
128
128
  # @return [Array]
129
129
  #
130
130
  # @example
131
- # Money::Currency.iso_codes()
131
+ # Money::Currency.all()
132
132
  # [#<Currency ..USD>, 'CAD', 'EUR']...
133
133
  def all
134
134
  table.keys.map do |curr|
@@ -206,6 +206,10 @@ class Money
206
206
  all.each { |c| yield(c) }
207
207
  end
208
208
 
209
+ def reset!
210
+ @@instances = {}
211
+ @table = Loader.load_currencies
212
+ end
209
213
 
210
214
  private
211
215
 
@@ -253,7 +257,7 @@ class Money
253
257
 
254
258
  attr_reader :id, :priority, :iso_code, :iso_numeric, :name, :symbol,
255
259
  :disambiguate_symbol, :html_entity, :subunit, :subunit_to_unit, :decimal_mark,
256
- :thousands_separator, :symbol_first, :smallest_denomination
260
+ :thousands_separator, :symbol_first, :smallest_denomination, :format
257
261
 
258
262
  alias_method :separator, :decimal_mark
259
263
  alias_method :delimiter, :thousands_separator
@@ -341,7 +345,7 @@ class Money
341
345
  # @example
342
346
  # Money::Currency.new(:usd) #=> #<Currency id: usd ...>
343
347
  def inspect
344
- "#<#{self.class.name} id: #{id}, priority: #{priority}, symbol_first: #{symbol_first}, thousands_separator: #{thousands_separator}, html_entity: #{html_entity}, decimal_mark: #{decimal_mark}, name: #{name}, symbol: #{symbol}, subunit_to_unit: #{subunit_to_unit}, exponent: #{exponent}, iso_code: #{iso_code}, iso_numeric: #{iso_numeric}, subunit: #{subunit}, smallest_denomination: #{smallest_denomination}>"
348
+ "#<#{self.class.name} id: #{id}, priority: #{priority}, symbol_first: #{symbol_first}, thousands_separator: #{thousands_separator}, html_entity: #{html_entity}, decimal_mark: #{decimal_mark}, name: #{name}, symbol: #{symbol}, subunit_to_unit: #{subunit_to_unit}, exponent: #{exponent}, iso_code: #{iso_code}, iso_numeric: #{iso_numeric}, subunit: #{subunit}, smallest_denomination: #{smallest_denomination}, format: #{format}>"
345
349
  end
346
350
 
347
351
  # Returns a string representation corresponding to the upcase +id+
@@ -441,6 +445,7 @@ class Money
441
445
  @symbol = data[:symbol]
442
446
  @symbol_first = data[:symbol_first]
443
447
  @thousands_separator = data[:thousands_separator]
448
+ @format = data[:format]
444
449
  end
445
450
  end
446
451
  end
data/lib/money/money.rb CHANGED
@@ -121,19 +121,27 @@ class Money
121
121
  #
122
122
  # @return [Boolean]
123
123
  #
124
- # @!attribute [rw] infinite_precision
125
- # Used to enable infinite precision cents
126
- #
127
- # @return [Boolean]
124
+ # @!attribute [rw] default_infinite_precision
125
+ # @return [Boolean] Use this to enable infinite precision cents as the
126
+ # global default
128
127
  #
129
128
  # @!attribute [rw] conversion_precision
130
129
  # Used to specify precision for converting Rational to BigDecimal
131
130
  #
132
131
  # @return [Integer]
133
- attr_accessor :default_bank, :default_formatting_rules,
134
- :infinite_precision, :conversion_precision
135
-
132
+ attr_accessor :default_formatting_rules, :default_infinite_precision, :conversion_precision
136
133
  attr_reader :use_i18n, :locale_backend
134
+ attr_writer :default_bank
135
+
136
+ def infinite_precision
137
+ warn '[DEPRECATION] `Money.infinite_precision` is deprecated - use `Money.default_infinite_precision` instead'
138
+ default_infinite_precision
139
+ end
140
+
141
+ def infinite_precision=(value)
142
+ warn '[DEPRECATION] `Money.infinite_precision=` is deprecated - use `Money.default_infinite_precision= ` instead'
143
+ self.default_infinite_precision = value
144
+ end
137
145
  end
138
146
 
139
147
  # @!attribute default_currency
@@ -160,6 +168,14 @@ class Money
160
168
  @default_currency = currency
161
169
  end
162
170
 
171
+ def self.default_bank
172
+ if @default_bank.respond_to?(:call)
173
+ @default_bank.call
174
+ else
175
+ @default_bank
176
+ end
177
+ end
178
+
163
179
  def self.locale_backend=(value)
164
180
  @locale_backend = value ? LocaleBackend.find(value) : nil
165
181
  end
@@ -195,7 +211,7 @@ class Money
195
211
  self.locale_backend = :legacy
196
212
 
197
213
  # Default to not using infinite precision cents
198
- self.infinite_precision = false
214
+ self.default_infinite_precision = false
199
215
 
200
216
  # Default to bankers rounding
201
217
  self.rounding_mode = BigDecimal::ROUND_HALF_EVEN
@@ -278,7 +294,8 @@ class Money
278
294
  #
279
295
  # @param [Numeric] amount The numerical value of the money.
280
296
  # @param [Currency, String, Symbol] currency The currency format.
281
- # @param [Money::Bank::*] bank The exchange bank to use.
297
+ # @param [Hash] options Optional settings for the new Money instance
298
+ # @option [Money::Bank::*] :bank The exchange bank to use.
282
299
  #
283
300
  # @example
284
301
  # Money.from_amount(23.45, "USD") # => #<Money fractional:2345 currency:USD>
@@ -287,13 +304,16 @@ class Money
287
304
  # @return [Money]
288
305
  #
289
306
  # @see #initialize
290
- def self.from_amount(amount, currency = default_currency, bank = default_bank)
307
+ def self.from_amount(amount, currency = default_currency, options = {})
291
308
  raise ArgumentError, "'amount' must be numeric" unless Numeric === amount
292
309
 
293
310
  currency = Currency.wrap(currency) || Money.default_currency
294
311
  value = amount.to_d * currency.subunit_to_unit
295
- value = value.round(0, rounding_mode) unless infinite_precision
296
- new(value, currency, bank)
312
+ new(value, currency, options)
313
+ end
314
+
315
+ class << self
316
+ alias_method :from_cents, :new
297
317
  end
298
318
 
299
319
  # Creates a new Money object of value given in the
@@ -307,7 +327,8 @@ class Money
307
327
  # argument, a Money will be created in that currency with fractional value
308
328
  # = 0.
309
329
  # @param [Currency, String, Symbol] currency The currency format.
310
- # @param [Money::Bank::*] bank The exchange bank to use.
330
+ # @param [Hash] options Optional settings for the new Money instance
331
+ # @option [Money::Bank::*] :bank The exchange bank to use.
311
332
  #
312
333
  # @return [Money]
313
334
  #
@@ -316,11 +337,17 @@ class Money
316
337
  # Money.new(100, "USD") #=> #<Money @fractional=100 @currency="USD">
317
338
  # Money.new(100, "EUR") #=> #<Money @fractional=100 @currency="EUR">
318
339
  #
319
- def initialize(obj, currency = Money.default_currency, bank = Money.default_bank)
340
+ def initialize( obj, currency = Money.default_currency, options = {})
341
+ # For backwards compatability, if options is not a Hash, treat it as a bank parameter
342
+ unless options.is_a?(Hash)
343
+ options = { bank: options }
344
+ end
345
+
320
346
  @fractional = as_d(obj.respond_to?(:fractional) ? obj.fractional : obj)
321
347
  @currency = obj.respond_to?(:currency) ? obj.currency : Currency.wrap(currency)
322
348
  @currency ||= Money.default_currency
323
- @bank = obj.respond_to?(:bank) ? obj.bank : bank
349
+ @bank = obj.respond_to?(:bank) ? obj.bank : options[:bank]
350
+ @bank ||= Money.default_bank
324
351
 
325
352
  # BigDecimal can be Infinity and NaN, money of that amount does not make sense
326
353
  raise ArgumentError, 'must be initialized with a finite value' unless @fractional.finite?
@@ -469,7 +496,7 @@ class Money
469
496
  if !new_currency || currency == new_currency
470
497
  self
471
498
  else
472
- self.class.new(fractional, new_currency, bank)
499
+ dup_with(currency: new_currency)
473
500
  end
474
501
  end
475
502
 
@@ -565,8 +592,8 @@ class Money
565
592
  # Money.new(100, "USD").allocate(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
566
593
  #
567
594
  def allocate(parts)
568
- amounts = Money::Allocation.generate(fractional, parts, !Money.infinite_precision)
569
- amounts.map { |amount| self.class.new(amount, currency) }
595
+ amounts = Money::Allocation.generate(fractional, parts, !Money.default_infinite_precision)
596
+ amounts.map { |amount| dup_with(fractional: amount) }
570
597
  end
571
598
  alias_method :split, :allocate
572
599
 
@@ -583,11 +610,11 @@ class Money
583
610
  # Money.new(10.1, 'USD').round #=> Money.new(10, 'USD')
584
611
  #
585
612
  # @see
586
- # Money.infinite_precision
613
+ # Money.default_infinite_precision
587
614
  #
588
615
  def round(rounding_mode = self.class.rounding_mode, rounding_precision = 0)
589
616
  rounded_amount = as_d(@fractional).round(rounding_precision, rounding_mode)
590
- self.class.new(rounded_amount, currency, bank)
617
+ dup_with(fractional: rounded_amount)
591
618
  end
592
619
 
593
620
  # Creates a formatted price string according to several rules.
@@ -618,6 +645,14 @@ class Money
618
645
  Money::Formatter::DEFAULTS[:decimal_mark]
619
646
  end
620
647
 
648
+ def dup_with(options = {})
649
+ self.class.new(
650
+ options[:fractional] || fractional,
651
+ options[:currency] || currency,
652
+ bank: options[:bank] || bank
653
+ )
654
+ end
655
+
621
656
  private
622
657
 
623
658
  def as_d(num)
@@ -629,7 +664,7 @@ class Money
629
664
  end
630
665
 
631
666
  def return_value(value)
632
- if self.class.infinite_precision
667
+ if self.class.default_infinite_precision
633
668
  value
634
669
  else
635
670
  value.round(0, self.class.rounding_mode).to_i
@@ -13,7 +13,13 @@ class Money
13
13
  # Array<Numeric> — allocates the amounts proportionally to the given array
14
14
  #
15
15
  def self.generate(amount, parts, whole_amounts = true)
16
- parts = parts.is_a?(Numeric) ? Array.new(parts, 1) : parts.dup
16
+ parts = if parts.is_a?(Numeric)
17
+ Array.new(parts, 1)
18
+ elsif parts.all?(&:zero?)
19
+ Array.new(parts.count, 1)
20
+ else
21
+ parts.dup
22
+ end
17
23
 
18
24
  raise ArgumentError, 'need at least one party' if parts.empty?
19
25
 
@@ -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, bank)
19
+ dup_with(fractional: -fractional)
20
20
  end
21
21
 
22
22
  # Checks whether two Money objects have the same currency and the same
@@ -57,7 +57,12 @@ class Money
57
57
  return unless other.respond_to?(:zero?) && other.zero?
58
58
  return other.is_a?(CoercedNumeric) ? 0 <=> fractional : fractional <=> 0
59
59
  end
60
- return 0 if zero? && other.zero?
60
+
61
+ # Always allow comparison with zero
62
+ if zero? || other.zero?
63
+ return fractional <=> other.fractional
64
+ end
65
+
61
66
  other = other.exchange_to(currency)
62
67
  fractional <=> other.fractional
63
68
  rescue Money::Bank::UnknownRate
@@ -132,10 +137,10 @@ class Money
132
137
  when Money
133
138
  other = other.exchange_to(currency)
134
139
  new_fractional = fractional.public_send(op, other.fractional)
135
- self.class.new(new_fractional, currency, bank)
140
+ dup_with(fractional: new_fractional)
136
141
  when CoercedNumeric
137
142
  raise TypeError, non_zero_message.call(other.value) unless other.zero?
138
- self.class.new(other.value.public_send(op, fractional), currency)
143
+ dup_with(fractional: other.value.public_send(op, fractional))
139
144
  when Numeric
140
145
  raise TypeError, non_zero_message.call(other) unless other.zero?
141
146
  self
@@ -162,7 +167,7 @@ class Money
162
167
  def *(value)
163
168
  value = value.value if value.is_a?(CoercedNumeric)
164
169
  if value.is_a? Numeric
165
- self.class.new(fractional * value, currency, bank)
170
+ dup_with(fractional: fractional * value)
166
171
  else
167
172
  raise TypeError, "Can't multiply a #{self.class.name} by a #{value.class.name}'s value"
168
173
  end
@@ -188,7 +193,7 @@ class Money
188
193
  fractional / as_d(value.exchange_to(currency).fractional).to_f
189
194
  else
190
195
  raise TypeError, 'Can not divide by Money' if value.is_a?(CoercedNumeric)
191
- self.class.new(fractional / as_d(value), currency, bank)
196
+ dup_with(fractional: fractional / as_d(value))
192
197
  end
193
198
  end
194
199
 
@@ -226,13 +231,13 @@ class Money
226
231
  def divmod_money(val)
227
232
  cents = val.exchange_to(currency).cents
228
233
  quotient, remainder = fractional.divmod(cents)
229
- [quotient, self.class.new(remainder, currency, bank)]
234
+ [quotient, dup_with(fractional: remainder)]
230
235
  end
231
236
  private :divmod_money
232
237
 
233
238
  def divmod_other(val)
234
239
  quotient, remainder = fractional.divmod(as_d(val))
235
- [self.class.new(quotient, currency, bank), self.class.new(remainder, currency, bank)]
240
+ [dup_with(fractional: quotient), dup_with(fractional: remainder)]
236
241
  end
237
242
  private :divmod_other
238
243
 
@@ -276,7 +281,7 @@ class Money
276
281
  if (fractional < 0 && val < 0) || (fractional > 0 && val > 0)
277
282
  self.modulo(val)
278
283
  else
279
- self.modulo(val) - (val.is_a?(Money) ? val : self.class.new(val, currency, bank))
284
+ self.modulo(val) - (val.is_a?(Money) ? val : dup_with(fractional: val))
280
285
  end
281
286
  end
282
287
 
@@ -287,7 +292,7 @@ class Money
287
292
  # @example
288
293
  # Money.new(-100).abs #=> #<Money @fractional=100>
289
294
  def abs
290
- self.class.new(fractional.abs, currency, bank)
295
+ dup_with(fractional: fractional.abs)
291
296
  end
292
297
 
293
298
  # Test if the money amount is zero.
@@ -211,6 +211,12 @@ class Money
211
211
  # Money.new(89000, :btc).format(drop_trailing_zeros: true) #=> B⃦0.00089
212
212
  # Money.new(110, :usd).format(drop_trailing_zeros: true) #=> $1.1
213
213
  #
214
+ # @option rules [Boolean] :delimiter_pattern (/(\d)(?=(?:\d{3})+(?:[^\d]{1}|$))/) Regular expression to set the placement
215
+ # for the thousands delimiter
216
+ #
217
+ # @example
218
+ # Money.new(89000, :btc).format(delimiter_pattern: /(\d)(?=\d)/) #=> B⃦8,9,0.00
219
+ #
214
220
  # @option rules [String] :format (nil) Provide a template for formatting. `%u` will be replaced
215
221
  # with the symbol (if present) and `%n` will be replaced with the number.
216
222
  #
@@ -330,7 +336,9 @@ class Money
330
336
 
331
337
  def format_whole_part(value)
332
338
  # Apply thousands_separator
333
- value.gsub regexp_format, "\\1#{thousands_separator}"
339
+ value.gsub(rules[:delimiter_pattern]) do |digit_to_delimit|
340
+ "#{digit_to_delimit}#{thousands_separator}"
341
+ end
334
342
  end
335
343
 
336
344
  def extract_whole_and_decimal_parts
@@ -347,7 +355,7 @@ class Money
347
355
  end
348
356
 
349
357
  def format_decimal_part(value)
350
- return nil if currency.decimal_places == 0 && !Money.infinite_precision
358
+ return nil if currency.decimal_places == 0 && !Money.default_infinite_precision
351
359
  return nil if rules[:no_cents]
352
360
  return nil if rules[:no_cents_if_whole] && value.to_i == 0
353
361
 
@@ -366,15 +374,6 @@ class Money
366
374
  (Money.locale_backend && Money.locale_backend.lookup(key, currency)) || DEFAULTS[key]
367
375
  end
368
376
 
369
- def regexp_format
370
- if rules[:south_asian_number_formatting]
371
- # from http://blog.revathskumar.com/2014/11/regex-comma-seperated-indian-currency-format.html
372
- /(\d+?)(?=(\d\d)+(\d)(?!\d))(\.\d+)?/
373
- else
374
- /(\d)(?=(?:\d{3})+(?:[^\d]{1}|$))/
375
- end
376
- end
377
-
378
377
  def symbol_value_from(rules)
379
378
  if rules.has_key?(:symbol)
380
379
  if rules[:symbol] === true
@@ -12,6 +12,7 @@ class Money
12
12
  @rules = localize_formatting_rules(@rules)
13
13
  @rules = translate_formatting_rules(@rules) if @rules[:translate]
14
14
  @rules[:format] ||= determine_format_from_formatting_rules(@rules)
15
+ @rules[:delimiter_pattern] ||= delimiter_pattern_rule(@rules)
15
16
 
16
17
  warn_about_deprecated_rules(@rules)
17
18
  end
@@ -79,6 +80,8 @@ class Money
79
80
  end
80
81
 
81
82
  def determine_format_from_formatting_rules(rules)
83
+ return currency.format if currency.format && !rules.has_key?(:symbol_position)
84
+
82
85
  symbol_position = symbol_position_from(rules)
83
86
 
84
87
  if symbol_position == :before
@@ -88,6 +91,15 @@ class Money
88
91
  end
89
92
  end
90
93
 
94
+ def delimiter_pattern_rule(rules)
95
+ if rules[:south_asian_number_formatting]
96
+ # from http://blog.revathskumar.com/2014/11/regex-comma-seperated-indian-currency-format.html
97
+ /(\d+?)(?=(\d\d)+(\d)(?!\d))(\.\d+)?/
98
+ else
99
+ /(\d)(?=(?:\d{3})+(?:[^\d]{1}|$))/
100
+ end
101
+ end
102
+
91
103
  def symbol_position_from(rules)
92
104
  if rules.has_key?(:symbol_position)
93
105
  if [:before, :after].include?(rules[:symbol_position])
data/lib/money/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Money
2
- VERSION = '6.13.8'
2
+ VERSION = '6.16.0'
3
3
  end
data/money.gemspec CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
20
20
  s.add_development_dependency "rake"
21
21
  s.add_development_dependency "rspec", "~> 3.4"
22
22
  s.add_development_dependency "yard", "~> 0.9.11"
23
- s.add_development_dependency "kramdown", "~> 1.1"
23
+ s.add_development_dependency "kramdown", "~> 2.3"
24
24
 
25
25
  s.files = `git ls-files -z -- config/* lib/* CHANGELOG.md LICENSE money.gemspec README.md`.split("\x0")
26
26
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: money
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.13.8
4
+ version: 6.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shane Emmons
8
8
  - Anthony Dmitriyev
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-07-04 00:00:00.000000000 Z
12
+ date: 2021-05-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: i18n
@@ -93,14 +93,14 @@ dependencies:
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '1.1'
96
+ version: '2.3'
97
97
  type: :development
98
98
  prerelease: false
99
99
  version_requirements: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '1.1'
103
+ version: '2.3'
104
104
  description: A Ruby Library for dealing with money and currency conversion.
105
105
  email:
106
106
  - shane@emmons.io
@@ -144,7 +144,7 @@ metadata:
144
144
  changelog_uri: https://github.com/RubyMoney/money/blob/master/CHANGELOG.md
145
145
  source_code_uri: https://github.com/RubyMoney/money/
146
146
  bug_tracker_uri: https://github.com/RubyMoney/money/issues
147
- post_install_message:
147
+ post_install_message:
148
148
  rdoc_options: []
149
149
  require_paths:
150
150
  - lib
@@ -159,8 +159,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
159
  - !ruby/object:Gem::Version
160
160
  version: '0'
161
161
  requirements: []
162
- rubygems_version: 3.1.2
163
- signing_key:
162
+ rubygems_version: 3.2.3
163
+ signing_key:
164
164
  specification_version: 4
165
165
  summary: A Ruby Library for dealing with money and currency conversion.
166
166
  test_files: []