|
224 | 224 | expect(Money.from_amount(1, "USD", bank).bank).to eq bank |
225 | 225 | end |
226 | 226 |
|
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 |
228 | 240 | allow(Money).to receive(:warn) |
| 241 | + allow(Money).to receive(:with_rounding_mode).and_call_original |
229 | 242 |
|
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 |
232 | 244 | 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 |
234 | 249 | expect(Money) |
235 | 250 | .to have_received(:warn) |
236 | 251 | .with('[DEPRECATION] calling `rounding_mode` with a block is deprecated. ' \ |
237 | 252 | 'Please use `.with_rounding_mode` instead.') |
| 253 | + expect(Money).to have_received(:with_rounding_mode).with(BigDecimal::ROUND_DOWN, &rounding_block) |
238 | 254 | end |
239 | 255 |
|
240 | 256 | it 'rounds using with_rounding_mode' do |
|
244 | 260 | end).to eq 1.99 |
245 | 261 | end |
246 | 262 |
|
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) |
250 | 266 |
|
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 |
253 | 309 | end |
254 | 310 | 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) |
255 | 334 | end |
256 | 335 | end |
257 | 336 |
|
@@ -337,23 +416,23 @@ def expectation.fractional |
337 | 416 |
|
338 | 417 | context "with a block" do |
339 | 418 | 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 |
341 | 420 | Money.new(1.9).fractional |
342 | 421 | end).to eq 1 |
343 | 422 |
|
344 | | - expect(Money.rounding_mode(BigDecimal::ROUND_UP) do |
| 423 | + expect(Money.with_rounding_mode(BigDecimal::ROUND_UP) do |
345 | 424 | Money.new(1.1).fractional |
346 | 425 | end).to eq 2 |
347 | 426 |
|
348 | 427 | expect(Money.rounding_mode).to eq BigDecimal::ROUND_HALF_EVEN |
349 | 428 | end |
350 | 429 |
|
351 | 430 | 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 |
353 | 432 | expect((Money.new(1_00) * "0.019".to_d).fractional).to eq 1 |
354 | 433 | end |
355 | 434 |
|
356 | | - Money.rounding_mode(BigDecimal::ROUND_UP) do |
| 435 | + Money.with_rounding_mode(BigDecimal::ROUND_UP) do |
357 | 436 | expect((Money.new(1_00) * "0.011".to_d).fractional).to eq 2 |
358 | 437 | end |
359 | 438 |
|
|
0 commit comments