money 6.5.1 → 6.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +209 -5
- data/LICENSE +18 -16
- data/README.md +321 -70
- data/config/currency_backwards_compatible.json +65 -0
- data/config/currency_iso.json +280 -94
- data/config/currency_non_iso.json +101 -3
- data/lib/money/bank/base.rb +1 -3
- data/lib/money/bank/variable_exchange.rb +88 -96
- data/lib/money/currency/heuristics.rb +1 -143
- data/lib/money/currency/loader.rb +15 -13
- data/lib/money/currency.rb +98 -81
- data/lib/money/locale_backend/base.rb +7 -0
- data/lib/money/locale_backend/currency.rb +11 -0
- data/lib/money/locale_backend/errors.rb +6 -0
- data/lib/money/locale_backend/i18n.rb +25 -0
- data/lib/money/locale_backend/legacy.rb +28 -0
- data/lib/money/money/allocation.rb +46 -0
- data/lib/money/money/arithmetic.rb +97 -52
- data/lib/money/money/constructors.rb +5 -6
- data/lib/money/money/formatter.rb +399 -0
- data/lib/money/money/formatting_rules.rb +142 -0
- data/lib/money/money/locale_backend.rb +22 -0
- data/lib/money/money.rb +268 -194
- data/lib/money/rates_store/memory.rb +120 -0
- data/lib/money/version.rb +1 -1
- data/money.gemspec +15 -20
- metadata +36 -59
- data/.coveralls.yml +0 -1
- data/.gitignore +0 -22
- data/.travis.yml +0 -13
- data/AUTHORS +0 -116
- data/CONTRIBUTING.md +0 -17
- data/Gemfile +0 -7
- data/Rakefile +0 -17
- data/lib/money/money/formatting.rb +0 -386
- data/spec/bank/base_spec.rb +0 -77
- data/spec/bank/single_currency_spec.rb +0 -11
- data/spec/bank/variable_exchange_spec.rb +0 -275
- data/spec/currency/heuristics_spec.rb +0 -84
- data/spec/currency_spec.rb +0 -321
- data/spec/money/arithmetic_spec.rb +0 -568
- data/spec/money/constructors_spec.rb +0 -75
- data/spec/money/formatting_spec.rb +0 -667
- data/spec/money_spec.rb +0 -745
- data/spec/spec_helper.rb +0 -23
@@ -1,5 +1,13 @@
|
|
1
1
|
class Money
|
2
2
|
module Arithmetic
|
3
|
+
# Wrapper for coerced numeric values to distinguish
|
4
|
+
# when numeric was on the 1st place in operation.
|
5
|
+
CoercedNumeric = Struct.new(:value) do
|
6
|
+
# Proxy #zero? method to skip unnecessary typecasts. See #- and #+.
|
7
|
+
def zero?
|
8
|
+
value.zero?
|
9
|
+
end
|
10
|
+
end
|
3
11
|
|
4
12
|
# Returns a money object with changed polarity.
|
5
13
|
#
|
@@ -8,40 +16,65 @@ class Money
|
|
8
16
|
# @example
|
9
17
|
# - Money.new(100) #=> #<Money @fractional=-100>
|
10
18
|
def -@
|
11
|
-
|
19
|
+
dup_with(fractional: -fractional)
|
12
20
|
end
|
13
21
|
|
14
|
-
# Checks whether two
|
15
|
-
# amount.
|
16
|
-
#
|
22
|
+
# Checks whether two Money objects have the same currency and the same
|
23
|
+
# amount. If Money objects have a different currency it will only be true
|
24
|
+
# if the amounts are both zero. Checks against objects that are not Money or
|
25
|
+
# a subclass will always return false.
|
17
26
|
#
|
18
27
|
# @param [Money] other_money Value to compare with.
|
19
28
|
#
|
20
29
|
# @return [Boolean]
|
21
30
|
#
|
22
31
|
# @example
|
23
|
-
# Money.new(100)
|
24
|
-
# Money.new(100)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
32
|
+
# Money.new(100).eql?(Money.new(101)) #=> false
|
33
|
+
# Money.new(100).eql?(Money.new(100)) #=> true
|
34
|
+
# Money.new(100, "USD").eql?(Money.new(100, "GBP")) #=> false
|
35
|
+
# Money.new(0, "USD").eql?(Money.new(0, "EUR")) #=> true
|
36
|
+
# Money.new(100).eql?("1.00") #=> false
|
37
|
+
def eql?(other_money)
|
38
|
+
if other_money.is_a?(Money)
|
39
|
+
(fractional == other_money.fractional && currency == other_money.currency) ||
|
40
|
+
(fractional == 0 && other_money.fractional == 0)
|
29
41
|
else
|
30
42
|
false
|
31
43
|
end
|
32
44
|
end
|
33
|
-
alias_method :eql?, :==
|
34
45
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
46
|
+
# Compares two Money objects. If money objects have a different currency it
|
47
|
+
# will attempt to convert the currency.
|
48
|
+
#
|
49
|
+
# @param [Money] other Value to compare with.
|
50
|
+
#
|
51
|
+
# @return [Integer]
|
52
|
+
#
|
53
|
+
# @raise [TypeError] when other object is not Money
|
54
|
+
#
|
55
|
+
def <=>(other)
|
56
|
+
unless other.is_a?(Money)
|
57
|
+
return unless other.respond_to?(:zero?) && other.zero?
|
58
|
+
return other.is_a?(CoercedNumeric) ? 0 <=> fractional : fractional <=> 0
|
59
|
+
end
|
60
|
+
|
61
|
+
# Always allow comparison with zero
|
62
|
+
if zero? || other.zero?
|
63
|
+
return fractional <=> other.fractional
|
44
64
|
end
|
65
|
+
|
66
|
+
other = other.exchange_to(currency)
|
67
|
+
fractional <=> other.fractional
|
68
|
+
rescue Money::Bank::UnknownRate
|
69
|
+
end
|
70
|
+
|
71
|
+
# Uses Comparable's implementation but raises ArgumentError if non-zero
|
72
|
+
# numeric value is given.
|
73
|
+
def ==(other)
|
74
|
+
if other.is_a?(Numeric) && !other.zero?
|
75
|
+
raise ArgumentError, 'Money#== supports only zero numerics'
|
76
|
+
end
|
77
|
+
super
|
45
78
|
end
|
46
79
|
|
47
80
|
# Test if the amount is positive. Returns +true+ if the money amount is
|
@@ -70,37 +103,51 @@ class Money
|
|
70
103
|
fractional < 0
|
71
104
|
end
|
72
105
|
|
106
|
+
# @method +(other)
|
73
107
|
# Returns a new Money object containing the sum of the two operands' monetary
|
74
108
|
# values. If +other_money+ has a different currency then its monetary value
|
75
109
|
# is automatically exchanged to this object's currency using +exchange_to+.
|
76
110
|
#
|
77
|
-
# @param [Money]
|
111
|
+
# @param [Money] other Other +Money+ object to add.
|
78
112
|
#
|
79
113
|
# @return [Money]
|
80
114
|
#
|
81
115
|
# @example
|
82
116
|
# Money.new(100) + Money.new(100) #=> #<Money @fractional=200>
|
83
|
-
|
84
|
-
|
85
|
-
other_money = other_money.exchange_to(currency)
|
86
|
-
Money.new(fractional + other_money.fractional, currency)
|
87
|
-
end
|
88
|
-
|
117
|
+
#
|
118
|
+
# @method -(other)
|
89
119
|
# Returns a new Money object containing the difference between the two
|
90
120
|
# operands' monetary values. If +other_money+ has a different currency then
|
91
121
|
# its monetary value is automatically exchanged to this object's currency
|
92
122
|
# using +exchange_to+.
|
93
123
|
#
|
94
|
-
# @param [Money]
|
124
|
+
# @param [Money] other Other +Money+ object to subtract.
|
95
125
|
#
|
96
126
|
# @return [Money]
|
97
127
|
#
|
98
128
|
# @example
|
99
129
|
# Money.new(100) - Money.new(99) #=> #<Money @fractional=1>
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
130
|
+
[:+, :-].each do |op|
|
131
|
+
non_zero_message = lambda do |value|
|
132
|
+
"Can't add or subtract a non-zero #{value.class.name} value"
|
133
|
+
end
|
134
|
+
|
135
|
+
define_method(op) do |other|
|
136
|
+
case other
|
137
|
+
when Money
|
138
|
+
other = other.exchange_to(currency)
|
139
|
+
new_fractional = fractional.public_send(op, other.fractional)
|
140
|
+
dup_with(fractional: new_fractional)
|
141
|
+
when CoercedNumeric
|
142
|
+
raise TypeError, non_zero_message.call(other.value) unless other.zero?
|
143
|
+
dup_with(fractional: other.value.public_send(op, fractional))
|
144
|
+
when Numeric
|
145
|
+
raise TypeError, non_zero_message.call(other) unless other.zero?
|
146
|
+
self
|
147
|
+
else
|
148
|
+
raise TypeError, "Unsupported argument type: #{other.class.name}"
|
149
|
+
end
|
150
|
+
end
|
104
151
|
end
|
105
152
|
|
106
153
|
# Multiplies the monetary value with the given number and returns a new
|
@@ -112,16 +159,17 @@ class Money
|
|
112
159
|
#
|
113
160
|
# @return [Money] The resulting money.
|
114
161
|
#
|
115
|
-
# @raise [
|
162
|
+
# @raise [TypeError] If +value+ is NOT a number.
|
116
163
|
#
|
117
164
|
# @example
|
118
165
|
# Money.new(100) * 2 #=> #<Money @fractional=200>
|
119
166
|
#
|
120
167
|
def *(value)
|
168
|
+
value = value.value if value.is_a?(CoercedNumeric)
|
121
169
|
if value.is_a? Numeric
|
122
|
-
|
170
|
+
dup_with(fractional: fractional * value)
|
123
171
|
else
|
124
|
-
raise
|
172
|
+
raise TypeError, "Can't multiply a #{self.class.name} by a #{value.class.name}'s value"
|
125
173
|
end
|
126
174
|
end
|
127
175
|
|
@@ -141,10 +189,11 @@ class Money
|
|
141
189
|
# Money.new(100) / Money.new(10) #=> 10.0
|
142
190
|
#
|
143
191
|
def /(value)
|
144
|
-
if value.is_a?(
|
192
|
+
if value.is_a?(self.class)
|
145
193
|
fractional / as_d(value.exchange_to(currency).fractional).to_f
|
146
194
|
else
|
147
|
-
Money
|
195
|
+
raise TypeError, 'Can not divide by Money' if value.is_a?(CoercedNumeric)
|
196
|
+
dup_with(fractional: fractional / as_d(value))
|
148
197
|
end
|
149
198
|
end
|
150
199
|
|
@@ -164,9 +213,9 @@ class Money
|
|
164
213
|
# Divide money by money or fixnum and return array containing quotient and
|
165
214
|
# modulus.
|
166
215
|
#
|
167
|
-
# @param [Money,
|
216
|
+
# @param [Money, Integer] val Number to divmod by.
|
168
217
|
#
|
169
|
-
# @return [Array<Money,Money>,Array<
|
218
|
+
# @return [Array<Money,Money>,Array<Integer,Money>]
|
170
219
|
#
|
171
220
|
# @example
|
172
221
|
# Money.new(100).divmod(9) #=> [#<Money @fractional=11>, #<Money @fractional=1>]
|
@@ -182,23 +231,19 @@ class Money
|
|
182
231
|
def divmod_money(val)
|
183
232
|
cents = val.exchange_to(currency).cents
|
184
233
|
quotient, remainder = fractional.divmod(cents)
|
185
|
-
[quotient,
|
234
|
+
[quotient, dup_with(fractional: remainder)]
|
186
235
|
end
|
187
236
|
private :divmod_money
|
188
237
|
|
189
238
|
def divmod_other(val)
|
190
|
-
|
191
|
-
|
192
|
-
[Money.new(quotient, currency), Money.new(remainder, currency)]
|
193
|
-
else
|
194
|
-
[div(val), Money.new(fractional.modulo(val), currency)]
|
195
|
-
end
|
239
|
+
quotient, remainder = fractional.divmod(as_d(val))
|
240
|
+
[dup_with(fractional: quotient), dup_with(fractional: remainder)]
|
196
241
|
end
|
197
242
|
private :divmod_other
|
198
243
|
|
199
244
|
# Equivalent to +self.divmod(val)[1]+
|
200
245
|
#
|
201
|
-
# @param [Money,
|
246
|
+
# @param [Money, Integer] val Number take modulo with.
|
202
247
|
#
|
203
248
|
# @return [Money]
|
204
249
|
#
|
@@ -211,7 +256,7 @@ class Money
|
|
211
256
|
|
212
257
|
# Synonym for +#modulo+.
|
213
258
|
#
|
214
|
-
# @param [Money,
|
259
|
+
# @param [Money, Integer] val Number take modulo with.
|
215
260
|
#
|
216
261
|
# @return [Money]
|
217
262
|
#
|
@@ -222,7 +267,7 @@ class Money
|
|
222
267
|
|
223
268
|
# If different signs +self.modulo(val) - val+ otherwise +self.modulo(val)+
|
224
269
|
#
|
225
|
-
# @param [Money,
|
270
|
+
# @param [Money, Integer] val Number to rake remainder with.
|
226
271
|
#
|
227
272
|
# @return [Money]
|
228
273
|
#
|
@@ -236,7 +281,7 @@ class Money
|
|
236
281
|
if (fractional < 0 && val < 0) || (fractional > 0 && val > 0)
|
237
282
|
self.modulo(val)
|
238
283
|
else
|
239
|
-
self.modulo(val) - (val.is_a?(Money) ? val :
|
284
|
+
self.modulo(val) - (val.is_a?(Money) ? val : dup_with(fractional: val))
|
240
285
|
end
|
241
286
|
end
|
242
287
|
|
@@ -247,7 +292,7 @@ class Money
|
|
247
292
|
# @example
|
248
293
|
# Money.new(-100).abs #=> #<Money @fractional=100>
|
249
294
|
def abs
|
250
|
-
|
295
|
+
dup_with(fractional: fractional.abs)
|
251
296
|
end
|
252
297
|
|
253
298
|
# Test if the money amount is zero.
|
@@ -279,7 +324,7 @@ class Money
|
|
279
324
|
# @example
|
280
325
|
# 2 * Money.new(10) #=> #<Money @fractional=20>
|
281
326
|
def coerce(other)
|
282
|
-
[self, other]
|
327
|
+
[self, CoercedNumeric.new(other)]
|
283
328
|
end
|
284
329
|
end
|
285
330
|
end
|
@@ -10,8 +10,7 @@ class Money
|
|
10
10
|
# @example
|
11
11
|
# Money.empty #=> #<Money @fractional=0>
|
12
12
|
def empty(currency = default_currency)
|
13
|
-
|
14
|
-
@empty[currency] ||= Money.new(0, currency).freeze
|
13
|
+
new(0, currency)
|
15
14
|
end
|
16
15
|
alias_method :zero, :empty
|
17
16
|
|
@@ -28,7 +27,7 @@ class Money
|
|
28
27
|
# n.cents #=> 100
|
29
28
|
# n.currency #=> #<Money::Currency id: cad>
|
30
29
|
def ca_dollar(cents)
|
31
|
-
|
30
|
+
new(cents, "CAD")
|
32
31
|
end
|
33
32
|
alias_method :cad, :ca_dollar
|
34
33
|
|
@@ -45,7 +44,7 @@ class Money
|
|
45
44
|
# n.cents #=> 100
|
46
45
|
# n.currency #=> #<Money::Currency id: usd>
|
47
46
|
def us_dollar(cents)
|
48
|
-
|
47
|
+
new(cents, "USD")
|
49
48
|
end
|
50
49
|
alias_method :usd, :us_dollar
|
51
50
|
|
@@ -61,7 +60,7 @@ class Money
|
|
61
60
|
# n.cents #=> 100
|
62
61
|
# n.currency #=> #<Money::Currency id: eur>
|
63
62
|
def euro(cents)
|
64
|
-
|
63
|
+
new(cents, "EUR")
|
65
64
|
end
|
66
65
|
alias_method :eur, :euro
|
67
66
|
|
@@ -77,7 +76,7 @@ class Money
|
|
77
76
|
# n.fractional #=> 100
|
78
77
|
# n.currency #=> #<Money::Currency id: gbp>
|
79
78
|
def pound_sterling(pence)
|
80
|
-
|
79
|
+
new(pence, "GBP")
|
81
80
|
end
|
82
81
|
alias_method :gbp, :pound_sterling
|
83
82
|
|