money 6.13.0 → 6.13.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|