Skip to content

Commit 20fba2a

Browse files
authored
Merge pull request #1128 from pawelma/main
Add ability to nest `Money.with_rounding_mode` blocks
2 parents c6f2ec6 + 56c2805 commit 20fba2a

File tree

4 files changed

+97
-14
lines changed

4 files changed

+97
-14
lines changed

AUTHORS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ Kenn Ejima
7777
Kenneth Salomon
7878
kirillian
7979
Laurynas Butkus
80+
Łukasz Wójcik
8081
Marcel Scherf
8182
Marco Otte-Witte
8283
Mateus Gomes
@@ -98,6 +99,7 @@ Nick Lozon
9899
Nihad Abbasov
99100
Olek Janiszewski
100101
Orien Madgwick
102+
Paweł Madejski
101103
Paul McMahon
102104
Paulo Diniz
103105
Pavan Sudarshan

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- Update thousands_separator for CHF
1515
- Add Caribbean Guilder (XCG) as replacement for Netherlands Antillean Gulden (ANG)
1616
- Add `Currency#cents_based?` to check if currency is cents-based
17+
- Add ability to nest `Money.with_rounding_mode` blocks
1718
- Allow `nil` to be used as a default_currency
1819

1920
## 6.19.0

lib/money/money.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,10 +266,11 @@ def self.rounding_mode(mode = nil)
266266
# Money.new(1200) * BigDecimal('0.029')
267267
# end
268268
def self.with_rounding_mode(mode)
269+
original_mode = Thread.current[:money_rounding_mode]
269270
Thread.current[:money_rounding_mode] = mode
270271
yield
271272
ensure
272-
Thread.current[:money_rounding_mode] = nil
273+
Thread.current[:money_rounding_mode] = original_mode
273274
end
274275

275276
# Adds a new exchange rate to the default bank and return the rate.

spec/money_spec.rb

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -224,17 +224,33 @@
224224
expect(Money.from_amount(1, "USD", bank).bank).to eq bank
225225
end
226226

227-
it 'warns about rounding_mode deprecation' do
227+
context 'given a currency is provided' do
228+
context 'and the currency is nil' do
229+
let(:currency) { nil }
230+
231+
it "should have the default currency" do
232+
expect(Money.from_amount(1, currency).currency).to eq Money.default_currency
233+
end
234+
end
235+
end
236+
end
237+
238+
describe '.with_rounding_mode' do
239+
it 'sets the .rounding_mode method deprecated' do
228240
allow(Money).to receive(:warn)
241+
allow(Money).to receive(:with_rounding_mode).and_call_original
229242

230-
expect(Money.from_amount(1.999).to_d).to eq 2
231-
expect(Money.rounding_mode(BigDecimal::ROUND_DOWN) do
243+
rounding_block = lambda do
232244
Money.from_amount(1.999).to_d
233-
end).to eq 1.99
245+
end
246+
247+
expect(Money.from_amount(1.999).to_d).to eq 2
248+
expect(Money.rounding_mode(BigDecimal::ROUND_DOWN, &rounding_block)).to eq 1.99
234249
expect(Money)
235250
.to have_received(:warn)
236251
.with('[DEPRECATION] calling `rounding_mode` with a block is deprecated. ' \
237252
'Please use `.with_rounding_mode` instead.')
253+
expect(Money).to have_received(:with_rounding_mode).with(BigDecimal::ROUND_DOWN, &rounding_block)
238254
end
239255

240256
it 'rounds using with_rounding_mode' do
@@ -244,14 +260,77 @@
244260
end).to eq 1.99
245261
end
246262

247-
context 'given a currency is provided' do
248-
context 'and the currency is nil' do
249-
let(:currency) { nil }
263+
it 'allows blocks nesting' do
264+
Money.with_rounding_mode(BigDecimal::ROUND_DOWN) do
265+
expect(Money.rounding_mode).to eq(BigDecimal::ROUND_DOWN)
250266

251-
it "should have the default currency" do
252-
expect(Money.from_amount(1, currency).currency).to eq Money.default_currency
267+
Money.with_rounding_mode(BigDecimal::ROUND_UP) do
268+
expect(Money.rounding_mode).to eq(BigDecimal::ROUND_UP)
269+
expect(Money.from_amount(2.137).to_d).to eq 2.14
270+
end
271+
272+
expect(
273+
Money.rounding_mode
274+
).to eq(BigDecimal::ROUND_DOWN), 'Outer mode should be restored after inner block'
275+
expect(Money.from_amount(2.137).to_d).to eq 2.13
276+
end
277+
278+
expect(
279+
Money.rounding_mode
280+
).to eq(BigDecimal::ROUND_HALF_EVEN), 'Original mode should be restored after outer block'
281+
expect(Money.from_amount(2.137).to_d).to eq 2.14
282+
end
283+
284+
it 'safely handles concurrent usage in different threads' do
285+
test_value = 1.999
286+
expected_down = 1.99
287+
expected_up = 2.00
288+
289+
results = Queue.new
290+
291+
test_money_with_rounding_mode = lambda do |rounding_mode|
292+
Thread.new do
293+
Money.with_rounding_mode(rounding_mode) do
294+
results.push({
295+
set_rounding_mode: rounding_mode,
296+
mode: Money.rounding_mode,
297+
result: Money.from_amount(test_value).to_d
298+
})
299+
300+
# Sleep to allow interleaving with other thread
301+
sleep 0.01
302+
303+
results.push({
304+
set_rounding_mode: rounding_mode,
305+
mode: Money.rounding_mode,
306+
result: Money.from_amount(test_value).to_d
307+
})
308+
end
253309
end
254310
end
311+
312+
[
313+
test_money_with_rounding_mode.call(BigDecimal::ROUND_DOWN),
314+
test_money_with_rounding_mode.call(BigDecimal::ROUND_UP)
315+
].each(&:join)
316+
317+
all_results = []
318+
all_results << results.pop until results.empty?
319+
320+
round_down_results = all_results.select { |r| r[:set_rounding_mode] == BigDecimal::ROUND_DOWN }
321+
round_up_results = all_results.select { |r| r[:set_rounding_mode] == BigDecimal::ROUND_UP }
322+
323+
round_down_results.each do |result|
324+
expect(result[:mode]).to eq(BigDecimal::ROUND_DOWN)
325+
expect(result[:result]).to eq(expected_down)
326+
end
327+
328+
round_up_results.each do |result|
329+
expect(result[:mode]).to eq(BigDecimal::ROUND_UP)
330+
expect(result[:result]).to eq(expected_up)
331+
end
332+
333+
expect(Money.rounding_mode).to eq(BigDecimal::ROUND_HALF_EVEN)
255334
end
256335
end
257336

@@ -337,23 +416,23 @@ def expectation.fractional
337416

338417
context "with a block" do
339418
it "respects the rounding_mode" do
340-
expect(Money.rounding_mode(BigDecimal::ROUND_DOWN) do
419+
expect(Money.with_rounding_mode(BigDecimal::ROUND_DOWN) do
341420
Money.new(1.9).fractional
342421
end).to eq 1
343422

344-
expect(Money.rounding_mode(BigDecimal::ROUND_UP) do
423+
expect(Money.with_rounding_mode(BigDecimal::ROUND_UP) do
345424
Money.new(1.1).fractional
346425
end).to eq 2
347426

348427
expect(Money.rounding_mode).to eq BigDecimal::ROUND_HALF_EVEN
349428
end
350429

351430
it "works for multiplication within a block" do
352-
Money.rounding_mode(BigDecimal::ROUND_DOWN) do
431+
Money.with_rounding_mode(BigDecimal::ROUND_DOWN) do
353432
expect((Money.new(1_00) * "0.019".to_d).fractional).to eq 1
354433
end
355434

356-
Money.rounding_mode(BigDecimal::ROUND_UP) do
435+
Money.with_rounding_mode(BigDecimal::ROUND_UP) do
357436
expect((Money.new(1_00) * "0.011".to_d).fractional).to eq 2
358437
end
359438

0 commit comments

Comments
 (0)