money 6.13.4 → 6.19.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.
data/lib/money/money.rb CHANGED
@@ -91,51 +91,71 @@ class Money
91
91
  class << self
92
92
 
93
93
  # @!attribute [rw] default_bank
94
- # @return [Money::Bank::Base] Each Money object is associated to a bank
95
- # object, which is responsible for currency exchange. This property
96
- # allows you to specify the default bank object. The default value for
97
- # this property is an instance of +Bank::VariableExchange.+ It allows
98
- # one to specify custom exchange rates.
94
+ # Used to set a default bank for currency exchange.
95
+ #
96
+ # Each Money object is associated with a bank
97
+ # object, which is responsible for currency exchange. This property
98
+ # allows you to specify the default bank object. The default value for
99
+ # this property is an instance of +Bank::VariableExchange.+ It allows
100
+ # one to specify custom exchange rates.
101
+ #
102
+ # @return [Money::Bank::Base]
99
103
  #
100
104
  # @!attribute default_formatting_rules
101
- # @return [Hash] Use this to define a default hash of rules for every time
102
- # +Money#format+ is called. Rules provided on method call will be
103
- # merged with the default ones. To overwrite a rule, just provide the
104
- # intended value while calling +format+.
105
+ # Used to define a default hash of rules for every time
106
+ # +Money#format+ is called. Rules provided on method call will be
107
+ # merged with the default ones. To overwrite a rule, just provide the
108
+ # intended value while calling +format+.
105
109
  #
106
- # @see +Money::Formatting#format+ for more details.
110
+ # @see Money::Formatter#initialize Money::Formatter for more details
107
111
  #
108
112
  # @example
109
113
  # Money.default_formatting_rules = { display_free: true }
110
114
  # Money.new(0, "USD").format # => "free"
111
115
  # Money.new(0, "USD").format(display_free: false) # => "$0.00"
112
116
  #
117
+ # @return [Hash]
118
+ #
113
119
  # @!attribute [rw] use_i18n
114
- # @return [Boolean] Use this to disable i18n even if it's used by other
115
- # objects in your app.
120
+ # Used to disable i18n even if it's used by other components of your app.
121
+ #
122
+ # @return [Boolean]
116
123
  #
117
- # @!attribute [rw] infinite_precision
118
- # @return [Boolean] Use this to enable infinite precision cents
124
+ # @!attribute [rw] default_infinite_precision
125
+ # @return [Boolean] Use this to enable infinite precision cents as the
126
+ # global default
119
127
  #
120
128
  # @!attribute [rw] conversion_precision
121
- # @return [Integer] Use this to specify precision for converting Rational
122
- # to BigDecimal
123
- attr_accessor :default_bank, :default_formatting_rules,
124
- :use_i18n, :infinite_precision, :conversion_precision,
125
- :locale_backend
126
-
127
- # @attr_writer rounding_mode Use this to specify the rounding mode
129
+ # Used to specify precision for converting Rational to BigDecimal
128
130
  #
129
- # @!attribute default_currency
130
- # @return [Money::Currency] The default currency, which is used when
131
- # +Money.new+ is called without an explicit currency argument. The
132
- # default value is Currency.new("USD"). The value must be a valid
133
- # +Money::Currency+ instance.
134
- attr_writer :rounding_mode, :default_currency
131
+ # @return [Integer]
132
+ attr_accessor :default_formatting_rules, :default_infinite_precision, :conversion_precision
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
135
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
136
145
  end
137
146
 
147
+ # @!attribute default_currency
148
+ # @return [Money::Currency] The default currency, which is used when
149
+ # +Money.new+ is called without an explicit currency argument. The
150
+ # default value is Currency.new("USD"). The value must be a valid
151
+ # +Money::Currency+ instance.
138
152
  def self.default_currency
153
+ if @using_deprecated_default_currency
154
+ warn '[WARNING] The default currency will change from `USD` to `nil` in the next major release. Make ' \
155
+ 'sure to set it explicitly using `Money.default_currency=` to avoid potential issues'
156
+ @using_deprecated_default_currency = false
157
+ end
158
+
139
159
  if @default_currency.respond_to?(:call)
140
160
  Money::Currency.new(@default_currency.call)
141
161
  else
@@ -143,10 +163,29 @@ class Money
143
163
  end
144
164
  end
145
165
 
166
+ def self.default_currency=(currency)
167
+ @using_deprecated_default_currency = false
168
+ @default_currency = currency
169
+ end
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
+
146
179
  def self.locale_backend=(value)
147
180
  @locale_backend = value ? LocaleBackend.find(value) : nil
148
181
  end
149
182
 
183
+ # @attr_writer rounding_mode Use this to specify the rounding mode
184
+ def self.rounding_mode=(new_rounding_mode)
185
+ @using_deprecated_default_rounding_mode = false
186
+ @rounding_mode = new_rounding_mode
187
+ end
188
+
150
189
  def self.use_i18n=(value)
151
190
  if value
152
191
  warn '[DEPRECATION] `use_i18n` is deprecated - use `Money.locale_backend = :i18n` instead for locale based formatting'
@@ -163,6 +202,7 @@ class Money
163
202
 
164
203
  # Set the default currency for creating new +Money+ object.
165
204
  self.default_currency = Currency.new("USD")
205
+ @using_deprecated_default_currency = true
166
206
 
167
207
  # Default to using i18n
168
208
  @use_i18n = true
@@ -171,10 +211,11 @@ class Money
171
211
  self.locale_backend = :legacy
172
212
 
173
213
  # Default to not using infinite precision cents
174
- self.infinite_precision = false
214
+ self.default_infinite_precision = false
175
215
 
176
216
  # Default to bankers rounding
177
217
  self.rounding_mode = BigDecimal::ROUND_HALF_EVEN
218
+ @using_deprecated_default_rounding_mode = true
178
219
 
179
220
  # Default the conversion of Rationals precision to 16
180
221
  self.conversion_precision = 16
@@ -192,18 +233,30 @@ class Money
192
233
  #
193
234
  # @return [BigDecimal::ROUND_MODE] rounding mode
194
235
  def self.rounding_mode(mode = nil)
195
- return Thread.current[:money_rounding_mode] || @rounding_mode unless mode
236
+ if mode
237
+ warn "[DEPRECATION] calling `rounding_mode` with a block is deprecated. Please use `.with_rounding_mode` instead."
238
+ return with_rounding_mode(mode) { yield }
239
+ end
240
+
241
+ return Thread.current[:money_rounding_mode] if Thread.current[:money_rounding_mode]
196
242
 
197
- warn "[DEPRECATION] calling `rounding_mode` with a block is deprecated. Please use `.with_rounding_mode` instead."
198
- with_rounding_mode(mode) { yield }
243
+ if @using_deprecated_default_rounding_mode
244
+ warn '[WARNING] The default rounding mode will change from `ROUND_HALF_EVEN` to `ROUND_HALF_UP` in the ' \
245
+ 'next major release. Set it explicitly using `Money.rounding_mode=` to avoid potential problems.'
246
+ @using_deprecated_default_rounding_mode = false
247
+ end
248
+
249
+ @rounding_mode
199
250
  end
200
251
 
201
- # This method temporarily changes the rounding mode. It will then return the
202
- # results of the block instead.
252
+ # Temporarily changes the rounding mode in a given block.
203
253
  #
204
254
  # @param [BigDecimal::ROUND_MODE] mode
205
255
  #
206
- # @return [BigDecimal::ROUND_MODE,Yield] block results
256
+ # @yield The block within which rounding mode will be changed. Its return
257
+ # value will also be the return value of the whole method.
258
+ #
259
+ # @return [Object] block results
207
260
  #
208
261
  # @example
209
262
  # fee = Money.with_rounding_mode(BigDecimal::ROUND_HALF_UP) do
@@ -241,7 +294,8 @@ class Money
241
294
  #
242
295
  # @param [Numeric] amount The numerical value of the money.
243
296
  # @param [Currency, String, Symbol] currency The currency format.
244
- # @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.
245
299
  #
246
300
  # @example
247
301
  # Money.from_amount(23.45, "USD") # => #<Money fractional:2345 currency:USD>
@@ -250,13 +304,17 @@ class Money
250
304
  # @return [Money]
251
305
  #
252
306
  # @see #initialize
253
- def self.from_amount(amount, currency = default_currency, bank = default_bank)
307
+ def self.from_amount(amount, currency = default_currency, options = {})
254
308
  raise ArgumentError, "'amount' must be numeric" unless Numeric === amount
255
309
 
256
310
  currency = Currency.wrap(currency) || Money.default_currency
257
311
  value = amount.to_d * currency.subunit_to_unit
258
- value = value.round(0, rounding_mode) unless infinite_precision
259
- new(value, currency, bank)
312
+ new(value, currency, options)
313
+ end
314
+
315
+ class << self
316
+ alias_method :from_cents, :new
317
+ alias_method :from_dollars, :from_amount
260
318
  end
261
319
 
262
320
  # Creates a new Money object of value given in the
@@ -270,7 +328,8 @@ class Money
270
328
  # argument, a Money will be created in that currency with fractional value
271
329
  # = 0.
272
330
  # @param [Currency, String, Symbol] currency The currency format.
273
- # @param [Money::Bank::*] bank The exchange bank to use.
331
+ # @param [Hash] options Optional settings for the new Money instance
332
+ # @option [Money::Bank::*] :bank The exchange bank to use.
274
333
  #
275
334
  # @return [Money]
276
335
  #
@@ -279,11 +338,17 @@ class Money
279
338
  # Money.new(100, "USD") #=> #<Money @fractional=100 @currency="USD">
280
339
  # Money.new(100, "EUR") #=> #<Money @fractional=100 @currency="EUR">
281
340
  #
282
- def initialize(obj, currency = Money.default_currency, bank = Money.default_bank)
341
+ def initialize( obj, currency = Money.default_currency, options = {})
342
+ # For backwards compatability, if options is not a Hash, treat it as a bank parameter
343
+ unless options.is_a?(Hash)
344
+ options = { bank: options }
345
+ end
346
+
283
347
  @fractional = as_d(obj.respond_to?(:fractional) ? obj.fractional : obj)
284
348
  @currency = obj.respond_to?(:currency) ? obj.currency : Currency.wrap(currency)
285
349
  @currency ||= Money.default_currency
286
- @bank = obj.respond_to?(:bank) ? obj.bank : bank
350
+ @bank = obj.respond_to?(:bank) ? obj.bank : options[:bank]
351
+ @bank ||= Money.default_bank
287
352
 
288
353
  # BigDecimal can be Infinity and NaN, money of that amount does not make sense
289
354
  raise ArgumentError, 'must be initialized with a finite value' unless @fractional.finite?
@@ -432,7 +497,7 @@ class Money
432
497
  if !new_currency || currency == new_currency
433
498
  self
434
499
  else
435
- self.class.new(fractional, new_currency, bank)
500
+ dup_with(currency: new_currency)
436
501
  end
437
502
  end
438
503
 
@@ -509,13 +574,15 @@ class Money
509
574
  exchange_to("EUR")
510
575
  end
511
576
 
512
- # Splits a given amount in parts without loosing pennies. The left-over pennies will be
513
- # distributed round-robin amongst the parties. This means that parties listed first will likely
514
- # receive more pennies than ones that are listed later.
577
+ # Splits a given amount in parts without losing pennies. The left-over pennies will be
578
+ # distributed round-robin amongst the parties. This means that parts listed first will likely
579
+ # receive more pennies than ones listed later.
515
580
  #
516
- # @param [Array<Numeric>, Numeric] pass [2, 1, 1] to give twice as much to party1 as party2 or
517
- # party3 which results in 50% of the cash to party1, 25% to party2, and 25% to party3. Passing a
518
- # number instead of an array will split the amount evenly (without loosing pennies when rounding).
581
+ # Pass [2, 1, 1] as input to give twice as much to part1 as part2 or
582
+ # part3 which results in 50% of the cash to party1, 25% to part2, and 25% to part3. Passing a
583
+ # number instead of an array will split the amount evenly (without losing pennies when rounding).
584
+ #
585
+ # @param [Array<Numeric>, Numeric] parts how amount should be distributed to parts
519
586
  #
520
587
  # @return [Array<Money>]
521
588
  #
@@ -526,8 +593,8 @@ class Money
526
593
  # Money.new(100, "USD").allocate(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
527
594
  #
528
595
  def allocate(parts)
529
- amounts = Money::Allocation.generate(fractional, parts, !Money.infinite_precision)
530
- amounts.map { |amount| self.class.new(amount, currency) }
596
+ amounts = Money::Allocation.generate(fractional, parts, !Money.default_infinite_precision)
597
+ amounts.map { |amount| dup_with(fractional: amount) }
531
598
  end
532
599
  alias_method :split, :allocate
533
600
 
@@ -544,16 +611,16 @@ class Money
544
611
  # Money.new(10.1, 'USD').round #=> Money.new(10, 'USD')
545
612
  #
546
613
  # @see
547
- # Money.infinite_precision
614
+ # Money.default_infinite_precision
548
615
  #
549
616
  def round(rounding_mode = self.class.rounding_mode, rounding_precision = 0)
550
617
  rounded_amount = as_d(@fractional).round(rounding_precision, rounding_mode)
551
- self.class.new(rounded_amount, currency, bank)
618
+ dup_with(fractional: rounded_amount)
552
619
  end
553
620
 
554
621
  # Creates a formatted price string according to several rules.
555
622
  #
556
- # @param [Hash] See Money::Formatter for the list of formatting options
623
+ # @param [Hash] rules See {Money::Formatter Money::Formatter} for the list of formatting options
557
624
  #
558
625
  # @return [String]
559
626
  #
@@ -579,6 +646,14 @@ class Money
579
646
  Money::Formatter::DEFAULTS[:decimal_mark]
580
647
  end
581
648
 
649
+ def dup_with(options = {})
650
+ self.class.new(
651
+ options[:fractional] || fractional,
652
+ options[:currency] || currency,
653
+ bank: options[:bank] || bank
654
+ )
655
+ end
656
+
582
657
  private
583
658
 
584
659
  def as_d(num)
@@ -590,7 +665,7 @@ class Money
590
665
  end
591
666
 
592
667
  def return_value(value)
593
- if self.class.infinite_precision
668
+ if self.class.default_infinite_precision
594
669
  value
595
670
  else
596
671
  value.round(0, self.class.rounding_mode).to_i
@@ -1,3 +1,5 @@
1
+ require 'monitor'
2
+
1
3
  class Money
2
4
  module RatesStore
3
5
 
@@ -17,11 +19,11 @@ class Money
17
19
  #
18
20
  # @param [Hash] opts Optional store options.
19
21
  # @option opts [Boolean] :without_mutex disables the usage of a mutex
20
- # @param [Hash] rt Optional initial exchange rate data.
21
- def initialize(opts = {}, rt = {})
22
- @options, @index = opts, rt
23
- @mutex = Mutex.new
24
- @in_transaction = false
22
+ # @param [Hash] rates Optional initial exchange rate data.
23
+ def initialize(opts = {}, rates = {})
24
+ @rates = rates
25
+ @options = opts
26
+ @guard = Monitor.new
25
27
  end
26
28
 
27
29
  # Registers a conversion rate and returns it. Uses +Mutex+ to synchronize data access.
@@ -37,7 +39,9 @@ class Money
37
39
  # store.add_rate("USD", "CAD", 1.24515)
38
40
  # store.add_rate("CAD", "USD", 0.803115)
39
41
  def add_rate(currency_iso_from, currency_iso_to, rate)
40
- transaction { index[rate_key_for(currency_iso_from, currency_iso_to)] = rate }
42
+ guard.synchronize do
43
+ rates[rate_key_for(currency_iso_from, currency_iso_to)] = rate
44
+ end
41
45
  end
42
46
 
43
47
  # Retrieve the rate for the given currencies. Uses +Mutex+ to synchronize data access.
@@ -54,24 +58,21 @@ class Money
54
58
  #
55
59
  # store.get_rate("USD", "CAD") #=> 1.24515
56
60
  def get_rate(currency_iso_from, currency_iso_to)
57
- transaction { index[rate_key_for(currency_iso_from, currency_iso_to)] }
61
+ guard.synchronize do
62
+ rates[rate_key_for(currency_iso_from, currency_iso_to)]
63
+ end
58
64
  end
59
65
 
60
66
  def marshal_dump
61
- [self.class, options, index]
67
+ guard.synchronize do
68
+ return [self.class, options, rates.dup]
69
+ end
62
70
  end
63
71
 
64
72
  # Wraps block execution in a thread-safe transaction
65
73
  def transaction(&block)
66
- if @in_transaction || options[:without_mutex]
67
- block.call self
68
- else
69
- @mutex.synchronize do
70
- @in_transaction = true
71
- result = block.call
72
- @in_transaction = false
73
- result
74
- end
74
+ guard.synchronize do
75
+ yield
75
76
  end
76
77
  end
77
78
 
@@ -88,19 +89,19 @@ class Money
88
89
  # puts [iso_from, iso_to, rate].join
89
90
  # end
90
91
  def each_rate(&block)
91
- enum = Enumerator.new do |yielder|
92
- index.each do |key, rate|
92
+ return to_enum(:each_rate) unless block_given?
93
+
94
+ guard.synchronize do
95
+ rates.each do |key, rate|
93
96
  iso_from, iso_to = key.split(INDEX_KEY_SEPARATOR)
94
- yielder.yield iso_from, iso_to, rate
97
+ yield iso_from, iso_to, rate
95
98
  end
96
99
  end
97
-
98
- block_given? ? enum.each(&block) : enum
99
100
  end
100
101
 
101
102
  private
102
103
 
103
- attr_reader :index, :options
104
+ attr_reader :rates, :options, :guard
104
105
 
105
106
  # Return the rate hashkey for the given currencies.
106
107
  #
data/lib/money/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Money
2
- VERSION = '6.13.4'
2
+ VERSION = '6.19.0'
3
3
  end
data/money.gemspec CHANGED
@@ -20,11 +20,9 @@ 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
- s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
27
- s.test_files = s.files.grep(%r{^(test|spec|features)/})
28
26
  s.require_paths = ["lib"]
29
27
 
30
28
  if s.respond_to?(:metadata)
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.4
4
+ version: 6.19.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: 2019-06-10 00:00:00.000000000 Z
12
+ date: 2024-03-06 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,9 +159,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
159
  - !ruby/object:Gem::Version
160
160
  version: '0'
161
161
  requirements: []
162
- rubyforge_project:
163
- rubygems_version: 2.6.8
164
- signing_key:
162
+ rubygems_version: 3.5.3
163
+ signing_key:
165
164
  specification_version: 4
166
165
  summary: A Ruby Library for dealing with money and currency conversion.
167
166
  test_files: []