shopify-money 2.0.0 → 2.2.1
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 +4 -4
- data/.gitignore +1 -2
- data/.ruby-version +1 -0
- data/Gemfile.lock +234 -0
- data/README.md +43 -9
- data/dev.yml +1 -1
- data/lib/money/allocator.rb +57 -25
- data/lib/money/helpers.rb +2 -0
- data/lib/money/money.rb +87 -53
- data/lib/money/splitter.rb +115 -0
- data/lib/money/version.rb +1 -1
- data/lib/money.rb +1 -0
- data/lib/rubocop/cop/money.rb +0 -1
- data/money.gemspec +1 -1
- data/spec/allocator_spec.rb +175 -25
- data/spec/deprecations_spec.rb +1 -1
- data/spec/helpers_spec.rb +2 -2
- data/spec/money_spec.rb +137 -7
- data/spec/splitter_spec.rb +104 -0
- metadata +12 -10
- data/lib/rubocop/cop/money/unsafe_to_money.rb +0 -35
- data/spec/rubocop/cop/money/unsafe_to_money_spec.rb +0 -63
data/lib/money/money.rb
CHANGED
@@ -10,6 +10,30 @@ class Money
|
|
10
10
|
attr_reader :value, :currency
|
11
11
|
def_delegators :@value, :zero?, :nonzero?, :positive?, :negative?, :to_i, :to_f, :hash
|
12
12
|
|
13
|
+
class ReverseOperationProxy
|
14
|
+
include Comparable
|
15
|
+
|
16
|
+
def initialize(value)
|
17
|
+
@value = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def <=>(other)
|
21
|
+
-(other <=> @value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def +(other)
|
25
|
+
other + @value
|
26
|
+
end
|
27
|
+
|
28
|
+
def -(other)
|
29
|
+
-(other - @value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def *(other)
|
33
|
+
other * @value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
13
37
|
class << self
|
14
38
|
extend Forwardable
|
15
39
|
attr_accessor :config
|
@@ -21,6 +45,8 @@ class Money
|
|
21
45
|
end
|
22
46
|
|
23
47
|
def new(value = 0, currency = nil)
|
48
|
+
return new_from_money(value, currency) if value.is_a?(Money)
|
49
|
+
|
24
50
|
value = Helpers.value_to_decimal(value)
|
25
51
|
currency = Helpers.value_to_currency(currency)
|
26
52
|
|
@@ -76,6 +102,28 @@ class Money
|
|
76
102
|
Money.current_currency = old_currency
|
77
103
|
end
|
78
104
|
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def new_from_money(amount, currency)
|
109
|
+
currency = Helpers.value_to_currency(currency)
|
110
|
+
|
111
|
+
if amount.no_currency?
|
112
|
+
return Money.new(amount.value, currency)
|
113
|
+
end
|
114
|
+
|
115
|
+
if amount.currency.compatible?(currency)
|
116
|
+
return amount
|
117
|
+
end
|
118
|
+
|
119
|
+
msg = "Money.new is attempting to change currency of an existing money object"
|
120
|
+
if Money.config.legacy_deprecations
|
121
|
+
Money.deprecate("#{msg}. A Money::IncompatibleCurrencyError will raise in the next major release")
|
122
|
+
return Money.new(amount.value, currency)
|
123
|
+
else
|
124
|
+
raise Money::IncompatibleCurrencyError, msg
|
125
|
+
end
|
126
|
+
end
|
79
127
|
end
|
80
128
|
configure
|
81
129
|
|
@@ -162,50 +210,23 @@ class Money
|
|
162
210
|
value == other.value
|
163
211
|
end
|
164
212
|
|
165
|
-
class ReverseOperationProxy
|
166
|
-
include Comparable
|
167
|
-
|
168
|
-
def initialize(value)
|
169
|
-
@value = value
|
170
|
-
end
|
171
|
-
|
172
|
-
def <=>(other)
|
173
|
-
-(other <=> @value)
|
174
|
-
end
|
175
|
-
|
176
|
-
def +(other)
|
177
|
-
other + @value
|
178
|
-
end
|
179
|
-
|
180
|
-
def -(other)
|
181
|
-
-(other - @value)
|
182
|
-
end
|
183
|
-
|
184
|
-
def *(other)
|
185
|
-
other * @value
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
213
|
def coerce(other)
|
190
214
|
raise TypeError, "Money can't be coerced into #{other.class}" unless other.is_a?(Numeric)
|
191
215
|
[ReverseOperationProxy.new(other), self]
|
192
216
|
end
|
193
217
|
|
194
|
-
def to_money(
|
195
|
-
if
|
196
|
-
return
|
218
|
+
def to_money(new_currency = nil)
|
219
|
+
if new_currency.nil?
|
220
|
+
return self
|
197
221
|
end
|
198
222
|
|
199
|
-
|
200
|
-
|
201
|
-
msg = "mathematical operation not permitted for Money objects with different currencies #{curr} and #{currency}"
|
202
|
-
if Money.config.legacy_deprecations
|
203
|
-
Money.deprecate("#{msg}. A Money::IncompatibleCurrencyError will raise in the next major release")
|
204
|
-
else
|
205
|
-
raise Money::IncompatibleCurrencyError, msg
|
206
|
-
end
|
223
|
+
if no_currency?
|
224
|
+
return Money.new(value, new_currency)
|
207
225
|
end
|
208
226
|
|
227
|
+
ensure_compatible_currency(Helpers.value_to_currency(new_currency),
|
228
|
+
"to_money is attempting to change currency of an existing money object from #{currency} to #{new_currency}")
|
229
|
+
|
209
230
|
self
|
210
231
|
end
|
211
232
|
|
@@ -293,12 +314,12 @@ class Money
|
|
293
314
|
#
|
294
315
|
# @param [2] number of parties.
|
295
316
|
#
|
296
|
-
# @return [
|
317
|
+
# @return [Enumerable<Money, Money, Money>]
|
297
318
|
#
|
298
319
|
# @example
|
299
|
-
# Money.new(100, "USD").split(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
|
320
|
+
# Money.new(100, "USD").split(3) #=> Enumerable[Money.new(34), Money.new(33), Money.new(33)]
|
300
321
|
def split(num)
|
301
|
-
|
322
|
+
Splitter.new(self, num)
|
302
323
|
end
|
303
324
|
|
304
325
|
# Calculate the splits evenly without losing pennies.
|
@@ -313,17 +334,7 @@ class Money
|
|
313
334
|
# @example
|
314
335
|
# Money.new(100, "USD").calculate_splits(3) #=> {Money.new(34) => 1, Money.new(33) => 2}
|
315
336
|
def calculate_splits(num)
|
316
|
-
|
317
|
-
subunits = self.subunits
|
318
|
-
low = Money.from_subunits(subunits / num, currency)
|
319
|
-
high = Money.from_subunits(low.subunits + 1, currency)
|
320
|
-
|
321
|
-
num_high = subunits % num
|
322
|
-
|
323
|
-
{}.tap do |result|
|
324
|
-
result[high] = num_high if num_high > 0
|
325
|
-
result[low] = num - num_high
|
326
|
-
end
|
337
|
+
Splitter.new(self, num).split.dup
|
327
338
|
end
|
328
339
|
|
329
340
|
# Clamps the value to be within the specified minimum and maximum. Returns
|
@@ -349,11 +360,34 @@ class Money
|
|
349
360
|
|
350
361
|
private
|
351
362
|
|
352
|
-
def arithmetic(
|
353
|
-
|
354
|
-
|
363
|
+
def arithmetic(other)
|
364
|
+
case other
|
365
|
+
when Money
|
366
|
+
ensure_compatible_currency(other.currency,
|
367
|
+
"mathematical operation not permitted for Money objects with different currencies #{other.currency} and #{currency}.")
|
368
|
+
yield(other)
|
355
369
|
|
356
|
-
|
370
|
+
when Numeric, String
|
371
|
+
yield(Money.new(other, currency))
|
372
|
+
|
373
|
+
else
|
374
|
+
if Money.config.legacy_deprecations && other.respond_to?(:to_money)
|
375
|
+
Money.deprecate("#{other.inspect} is being implicitly coerced into a Money object. Call `to_money` on this object to transform it into a money explicitly. An TypeError will raise in the next major release")
|
376
|
+
yield(other.to_money(currency))
|
377
|
+
else
|
378
|
+
raise TypeError, "#{other.class.name} can't be coerced into Money"
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
def ensure_compatible_currency(other_currency, msg)
|
384
|
+
return if currency.compatible?(other_currency)
|
385
|
+
|
386
|
+
if Money.config.legacy_deprecations
|
387
|
+
Money.deprecate("#{msg}. A Money::IncompatibleCurrencyError will raise in the next major release")
|
388
|
+
else
|
389
|
+
raise Money::IncompatibleCurrencyError, msg
|
390
|
+
end
|
357
391
|
end
|
358
392
|
|
359
393
|
def calculated_currency(other)
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Money
|
4
|
+
class Splitter
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(money, num)
|
8
|
+
@num = Integer(num)
|
9
|
+
raise ArgumentError, "need at least one party" if num < 1
|
10
|
+
@money = money
|
11
|
+
@split = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
protected attr_writer :split
|
15
|
+
|
16
|
+
def split
|
17
|
+
@split ||= begin
|
18
|
+
subunits = @money.subunits
|
19
|
+
low = Money.from_subunits(subunits / @num, @money.currency)
|
20
|
+
high = Money.from_subunits(low.subunits + 1, @money.currency)
|
21
|
+
|
22
|
+
num_high = subunits % @num
|
23
|
+
|
24
|
+
split = {}
|
25
|
+
split[high] = num_high if num_high > 0
|
26
|
+
split[low] = @num - num_high
|
27
|
+
split.freeze
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :to_ary, :to_a
|
32
|
+
|
33
|
+
def first(count = (count_undefined = true))
|
34
|
+
if count_undefined
|
35
|
+
each do |money|
|
36
|
+
return money
|
37
|
+
end
|
38
|
+
else
|
39
|
+
if count >= size
|
40
|
+
to_a
|
41
|
+
else
|
42
|
+
result = Array.new(count)
|
43
|
+
index = 0
|
44
|
+
each do |money|
|
45
|
+
result[index] = money
|
46
|
+
index += 1
|
47
|
+
break if index == count
|
48
|
+
end
|
49
|
+
result
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def last(count = (count_undefined = true))
|
55
|
+
if count_undefined
|
56
|
+
reverse_each do |money|
|
57
|
+
return money
|
58
|
+
end
|
59
|
+
else
|
60
|
+
if count >= size
|
61
|
+
to_a
|
62
|
+
else
|
63
|
+
result = Array.new(count)
|
64
|
+
index = 0
|
65
|
+
reverse_each do |money|
|
66
|
+
result[index] = money
|
67
|
+
index += 1
|
68
|
+
break if index == count
|
69
|
+
end
|
70
|
+
result.reverse!
|
71
|
+
result
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def [](index)
|
77
|
+
offset = 0
|
78
|
+
split.each do |money, count|
|
79
|
+
offset += count
|
80
|
+
if index < offset
|
81
|
+
return money
|
82
|
+
end
|
83
|
+
end
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def reverse_each(&block)
|
88
|
+
split.reverse_each do |money, count|
|
89
|
+
count.times do
|
90
|
+
yield money
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def each(&block)
|
96
|
+
split.each do |money, count|
|
97
|
+
count.times do
|
98
|
+
yield money
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def reverse
|
104
|
+
copy = dup
|
105
|
+
copy.split = split.reverse_each.to_h.freeze
|
106
|
+
copy
|
107
|
+
end
|
108
|
+
|
109
|
+
def size
|
110
|
+
count = 0
|
111
|
+
split.each_value { |c| count += c }
|
112
|
+
count
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/money/version.rb
CHANGED
data/lib/money.rb
CHANGED
@@ -5,6 +5,7 @@ require_relative 'money/helpers'
|
|
5
5
|
require_relative 'money/currency'
|
6
6
|
require_relative 'money/null_currency'
|
7
7
|
require_relative 'money/allocator'
|
8
|
+
require_relative 'money/splitter'
|
8
9
|
require_relative 'money/config'
|
9
10
|
require_relative 'money/money'
|
10
11
|
require_relative 'money/errors'
|
data/lib/rubocop/cop/money.rb
CHANGED
data/money.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.add_development_dependency("rails", "~> 6.0")
|
21
21
|
s.add_development_dependency("rspec", "~> 3.2")
|
22
22
|
s.add_development_dependency("database_cleaner", "~> 1.6")
|
23
|
-
s.add_development_dependency("sqlite3"
|
23
|
+
s.add_development_dependency("sqlite3")
|
24
24
|
|
25
25
|
s.required_ruby_version = '>= 3.0'
|
26
26
|
|
data/spec/allocator_spec.rb
CHANGED
@@ -8,34 +8,38 @@ RSpec.describe "Allocator" do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
specify "#allocate does not lose pennies" do
|
11
|
-
|
12
|
-
expect(
|
13
|
-
expect(
|
11
|
+
monies = new_allocator(0.05).allocate([0.3,0.7])
|
12
|
+
expect(monies[0]).to eq(Money.new(0.02))
|
13
|
+
expect(monies[1]).to eq(Money.new(0.03))
|
14
14
|
end
|
15
15
|
|
16
16
|
specify "#allocate does not lose dollars with non-decimal currency" do
|
17
|
-
|
18
|
-
expect(
|
19
|
-
expect(
|
17
|
+
monies = new_allocator(5, 'JPY').allocate([0.3,0.7])
|
18
|
+
expect(monies[0]).to eq(Money.new(2, 'JPY'))
|
19
|
+
expect(monies[1]).to eq(Money.new(3, 'JPY'))
|
20
20
|
end
|
21
21
|
|
22
22
|
specify "#allocate does not lose dollars with three decimal currency" do
|
23
|
-
|
24
|
-
expect(
|
25
|
-
expect(
|
23
|
+
monies = new_allocator(0.005, 'JOD').allocate([0.3,0.7])
|
24
|
+
expect(monies[0]).to eq(Money.new(0.002, 'JOD'))
|
25
|
+
expect(monies[1]).to eq(Money.new(0.003, 'JOD'))
|
26
26
|
end
|
27
27
|
|
28
28
|
specify "#allocate does not lose pennies even when given a lossy split" do
|
29
|
-
|
30
|
-
expect(
|
31
|
-
expect(
|
32
|
-
expect(
|
29
|
+
monies = new_allocator(1).allocate([0.333,0.333, 0.333])
|
30
|
+
expect(monies[0].subunits).to eq(34)
|
31
|
+
expect(monies[1].subunits).to eq(33)
|
32
|
+
expect(monies[2].subunits).to eq(33)
|
33
33
|
end
|
34
34
|
|
35
35
|
specify "#allocate requires total to be less than 1" do
|
36
36
|
expect { new_allocator(0.05).allocate([0.5,0.6]) }.to raise_error(ArgumentError)
|
37
37
|
end
|
38
38
|
|
39
|
+
specify "#allocate requires at least one split" do
|
40
|
+
expect { new_allocator(0.05).allocate([]) }.to raise_error(ArgumentError)
|
41
|
+
end
|
42
|
+
|
39
43
|
specify "#allocate will use rationals if provided" do
|
40
44
|
splits = [128400,20439,14589,14589,25936].map{ |num| Rational(num, 203953) } # sums to > 1 if converted to float
|
41
45
|
expect(new_allocator(2.25).allocate(splits)).to eq([Money.new(1.42), Money.new(0.23), Money.new(0.16), Money.new(0.16), Money.new(0.28)])
|
@@ -53,27 +57,173 @@ RSpec.describe "Allocator" do
|
|
53
57
|
expect { new_allocator(1).allocate([rate, 1 - rate]) }.not_to raise_error
|
54
58
|
end
|
55
59
|
|
60
|
+
specify "#allocate raise ArgumentError when invalid strategy is provided" do
|
61
|
+
expect { new_allocator(0.03).allocate([0.5, 0.5], :bad_strategy_name) }.to raise_error(ArgumentError, "Invalid strategy. Valid options: :roundrobin, :roundrobin_reverse, :nearest")
|
62
|
+
end
|
63
|
+
|
64
|
+
specify "#allocate raises an error when splits exceed 100%" do
|
65
|
+
expect { new_allocator(0.03).allocate([0.5, 0.6]) }.to raise_error(ArgumentError, "allocations add to more than 100%")
|
66
|
+
end
|
67
|
+
|
68
|
+
specify "#allocate scales up allocations less than 100%, preserving the relative magnitude of each chunk" do
|
69
|
+
# Allocations sum to 0.3
|
70
|
+
# This is analogous to new_allocator(12).allocate([1/3, 2/3])
|
71
|
+
monies = new_allocator(12).allocate([0.1, 0.2])
|
72
|
+
expect(monies[0]).to eq(Money.new(4))
|
73
|
+
expect(monies[1]).to eq(Money.new(8))
|
74
|
+
|
75
|
+
# Allocations sum to .661
|
76
|
+
monies = new_allocator(488.51).allocate([0.111, 0.05, 0.5])
|
77
|
+
expect(monies[0]).to eq(Money.new(82.04)) # <-- leftover penny
|
78
|
+
expect(monies[1]).to eq(Money.new(36.95))
|
79
|
+
expect(monies[2]).to eq(Money.new(369.52))
|
80
|
+
|
81
|
+
monies = new_allocator(488.51).allocate([0.111, 0.05, 0.5], :roundrobin_reverse)
|
82
|
+
expect(monies[0]).to eq(Money.new(82.03))
|
83
|
+
expect(monies[1]).to eq(Money.new(36.95))
|
84
|
+
expect(monies[2]).to eq(Money.new(369.53)) # <-- leftover penny
|
85
|
+
|
86
|
+
monies = new_allocator(488.51).allocate([0.05, 0.111, 0.5], :nearest)
|
87
|
+
expect(monies[0]).to eq(Money.new(36.95))
|
88
|
+
expect(monies[1]).to eq(Money.new(82.04)) # <-- leftover penny
|
89
|
+
expect(monies[2]).to eq(Money.new(369.52))
|
90
|
+
end
|
91
|
+
|
56
92
|
specify "#allocate fills pennies from beginning to end with roundrobin strategy" do
|
57
|
-
|
58
|
-
|
59
|
-
expect(
|
93
|
+
#round robin for 1 penny
|
94
|
+
monies = new_allocator(0.03).allocate([0.5, 0.5], :roundrobin)
|
95
|
+
expect(monies[0]).to eq(Money.new(0.02)) # <-- gets 1 penny
|
96
|
+
expect(monies[1]).to eq(Money.new(0.01)) # <-- gets no penny
|
97
|
+
|
98
|
+
#round robin for 2 pennies
|
99
|
+
monies = new_allocator(10.55).allocate([0.25, 0.5, 0.25], :roundrobin)
|
100
|
+
expect(monies[0]).to eq(Money.new(2.64)) # <-- gets 1 penny
|
101
|
+
expect(monies[1]).to eq(Money.new(5.28)) # <-- gets 1 penny
|
102
|
+
expect(monies[2]).to eq(Money.new(2.63)) # <-- gets no penny
|
103
|
+
|
104
|
+
#round robin for 3 pennies
|
105
|
+
monies = new_allocator(195.35).allocate([0.025, 0.025, 0.125, 0.125, 0.4, 0.3], :roundrobin)
|
106
|
+
expect(monies[0]).to eq(Money.new(4.89)) # <-- gets 1 penny
|
107
|
+
expect(monies[1]).to eq(Money.new(4.89)) # <-- gets 1 penny
|
108
|
+
expect(monies[2]).to eq(Money.new(24.42)) # <-- gets 1 penny
|
109
|
+
expect(monies[3]).to eq(Money.new(24.41)) # <-- gets no penny
|
110
|
+
expect(monies[4]).to eq(Money.new(78.14)) # <-- gets no penny
|
111
|
+
expect(monies[5]).to eq(Money.new(58.60)) # <-- gets no penny
|
112
|
+
|
113
|
+
#round robin for 0 pennies
|
114
|
+
monies = new_allocator(101).allocate([0.25, 0.25, 0.25, 0.25], :roundrobin)
|
115
|
+
expect(monies[0]).to eq(Money.new(25.25)) # <-- gets no penny
|
116
|
+
expect(monies[1]).to eq(Money.new(25.25)) # <-- gets no penny
|
117
|
+
expect(monies[2]).to eq(Money.new(25.25)) # <-- gets no penny
|
118
|
+
expect(monies[3]).to eq(Money.new(25.25)) # <-- gets no penny
|
60
119
|
end
|
61
120
|
|
62
121
|
specify "#allocate fills pennies from end to beginning with roundrobin_reverse strategy" do
|
63
|
-
|
64
|
-
|
65
|
-
expect(
|
122
|
+
#round robin reverse for 1 penny
|
123
|
+
monies = new_allocator(0.05).allocate([0.3,0.7], :roundrobin_reverse)
|
124
|
+
expect(monies[0]).to eq(Money.new(0.01)) # <-- gets no penny
|
125
|
+
expect(monies[1]).to eq(Money.new(0.04)) # <-- gets 1 penny
|
126
|
+
|
127
|
+
#round robin reverse for 2 pennies
|
128
|
+
monies = new_allocator(10.55).allocate([0.25, 0.5, 0.25], :roundrobin_reverse)
|
129
|
+
expect(monies[0]).to eq(Money.new(2.63)) # <-- gets no penny
|
130
|
+
expect(monies[1]).to eq(Money.new(5.28)) # <-- gets 1 penny
|
131
|
+
expect(monies[2]).to eq(Money.new(2.64)) # <-- gets 1 penny
|
132
|
+
|
133
|
+
#round robin reverse for 3 pennies
|
134
|
+
monies = new_allocator(195.35).allocate([0.025, 0.025, 0.125, 0.125, 0.4, 0.3], :roundrobin_reverse)
|
135
|
+
expect(monies[0]).to eq(Money.new(4.88)) # <-- gets no penny
|
136
|
+
expect(monies[1]).to eq(Money.new(4.88)) # <-- gets no penny
|
137
|
+
expect(monies[2]).to eq(Money.new(24.41)) # <-- gets no penny
|
138
|
+
expect(monies[3]).to eq(Money.new(24.42)) # <-- gets 1 penny
|
139
|
+
expect(monies[4]).to eq(Money.new(78.15)) # <-- gets 1 penny
|
140
|
+
expect(monies[5]).to eq(Money.new(58.61)) # <-- gets 1 penny
|
141
|
+
|
142
|
+
#round robin reverse for 0 pennies
|
143
|
+
monies = new_allocator(101).allocate([0.25, 0.25, 0.25, 0.25], :roundrobin_reverse)
|
144
|
+
expect(monies[0]).to eq(Money.new(25.25)) # <-- gets no penny
|
145
|
+
expect(monies[1]).to eq(Money.new(25.25)) # <-- gets no penny
|
146
|
+
expect(monies[2]).to eq(Money.new(25.25)) # <-- gets no penny
|
147
|
+
expect(monies[3]).to eq(Money.new(25.25)) # <-- gets no penny
|
66
148
|
end
|
67
149
|
|
68
|
-
specify "#allocate
|
69
|
-
|
150
|
+
specify "#allocate :nearest strategy distributes pennies first to the number which is nearest to the next whole cent" do
|
151
|
+
#nearest for 1 penny
|
152
|
+
monies = new_allocator(0.03).allocate([0.5, 0.5], :nearest)
|
153
|
+
expect(monies[0]).to eq(Money.new(0.02)) # <-- gets 1 penny
|
154
|
+
expect(monies[1]).to eq(Money.new(0.01)) # <-- gets no penny
|
155
|
+
|
156
|
+
#Nearest for 2 pennies
|
157
|
+
monies = new_allocator(10.55).allocate([0.25, 0.5, 0.25], :nearest)
|
158
|
+
expect(monies[0]).to eq(Money.new(2.64)) # <-- gets 1 penny
|
159
|
+
expect(monies[1]).to eq(Money.new(5.27)) # <-- gets no penny
|
160
|
+
expect(monies[2]).to eq(Money.new(2.64)) # <-- gets 1 penny
|
161
|
+
|
162
|
+
#Nearest for 3 pennies
|
163
|
+
monies = new_allocator(195.35).allocate([0.025, 0.025, 0.125, 0.125, 0.4, 0.3], :nearest)
|
164
|
+
expect(monies[0]).to eq(Money.new(4.88)) # <-- gets no penny
|
165
|
+
expect(monies[1]).to eq(Money.new(4.88)) # <-- gets no penny
|
166
|
+
expect(monies[2]).to eq(Money.new(24.42)) # <-- gets 1 penny
|
167
|
+
expect(monies[3]).to eq(Money.new(24.42)) # <-- gets 1 penny
|
168
|
+
expect(monies[4]).to eq(Money.new(78.14)) # <-- gets no penny
|
169
|
+
expect(monies[5]).to eq(Money.new(58.61)) # <-- gets 1 penny
|
170
|
+
|
171
|
+
#Nearest for 0 pennies
|
172
|
+
monies = new_allocator(101).allocate([0.25, 0.25, 0.25, 0.25], :nearest)
|
173
|
+
expect(monies[0]).to eq(Money.new(25.25)) # <-- gets no penny
|
174
|
+
expect(monies[1]).to eq(Money.new(25.25)) # <-- gets no penny
|
175
|
+
expect(monies[2]).to eq(Money.new(25.25)) # <-- gets no penny
|
176
|
+
expect(monies[3]).to eq(Money.new(25.25)) # <-- gets no penny
|
70
177
|
end
|
71
178
|
|
72
|
-
specify "#allocate
|
73
|
-
|
74
|
-
|
75
|
-
expect(
|
179
|
+
specify "#allocate :roundrobin strategy distributes leftovers from left to right when the currency does not use two decimal places (e.g. `JPY`)" do
|
180
|
+
#Roundrobin for 1 yen
|
181
|
+
monies = new_allocator(31, 'JPY').allocate([0.5,0.5], :roundrobin)
|
182
|
+
expect(monies[0]).to eq(Money.new(16, 'JPY'))
|
183
|
+
expect(monies[1]).to eq(Money.new(15, 'JPY'))
|
184
|
+
|
185
|
+
#Roundrobin for 3 yen
|
186
|
+
monies = new_allocator(19535, "JPY").allocate([0.025, 0.025, 0.125, 0.125, 0.4, 0.3], :roundrobin)
|
187
|
+
expect(monies[0]).to eq(Money.new(489, "JPY")) # <-- gets 1 yen
|
188
|
+
expect(monies[1]).to eq(Money.new(489, "JPY")) # <-- gets 1 yen
|
189
|
+
expect(monies[2]).to eq(Money.new(2442, "JPY")) # <-- gets 1 yen
|
190
|
+
expect(monies[3]).to eq(Money.new(2441, "JPY")) # <-- gets no yen
|
191
|
+
expect(monies[4]).to eq(Money.new(7814, "JPY")) # <-- gets no yen
|
192
|
+
expect(monies[5]).to eq(Money.new(5860, "JPY")) # <-- gets no yen
|
76
193
|
end
|
194
|
+
|
195
|
+
specify "#allocate :roundrobin_reverse strategy distributes leftovers from right to left when the currency does not use two decimal places (e.g. `JPY`)" do
|
196
|
+
#Roundrobin for 1 yen
|
197
|
+
monies = new_allocator(31, 'JPY').allocate([0.5,0.5], :roundrobin_reverse)
|
198
|
+
expect(monies[0]).to eq(Money.new(15, 'JPY'))
|
199
|
+
expect(monies[1]).to eq(Money.new(16, 'JPY'))
|
200
|
+
|
201
|
+
#Roundrobin for 3 yen
|
202
|
+
monies = new_allocator(19535, "JPY").allocate([0.025, 0.025, 0.125, 0.125, 0.4, 0.3], :roundrobin_reverse)
|
203
|
+
expect(monies[0]).to eq(Money.new(488, "JPY")) # <-- gets no yen
|
204
|
+
expect(monies[1]).to eq(Money.new(488, "JPY")) # <-- gets no yen
|
205
|
+
expect(monies[2]).to eq(Money.new(2441, "JPY")) # <-- gets no yen
|
206
|
+
expect(monies[3]).to eq(Money.new(2442, "JPY")) # <-- gets 1 yen
|
207
|
+
expect(monies[4]).to eq(Money.new(7815, "JPY")) # <-- gets 1 yen
|
208
|
+
expect(monies[5]).to eq(Money.new(5861, "JPY")) # <-- gets 1 yen
|
209
|
+
end
|
210
|
+
|
211
|
+
specify "#allocate :nearest strategy distributes leftovers to the nearest whole subunity when the currency does not use two decimal places (e.g. `JPY`)" do
|
212
|
+
#Nearest for 1 yen
|
213
|
+
monies = new_allocator(31, 'JPY').allocate([0.5,0.5], :nearest)
|
214
|
+
expect(monies[0]).to eq(Money.new(16, 'JPY'))
|
215
|
+
expect(monies[1]).to eq(Money.new(15, 'JPY'))
|
216
|
+
|
217
|
+
#Nearest for 3 yen
|
218
|
+
monies = new_allocator(19535, "JPY").allocate([0.025, 0.025, 0.125, 0.125, 0.4, 0.3], :nearest)
|
219
|
+
expect(monies[0]).to eq(Money.new(488, "JPY")) # <-- gets no yen
|
220
|
+
expect(monies[1]).to eq(Money.new(488, "JPY")) # <-- gets no yen
|
221
|
+
expect(monies[2]).to eq(Money.new(2442, "JPY")) # <-- gets 1 yen
|
222
|
+
expect(monies[3]).to eq(Money.new(2442, "JPY")) # <-- gets 1 yen
|
223
|
+
expect(monies[4]).to eq(Money.new(7814, "JPY")) # <-- gets no yen
|
224
|
+
expect(monies[5]).to eq(Money.new(5861, "JPY")) # <-- gets 1 yen
|
225
|
+
end
|
226
|
+
|
77
227
|
end
|
78
228
|
|
79
229
|
describe 'allocate_max_amounts' do
|
data/spec/deprecations_spec.rb
CHANGED
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
|
4
4
|
RSpec.describe "deprecations" do
|
5
5
|
it "has the deprecation_horizon as the next major release" do
|
6
|
-
allow(Money).to receive(:const_get).with('VERSION').and_return("2.
|
6
|
+
allow(Money).to receive(:const_get).with('VERSION').and_return("2.1.0")
|
7
7
|
expect(Money.active_support_deprecator.deprecation_horizon).to eq("3.0.0")
|
8
8
|
end
|
9
9
|
end
|
data/spec/helpers_spec.rb
CHANGED
@@ -7,8 +7,8 @@ RSpec.describe Money::Helpers do
|
|
7
7
|
let (:amount) { BigDecimal('1.23') }
|
8
8
|
let (:money) { Money.new(amount) }
|
9
9
|
|
10
|
-
it '
|
11
|
-
expect
|
10
|
+
it 'returns the value of a money object' do
|
11
|
+
expect(subject.value_to_decimal(money)).to eq(amount)
|
12
12
|
end
|
13
13
|
|
14
14
|
it 'returns itself if it is already a big decimal' do
|