Skip to content

Commit 4766542

Browse files
authored
Merge pull request #1146 from sunny/add-to_nearest_cash_value
Add `Money#to_nearest_cash_value` to return a rounded Money instance to the smallest denomination
2 parents e833146 + 8b81987 commit 4766542

File tree

7 files changed

+80
-15
lines changed

7 files changed

+80
-15
lines changed

AUTHORS

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
Abhay Kumar
22
Adrian Longley
3+
Alex Speller
34
Alexander Donkin
45
Alexander Ross
5-
Alex Speller
66
Andreas Loupasakis
77
Andrei
88
Andrew White
@@ -49,8 +49,8 @@ Greg Byrne
4949
Hakan Ensari
5050
Hongli Lai
5151
Ilia Lobsanov
52-
Ivan Shamatov
5352
Ingo Wichmann
53+
Ivan Shamatov
5454
Jack Spiva
5555
Jacob Atzen
5656
James Cotterill
@@ -83,8 +83,8 @@ Marco Otte-Witte
8383
Mateus Gomes
8484
Mateusz Wolsza
8585
Matias Korhonen
86-
Matthew McEachen
8786
Matt Jankowski
87+
Matthew McEachen
8888
Max Melentiev
8989
Michael Irwin
9090
Michael J. Cohen
@@ -96,14 +96,15 @@ Mike Połétyn
9696
Musannif Zahir
9797
Neil Middleton
9898
Nick Lozon
99+
Nicolay Hvidsten
99100
Nihad Abbasov
100101
Olek Janiszewski
101102
Orien Madgwick
102-
Paweł Madejski
103103
Paul McMahon
104104
Paulo Diniz
105105
Pavan Sudarshan
106106
Pavel Gabriel
107+
Paweł Madejski
107108
pconnor
108109
Pedro Nascimento
109110
Pelle Braendgaard
@@ -119,9 +120,11 @@ sankaranarayanan
119120
Scott Pierce
120121
Semyon Perepelitsa
121122
Shane Emmons
123+
Simon Neutert
122124
Simone Carletti
123125
Spencer Rinehart
124126
Steve Morris
127+
Sunny Ripert
125128
Thomas E Enebo
126129
Thomas Weymuth
127130
Ticean Bennett
@@ -142,5 +145,3 @@ Yuri Sidorov
142145
Yuusuke Takizawa
143146
Zubin Henner
144147
Бродяной Александр
145-
Nicolay Hvidsten
146-
Simon Neutert

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
- Add Zimbabwe Gold (ZWG) currency
2020
- Update thousands_separator for CHF
2121
- Add Caribbean Guilder (XCG) as replacement for Netherlands Antillean Gulden (ANG)
22-
- Add `Currency#cents_based?` to check if currency is cents-based
22+
- Add `Money#to_nearest_cash_value` to return a rounded Money instance to the smallest denomination
23+
- Deprecate `Money#round_to_nearest_cash_value` in favor of calling `to_nearest_cash_value.fractional`
24+
- Add `Money::Currency#cents_based?` to check if currency is cents-based
2325
- Add ability to nest `Money.with_rounding_mode` blocks
2426
- Allow `nil` to be used as a default_currency
2527
- Add ability to nest `Money.with_bank` blocks

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
3. Make sure everything is working: `bundle exec rake spec`
88
4. Make your changes
99
5. Test your changes
10-
5. Create a Pull Request
11-
6. Celebrate!!!!!
10+
6. Create a Pull Request
11+
7. Celebrate! 🎉
1212

1313
## Notes
1414

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ Money.from_amount(2.34567).format #=> "$2.34567"
471471

472472
To round to the nearest cent (or anything more precise), you can use the `round` method. However, note that the `round` method on a `Money` object does not work the same way as a normal Ruby `Float` object. Money's `round` method accepts different arguments. The first argument to the round method is the rounding mode, while the second argument is the level of precision relative to the cent.
473473

474-
```
474+
```ruby
475475
# Float
476476
2.34567.round #=> 2
477477
2.34567.round(2) #=> 2.35
@@ -484,10 +484,18 @@ Money.from_cents(2.34567).round(BigDecimal::ROUND_DOWN, 2).format #=> "$0.0234"
484484
```
485485

486486
You can set the default rounding mode by passing one of the `BigDecimal` mode enumerables like so:
487+
487488
```ruby
488489
Money.rounding_mode = BigDecimal::ROUND_HALF_EVEN
489490
```
490-
See [BigDecimal::ROUND_MODE](https://ruby-doc.org/3.4.1/gems/bigdecimal/BigDecimal.html#ROUND_MODE) for more information
491+
492+
See [BigDecimal::ROUND_MODE](https://ruby-doc.org/3.4.1/gems/bigdecimal/BigDecimal.html#ROUND_MODE) for more information.
493+
494+
To round to the nearest cash value in currencies without small denominations:
495+
496+
```ruby
497+
Money.from_cents(11_11, "CHF").to_nearest_cash_value.format # => "CHF 11.10"
498+
```
491499

492500
## Ruby on Rails
493501

@@ -527,7 +535,7 @@ This will work seamlessly with [rails-i18n](https://github.com/svenfuchs/rails-i
527535

528536
If you wish to disable this feature and use defaults instead:
529537

530-
``` ruby
538+
```ruby
531539
Money.locale_backend = nil
532540
```
533541

lib/money/money.rb

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,31 @@ def fractional
6969
#
7070
# @see infinite_precision
7171
def round_to_nearest_cash_value
72+
warn "[DEPRECATION] `round_to_nearest_cash_value` is deprecated - use " \
73+
"`to_nearest_cash_value.fractional` instead"
74+
75+
to_nearest_cash_value.fractional
76+
end
77+
78+
# Round a given amount of money to the nearest possible money in cash value.
79+
# For example, in Swiss franc (CHF), the smallest possible amount of cash
80+
# value is CHF 0.05. Therefore, this method rounds CHF 0.07 to CHF 0.05, and
81+
# CHF 0.08 to CHF 0.10.
82+
#
83+
# @return [Money]
84+
def to_nearest_cash_value
7285
unless self.currency.smallest_denomination
73-
raise UndefinedSmallestDenomination, 'Smallest denomination of this currency is not defined'
86+
raise UndefinedSmallestDenomination,
87+
"Smallest denomination of this currency is not defined"
7488
end
7589

7690
fractional = as_d(@fractional)
7791
smallest_denomination = as_d(self.currency.smallest_denomination)
78-
rounded_value = (fractional / smallest_denomination).round(0, self.class.rounding_mode) * smallest_denomination
92+
rounded_value =
93+
(fractional / smallest_denomination)
94+
.round(0, self.class.rounding_mode) * smallest_denomination
7995

80-
return_value(rounded_value)
96+
dup_with(fractional: return_value(rounded_value))
8197
end
8298

8399
# @!attribute [r] currency

sig/lib/money/money.rbs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ class Money
5858
# @see Money.default_infinite_precision
5959
def round_to_nearest_cash_value: () -> (Integer | BigDecimal)
6060

61+
# Round a given amount of money to the nearest possible money in cash value.
62+
# For example, in Swiss franc (CHF), the smallest possible amount of cash
63+
# value is CHF 0.05. Therefore, this method rounds CHF 0.07 to CHF 0.05, and
64+
# CHF 0.08 to CHF 0.10.
65+
#
66+
# @return [Money]
67+
def to_nearest_cash_value: () -> Money
68+
6169
attr_reader currency: Currency
6270

6371
attr_reader bank: untyped

spec/money_spec.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,36 @@ def expectation.fractional
562562
end
563563
end
564564

565+
describe "#to_nearest_cash_value" do
566+
it "rounds to the nearest possible cash value" do
567+
expect(Money.new(23_50, "AED").to_nearest_cash_value).to eq(Money.new(23_50, "AED"))
568+
expect(Money.new(-23_50, "AED").to_nearest_cash_value).to eq(Money.new(-23_50, "AED"))
569+
expect(Money.new(22_13, "AED").to_nearest_cash_value).to eq(Money.new(22_25, "AED"))
570+
expect(Money.new(-22_13, "AED").to_nearest_cash_value).to eq(Money.new(-22_25, "AED"))
571+
expect(Money.new(22_12, "AED").to_nearest_cash_value).to eq(Money.new(22_00, "AED"))
572+
expect(Money.new(-22_12, "AED").to_nearest_cash_value).to eq(Money.new(-22_00, "AED"))
573+
574+
expect(Money.new(1_78, "CHF").to_nearest_cash_value).to eq(Money.new(1_80, "CHF"))
575+
expect(Money.new(-1_78, "CHF").to_nearest_cash_value).to eq(Money.new(-1_80, "CHF"))
576+
expect(Money.new(1_77, "CHF").to_nearest_cash_value).to eq(Money.new(1_75, "CHF"))
577+
expect(Money.new(-1_77, "CHF").to_nearest_cash_value).to eq(Money.new(-1_75, "CHF"))
578+
expect(Money.new(1_75, "CHF").to_nearest_cash_value).to eq(Money.new(1_75, "CHF"))
579+
expect(Money.new(-1_75, "CHF").to_nearest_cash_value).to eq(Money.new(-1_75, "CHF"))
580+
581+
expect(Money.new(2_99, "USD").to_nearest_cash_value).to eq(Money.new(2_99, "USD"))
582+
expect(Money.new(-2_99, "USD").to_nearest_cash_value).to eq(Money.new(-2_99, "USD"))
583+
expect(Money.new(3_00, "USD").to_nearest_cash_value).to eq(Money.new(3_00, "USD"))
584+
expect(Money.new(-3_00, "USD").to_nearest_cash_value).to eq(Money.new(-3_00, "USD"))
585+
expect(Money.new( 3_01, "USD").to_nearest_cash_value).to eq(Money.new(3_01, "USD"))
586+
expect(Money.new(-3_01, "USD").to_nearest_cash_value).to eq(Money.new(-3_01, "USD"))
587+
end
588+
589+
it "raises an error if smallest denomination is not defined" do
590+
expect {Money.new(1_00, "XAG").to_nearest_cash_value}
591+
.to raise_error(Money::UndefinedSmallestDenomination)
592+
end
593+
end
594+
565595
describe "#amount" do
566596
it "returns the amount of cents as dollars" do
567597
expect(Money.new(1_00).amount).to eq 1

0 commit comments

Comments
 (0)