money 6.9.0 → 6.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +131 -3
  3. data/LICENSE +17 -17
  4. data/README.md +181 -71
  5. data/config/currency_backwards_compatible.json +65 -0
  6. data/config/currency_iso.json +119 -56
  7. data/config/currency_non_iso.json +35 -2
  8. data/lib/money/bank/variable_exchange.rb +22 -12
  9. data/lib/money/currency/loader.rb +15 -13
  10. data/lib/money/currency.rb +38 -39
  11. data/lib/money/locale_backend/base.rb +7 -0
  12. data/lib/money/locale_backend/currency.rb +11 -0
  13. data/lib/money/locale_backend/errors.rb +6 -0
  14. data/lib/money/locale_backend/i18n.rb +25 -0
  15. data/lib/money/locale_backend/legacy.rb +28 -0
  16. data/lib/money/money/allocation.rb +46 -0
  17. data/lib/money/money/arithmetic.rb +33 -15
  18. data/lib/money/money/constructors.rb +1 -2
  19. data/lib/money/money/formatter.rb +399 -0
  20. data/lib/money/money/formatting_rules.rb +142 -0
  21. data/lib/money/money/locale_backend.rb +22 -0
  22. data/lib/money/money.rb +235 -187
  23. data/lib/money/rates_store/memory.rb +24 -24
  24. data/lib/money/version.rb +1 -1
  25. data/money.gemspec +14 -8
  26. metadata +36 -56
  27. data/.coveralls.yml +0 -1
  28. data/.gitignore +0 -23
  29. data/.rspec +0 -1
  30. data/.travis.yml +0 -26
  31. data/AUTHORS +0 -126
  32. data/CONTRIBUTING.md +0 -17
  33. data/Gemfile +0 -16
  34. data/Rakefile +0 -17
  35. data/lib/money/money/formatting.rb +0 -426
  36. data/spec/bank/base_spec.rb +0 -79
  37. data/spec/bank/single_currency_spec.rb +0 -13
  38. data/spec/bank/variable_exchange_spec.rb +0 -265
  39. data/spec/currency/heuristics_spec.rb +0 -11
  40. data/spec/currency/loader_spec.rb +0 -19
  41. data/spec/currency_spec.rb +0 -359
  42. data/spec/money/arithmetic_spec.rb +0 -693
  43. data/spec/money/constructors_spec.rb +0 -103
  44. data/spec/money/formatting_spec.rb +0 -757
  45. data/spec/money_spec.rb +0 -778
  46. data/spec/rates_store/memory_spec.rb +0 -69
  47. data/spec/spec_helper.rb +0 -28
data/lib/money/money.rb CHANGED
@@ -3,7 +3,9 @@ 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
+ require "money/money/allocation"
8
+ require "money/money/locale_backend"
7
9
 
8
10
  # "Money is any object or record that is generally accepted as payment for
9
11
  # goods and services and repayment of debts in a given socio-economic context
@@ -17,7 +19,6 @@ require "money/money/formatting"
17
19
  class Money
18
20
  include Comparable
19
21
  include Money::Arithmetic
20
- include Money::Formatting
21
22
  extend Constructors
22
23
 
23
24
  # Raised when smallest denomination of a currency is not defined
@@ -90,50 +91,71 @@ class Money
90
91
  class << self
91
92
 
92
93
  # @!attribute [rw] default_bank
93
- # @return [Money::Bank::Base] Each Money object is associated to a bank
94
- # object, which is responsible for currency exchange. This property
95
- # allows you to specify the default bank object. The default value for
96
- # this property is an instance of +Bank::VariableExchange.+ It allows
97
- # 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]
98
103
  #
99
104
  # @!attribute default_formatting_rules
100
- # @return [Hash] Use this to define a default hash of rules for every time
101
- # +Money#format+ is called. Rules provided on method call will be
102
- # merged with the default ones. To overwrite a rule, just provide the
103
- # 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+.
104
109
  #
105
- # @see +Money::Formatting#format+ for more details.
110
+ # @see Money::Formatter#initialize Money::Formatter for more details
106
111
  #
107
112
  # @example
108
- # Money.default_formatting_rules = { :display_free => true }
113
+ # Money.default_formatting_rules = { display_free: true }
109
114
  # Money.new(0, "USD").format # => "free"
110
- # Money.new(0, "USD").format(:display_free => false) # => "$0.00"
115
+ # Money.new(0, "USD").format(display_free: false) # => "$0.00"
116
+ #
117
+ # @return [Hash]
111
118
  #
112
119
  # @!attribute [rw] use_i18n
113
- # @return [Boolean] Use this to disable i18n even if it's used by other
114
- # objects in your app.
120
+ # Used to disable i18n even if it's used by other components of your app.
121
+ #
122
+ # @return [Boolean]
115
123
  #
116
- # @!attribute [rw] infinite_precision
117
- # @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
118
127
  #
119
128
  # @!attribute [rw] conversion_precision
120
- # @return [Integer] Use this to specify precision for converting Rational
121
- # to BigDecimal
122
- attr_accessor :default_bank, :default_formatting_rules,
123
- :use_i18n, :infinite_precision, :conversion_precision
124
-
125
- # @attr_writer rounding_mode Use this to specify the rounding mode
129
+ # Used to specify precision for converting Rational to BigDecimal
126
130
  #
127
- # @!attribute default_currency
128
- # @return [Money::Currency] The default currency, which is used when
129
- # +Money.new+ is called without an explicit currency argument. The
130
- # default value is Currency.new("USD"). The value must be a valid
131
- # +Money::Currency+ instance.
132
- 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
133
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
134
145
  end
135
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.
136
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
+
137
159
  if @default_currency.respond_to?(:call)
138
160
  Money::Currency.new(@default_currency.call)
139
161
  else
@@ -141,21 +163,59 @@ class Money
141
163
  end
142
164
  end
143
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
+
179
+ def self.locale_backend=(value)
180
+ @locale_backend = value ? LocaleBackend.find(value) : nil
181
+ end
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
+
189
+ def self.use_i18n=(value)
190
+ if value
191
+ warn '[DEPRECATION] `use_i18n` is deprecated - use `Money.locale_backend = :i18n` instead for locale based formatting'
192
+ else
193
+ warn '[DEPRECATION] `use_i18n` is deprecated - use `Money.locale_backend = :currency` instead for currency based formatting'
194
+ end
195
+
196
+ @use_i18n = value
197
+ end
198
+
144
199
  def self.setup_defaults
145
200
  # Set the default bank for creating new +Money+ objects.
146
201
  self.default_bank = Bank::VariableExchange.instance
147
202
 
148
203
  # Set the default currency for creating new +Money+ object.
149
204
  self.default_currency = Currency.new("USD")
205
+ @using_deprecated_default_currency = true
150
206
 
151
207
  # Default to using i18n
152
- self.use_i18n = true
208
+ @use_i18n = true
209
+
210
+ # Default to using legacy locale backend
211
+ self.locale_backend = :legacy
153
212
 
154
213
  # Default to not using infinite precision cents
155
- self.infinite_precision = false
214
+ self.default_infinite_precision = false
156
215
 
157
216
  # Default to bankers rounding
158
217
  self.rounding_mode = BigDecimal::ROUND_HALF_EVEN
218
+ @using_deprecated_default_rounding_mode = true
159
219
 
160
220
  # Default the conversion of Rationals precision to 16
161
221
  self.conversion_precision = 16
@@ -167,32 +227,48 @@ class Money
167
227
 
168
228
  setup_defaults
169
229
 
170
- # Use this to return the rounding mode. You may also pass a
171
- # rounding mode and a block to temporarily change it. It will
172
- # then return the results of the block instead.
230
+ # Use this to return the rounding mode.
231
+ #
232
+ # @param [BigDecimal::ROUND_MODE] mode
233
+ #
234
+ # @return [BigDecimal::ROUND_MODE] rounding mode
235
+ def self.rounding_mode(mode = nil)
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]
242
+
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
250
+ end
251
+
252
+ # Temporarily changes the rounding mode in a given block.
173
253
  #
174
254
  # @param [BigDecimal::ROUND_MODE] mode
175
255
  #
176
- # @return [BigDecimal::ROUND_MODE,Yield] rounding mode or 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
177
260
  #
178
261
  # @example
179
- # fee = Money.rounding_mode(BigDecimal::ROUND_HALF_UP) do
180
- # Money.new(1200) * BigDecimal.new('0.029')
262
+ # fee = Money.with_rounding_mode(BigDecimal::ROUND_HALF_UP) do
263
+ # Money.new(1200) * BigDecimal('0.029')
181
264
  # end
182
- def self.rounding_mode(mode=nil)
183
- if mode.nil?
184
- Thread.current[:money_rounding_mode] || @rounding_mode
185
- else
186
- begin
187
- Thread.current[:money_rounding_mode] = mode
188
- yield
189
- ensure
190
- Thread.current[:money_rounding_mode] = nil
191
- end
192
- end
265
+ def self.with_rounding_mode(mode)
266
+ Thread.current[:money_rounding_mode] = mode
267
+ yield
268
+ ensure
269
+ Thread.current[:money_rounding_mode] = nil
193
270
  end
194
271
 
195
-
196
272
  # Adds a new exchange rate to the default bank and return the rate.
197
273
  #
198
274
  # @param [Currency, String, Symbol] from_currency Currency to exchange from.
@@ -218,7 +294,8 @@ class Money
218
294
  #
219
295
  # @param [Numeric] amount The numerical value of the money.
220
296
  # @param [Currency, String, Symbol] currency The currency format.
221
- # @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.
222
299
  #
223
300
  # @example
224
301
  # Money.from_amount(23.45, "USD") # => #<Money fractional:2345 currency:USD>
@@ -227,12 +304,16 @@ class Money
227
304
  # @return [Money]
228
305
  #
229
306
  # @see #initialize
230
- def self.from_amount(amount, currency = default_currency, bank = default_bank)
231
- Numeric === amount or raise ArgumentError, "'amount' must be numeric"
307
+ def self.from_amount(amount, currency = default_currency, options = {})
308
+ raise ArgumentError, "'amount' must be numeric" unless Numeric === amount
309
+
232
310
  currency = Currency.wrap(currency) || Money.default_currency
233
311
  value = amount.to_d * currency.subunit_to_unit
234
- value = value.round(0, rounding_mode) unless infinite_precision
235
- new(value, currency, bank)
312
+ new(value, currency, options)
313
+ end
314
+
315
+ class << self
316
+ alias_method :from_cents, :new
236
317
  end
237
318
 
238
319
  # Creates a new Money object of value given in the
@@ -246,7 +327,8 @@ class Money
246
327
  # argument, a Money will be created in that currency with fractional value
247
328
  # = 0.
248
329
  # @param [Currency, String, Symbol] currency The currency format.
249
- # @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.
250
332
  #
251
333
  # @return [Money]
252
334
  #
@@ -255,11 +337,20 @@ class Money
255
337
  # Money.new(100, "USD") #=> #<Money @fractional=100 @currency="USD">
256
338
  # Money.new(100, "EUR") #=> #<Money @fractional=100 @currency="EUR">
257
339
  #
258
- def initialize(obj, currency = Money.default_currency, bank = Money.default_bank)
259
- @fractional = obj.respond_to?(:fractional) ? obj.fractional : as_d(obj)
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
+
346
+ @fractional = as_d(obj.respond_to?(:fractional) ? obj.fractional : obj)
260
347
  @currency = obj.respond_to?(:currency) ? obj.currency : Currency.wrap(currency)
261
348
  @currency ||= Money.default_currency
262
- @bank = obj.respond_to?(:bank) ? obj.bank : bank
349
+ @bank = obj.respond_to?(:bank) ? obj.bank : options[:bank]
350
+ @bank ||= Money.default_bank
351
+
352
+ # BigDecimal can be Infinity and NaN, money of that amount does not make sense
353
+ raise ArgumentError, 'must be initialized with a finite value' unless @fractional.finite?
263
354
  end
264
355
 
265
356
  # Assuming using a currency using dollars:
@@ -271,7 +362,7 @@ class Money
271
362
  # @return [BigDecimal]
272
363
  #
273
364
  # @example
274
- # Money.new(1_00, "USD").dollars # => BigDecimal.new("1.00")
365
+ # Money.new(1_00, "USD").dollars # => BigDecimal("1.00")
275
366
  #
276
367
  # @see #amount
277
368
  # @see #to_d
@@ -286,7 +377,7 @@ class Money
286
377
  # @return [BigDecimal]
287
378
  #
288
379
  # @example
289
- # Money.new(1_00, "USD").amount # => BigDecimal.new("1.00")
380
+ # Money.new(1_00, "USD").amount # => BigDecimal("1.00")
290
381
  #
291
382
  # @see #to_d
292
383
  # @see #fractional
@@ -302,6 +393,7 @@ class Money
302
393
  # @example
303
394
  # Money.new(100, :USD).currency_as_string #=> "USD"
304
395
  def currency_as_string
396
+ warn "[DEPRECATION] `currency_as_string` is deprecated. Please use `.currency.to_s` instead."
305
397
  currency.to_s
306
398
  end
307
399
 
@@ -314,6 +406,8 @@ class Money
314
406
  # @example
315
407
  # Money.new(100).currency_as_string("CAD") #=> #<Money::Currency id: cad>
316
408
  def currency_as_string=(val)
409
+ warn "[DEPRECATION] `currency_as_string=` is deprecated - Money instances are immutable." \
410
+ " Please use `with_currency` instead."
317
411
  @currency = Currency.wrap(val)
318
412
  end
319
413
 
@@ -352,19 +446,10 @@ class Money
352
446
  # @example
353
447
  # Money.ca_dollar(100).to_s #=> "1.00"
354
448
  def to_s
355
- unit, subunit, fraction = strings_from_fractional
356
-
357
- str = if currency.decimal_places == 0
358
- if fraction == ""
359
- unit
360
- else
361
- "#{unit}#{decimal_mark}#{fraction}"
362
- end
363
- else
364
- "#{unit}#{decimal_mark}#{pad_subunit(subunit)}#{fraction}"
365
- end
366
-
367
- fractional < 0 ? "-#{str}" : str
449
+ format thousands_separator: '',
450
+ no_cents_if_whole: currency.decimal_places == 0,
451
+ symbol: false,
452
+ ignore_defaults: true
368
453
  end
369
454
 
370
455
  # Return the amount of money as a BigDecimal.
@@ -372,7 +457,7 @@ class Money
372
457
  # @return [BigDecimal]
373
458
  #
374
459
  # @example
375
- # Money.us_dollar(1_00).to_d #=> BigDecimal.new("1.00")
460
+ # Money.us_dollar(1_00).to_d #=> BigDecimal("1.00")
376
461
  def to_d
377
462
  as_d(fractional) / as_d(currency.subunit_to_unit)
378
463
  end
@@ -400,7 +485,22 @@ class Money
400
485
  to_d.to_f
401
486
  end
402
487
 
403
- # Conversation to +self+.
488
+ # Returns a new Money instance in a given currency leaving the amount intact
489
+ # and not performing currency conversion.
490
+ #
491
+ # @param [Currency, String, Symbol] new_currency Currency of the new object.
492
+ #
493
+ # @return [self]
494
+ def with_currency(new_currency)
495
+ new_currency = Currency.wrap(new_currency)
496
+ if !new_currency || currency == new_currency
497
+ self
498
+ else
499
+ dup_with(currency: new_currency)
500
+ end
501
+ end
502
+
503
+ # Conversion to +self+.
404
504
  #
405
505
  # @return [self]
406
506
  def to_money(given_currency = nil)
@@ -473,54 +573,29 @@ class Money
473
573
  exchange_to("EUR")
474
574
  end
475
575
 
476
- # Allocates money between different parties without losing pennies.
477
- # After the mathematical split has been performed, leftover pennies will
478
- # be distributed round-robin amongst the parties. This means that parties
479
- # listed first will likely receive more pennies than ones that are listed later
480
- #
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.
482
- #
483
- # @return [Array<Money>]
576
+ # Splits a given amount in parts without losing pennies. The left-over pennies will be
577
+ # distributed round-robin amongst the parties. This means that parts listed first will likely
578
+ # receive more pennies than ones listed later.
484
579
  #
485
- # @example
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)]
580
+ # Pass [2, 1, 1] as input to give twice as much to part1 as part2 or
581
+ # part3 which results in 50% of the cash to party1, 25% to part2, and 25% to part3. Passing a
582
+ # number instead of an array will split the amount evenly (without losing pennies when rounding).
488
583
  #
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)
497
-
498
- unless self.class.infinite_precision
499
- delta = left_over > 0 ? 1 : -1
500
- # Distribute left over pennies amongst allocations
501
- left_over.to_i.abs.times { |i| amounts[i % amounts.length] += delta }
502
- end
503
-
504
- amounts.collect { |fractional| self.class.new(fractional, currency) }
505
- end
506
-
507
- # Split money amongst parties evenly without losing pennies.
508
- #
509
- # @param [Numeric] num number of parties.
584
+ # @param [Array<Numeric>, Numeric] parts how amount should be distributed to parts
510
585
  #
511
586
  # @return [Array<Money>]
512
587
  #
513
588
  # @example
514
- # Money.new(100, "USD").split(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
515
- def split(num)
516
- raise ArgumentError, "need at least one party" if num < 1
517
-
518
- if self.class.infinite_precision
519
- split_infinite(num)
520
- else
521
- split_flat(num)
522
- end
589
+ # Money.new(5, "USD").allocate([3, 7]) #=> [Money.new(2), Money.new(3)]
590
+ # Money.new(100, "USD").allocate([1, 1, 1]) #=> [Money.new(34), Money.new(33), Money.new(33)]
591
+ # Money.new(100, "USD").allocate(2) #=> [Money.new(50), Money.new(50)]
592
+ # Money.new(100, "USD").allocate(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
593
+ #
594
+ def allocate(parts)
595
+ amounts = Money::Allocation.generate(fractional, parts, !Money.default_infinite_precision)
596
+ amounts.map { |amount| dup_with(fractional: amount) }
523
597
  end
598
+ alias_method :split, :allocate
524
599
 
525
600
  # Round the monetary amount to smallest unit of coinage.
526
601
  #
@@ -535,95 +610,68 @@ class Money
535
610
  # Money.new(10.1, 'USD').round #=> Money.new(10, 'USD')
536
611
  #
537
612
  # @see
538
- # Money.infinite_precision
613
+ # Money.default_infinite_precision
539
614
  #
540
- def round(rounding_mode = self.class.rounding_mode)
541
- if self.class.infinite_precision
542
- self.class.new(fractional.round(0, rounding_mode), self.currency)
543
- else
544
- self
545
- end
546
- end
547
-
548
- private
549
-
550
- def as_d(num)
551
- if num.respond_to?(:to_d)
552
- num.is_a?(Rational) ? num.to_d(self.class.conversion_precision) : num.to_d
553
- else
554
- BigDecimal.new(num.to_s.empty? ? 0 : num.to_s)
555
- end
556
- end
557
-
558
- def strings_from_fractional
559
- unit, subunit = fractional().abs.divmod(currency.subunit_to_unit)
560
-
561
- if self.class.infinite_precision
562
- strings_for_infinite_precision(unit, subunit)
563
- else
564
- strings_for_base_precision(unit, subunit)
565
- end
566
- end
567
-
568
- def strings_for_infinite_precision(unit, subunit)
569
- subunit, fraction = subunit.divmod(BigDecimal("1"))
570
- fraction = fraction.to_s("F")[2..-1] # want fractional part "0.xxx"
571
- fraction = "" if fraction =~ /^0+$/
572
-
573
- [unit.to_i.to_s, subunit.to_i.to_s, fraction]
574
- end
575
-
576
- def strings_for_base_precision(unit, subunit)
577
- [unit.to_s, subunit.to_s, ""]
615
+ def round(rounding_mode = self.class.rounding_mode, rounding_precision = 0)
616
+ rounded_amount = as_d(@fractional).round(rounding_precision, rounding_mode)
617
+ dup_with(fractional: rounded_amount)
578
618
  end
579
619
 
580
- def pad_subunit(subunit)
581
- cnt = currency.decimal_places
582
- padding = "0" * cnt
583
- "#{padding}#{subunit}"[-1 * cnt, cnt]
620
+ # Creates a formatted price string according to several rules.
621
+ #
622
+ # @param [Hash] rules See {Money::Formatter Money::Formatter} for the list of formatting options
623
+ #
624
+ # @return [String]
625
+ #
626
+ def format(*rules)
627
+ Money::Formatter.new(self, *rules).to_s
584
628
  end
585
629
 
586
- def allocations_from_splits(splits)
587
- splits.inject(0) { |sum, n| sum + as_d(n) }
630
+ # Returns a thousands separator according to the locale
631
+ #
632
+ # @return [String]
633
+ #
634
+ def thousands_separator
635
+ (locale_backend && locale_backend.lookup(:thousands_separator, currency)) ||
636
+ Money::Formatter::DEFAULTS[:thousands_separator]
588
637
  end
589
638
 
590
- def amounts_from_splits(allocations, splits)
591
- left_over = fractional
592
-
593
- amounts = splits.map do |ratio|
594
- if self.class.infinite_precision
595
- fractional * ratio
596
- else
597
- (fractional * ratio / allocations).truncate.tap do |frac|
598
- left_over -= frac
599
- end
600
- end
601
- end
602
-
603
- [amounts, left_over]
639
+ # Returns a decimal mark according to the locale
640
+ #
641
+ # @return [String]
642
+ #
643
+ def decimal_mark
644
+ (locale_backend && locale_backend.lookup(:decimal_mark, currency)) ||
645
+ Money::Formatter::DEFAULTS[:decimal_mark]
604
646
  end
605
647
 
606
- def split_infinite(num)
607
- amt = div(as_d(num))
608
- 1.upto(num).map{amt}
648
+ def dup_with(options = {})
649
+ self.class.new(
650
+ options[:fractional] || fractional,
651
+ options[:currency] || currency,
652
+ bank: options[:bank] || bank
653
+ )
609
654
  end
610
655
 
611
- def split_flat(num)
612
- low = self.class.new(fractional / num, currency)
613
- high = self.class.new(low.fractional + 1, currency)
614
-
615
- remainder = fractional % num
656
+ private
616
657
 
617
- Array.new(num).each_with_index.map do |_, index|
618
- index < remainder ? high : low
658
+ def as_d(num)
659
+ if num.respond_to?(:to_d)
660
+ num.is_a?(Rational) ? num.to_d(self.class.conversion_precision) : num.to_d
661
+ else
662
+ BigDecimal(num.to_s.empty? ? 0 : num.to_s)
619
663
  end
620
664
  end
621
665
 
622
666
  def return_value(value)
623
- if self.class.infinite_precision
667
+ if self.class.default_infinite_precision
624
668
  value
625
669
  else
626
670
  value.round(0, self.class.rounding_mode).to_i
627
671
  end
628
672
  end
673
+
674
+ def locale_backend
675
+ self.class.locale_backend
676
+ end
629
677
  end