money 6.13.0 → 6.13.8
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 +5 -5
- data/CHANGELOG.md +49 -2
- data/README.md +93 -3
- data/config/currency_backwards_compatible.json +31 -0
- data/config/currency_iso.json +18 -18
- data/lib/money/bank/variable_exchange.rb +14 -6
- data/lib/money/currency.rb +4 -4
- data/lib/money/currency/loader.rb +15 -13
- data/lib/money/locale_backend/currency.rb +11 -0
- data/lib/money/locale_backend/i18n.rb +2 -1
- data/lib/money/locale_backend/legacy.rb +2 -2
- data/lib/money/money.rb +100 -52
- data/lib/money/money/allocation.rb +8 -5
- data/lib/money/money/arithmetic.rb +20 -10
- data/lib/money/money/formatter.rb +3 -0
- data/lib/money/money/formatting_rules.rb +15 -5
- data/lib/money/money/locale_backend.rb +3 -1
- data/lib/money/rates_store/memory.rb +24 -23
- data/lib/money/version.rb +1 -1
- data/money.gemspec +4 -4
- metadata +11 -51
- data/.coveralls.yml +0 -1
- data/.gitignore +0 -23
- data/.rspec +0 -2
- data/.travis.yml +0 -32
- data/AUTHORS +0 -131
- data/CONTRIBUTING.md +0 -17
- data/Gemfile +0 -16
- data/Rakefile +0 -17
- data/spec/bank/base_spec.rb +0 -79
- data/spec/bank/single_currency_spec.rb +0 -13
- data/spec/bank/variable_exchange_spec.rb +0 -265
- data/spec/currency/heuristics_spec.rb +0 -11
- data/spec/currency/loader_spec.rb +0 -19
- data/spec/currency_spec.rb +0 -400
- data/spec/locale_backend/i18n_spec.rb +0 -62
- data/spec/locale_backend/legacy_spec.rb +0 -74
- data/spec/money/allocation_spec.rb +0 -130
- data/spec/money/arithmetic_spec.rb +0 -756
- data/spec/money/constructors_spec.rb +0 -91
- data/spec/money/formatting_spec.rb +0 -855
- data/spec/money/locale_backend_spec.rb +0 -14
- data/spec/money_spec.rb +0 -880
- data/spec/rates_store/memory_spec.rb +0 -80
- data/spec/spec_helper.rb +0 -30
- data/spec/support/shared_examples/money_examples.rb +0 -14
@@ -3,21 +3,23 @@ class Money
|
|
3
3
|
module Loader
|
4
4
|
DATA_PATH = File.expand_path("../../../../config", __FILE__)
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
class << self
|
7
|
+
# Loads and returns the currencies stored in JSON files in the config directory.
|
8
|
+
#
|
9
|
+
# @return [Hash]
|
10
|
+
def load_currencies
|
11
|
+
currencies = parse_currency_file("currency_iso.json")
|
12
|
+
currencies.merge! parse_currency_file("currency_non_iso.json")
|
13
|
+
currencies.merge! parse_currency_file("currency_backwards_compatible.json")
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
+
private
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
def parse_currency_file(filename)
|
19
|
+
json = File.read("#{DATA_PATH}/#{filename}")
|
20
|
+
json.force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
|
21
|
+
JSON.parse(json, symbolize_names: true)
|
22
|
+
end
|
21
23
|
end
|
22
24
|
end
|
23
25
|
end
|
@@ -9,9 +9,9 @@ class Money
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def lookup(key, currency)
|
12
|
-
|
13
|
-
warn '[DEPRECATION] `use_i18n` is deprecated - use `Money.locale_backend = :i18n` instead'
|
12
|
+
warn '[DEPRECATION] You are using the default localization behaviour that will change in the next major release. Find out more - https://github.com/RubyMoney/money#deprecation'
|
14
13
|
|
14
|
+
if Money.use_i18n
|
15
15
|
i18n_backend.lookup(key, nil) || currency.public_send(key)
|
16
16
|
else
|
17
17
|
currency.public_send(key)
|
data/lib/money/money.rb
CHANGED
@@ -91,51 +91,63 @@ class Money
|
|
91
91
|
class << self
|
92
92
|
|
93
93
|
# @!attribute [rw] default_bank
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
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
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
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
|
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
|
-
#
|
115
|
-
#
|
120
|
+
# Used to disable i18n even if it's used by other components of your app.
|
121
|
+
#
|
122
|
+
# @return [Boolean]
|
116
123
|
#
|
117
124
|
# @!attribute [rw] infinite_precision
|
118
|
-
#
|
125
|
+
# Used to enable infinite precision cents
|
126
|
+
#
|
127
|
+
# @return [Boolean]
|
119
128
|
#
|
120
129
|
# @!attribute [rw] conversion_precision
|
121
|
-
#
|
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
|
130
|
+
# Used to specify precision for converting Rational to BigDecimal
|
128
131
|
#
|
129
|
-
#
|
130
|
-
|
131
|
-
|
132
|
-
# default value is Currency.new("USD"). The value must be a valid
|
133
|
-
# +Money::Currency+ instance.
|
134
|
-
attr_writer :rounding_mode, :default_currency
|
132
|
+
# @return [Integer]
|
133
|
+
attr_accessor :default_bank, :default_formatting_rules,
|
134
|
+
:infinite_precision, :conversion_precision
|
135
135
|
|
136
|
+
attr_reader :use_i18n, :locale_backend
|
136
137
|
end
|
137
138
|
|
139
|
+
# @!attribute default_currency
|
140
|
+
# @return [Money::Currency] The default currency, which is used when
|
141
|
+
# +Money.new+ is called without an explicit currency argument. The
|
142
|
+
# default value is Currency.new("USD"). The value must be a valid
|
143
|
+
# +Money::Currency+ instance.
|
138
144
|
def self.default_currency
|
145
|
+
if @using_deprecated_default_currency
|
146
|
+
warn '[WARNING] The default currency will change from `USD` to `nil` in the next major release. Make ' \
|
147
|
+
'sure to set it explicitly using `Money.default_currency=` to avoid potential issues'
|
148
|
+
@using_deprecated_default_currency = false
|
149
|
+
end
|
150
|
+
|
139
151
|
if @default_currency.respond_to?(:call)
|
140
152
|
Money::Currency.new(@default_currency.call)
|
141
153
|
else
|
@@ -143,13 +155,26 @@ class Money
|
|
143
155
|
end
|
144
156
|
end
|
145
157
|
|
158
|
+
def self.default_currency=(currency)
|
159
|
+
@using_deprecated_default_currency = false
|
160
|
+
@default_currency = currency
|
161
|
+
end
|
162
|
+
|
146
163
|
def self.locale_backend=(value)
|
147
164
|
@locale_backend = value ? LocaleBackend.find(value) : nil
|
148
165
|
end
|
149
166
|
|
167
|
+
# @attr_writer rounding_mode Use this to specify the rounding mode
|
168
|
+
def self.rounding_mode=(new_rounding_mode)
|
169
|
+
@using_deprecated_default_rounding_mode = false
|
170
|
+
@rounding_mode = new_rounding_mode
|
171
|
+
end
|
172
|
+
|
150
173
|
def self.use_i18n=(value)
|
151
174
|
if value
|
152
|
-
warn '[DEPRECATION] `use_i18n` is deprecated - use `Money.locale_backend = :i18n` instead'
|
175
|
+
warn '[DEPRECATION] `use_i18n` is deprecated - use `Money.locale_backend = :i18n` instead for locale based formatting'
|
176
|
+
else
|
177
|
+
warn '[DEPRECATION] `use_i18n` is deprecated - use `Money.locale_backend = :currency` instead for currency based formatting'
|
153
178
|
end
|
154
179
|
|
155
180
|
@use_i18n = value
|
@@ -161,6 +186,7 @@ class Money
|
|
161
186
|
|
162
187
|
# Set the default currency for creating new +Money+ object.
|
163
188
|
self.default_currency = Currency.new("USD")
|
189
|
+
@using_deprecated_default_currency = true
|
164
190
|
|
165
191
|
# Default to using i18n
|
166
192
|
@use_i18n = true
|
@@ -173,6 +199,7 @@ class Money
|
|
173
199
|
|
174
200
|
# Default to bankers rounding
|
175
201
|
self.rounding_mode = BigDecimal::ROUND_HALF_EVEN
|
202
|
+
@using_deprecated_default_rounding_mode = true
|
176
203
|
|
177
204
|
# Default the conversion of Rationals precision to 16
|
178
205
|
self.conversion_precision = 16
|
@@ -184,32 +211,48 @@ class Money
|
|
184
211
|
|
185
212
|
setup_defaults
|
186
213
|
|
187
|
-
# Use this to return the rounding mode.
|
188
|
-
# rounding mode and a block to temporarily change it. It will
|
189
|
-
# then return the results of the block instead.
|
214
|
+
# Use this to return the rounding mode.
|
190
215
|
#
|
191
216
|
# @param [BigDecimal::ROUND_MODE] mode
|
192
217
|
#
|
193
|
-
# @return [BigDecimal::ROUND_MODE
|
218
|
+
# @return [BigDecimal::ROUND_MODE] rounding mode
|
219
|
+
def self.rounding_mode(mode = nil)
|
220
|
+
if mode
|
221
|
+
warn "[DEPRECATION] calling `rounding_mode` with a block is deprecated. Please use `.with_rounding_mode` instead."
|
222
|
+
return with_rounding_mode(mode) { yield }
|
223
|
+
end
|
224
|
+
|
225
|
+
return Thread.current[:money_rounding_mode] if Thread.current[:money_rounding_mode]
|
226
|
+
|
227
|
+
if @using_deprecated_default_rounding_mode
|
228
|
+
warn '[WARNING] The default rounding mode will change from `ROUND_HALF_EVEN` to `ROUND_HALF_UP` in the ' \
|
229
|
+
'next major release. Set it explicitly using `Money.rounding_mode=` to avoid potential problems.'
|
230
|
+
@using_deprecated_default_rounding_mode = false
|
231
|
+
end
|
232
|
+
|
233
|
+
@rounding_mode
|
234
|
+
end
|
235
|
+
|
236
|
+
# Temporarily changes the rounding mode in a given block.
|
237
|
+
#
|
238
|
+
# @param [BigDecimal::ROUND_MODE] mode
|
239
|
+
#
|
240
|
+
# @yield The block within which rounding mode will be changed. Its return
|
241
|
+
# value will also be the return value of the whole method.
|
242
|
+
#
|
243
|
+
# @return [Object] block results
|
194
244
|
#
|
195
245
|
# @example
|
196
|
-
# fee = Money.
|
246
|
+
# fee = Money.with_rounding_mode(BigDecimal::ROUND_HALF_UP) do
|
197
247
|
# Money.new(1200) * BigDecimal('0.029')
|
198
248
|
# end
|
199
|
-
def self.
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
Thread.current[:money_rounding_mode] = mode
|
205
|
-
yield
|
206
|
-
ensure
|
207
|
-
Thread.current[:money_rounding_mode] = nil
|
208
|
-
end
|
209
|
-
end
|
249
|
+
def self.with_rounding_mode(mode)
|
250
|
+
Thread.current[:money_rounding_mode] = mode
|
251
|
+
yield
|
252
|
+
ensure
|
253
|
+
Thread.current[:money_rounding_mode] = nil
|
210
254
|
end
|
211
255
|
|
212
|
-
|
213
256
|
# Adds a new exchange rate to the default bank and return the rate.
|
214
257
|
#
|
215
258
|
# @param [Currency, String, Symbol] from_currency Currency to exchange from.
|
@@ -274,10 +317,13 @@ class Money
|
|
274
317
|
# Money.new(100, "EUR") #=> #<Money @fractional=100 @currency="EUR">
|
275
318
|
#
|
276
319
|
def initialize(obj, currency = Money.default_currency, bank = Money.default_bank)
|
277
|
-
@fractional = obj.respond_to?(:fractional) ? obj.fractional :
|
320
|
+
@fractional = as_d(obj.respond_to?(:fractional) ? obj.fractional : obj)
|
278
321
|
@currency = obj.respond_to?(:currency) ? obj.currency : Currency.wrap(currency)
|
279
322
|
@currency ||= Money.default_currency
|
280
323
|
@bank = obj.respond_to?(:bank) ? obj.bank : bank
|
324
|
+
|
325
|
+
# BigDecimal can be Infinity and NaN, money of that amount does not make sense
|
326
|
+
raise ArgumentError, 'must be initialized with a finite value' unless @fractional.finite?
|
281
327
|
end
|
282
328
|
|
283
329
|
# Assuming using a currency using dollars:
|
@@ -500,13 +546,15 @@ class Money
|
|
500
546
|
exchange_to("EUR")
|
501
547
|
end
|
502
548
|
|
503
|
-
# Splits a given amount in parts without
|
504
|
-
# distributed round-robin amongst the parties. This means that
|
505
|
-
# receive more pennies than ones
|
549
|
+
# Splits a given amount in parts without losing pennies. The left-over pennies will be
|
550
|
+
# distributed round-robin amongst the parties. This means that parts listed first will likely
|
551
|
+
# receive more pennies than ones listed later.
|
552
|
+
#
|
553
|
+
# Pass [2, 1, 1] as input to give twice as much to part1 as part2 or
|
554
|
+
# part3 which results in 50% of the cash to party1, 25% to part2, and 25% to part3. Passing a
|
555
|
+
# number instead of an array will split the amount evenly (without losing pennies when rounding).
|
506
556
|
#
|
507
|
-
# @param [Array<Numeric>, Numeric]
|
508
|
-
# party3 which results in 50% of the cash to party1, 25% to party2, and 25% to party3. Passing a
|
509
|
-
# number instead of an array will split the amount evenly (without loosing pennies when rounding).
|
557
|
+
# @param [Array<Numeric>, Numeric] parts how amount should be distributed to parts
|
510
558
|
#
|
511
559
|
# @return [Array<Money>]
|
512
560
|
#
|
@@ -544,7 +592,7 @@ class Money
|
|
544
592
|
|
545
593
|
# Creates a formatted price string according to several rules.
|
546
594
|
#
|
547
|
-
# @param [Hash] See Money::Formatter for the list of formatting options
|
595
|
+
# @param [Hash] rules See {Money::Formatter Money::Formatter} for the list of formatting options
|
548
596
|
#
|
549
597
|
# @return [String]
|
550
598
|
#
|
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
class Money
|
4
4
|
class Allocation
|
5
|
-
# Splits a given amount in parts without
|
6
|
-
# The left-over pennies will be distributed round-robin amongst the
|
7
|
-
#
|
5
|
+
# Splits a given amount in parts without losing pennies.
|
6
|
+
# The left-over pennies will be distributed round-robin amongst the parts. This means that
|
7
|
+
# parts listed first will likely receive more pennies than the ones listed later.
|
8
8
|
#
|
9
9
|
# The results should always add up to the original amount.
|
10
10
|
#
|
@@ -24,8 +24,11 @@ class Money
|
|
24
24
|
parts_sum = parts.inject(0, :+)
|
25
25
|
part = parts.pop
|
26
26
|
|
27
|
-
current_split =
|
28
|
-
|
27
|
+
current_split = 0
|
28
|
+
if parts_sum > 0
|
29
|
+
current_split = remaining_amount * part / parts_sum
|
30
|
+
current_split = current_split.truncate if whole_amounts
|
31
|
+
end
|
29
32
|
|
30
33
|
result.unshift current_split
|
31
34
|
remaining_amount -= current_split
|
@@ -46,7 +46,7 @@ class Money
|
|
46
46
|
# Compares two Money objects. If money objects have a different currency it
|
47
47
|
# will attempt to convert the currency.
|
48
48
|
#
|
49
|
-
# @param [Money]
|
49
|
+
# @param [Money] other Value to compare with.
|
50
50
|
#
|
51
51
|
# @return [Integer]
|
52
52
|
#
|
@@ -103,7 +103,7 @@ class Money
|
|
103
103
|
# values. If +other_money+ has a different currency then its monetary value
|
104
104
|
# is automatically exchanged to this object's currency using +exchange_to+.
|
105
105
|
#
|
106
|
-
# @param [Money]
|
106
|
+
# @param [Money] other Other +Money+ object to add.
|
107
107
|
#
|
108
108
|
# @return [Money]
|
109
109
|
#
|
@@ -116,22 +116,32 @@ class Money
|
|
116
116
|
# its monetary value is automatically exchanged to this object's currency
|
117
117
|
# using +exchange_to+.
|
118
118
|
#
|
119
|
-
# @param [Money]
|
119
|
+
# @param [Money] other Other +Money+ object to subtract.
|
120
120
|
#
|
121
121
|
# @return [Money]
|
122
122
|
#
|
123
123
|
# @example
|
124
124
|
# Money.new(100) - Money.new(99) #=> #<Money @fractional=1>
|
125
125
|
[:+, :-].each do |op|
|
126
|
+
non_zero_message = lambda do |value|
|
127
|
+
"Can't add or subtract a non-zero #{value.class.name} value"
|
128
|
+
end
|
129
|
+
|
126
130
|
define_method(op) do |other|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
131
|
+
case other
|
132
|
+
when Money
|
133
|
+
other = other.exchange_to(currency)
|
134
|
+
new_fractional = fractional.public_send(op, other.fractional)
|
135
|
+
self.class.new(new_fractional, currency, bank)
|
136
|
+
when CoercedNumeric
|
137
|
+
raise TypeError, non_zero_message.call(other.value) unless other.zero?
|
138
|
+
self.class.new(other.value.public_send(op, fractional), currency)
|
139
|
+
when Numeric
|
140
|
+
raise TypeError, non_zero_message.call(other) unless other.zero?
|
141
|
+
self
|
142
|
+
else
|
143
|
+
raise TypeError, "Unsupported argument type: #{other.class.name}"
|
132
144
|
end
|
133
|
-
other = other.exchange_to(currency)
|
134
|
-
self.class.new(fractional.public_send(op, other.fractional), currency, bank)
|
135
145
|
end
|
136
146
|
end
|
137
147
|
|
@@ -204,6 +204,9 @@ class Money
|
|
204
204
|
# # CAD: "CAD$"
|
205
205
|
# Money.new(10000, "CAD").format(translate: true) #=> "CAD$100.00"
|
206
206
|
#
|
207
|
+
# @option rules [Boolean] :drop_trailing_zeros (false) Ignore trailing zeros after
|
208
|
+
# the decimal mark
|
209
|
+
#
|
207
210
|
# @example
|
208
211
|
# Money.new(89000, :btc).format(drop_trailing_zeros: true) #=> B⃦0.00089
|
209
212
|
# Money.new(110, :usd).format(drop_trailing_zeros: true) #=> $1.1
|
@@ -38,14 +38,22 @@ class Money
|
|
38
38
|
rules = {}
|
39
39
|
elsif rules.size == 1
|
40
40
|
rules = rules.pop
|
41
|
-
rules =
|
41
|
+
rules = rules.dup if rules.is_a?(Hash)
|
42
|
+
|
43
|
+
if rules.is_a?(Symbol)
|
44
|
+
warn '[DEPRECATION] Use Hash when passing rules to Money#format.'
|
45
|
+
rules = { rules => true }
|
46
|
+
end
|
42
47
|
end
|
48
|
+
|
43
49
|
if !rules.include?(:decimal_mark) && rules.include?(:separator)
|
44
50
|
rules[:decimal_mark] = rules[:separator]
|
45
51
|
end
|
52
|
+
|
46
53
|
if !rules.include?(:thousands_separator) && rules.include?(:delimiter)
|
47
54
|
rules[:thousands_separator] = rules[:delimiter]
|
48
55
|
end
|
56
|
+
|
49
57
|
rules
|
50
58
|
end
|
51
59
|
|
@@ -96,15 +104,18 @@ class Money
|
|
96
104
|
|
97
105
|
def warn_about_deprecated_rules(rules)
|
98
106
|
if rules.has_key?(:symbol_position)
|
99
|
-
|
107
|
+
position = rules[:symbol_position]
|
108
|
+
template = position == :before ? '%u %n' : '%n %u'
|
109
|
+
|
110
|
+
warn "[DEPRECATION] `symbol_position: :#{position}` is deprecated - you can replace it with `format: #{template}`"
|
100
111
|
end
|
101
112
|
|
102
113
|
if rules.has_key?(:symbol_before_without_space)
|
103
|
-
warn
|
114
|
+
warn "[DEPRECATION] `symbol_before_without_space:` option is deprecated - you can replace it with `format: '%u%n'`"
|
104
115
|
end
|
105
116
|
|
106
117
|
if rules.has_key?(:symbol_after_without_space)
|
107
|
-
warn
|
118
|
+
warn "[DEPRECATION] `symbol_after_without_space:` option is deprecated - you can replace it with `format: '%n%u'`"
|
108
119
|
end
|
109
120
|
|
110
121
|
if rules.has_key?(:html)
|
@@ -114,7 +125,6 @@ class Money
|
|
114
125
|
if rules.has_key?(:html_wrap_symbol)
|
115
126
|
warn "[DEPRECATION] `html_wrap_symbol` is deprecated - use `html_wrap` instead. Please note that `html_wrap` will wrap all parts of currency."
|
116
127
|
end
|
117
|
-
|
118
128
|
end
|
119
129
|
end
|
120
130
|
end
|