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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 591c60e355144246085ac254fe82b099c85ab4bf7f1e37672b8082331bd9d0aa
4
- data.tar.gz: 493384ced59c9a37ebc798b4cb499bd0532ea30243c8248704bf0df6f776ca0f
3
+ metadata.gz: 75140fb176288187177a79f673b9741b69a4ae96e02b314139318d7ff817b8b9
4
+ data.tar.gz: d98030d40029dbb686b83b090ab11e43628a194bd2902ba130f5161e860dc839
5
5
  SHA512:
6
- metadata.gz: 1867df0795206ccd5996b1d8471f23f60340ace3e5e685ddbb98f601402a7748aade38263dbc7f12971c1e4368919cb0122c53eacf0b96b96df44189be3ca32e
7
- data.tar.gz: 9ed8869fce8e66c4e9aee275ac3a28c24db47fd2ae8537f2ff3ee100541f122cd9b8b87e7ca102567b976548eb04b53b06f6c560f2e6e12dc3ca506e0bb87f01
6
+ metadata.gz: 3e140de24fea5b7c90b5044f4b9732e013187a6cad34c7ea6bd9d8119cd118907c7faeb934ed533085389e3e55f7ba0649697e7563ec87380c6fc62ff9a1c935
7
+ data.tar.gz: 5a1d77f26c7886fc1a96edee8a920df4262385fce001209d640c9427e986e9d95d2d9a8232fca17a670758f45a7bc219d728386c080606d1f8bc348cb71ba0a3
data/.gitignore CHANGED
@@ -46,6 +46,5 @@ pkg
46
46
 
47
47
  # For rubinius:
48
48
  #*.rbc
49
- Gemfile.lock
50
49
 
51
- .rspec_status
50
+ .rspec_status
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
- # If your application is currently handling only one currency,
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
@@ -3,7 +3,7 @@
3
3
  ---
4
4
  name: money
5
5
  up:
6
- - ruby: 3.3.0
6
+ - ruby
7
7
  - bundler
8
8
  commands:
9
9
  test: bundle exec rspec
@@ -9,18 +9,27 @@ class Money
9
9
 
10
10
  ONE = BigDecimal("1")
11
11
 
12
- # Allocates money between different parties without losing pennies.
13
- # After the mathematically split has been performed, left over pennies will
14
- # be distributed round-robin amongst the parties. This means that parties
15
- # listed first will likely receive more pennies than ones that are listed later
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 pennies will be accumulated starting from the first allocation left to right
23
- # - `:roundrobin_reverse`: leftover pennies will be accumulated starting from the last allocation right to left
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 pennies distributed reverse order when using roundrobin_reverse strategy
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, "splits add to more than 100%"
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[allocation_index_for(strategy, amounts.length, i)] += 1
84
+ amounts[order[i]][:whole_subunits] += 1
56
85
  end
57
86
 
58
- amounts.collect { |subunits| Money.from_subunits(subunits, currency) }
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 pennies will be assigned round-robin up to the maximum specified.
63
- # Pennies are dropped when the maximums are attained.
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.collect do |ratio|
123
- frac = (subunits_to_split * ratio / allocations.to_r).floor
124
- left_over -= frac
125
- frac
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
- def allocation_index_for(strategy, length, idx)
136
- case strategy
137
- when :roundrobin
138
- idx % length
139
- when :roundrobin_reverse
140
- length - (idx % length) - 1
141
- else
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
data/lib/money/helpers.rb CHANGED
@@ -16,6 +16,8 @@ class Money
16
16
  def value_to_decimal(num)
17
17
  value =
18
18
  case num
19
+ when Money
20
+ num.value
19
21
  when BigDecimal
20
22
  num
21
23
  when nil, 0, ''