shopify-money 2.0.0 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75140fb176288187177a79f673b9741b69a4ae96e02b314139318d7ff817b8b9
|
4
|
+
data.tar.gz: d98030d40029dbb686b83b090ab11e43628a194bd2902ba130f5161e860dc839
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e140de24fea5b7c90b5044f4b9732e013187a6cad34c7ea6bd9d8119cd118907c7faeb934ed533085389e3e55f7ba0649697e7563ec87380c6fc62ff9a1c935
|
7
|
+
data.tar.gz: 5a1d77f26c7886fc1a96edee8a920df4262385fce001209d640c9427e986e9d95d2d9a8232fca17a670758f45a7bc219d728386c080606d1f8bc348cb71ba0a3
|
data/.gitignore
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.3.0
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
shopify-money (2.2.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
actioncable (6.1.7.7)
|
10
|
+
actionpack (= 6.1.7.7)
|
11
|
+
activesupport (= 6.1.7.7)
|
12
|
+
nio4r (~> 2.0)
|
13
|
+
websocket-driver (>= 0.6.1)
|
14
|
+
actionmailbox (6.1.7.7)
|
15
|
+
actionpack (= 6.1.7.7)
|
16
|
+
activejob (= 6.1.7.7)
|
17
|
+
activerecord (= 6.1.7.7)
|
18
|
+
activestorage (= 6.1.7.7)
|
19
|
+
activesupport (= 6.1.7.7)
|
20
|
+
mail (>= 2.7.1)
|
21
|
+
actionmailer (6.1.7.7)
|
22
|
+
actionpack (= 6.1.7.7)
|
23
|
+
actionview (= 6.1.7.7)
|
24
|
+
activejob (= 6.1.7.7)
|
25
|
+
activesupport (= 6.1.7.7)
|
26
|
+
mail (~> 2.5, >= 2.5.4)
|
27
|
+
rails-dom-testing (~> 2.0)
|
28
|
+
actionpack (6.1.7.7)
|
29
|
+
actionview (= 6.1.7.7)
|
30
|
+
activesupport (= 6.1.7.7)
|
31
|
+
rack (~> 2.0, >= 2.0.9)
|
32
|
+
rack-test (>= 0.6.3)
|
33
|
+
rails-dom-testing (~> 2.0)
|
34
|
+
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
35
|
+
actiontext (6.1.7.7)
|
36
|
+
actionpack (= 6.1.7.7)
|
37
|
+
activerecord (= 6.1.7.7)
|
38
|
+
activestorage (= 6.1.7.7)
|
39
|
+
activesupport (= 6.1.7.7)
|
40
|
+
nokogiri (>= 1.8.5)
|
41
|
+
actionview (6.1.7.7)
|
42
|
+
activesupport (= 6.1.7.7)
|
43
|
+
builder (~> 3.1)
|
44
|
+
erubi (~> 1.4)
|
45
|
+
rails-dom-testing (~> 2.0)
|
46
|
+
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
47
|
+
activejob (6.1.7.7)
|
48
|
+
activesupport (= 6.1.7.7)
|
49
|
+
globalid (>= 0.3.6)
|
50
|
+
activemodel (6.1.7.7)
|
51
|
+
activesupport (= 6.1.7.7)
|
52
|
+
activerecord (6.1.7.7)
|
53
|
+
activemodel (= 6.1.7.7)
|
54
|
+
activesupport (= 6.1.7.7)
|
55
|
+
activestorage (6.1.7.7)
|
56
|
+
actionpack (= 6.1.7.7)
|
57
|
+
activejob (= 6.1.7.7)
|
58
|
+
activerecord (= 6.1.7.7)
|
59
|
+
activesupport (= 6.1.7.7)
|
60
|
+
marcel (~> 1.0)
|
61
|
+
mini_mime (>= 1.1.0)
|
62
|
+
activesupport (6.1.7.7)
|
63
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
64
|
+
i18n (>= 1.6, < 2)
|
65
|
+
minitest (>= 5.1)
|
66
|
+
tzinfo (~> 2.0)
|
67
|
+
zeitwerk (~> 2.3)
|
68
|
+
ast (2.4.2)
|
69
|
+
builder (3.2.4)
|
70
|
+
byebug (11.1.3)
|
71
|
+
coderay (1.1.3)
|
72
|
+
concurrent-ruby (1.2.3)
|
73
|
+
crass (1.0.6)
|
74
|
+
database_cleaner (1.99.0)
|
75
|
+
date (3.3.4)
|
76
|
+
diff-lcs (1.5.1)
|
77
|
+
docile (1.4.0)
|
78
|
+
erubi (1.12.0)
|
79
|
+
globalid (1.2.1)
|
80
|
+
activesupport (>= 6.1)
|
81
|
+
i18n (1.14.4)
|
82
|
+
concurrent-ruby (~> 1.0)
|
83
|
+
jaro_winkler (1.5.6)
|
84
|
+
loofah (2.22.0)
|
85
|
+
crass (~> 1.0.2)
|
86
|
+
nokogiri (>= 1.12.0)
|
87
|
+
mail (2.8.1)
|
88
|
+
mini_mime (>= 0.1.1)
|
89
|
+
net-imap
|
90
|
+
net-pop
|
91
|
+
net-smtp
|
92
|
+
marcel (1.0.4)
|
93
|
+
method_source (1.0.0)
|
94
|
+
mini_mime (1.1.5)
|
95
|
+
minitest (5.22.3)
|
96
|
+
net-imap (0.4.10)
|
97
|
+
date
|
98
|
+
net-protocol
|
99
|
+
net-pop (0.1.2)
|
100
|
+
net-protocol
|
101
|
+
net-protocol (0.2.2)
|
102
|
+
timeout
|
103
|
+
net-smtp (0.5.0)
|
104
|
+
net-protocol
|
105
|
+
nio4r (2.7.1)
|
106
|
+
nokogiri (1.16.3-aarch64-linux)
|
107
|
+
racc (~> 1.4)
|
108
|
+
nokogiri (1.16.3-arm-linux)
|
109
|
+
racc (~> 1.4)
|
110
|
+
nokogiri (1.16.3-arm64-darwin)
|
111
|
+
racc (~> 1.4)
|
112
|
+
nokogiri (1.16.3-x86-linux)
|
113
|
+
racc (~> 1.4)
|
114
|
+
nokogiri (1.16.3-x86_64-darwin)
|
115
|
+
racc (~> 1.4)
|
116
|
+
nokogiri (1.16.3-x86_64-linux)
|
117
|
+
racc (~> 1.4)
|
118
|
+
parallel (1.24.0)
|
119
|
+
parser (3.3.0.5)
|
120
|
+
ast (~> 2.4.1)
|
121
|
+
racc
|
122
|
+
pry (0.14.2)
|
123
|
+
coderay (~> 1.1)
|
124
|
+
method_source (~> 1.0)
|
125
|
+
pry-byebug (3.10.1)
|
126
|
+
byebug (~> 11.0)
|
127
|
+
pry (>= 0.13, < 0.15)
|
128
|
+
racc (1.7.3)
|
129
|
+
rack (2.2.9)
|
130
|
+
rack-test (2.1.0)
|
131
|
+
rack (>= 1.3)
|
132
|
+
rails (6.1.7.7)
|
133
|
+
actioncable (= 6.1.7.7)
|
134
|
+
actionmailbox (= 6.1.7.7)
|
135
|
+
actionmailer (= 6.1.7.7)
|
136
|
+
actionpack (= 6.1.7.7)
|
137
|
+
actiontext (= 6.1.7.7)
|
138
|
+
actionview (= 6.1.7.7)
|
139
|
+
activejob (= 6.1.7.7)
|
140
|
+
activemodel (= 6.1.7.7)
|
141
|
+
activerecord (= 6.1.7.7)
|
142
|
+
activestorage (= 6.1.7.7)
|
143
|
+
activesupport (= 6.1.7.7)
|
144
|
+
bundler (>= 1.15.0)
|
145
|
+
railties (= 6.1.7.7)
|
146
|
+
sprockets-rails (>= 2.0.0)
|
147
|
+
rails-dom-testing (2.2.0)
|
148
|
+
activesupport (>= 5.0.0)
|
149
|
+
minitest
|
150
|
+
nokogiri (>= 1.6)
|
151
|
+
rails-html-sanitizer (1.6.0)
|
152
|
+
loofah (~> 2.21)
|
153
|
+
nokogiri (~> 1.14)
|
154
|
+
railties (6.1.7.7)
|
155
|
+
actionpack (= 6.1.7.7)
|
156
|
+
activesupport (= 6.1.7.7)
|
157
|
+
method_source
|
158
|
+
rake (>= 12.2)
|
159
|
+
thor (~> 1.0)
|
160
|
+
rainbow (3.1.1)
|
161
|
+
rake (13.2.0)
|
162
|
+
rexml (3.2.6)
|
163
|
+
rspec (3.13.0)
|
164
|
+
rspec-core (~> 3.13.0)
|
165
|
+
rspec-expectations (~> 3.13.0)
|
166
|
+
rspec-mocks (~> 3.13.0)
|
167
|
+
rspec-core (3.13.0)
|
168
|
+
rspec-support (~> 3.13.0)
|
169
|
+
rspec-expectations (3.13.0)
|
170
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
171
|
+
rspec-support (~> 3.13.0)
|
172
|
+
rspec-mocks (3.13.0)
|
173
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
174
|
+
rspec-support (~> 3.13.0)
|
175
|
+
rspec-support (3.13.1)
|
176
|
+
rubocop (0.81.0)
|
177
|
+
jaro_winkler (~> 1.5.1)
|
178
|
+
parallel (~> 1.10)
|
179
|
+
parser (>= 2.7.0.1)
|
180
|
+
rainbow (>= 2.2.2, < 4.0)
|
181
|
+
rexml
|
182
|
+
ruby-progressbar (~> 1.7)
|
183
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
184
|
+
ruby-progressbar (1.13.0)
|
185
|
+
simplecov (0.22.0)
|
186
|
+
docile (~> 1.1)
|
187
|
+
simplecov-html (~> 0.11)
|
188
|
+
simplecov_json_formatter (~> 0.1)
|
189
|
+
simplecov-html (0.12.3)
|
190
|
+
simplecov_json_formatter (0.1.4)
|
191
|
+
sprockets (4.2.1)
|
192
|
+
concurrent-ruby (~> 1.0)
|
193
|
+
rack (>= 2.2.4, < 4)
|
194
|
+
sprockets-rails (3.4.2)
|
195
|
+
actionpack (>= 5.2)
|
196
|
+
activesupport (>= 5.2)
|
197
|
+
sprockets (>= 3.0.0)
|
198
|
+
sqlite3 (1.7.3-aarch64-linux)
|
199
|
+
sqlite3 (1.7.3-arm-linux)
|
200
|
+
sqlite3 (1.7.3-arm64-darwin)
|
201
|
+
sqlite3 (1.7.3-x86-linux)
|
202
|
+
sqlite3 (1.7.3-x86_64-darwin)
|
203
|
+
sqlite3 (1.7.3-x86_64-linux)
|
204
|
+
thor (1.3.1)
|
205
|
+
timeout (0.4.1)
|
206
|
+
tzinfo (2.0.6)
|
207
|
+
concurrent-ruby (~> 1.0)
|
208
|
+
unicode-display_width (1.8.0)
|
209
|
+
websocket-driver (0.7.6)
|
210
|
+
websocket-extensions (>= 0.1.0)
|
211
|
+
websocket-extensions (0.1.5)
|
212
|
+
zeitwerk (2.6.13)
|
213
|
+
|
214
|
+
PLATFORMS
|
215
|
+
aarch64-linux
|
216
|
+
arm-linux
|
217
|
+
arm64-darwin
|
218
|
+
x86-linux
|
219
|
+
x86_64-darwin
|
220
|
+
x86_64-linux
|
221
|
+
|
222
|
+
DEPENDENCIES
|
223
|
+
bundler
|
224
|
+
database_cleaner (~> 1.6)
|
225
|
+
pry-byebug
|
226
|
+
rails (~> 6.0)
|
227
|
+
rspec (~> 3.2)
|
228
|
+
rubocop (~> 0.81.0)
|
229
|
+
shopify-money!
|
230
|
+
simplecov
|
231
|
+
sqlite3
|
232
|
+
|
233
|
+
BUNDLED WITH
|
234
|
+
2.5.3
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
[![tests](https://github.com/Shopify/money/workflows/tests/badge.svg)](https://github.com/Shopify/money/actions?query=workflow%3Atests+branch%3Amain)
|
4
4
|
|
5
5
|
|
6
|
-
money_column expects a DECIMAL(21,3) database field.
|
6
|
+
`money_column` expects a `DECIMAL(21,3)` database field.
|
7
7
|
|
8
8
|
### Features
|
9
9
|
|
@@ -11,7 +11,8 @@ money_column expects a DECIMAL(21,3) database field.
|
|
11
11
|
- Provides a `Money::Currency` class which encapsulates all information about a monetary unit.
|
12
12
|
- Represents monetary values as decimals. No need to convert your amounts every time you use them. Easily understand the data in your DB.
|
13
13
|
- Does NOT provide APIs for exchanging money from one currency to another.
|
14
|
-
- Will not lose pennies during divisions
|
14
|
+
- Will not lose pennies during divisions. For instance, given $1 / 3 the resulting chunks will be .34, .33, and .33. Notice that one chunk is larger than the others, so the result still adds to $1.
|
15
|
+
- Allows callers to select a rounding strategy when dividing, to determine the order in which leftover pennies are given out.
|
15
16
|
|
16
17
|
## Installation
|
17
18
|
|
@@ -57,6 +58,30 @@ m.allocate([Rational(2, 3), Rational(1, 3)]).map(&:value) == [666.67, 333.33]
|
|
57
58
|
m.allocate_max_amounts([500, 300, 200]).map(&:value) == [500, 300, 200]
|
58
59
|
m.allocate_max_amounts([500, 300, 300]).map(&:value) == [454.55, 272.73, 272.72]
|
59
60
|
|
61
|
+
## Selectable rounding strategies during division
|
62
|
+
|
63
|
+
# Assigns leftover subunits left to right
|
64
|
+
m = Money::Allocator.new(Money.new(10.55, "USD"))
|
65
|
+
monies = m.allocate([0.25, 0.5, 0.25], :roundrobin)
|
66
|
+
#monies[0] == 2.64 <-- gets 1 penny
|
67
|
+
#monies[1] == 5.28 <-- gets 1 penny
|
68
|
+
#monies[2] == 2.63 <-- gets no penny
|
69
|
+
|
70
|
+
# Assigns leftover subunits right to left
|
71
|
+
m = Money::Allocator.new(Money.new(10.55, "USD"))
|
72
|
+
monies = m.allocate([0.25, 0.5, 0.25], :roundrobin_reverse)
|
73
|
+
#monies[0] == 2.63 <-- gets no penny
|
74
|
+
#monies[1] == 5.28 <-- gets 1 penny
|
75
|
+
#monies[2] == 2.64 <-- gets 1 penny
|
76
|
+
|
77
|
+
# Assigns leftover subunits to the nearest whole subunit
|
78
|
+
m = Money::Allocator.new(Money.new(10.55, "USD"))
|
79
|
+
monies = m.allocate([0.25, 0.5, 0.25], :nearest)
|
80
|
+
#monies[0] == 2.64 <-- gets 1 penny
|
81
|
+
#monies[1] == 5.27 <-- gets no penny
|
82
|
+
#monies[2] == 2.64 <-- gets 1 penny
|
83
|
+
# $2.6375 is closer to the next whole penny than $5.275
|
84
|
+
|
60
85
|
# Clamp
|
61
86
|
Money.new(50, "USD").clamp(1, 100) == Money.new(50, "USD")
|
62
87
|
|
@@ -170,17 +195,11 @@ require:
|
|
170
195
|
|
171
196
|
Money/MissingCurrency:
|
172
197
|
Enabled: true
|
173
|
-
#
|
174
|
-
# it can autocorrect this by specifying a default currency.
|
175
|
-
ReplacementCurrency: CAD
|
198
|
+
# ReplacementCurrency: CAD
|
176
199
|
|
177
200
|
Money/ZeroMoney:
|
178
201
|
Enabled: true
|
179
|
-
# Same here:
|
180
202
|
# ReplacementCurrency: CAD
|
181
|
-
|
182
|
-
Money/UnsafeToMoney:
|
183
|
-
Enabled: true
|
184
203
|
```
|
185
204
|
|
186
205
|
## Contributing to money
|
@@ -193,6 +212,21 @@ Money/UnsafeToMoney:
|
|
193
212
|
- Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
194
213
|
- Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
195
214
|
|
215
|
+
### Releasing
|
216
|
+
|
217
|
+
To release a new version of the gem, follow these steps:
|
218
|
+
|
219
|
+
- Audit what has changed since the last version of the gem was released.
|
220
|
+
- Determine what the next version number should be, according to [Semantic Versioning](https://semver.org/).
|
221
|
+
- Open a PR to update the version accordingly in `lib/money/version`.
|
222
|
+
- After getting approval, merge the PR.
|
223
|
+
- [**Publish** a release in Github](https://github.com/Shopify/money/releases/new):
|
224
|
+
- Target the `main` branch with a tag matching the new version, prefixed with `v` (e.g. `v1.2.3`).
|
225
|
+
- Use the "Generate Release Notes" button to help generate the copy for the release. Include **consumer facing changes** in the release notes.
|
226
|
+
- Deploy the new version to Rubygems using [ShipIt](https://shipit.shopify.io/shopify/money/production).
|
227
|
+
- For more information see [the publish a gem vault page](https://vault.shopify.io/page/Publish-a-new-version-of-an-internal-gem~dhbc57d.md)
|
228
|
+
- You are now responsible to [merge the bump PR in core](https://github.com/Shopify/shopify/pulls?q=is%3Aopen+is%3Apr+author%3Aapp%2Fdependabot+shopify-money+)
|
229
|
+
|
196
230
|
## Copyright
|
197
231
|
|
198
232
|
Copyright (c) 2011 Shopify. See LICENSE.txt for
|
data/dev.yml
CHANGED
data/lib/money/allocator.rb
CHANGED
@@ -9,18 +9,27 @@ class Money
|
|
9
9
|
|
10
10
|
ONE = BigDecimal("1")
|
11
11
|
|
12
|
-
# Allocates money between different parties without losing
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
12
|
+
# Allocates money between different parties without losing subunits. A "subunit"
|
13
|
+
# in this context is the smallest unit of a currency that can be divided no
|
14
|
+
# further. In USD the unit is dollars and the subunit is cents. In JPY the unit
|
15
|
+
# is yen and the subunit is also yen. So given $1 divided by 3, the resulting subunits
|
16
|
+
# should be [34¢, 33¢, 33¢]. Notice that one of these chunks is larger than the other
|
17
|
+
# two, because we cannot transact in amounts less than 1 subunit.
|
18
|
+
#
|
19
|
+
# After the mathematically split has been performed, left over subunits will
|
20
|
+
# be distributed round-robin or nearest-subunit strategy amongst the parties.
|
21
|
+
# Round-robin strategy has the virtue of being easier to understand, while
|
22
|
+
# nearest-subunit is a more complex alogirthm that results in the most fair
|
23
|
+
# distribution.
|
16
24
|
#
|
17
25
|
# @param splits [Array<Numeric>]
|
18
26
|
# @param strategy Symbol
|
19
27
|
# @return [Array<Money>]
|
20
28
|
#
|
21
29
|
# Strategies:
|
22
|
-
# - `:roundrobin` (default): leftover
|
23
|
-
# - `:roundrobin_reverse`: leftover
|
30
|
+
# - `:roundrobin` (default): leftover subunits will be accumulated starting from the first allocation left to right
|
31
|
+
# - `:roundrobin_reverse`: leftover subunits will be accumulated starting from the last allocation right to left
|
32
|
+
# - `:nearest`: leftover subunits will by given first to the party closest to the next whole subunit
|
24
33
|
#
|
25
34
|
# @example
|
26
35
|
# Money.new(5, "USD").allocate([0.50, 0.25, 0.25])
|
@@ -38,29 +47,49 @@ class Money
|
|
38
47
|
# Money.new(30, "USD").allocate([Rational(2, 3), Rational(1, 3)])
|
39
48
|
# #=> [#<Money value:20.00 currency:USD>, #<Money value:10.00 currency:USD>]
|
40
49
|
|
41
|
-
# @example left over
|
50
|
+
# @example left over subunits distributed reverse order when using roundrobin_reverse strategy
|
42
51
|
# Money.new(10.01, "USD").allocate([0.5, 0.5], :roundrobin_reverse)
|
43
52
|
# #=> [#<Money value:5.00 currency:USD>, #<Money value:5.01 currency:USD>]
|
53
|
+
|
54
|
+
# @examples left over subunits distributed by nearest strategy
|
55
|
+
# Money.new(10.55, "USD").allocate([0.25, 0.5, 0.25], :nearest)
|
56
|
+
# #=> [#<Money value:2.64 currency:USD>, #<Money value:5.27 currency:USD>, #<Money value:2.64 currency:USD>]
|
57
|
+
|
44
58
|
def allocate(splits, strategy = :roundrobin)
|
59
|
+
if splits.empty?
|
60
|
+
raise ArgumentError, 'at least one split must be provided'
|
61
|
+
end
|
62
|
+
|
45
63
|
splits.map!(&:to_r)
|
46
64
|
allocations = splits.inject(0, :+)
|
47
65
|
|
48
66
|
if (allocations - ONE) > Float::EPSILON
|
49
|
-
raise ArgumentError, "
|
67
|
+
raise ArgumentError, "allocations add to more than 100%"
|
50
68
|
end
|
51
69
|
|
52
70
|
amounts, left_over = amounts_from_splits(allocations, splits)
|
53
71
|
|
72
|
+
order = case strategy
|
73
|
+
when :roundrobin
|
74
|
+
(0...left_over).to_a
|
75
|
+
when :roundrobin_reverse
|
76
|
+
(0...amounts.length).to_a.reverse
|
77
|
+
when :nearest
|
78
|
+
rank_by_nearest(amounts)
|
79
|
+
else
|
80
|
+
raise ArgumentError, "Invalid strategy. Valid options: :roundrobin, :roundrobin_reverse, :nearest"
|
81
|
+
end
|
82
|
+
|
54
83
|
left_over.to_i.times do |i|
|
55
|
-
amounts[
|
84
|
+
amounts[order[i]][:whole_subunits] += 1
|
56
85
|
end
|
57
86
|
|
58
|
-
amounts.
|
87
|
+
amounts.map { |amount| Money.from_subunits(amount[:whole_subunits], currency) }
|
59
88
|
end
|
60
89
|
|
61
90
|
# Allocates money between different parties up to the maximum amounts specified.
|
62
|
-
# Left over
|
63
|
-
#
|
91
|
+
# Left over subunits will be assigned round-robin up to the maximum specified.
|
92
|
+
# Subunits are dropped when the maximums are attained.
|
64
93
|
#
|
65
94
|
# @example
|
66
95
|
# Money.new(30.75).allocate_max_amounts([Money.new(26), Money.new(4.75)])
|
@@ -90,6 +119,7 @@ class Money
|
|
90
119
|
total_allocatable = [maximums_total.subunits, self.subunits].min
|
91
120
|
|
92
121
|
subunits_amounts, left_over = amounts_from_splits(1, splits, total_allocatable)
|
122
|
+
subunits_amounts.map! { |amount| amount[:whole_subunits] }
|
93
123
|
|
94
124
|
subunits_amounts.each_with_index do |amount, index|
|
95
125
|
break unless left_over > 0
|
@@ -119,10 +149,14 @@ class Money
|
|
119
149
|
|
120
150
|
left_over = subunits_to_split
|
121
151
|
|
122
|
-
amounts = splits.
|
123
|
-
|
124
|
-
|
125
|
-
|
152
|
+
amounts = splits.map do |ratio|
|
153
|
+
whole_subunits = (subunits_to_split * ratio / allocations.to_r).floor
|
154
|
+
fractional_subunits = (subunits_to_split * ratio / allocations.to_r).to_f - whole_subunits
|
155
|
+
left_over -= whole_subunits
|
156
|
+
{
|
157
|
+
:whole_subunits => whole_subunits,
|
158
|
+
:fractional_subunits => fractional_subunits
|
159
|
+
}
|
126
160
|
end
|
127
161
|
|
128
162
|
[amounts, left_over]
|
@@ -132,15 +166,13 @@ class Money
|
|
132
166
|
splits.all? { |split| split.is_a?(Rational) }
|
133
167
|
end
|
134
168
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
raise ArgumentError, "Invalid strategy. Valid options: :roundrobin, :roundrobin_reverse"
|
143
|
-
end
|
169
|
+
# Given a list of decimal numbers, return a list ordered by which is nearest to the next whole number.
|
170
|
+
# For instance, given inputs [1.1, 1.5, 1.9] the correct ranking is 2, 1, 0. This is because 1.9 is nearly 2.
|
171
|
+
# Note that we are not ranking by absolute size, we only care about the distance between our input number and
|
172
|
+
# the next whole number. Similarly, given the input [9.1, 5.5, 3.9] the correct ranking is *still* 2, 1, 0. This
|
173
|
+
# is because 3.9 is nearer to 4 than 9.1 is to 10.
|
174
|
+
def rank_by_nearest(amounts)
|
175
|
+
amounts.each_with_index.sort_by{ |amount, i| 1 - amount[:fractional_subunits] }.map(&:last)
|
144
176
|
end
|
145
177
|
end
|
146
178
|
end
|