minting 0.3.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +117 -49
- data/Rakefile +21 -0
- data/lib/minting/mint/currency.rb +1 -3
- data/lib/minting/mint/registry.rb +129 -13
- data/lib/minting/money/allocation.rb +14 -18
- data/lib/minting/money/arithmetics.rb +12 -16
- data/lib/minting/money/coercion.rb +0 -2
- data/lib/minting/money/comparable.rb +7 -14
- data/lib/minting/money/conversion.rb +0 -2
- data/lib/minting/money/money.rb +12 -5
- data/lib/minting/version.rb +1 -1
- data/minting.gemspec +2 -2
- metadata +4 -8
- data/lib/minting/currency.rb +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4dfd2f98840b009208b9cccab86e9ca5d093300351e9e2031e70e03b93315638
|
4
|
+
data.tar.gz: 47a92df548558e20fd7bea3caa10a8966db91341d998e9712405af3d97fe12ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 175607c53124f16cac60c699a6ed6f042b3c3e960055bca1ccb7ba822a05b6cf8401fb66902e148a9ed50b43524ce6e708feaa72cac15f6ddd8b9cf3e9189cc6
|
7
|
+
data.tar.gz: 4fc09433abd38014d0d99c16aed5f88eb36ad039e422b0ffa305d3335da1753a23bae2bfcf775ce6142edf844463ed3942710d87f034375f99e04ff1b7e92812
|
data/README.md
CHANGED
@@ -1,61 +1,63 @@
|
|
1
1
|
# Minting
|
2
2
|
|
3
|
-
|
3
|
+
Fast, precise, and developer-friendly money handling for Ruby.
|
4
4
|
|
5
|
-
|
5
|
+
[](https://badge.fury.io/rb/minting)
|
6
6
|
|
7
|
-
##
|
7
|
+
## Why Minting?
|
8
8
|
|
9
|
-
|
9
|
+
**Tired of floating-point errors in financial calculations?** Minting uses Rational numbers for perfect precision.
|
10
10
|
|
11
|
-
|
12
|
-
bundle add minting
|
13
|
-
bundle install
|
14
|
-
```
|
11
|
+
**Need performance?** Minting is 2x faster than alternatives.
|
15
12
|
|
16
|
-
|
13
|
+
**Want a clean API?** Minting provides an intuitive interface with helpful error messages.
|
17
14
|
|
18
|
-
|
19
|
-
gem 'minting'
|
20
|
-
```
|
15
|
+
**Looking for a proven alternative?** Check out the established [Money gem](https://github.com/RubyMoney/money) with thousands of stars on GitHub.
|
21
16
|
|
22
|
-
|
17
|
+
**Rails**? Use the [minting-rails](https://github.com/gferraz/minting-rails) companion gem
|
18
|
+
|
19
|
+
## Quick start
|
23
20
|
|
24
21
|
```ruby
|
25
|
-
|
26
|
-
```
|
22
|
+
require 'minting'
|
27
23
|
|
28
|
-
|
24
|
+
price = Mint.money(19.99, 'USD') #=> [USD 19.99]
|
25
|
+
tax = price * 0.08 #=> [USD 1.60]
|
26
|
+
total = price + tax #=> [USD 21.59]
|
29
27
|
|
30
|
-
|
31
|
-
|
28
|
+
total.to_s #=> "$21.59"
|
29
|
+
total.currency_code #=> "USD"
|
32
30
|
```
|
33
31
|
|
34
|
-
|
32
|
+
## Features
|
35
33
|
|
36
|
-
|
37
|
-
|
38
|
-
|
34
|
+
- Arithmetic: `+ - * /`, unary minus, `abs`
|
35
|
+
- Comparisons: `==`, `<=>`, `zero?`, `nonzero?`, `positive?`, `negative?`
|
36
|
+
- Formatting: `to_s` with custom formats, delimiters, separators
|
37
|
+
- Serialization: `to_json`, `to_i`, `to_f`, `to_r`, `to_d`
|
38
|
+
- Allocation utilities: `split(quantity)`, `allocate([ratios])`
|
39
|
+
- Numeric Refinements for ergonomics: `10.dollars`, `3.euros`, `4.to_money('USD')`
|
40
|
+
- Currency registry with 117+ currencies and custom registration
|
39
41
|
|
40
42
|
## Usage
|
41
43
|
|
42
44
|
```ruby
|
43
45
|
require 'minting'
|
44
46
|
|
45
|
-
#
|
46
|
-
|
47
|
-
ten_dollars.to_i #=> 10
|
48
|
-
ten_dollars.currency_code #=> "USD"
|
47
|
+
# Create money
|
48
|
+
ten = Mint.money(10, 'USD') #=> [USD 10.00]
|
49
49
|
|
50
|
-
#
|
51
|
-
|
50
|
+
# Create money using Numeric refinements
|
51
|
+
using Mint
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
1.dollar == Mint.money(1, 'USD') #=> true
|
54
|
+
ten = 10.dollars #=> [USD 10.00]
|
55
|
+
4.to_money('USD') #=> [USD 4.00]
|
56
56
|
|
57
|
-
|
58
|
-
|
57
|
+
# Comparisons
|
58
|
+
ten == 10.dollars #=> true
|
59
|
+
ten == Mint.money(10, 'EUR') #=> false
|
60
|
+
ten > Mint.money(9.99, 'USD') #=> true
|
59
61
|
|
60
62
|
# Format (uses Kernel.format internally)
|
61
63
|
price = Mint.money(9.99, 'USD')
|
@@ -67,7 +69,7 @@ price.to_s(format: '%<symbol>s%<amount>+f') #=> "$+9.99",
|
|
67
69
|
(-price).to_s(format: '%<amount>f') #=> "-9.99",
|
68
70
|
|
69
71
|
# Format with padding
|
70
|
-
price_in_euros =
|
72
|
+
price_in_euros = Mint.money(12.34, 'EUR')
|
71
73
|
|
72
74
|
price.to_s(format: '--%<amount>7d') #=> "-- 9"
|
73
75
|
price.to_s(format: ' %<amount>10f %<currency>s') #=> " 9.99 USD"
|
@@ -77,31 +79,97 @@ price_in_euros.to_s(format: '%<symbol>2s%<amount>+10f') #=> " € +12.34"
|
|
77
79
|
|
78
80
|
# Json serialization
|
79
81
|
|
80
|
-
price.to_json # "{"currency": "USD", "amount": "9.99"}
|
82
|
+
price.to_json # => "{ "currency": "USD", "amount": "9.99" }"
|
81
83
|
|
82
|
-
#
|
84
|
+
# Proportional allocation and split
|
83
85
|
|
84
|
-
|
85
|
-
|
86
|
+
ten.split(3) #=> [[USD 3.34], [USD 3.33], [USD 3.33]]
|
87
|
+
ten.allocate([1, 2, 3]) #=> [[USD 1.67], [USD 3.33], [USD 5.00]]
|
86
88
|
|
87
|
-
|
89
|
+
# Ranges and enumeration are supported
|
88
90
|
|
89
|
-
|
90
|
-
|
91
|
+
1.dollar..10.dollars #=> [USD 1.00]..[USD 10.00]
|
92
|
+
(1.dollar..3.dollars).step(1.dollar).to_a #=> [[USD 1.00], [USD 2.00], [USD 3.00]]
|
91
93
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
4.to_money('USD') == 4.dollars #=> true
|
94
|
+
```
|
95
|
+
|
96
|
+
## Installation
|
96
97
|
|
98
|
+
Option 1: Via bundler command
|
99
|
+
|
100
|
+
```shell
|
101
|
+
bundle add minting
|
102
|
+
bundle install
|
97
103
|
```
|
98
104
|
|
99
|
-
|
105
|
+
Option 2: add the line below to your application's Gemfile:
|
100
106
|
|
101
|
-
|
102
|
-
|
103
|
-
|
107
|
+
```ruby
|
108
|
+
gem 'minting'
|
109
|
+
```
|
110
|
+
|
111
|
+
or, if you want latest development version from Github
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
gem 'minting', git: 'https://github.com/gferraz/minting.git'
|
115
|
+
```
|
116
|
+
|
117
|
+
and execute:
|
118
|
+
|
119
|
+
```shell
|
120
|
+
bundle install
|
121
|
+
```
|
122
|
+
|
123
|
+
Option 3: Install it yourself with:
|
124
|
+
|
125
|
+
```shell
|
126
|
+
gem install minting
|
127
|
+
```
|
128
|
+
|
129
|
+
## Roadmap
|
130
|
+
|
131
|
+
- Improve formatting features
|
132
|
+
- Localization (I18n-aware formatting)
|
133
|
+
- `Mint.parse` for parsing human strings into money
|
134
|
+
- Basic exchange-rate conversions
|
104
135
|
|
105
136
|
## Contributing
|
106
137
|
|
107
138
|
Bug reports and pull requests are welcome on GitHub at <https://github.com/gferraz/minting>.
|
139
|
+
|
140
|
+
1. Fork and create a feature branch
|
141
|
+
2. Run the test suite: `rake`
|
142
|
+
3. Run performance suites as needed: `BENCH=true rake bench:performance`
|
143
|
+
4. Open a PR with a clear description and benchmarks if relevant
|
144
|
+
|
145
|
+
|
146
|
+
## Performance
|
147
|
+
|
148
|
+
This gem includes a performance suite under `test/performance`:
|
149
|
+
|
150
|
+
- Core operations (creation, arithmetic, comparisons)
|
151
|
+
- Algorithm benchmarks (split, allocate)
|
152
|
+
- Memory and GC pressure tests
|
153
|
+
- Competitive benchmarks vs `money` gem
|
154
|
+
|
155
|
+
On a typical machine, reference numbers are:
|
156
|
+
|
157
|
+
- Money creation: ~1.6M ops/sec
|
158
|
+
- Addition: ~1.7M ops/sec
|
159
|
+
|
160
|
+
Run locally:
|
161
|
+
|
162
|
+
```bash
|
163
|
+
# All performance suites
|
164
|
+
BENCH=true rake bench:performance
|
165
|
+
|
166
|
+
# Competitive vs money gem
|
167
|
+
BENCH=true rake bench:competitive
|
168
|
+
|
169
|
+
# Regression checks
|
170
|
+
rake bench:regression
|
171
|
+
```
|
172
|
+
|
173
|
+
## License
|
174
|
+
|
175
|
+
MIT
|
data/Rakefile
CHANGED
@@ -16,6 +16,27 @@ Rake::TestTask.new(:bench) do |t|
|
|
16
16
|
t.pattern = 'test/**/*_benchmark.rb'
|
17
17
|
end
|
18
18
|
|
19
|
+
# Performance benchmark tasks
|
20
|
+
Rake::TestTask.new('bench:performance') do |t|
|
21
|
+
t.libs = %w[lib test]
|
22
|
+
t.pattern = 'test/performance/*_benchmark.rb'
|
23
|
+
t.ruby_opts << '-r test_helper.rb'
|
24
|
+
end
|
25
|
+
|
26
|
+
Rake::TestTask.new('bench:regression') do |t|
|
27
|
+
t.libs = %w[lib test]
|
28
|
+
t.pattern = 'test/performance/regression_benchmark.rb'
|
29
|
+
t.ruby_opts << '-r test_helper.rb'
|
30
|
+
end
|
31
|
+
|
32
|
+
Rake::TestTask.new('bench:competitive') do |t|
|
33
|
+
t.libs = %w[lib test]
|
34
|
+
t.pattern = 'test/performance/competitive_benchmark.rb'
|
35
|
+
t.ruby_opts << '-r test_helper.rb'
|
36
|
+
end
|
37
|
+
|
38
|
+
task 'bench:all' => ['bench', 'bench:performance']
|
39
|
+
|
19
40
|
RuboCop::RakeTask.new(:cop)
|
20
41
|
|
21
42
|
task default: :test
|
@@ -1,7 +1,5 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
module Mint
|
4
|
-
# Represents a specific currency unit, identified by ISO 4217 alphabetic
|
2
|
+
# Represents a specific currency unit, identified by ISO 4217 alphabetic code
|
5
3
|
#
|
6
4
|
# @see https://www.iso.org/iso-4217-currency-codes.html
|
7
5
|
class Currency
|
@@ -1,12 +1,11 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
# :nodoc
|
4
2
|
module Mint
|
5
3
|
def self.money(amount, currency_code)
|
6
4
|
currency = currency(currency_code)
|
7
|
-
|
5
|
+
return Money.new(amount, currency) if currency
|
8
6
|
|
9
|
-
|
7
|
+
available = currencies.keys.join(', ')
|
8
|
+
raise ArgumentError, "Currency [#{currency_code}] not registered. Available: #{available}"
|
10
9
|
end
|
11
10
|
|
12
11
|
def self.currency(currency)
|
@@ -29,7 +28,7 @@ module Mint
|
|
29
28
|
code = code.to_s
|
30
29
|
unless code.match?(/^[A-Z_]+$/)
|
31
30
|
raise ArgumentError,
|
32
|
-
"Currency code must be String or Symbol ('USD', :EUR)"
|
31
|
+
"Currency code must be String or Symbol ('USD', :EUR, 'FUEL', 'MY_COIN')"
|
33
32
|
end
|
34
33
|
if currencies[code]
|
35
34
|
raise KeyError,
|
@@ -42,19 +41,136 @@ module Mint
|
|
42
41
|
|
43
42
|
def self.currencies
|
44
43
|
@currencies ||= {
|
45
|
-
|
46
|
-
'
|
47
|
-
'CAD' => Currency.new('CAD', subunit: 2, symbol: 'R$'),
|
48
|
-
'CHF' => Currency.new('CHF', subunit: 2, symbol: 'Fr'),
|
49
|
-
'CNY' => Currency.new('CNY', subunit: 2, symbol: '¥'),
|
44
|
+
# Major Global Currencies
|
45
|
+
'USD' => Currency.new('USD', subunit: 2, symbol: '$'),
|
50
46
|
'EUR' => Currency.new('EUR', subunit: 2, symbol: '€'),
|
51
47
|
'GBP' => Currency.new('GBP', subunit: 2, symbol: '£'),
|
52
48
|
'JPY' => Currency.new('JPY', subunit: 0, symbol: '¥'),
|
53
|
-
'
|
49
|
+
'CHF' => Currency.new('CHF', subunit: 2, symbol: 'Fr'),
|
50
|
+
'CAD' => Currency.new('CAD', subunit: 2, symbol: '$'),
|
51
|
+
'AUD' => Currency.new('AUD', subunit: 2, symbol: '$'),
|
52
|
+
'CNY' => Currency.new('CNY', subunit: 2, symbol: '¥'),
|
53
|
+
'SEK' => Currency.new('SEK', subunit: 2, symbol: 'kr'),
|
54
54
|
'NZD' => Currency.new('NZD', subunit: 2, symbol: '$'),
|
55
|
+
|
56
|
+
# Asia-Pacific
|
57
|
+
'HKD' => Currency.new('HKD', subunit: 2, symbol: 'HK$'),
|
58
|
+
'SGD' => Currency.new('SGD', subunit: 2, symbol: 'S$'),
|
59
|
+
'KRW' => Currency.new('KRW', subunit: 0, symbol: '₩'),
|
60
|
+
'INR' => Currency.new('INR', subunit: 2, symbol: '₹'),
|
61
|
+
'THB' => Currency.new('THB', subunit: 2, symbol: '฿'),
|
62
|
+
'MYR' => Currency.new('MYR', subunit: 2, symbol: 'RM'),
|
63
|
+
'IDR' => Currency.new('IDR', subunit: 2, symbol: 'Rp'),
|
64
|
+
'PHP' => Currency.new('PHP', subunit: 2, symbol: '₱'),
|
65
|
+
'VND' => Currency.new('VND', subunit: 0, symbol: '₫'),
|
66
|
+
'TWD' => Currency.new('TWD', subunit: 2, symbol: 'NT$'),
|
67
|
+
'PKR' => Currency.new('PKR', subunit: 2, symbol: '₨'),
|
68
|
+
'BDT' => Currency.new('BDT', subunit: 2, symbol: '৳'),
|
69
|
+
'LKR' => Currency.new('LKR', subunit: 2, symbol: '₨'),
|
70
|
+
'NPR' => Currency.new('NPR', subunit: 2, symbol: '₨'),
|
71
|
+
'MMK' => Currency.new('MMK', subunit: 2, symbol: 'K'),
|
72
|
+
'KHR' => Currency.new('KHR', subunit: 2, symbol: '៛'),
|
73
|
+
'LAK' => Currency.new('LAK', subunit: 2, symbol: '₭'),
|
74
|
+
'BND' => Currency.new('BND', subunit: 2, symbol: 'B$'),
|
75
|
+
|
76
|
+
# Middle East & Central Asia
|
77
|
+
'AED' => Currency.new('AED', subunit: 2, symbol: 'د.إ'),
|
78
|
+
'SAR' => Currency.new('SAR', subunit: 2, symbol: '﷼'),
|
79
|
+
'QAR' => Currency.new('QAR', subunit: 2, symbol: '﷼'),
|
80
|
+
'KWD' => Currency.new('KWD', subunit: 3, symbol: 'د.ك'),
|
81
|
+
'BHD' => Currency.new('BHD', subunit: 3, symbol: '.د.ب'),
|
82
|
+
'OMR' => Currency.new('OMR', subunit: 3, symbol: '﷼'),
|
83
|
+
'JOD' => Currency.new('JOD', subunit: 3, symbol: 'د.ا'),
|
84
|
+
'ILS' => Currency.new('ILS', subunit: 2, symbol: '₪'),
|
85
|
+
'TRY' => Currency.new('TRY', subunit: 2, symbol: '₺'),
|
86
|
+
'IRR' => Currency.new('IRR', subunit: 2, symbol: '﷼'),
|
87
|
+
'IQD' => Currency.new('IQD', subunit: 3, symbol: 'د.ع'),
|
88
|
+
'AFN' => Currency.new('AFN', subunit: 2, symbol: '؋'),
|
89
|
+
'KZT' => Currency.new('KZT', subunit: 2, symbol: '₸'),
|
90
|
+
'UZS' => Currency.new('UZS', subunit: 2, symbol: 'лв'),
|
91
|
+
'KGS' => Currency.new('KGS', subunit: 2, symbol: 'лв'),
|
92
|
+
'TJS' => Currency.new('TJS', subunit: 2, symbol: 'SM'),
|
93
|
+
|
94
|
+
# Europe
|
95
|
+
'NOK' => Currency.new('NOK', subunit: 2, symbol: 'kr'),
|
96
|
+
'DKK' => Currency.new('DKK', subunit: 2, symbol: 'kr'),
|
97
|
+
'ISK' => Currency.new('ISK', subunit: 0, symbol: 'kr'),
|
98
|
+
'PLN' => Currency.new('PLN', subunit: 2, symbol: 'zł'),
|
99
|
+
'CZK' => Currency.new('CZK', subunit: 2, symbol: 'Kč'),
|
100
|
+
'HUF' => Currency.new('HUF', subunit: 2, symbol: 'Ft'),
|
101
|
+
'RON' => Currency.new('RON', subunit: 2, symbol: 'lei'),
|
102
|
+
'BGN' => Currency.new('BGN', subunit: 2, symbol: 'лв'),
|
103
|
+
'HRK' => Currency.new('HRK', subunit: 2, symbol: 'kn'),
|
104
|
+
'RSD' => Currency.new('RSD', subunit: 2, symbol: 'Дин.'),
|
105
|
+
'RUB' => Currency.new('RUB', subunit: 2, symbol: '₽'),
|
106
|
+
'UAH' => Currency.new('UAH', subunit: 2, symbol: '₴'),
|
107
|
+
'BYN' => Currency.new('BYN', subunit: 2, symbol: 'Br'),
|
108
|
+
'MDL' => Currency.new('MDL', subunit: 2, symbol: 'L'),
|
109
|
+
'GEL' => Currency.new('GEL', subunit: 2, symbol: '₾'),
|
110
|
+
'AMD' => Currency.new('AMD', subunit: 2, symbol: '֏'),
|
111
|
+
'AZN' => Currency.new('AZN', subunit: 2, symbol: '₼'),
|
112
|
+
|
113
|
+
# Africa
|
114
|
+
'ZAR' => Currency.new('ZAR', subunit: 2, symbol: 'R'),
|
115
|
+
'EGP' => Currency.new('EGP', subunit: 2, symbol: '£'),
|
116
|
+
'NGN' => Currency.new('NGN', subunit: 2, symbol: '₦'),
|
117
|
+
'KES' => Currency.new('KES', subunit: 2, symbol: 'KSh'),
|
118
|
+
'GHS' => Currency.new('GHS', subunit: 2, symbol: '¢'),
|
119
|
+
'UGX' => Currency.new('UGX', subunit: 0, symbol: 'USh'),
|
120
|
+
'TZS' => Currency.new('TZS', subunit: 2, symbol: 'TSh'),
|
121
|
+
'ETB' => Currency.new('ETB', subunit: 2, symbol: 'Br'),
|
122
|
+
'MAD' => Currency.new('MAD', subunit: 2, symbol: 'د.م.'),
|
123
|
+
'TND' => Currency.new('TND', subunit: 3, symbol: 'د.ت'),
|
124
|
+
'DZD' => Currency.new('DZD', subunit: 2, symbol: 'د.ج'),
|
125
|
+
'LYD' => Currency.new('LYD', subunit: 3, symbol: 'ل.د'),
|
126
|
+
'AOA' => Currency.new('AOA', subunit: 2, symbol: 'Kz'),
|
127
|
+
'BWP' => Currency.new('BWP', subunit: 2, symbol: 'P'),
|
128
|
+
'NAD' => Currency.new('NAD', subunit: 2, symbol: 'N$'),
|
129
|
+
'SZL' => Currency.new('SZL', subunit: 2, symbol: 'L'),
|
130
|
+
'LSL' => Currency.new('LSL', subunit: 2, symbol: 'L'),
|
131
|
+
'MZN' => Currency.new('MZN', subunit: 2, symbol: 'MT'),
|
132
|
+
'ZMW' => Currency.new('ZMW', subunit: 2, symbol: 'ZK'),
|
133
|
+
'MWK' => Currency.new('MWK', subunit: 2, symbol: 'MK'),
|
134
|
+
'RWF' => Currency.new('RWF', subunit: 0, symbol: 'R₣'),
|
135
|
+
'BIF' => Currency.new('BIF', subunit: 0, symbol: 'FBu'),
|
136
|
+
|
137
|
+
# Americas
|
138
|
+
'MXN' => Currency.new('MXN', subunit: 2, symbol: '$'),
|
139
|
+
'BRL' => Currency.new('BRL', subunit: 2, symbol: 'R$'),
|
140
|
+
'ARS' => Currency.new('ARS', subunit: 2, symbol: '$'),
|
141
|
+
'CLP' => Currency.new('CLP', subunit: 0, symbol: '$'),
|
55
142
|
'PEN' => Currency.new('PEN', subunit: 2, symbol: 'S/.'),
|
56
|
-
'
|
57
|
-
'
|
143
|
+
'COP' => Currency.new('COP', subunit: 2, symbol: '$'),
|
144
|
+
'VES' => Currency.new('VES', subunit: 2, symbol: 'Bs.'),
|
145
|
+
'UYU' => Currency.new('UYU', subunit: 2, symbol: '$U'),
|
146
|
+
'PYG' => Currency.new('PYG', subunit: 0, symbol: 'Gs'),
|
147
|
+
'BOB' => Currency.new('BOB', subunit: 2, symbol: '$b'),
|
148
|
+
'CRC' => Currency.new('CRC', subunit: 2, symbol: '₡'),
|
149
|
+
'GTQ' => Currency.new('GTQ', subunit: 2, symbol: 'Q'),
|
150
|
+
'HNL' => Currency.new('HNL', subunit: 2, symbol: 'L'),
|
151
|
+
'NIO' => Currency.new('NIO', subunit: 2, symbol: 'C$'),
|
152
|
+
'PAB' => Currency.new('PAB', subunit: 2, symbol: 'B/.'),
|
153
|
+
'DOP' => Currency.new('DOP', subunit: 2, symbol: 'RD$'),
|
154
|
+
'HTG' => Currency.new('HTG', subunit: 2, symbol: 'G'),
|
155
|
+
'JMD' => Currency.new('JMD', subunit: 2, symbol: 'J$'),
|
156
|
+
'TTD' => Currency.new('TTD', subunit: 2, symbol: 'TT$'),
|
157
|
+
'BBD' => Currency.new('BBD', subunit: 2, symbol: 'Bds$'),
|
158
|
+
'BSD' => Currency.new('BSD', subunit: 2, symbol: 'B$'),
|
159
|
+
'BZD' => Currency.new('BZD', subunit: 2, symbol: 'BZ$'),
|
160
|
+
'GYD' => Currency.new('GYD', subunit: 2, symbol: 'G$'),
|
161
|
+
'SRD' => Currency.new('SRD', subunit: 2, symbol: 'Sr$'),
|
162
|
+
|
163
|
+
# Pacific & Others
|
164
|
+
'FJD' => Currency.new('FJD', subunit: 2, symbol: 'FJ$'),
|
165
|
+
'PGK' => Currency.new('PGK', subunit: 2, symbol: 'K'),
|
166
|
+
'SBD' => Currency.new('SBD', subunit: 2, symbol: 'SI$'),
|
167
|
+
'VUV' => Currency.new('VUV', subunit: 0, symbol: 'VT'),
|
168
|
+
'TOP' => Currency.new('TOP', subunit: 2, symbol: 'T$'),
|
169
|
+
'WST' => Currency.new('WST', subunit: 2, symbol: 'WS$'),
|
170
|
+
'XCD' => Currency.new('XCD', subunit: 2, symbol: 'EC$'),
|
171
|
+
'XOF' => Currency.new('XOF', subunit: 0, symbol: 'CFA'),
|
172
|
+
'XAF' => Currency.new('XAF', subunit: 0, symbol: 'FCFA'),
|
173
|
+
'XPF' => Currency.new('XPF', subunit: 0, symbol: '₣')
|
58
174
|
}
|
59
175
|
end
|
60
176
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
module Mint
|
4
2
|
# :nodoc
|
5
3
|
# split and allocation methods
|
@@ -8,32 +6,30 @@ module Mint
|
|
8
6
|
raise ArgumentError, 'Need at least 1 proportion element' if proportions.empty?
|
9
7
|
|
10
8
|
whole = proportions.sum.to_r
|
11
|
-
|
12
|
-
left_over
|
13
|
-
allocate_left_over(allocation, left_over)
|
9
|
+
amounts = proportions.map! { |rate| (amount * rate.to_r / whole).round(currency.subunit) }
|
10
|
+
allocate_left_over!(amounts: amounts, left_over: amount - amounts.sum)
|
14
11
|
end
|
15
12
|
|
16
13
|
def split(quantity)
|
17
|
-
unless
|
14
|
+
unless quantity.positive? && quantity.integer?
|
18
15
|
raise ArgumentError,
|
19
|
-
'
|
16
|
+
'quantity must be an integer > 0'
|
20
17
|
end
|
21
18
|
|
22
|
-
fraction =
|
23
|
-
|
24
|
-
|
25
|
-
allocate_left_over(allocation, left_over)
|
19
|
+
fraction = (amount / quantity).round(currency.subunit)
|
20
|
+
allocate_left_over!(amounts: Array.new(quantity, fraction),
|
21
|
+
left_over: amount - (fraction * quantity))
|
26
22
|
end
|
27
23
|
|
28
24
|
private
|
29
25
|
|
30
|
-
def allocate_left_over(
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
def allocate_left_over!(amounts:, left_over:)
|
27
|
+
if left_over.nonzero?
|
28
|
+
minimum = left_over.positive? ? currency.minimum_amount : -currency.minimum_amount
|
29
|
+
last_slot = (left_over / minimum).to_i - 1
|
30
|
+
(0..last_slot).each { |slot| amounts[slot] += minimum }
|
31
|
+
end
|
32
|
+
amounts.map { mint(it) }
|
37
33
|
end
|
38
34
|
end
|
39
35
|
end
|
@@ -1,31 +1,27 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
module Mint
|
4
2
|
# :nodoc
|
5
|
-
# Arithmetic
|
3
|
+
# Arithmetic functions for money objects
|
6
4
|
class Money
|
7
|
-
def abs
|
8
|
-
mint(amount.abs)
|
9
|
-
end
|
5
|
+
def abs = mint(amount.abs)
|
10
6
|
|
11
|
-
def negative?
|
12
|
-
amount.negative?
|
13
|
-
end
|
7
|
+
def negative? = amount.negative?
|
14
8
|
|
15
|
-
def positive?
|
16
|
-
|
17
|
-
|
9
|
+
def positive? = amount.positive?
|
10
|
+
|
11
|
+
def succ = mint(amount + currency.minimum_amount)
|
18
12
|
|
19
13
|
def +(addend)
|
20
|
-
return
|
21
|
-
return
|
14
|
+
return self if addend.respond_to?(:zero?) && addend.zero?
|
15
|
+
return mint(amount + addend.amount) if addend.is_a?(Money) && same_currency?(addend)
|
22
16
|
|
23
17
|
raise TypeError, "#{addend} can't be added to #{self}"
|
24
18
|
end
|
25
19
|
|
26
20
|
def -(subtrahend)
|
27
|
-
return self if subtrahend.zero?
|
28
|
-
|
21
|
+
return self if subtrahend.respond_to?(:zero?) && subtrahend.zero?
|
22
|
+
if subtrahend.is_a?(Money) && same_currency?(subtrahend)
|
23
|
+
return mint(amount - subtrahend.amount)
|
24
|
+
end
|
29
25
|
|
30
26
|
raise TypeError, "#{subtrahend} can't be subtracted from #{self}"
|
31
27
|
end
|
@@ -1,27 +1,24 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
module Mint
|
4
2
|
# :nodoc
|
5
|
-
#
|
3
|
+
# Comparison methods
|
6
4
|
class Money
|
7
5
|
include Comparable
|
8
6
|
|
9
7
|
# @return true if both are zero, or both have same amount and same currency
|
10
8
|
def ==(other)
|
11
|
-
return true if other.
|
9
|
+
return true if zero? && other.respond_to?(:zero?) && other.zero?
|
12
10
|
return false unless other.is_a?(Mint::Money)
|
13
|
-
return false if nonzero? && currency != other.currency
|
14
11
|
|
15
|
-
amount == other.amount
|
12
|
+
amount == other.amount && currency == other.currency
|
16
13
|
end
|
17
14
|
|
18
15
|
# @example
|
19
|
-
# two_usd == Mint.money(2r,
|
20
|
-
# two_usd > 0
|
21
|
-
# two_usd > Mint.money(
|
16
|
+
# two_usd == Mint.money(2r, 'USD']) #=> [$ 2.00]
|
17
|
+
# two_usd > 0 #=> true
|
18
|
+
# two_usd > Mint.money(2, 'USD']) #=> true
|
22
19
|
# two_usd > 1
|
23
20
|
# => TypeError: [$ 2.00] can't be compared to 1
|
24
|
-
# two_usd > Mint.money(2,
|
21
|
+
# two_usd > Mint.money(2, 'BRL'])
|
25
22
|
# => TypeError: [$ 2.00] can't be compared to [R$ 2.00]
|
26
23
|
#
|
27
24
|
def <=>(other)
|
@@ -38,10 +35,6 @@ module Mint
|
|
38
35
|
self == other
|
39
36
|
end
|
40
37
|
|
41
|
-
def hash
|
42
|
-
@hash ||= zero? ? 0.hash : [amount, currency].hash
|
43
|
-
end
|
44
|
-
|
45
38
|
def nonzero?
|
46
39
|
amount.nonzero?
|
47
40
|
end
|
data/lib/minting/money/money.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
module Mint
|
4
|
-
# Money is a value object for monetary values
|
5
|
-
# Money is immutable
|
6
2
|
class Money
|
7
|
-
DEFAULT_FORMAT = '%<symbol>s%<amount>f'
|
3
|
+
DEFAULT_FORMAT = '%<symbol>s%<amount>f'.freeze
|
8
4
|
|
9
5
|
attr_reader :amount, :currency
|
10
6
|
|
7
|
+
# Creates a new Money immutable object with the specified amount and currency
|
8
|
+
# @param amount [Numeric] The monetary amount
|
9
|
+
# @param currency [Currency] The currency object
|
10
|
+
# @raise [ArgumentError] If amount is not numeric or currency is invalid
|
11
11
|
def initialize(amount, currency)
|
12
12
|
raise ArgumentError, 'amount must be Numeric' unless amount.is_a?(Numeric)
|
13
13
|
|
@@ -24,6 +24,13 @@ module Mint
|
|
24
24
|
currency.code
|
25
25
|
end
|
26
26
|
|
27
|
+
def hash
|
28
|
+
@hash ||= zero? ? 0.hash : [amount, currency.code].hash
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns a new Money object with the specified amount, or self if unchanged
|
32
|
+
# @param new_amount [Numeric] The new amount
|
33
|
+
# @return [Money] A new Money object or self
|
27
34
|
def mint(new_amount)
|
28
35
|
new_amount.to_r == amount ? self : Money.new(new_amount, currency)
|
29
36
|
end
|
data/lib/minting/version.rb
CHANGED
data/minting.gemspec
CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
# Prevent pushing this gem to RubyGems.org.
|
16
16
|
# To allow pushes either set the 'allowed_push_host' to allow pushing to
|
17
17
|
# a single host or delete this section to allow pushing to any host.
|
18
|
-
raise 'RubyGems 3.
|
18
|
+
raise 'RubyGems 3.2 or newer is required' unless s.respond_to?(:metadata)
|
19
19
|
|
20
20
|
s.metadata = {
|
21
21
|
'bug_tracker_uri' => "#{s.homepage}/issues",
|
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
|
|
26
26
|
'rubygems_mfa_required' => 'true'
|
27
27
|
}
|
28
28
|
|
29
|
-
s.required_ruby_version = '>= 3.
|
29
|
+
s.required_ruby_version = '>= 3.2.0'
|
30
30
|
|
31
31
|
s.files = Dir.glob('{bin,doc,lib}/**/*')
|
32
32
|
s.files += %w[minting.gemspec Rakefile README.md]
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minting
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gilson Ferraz
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: Library to manipulate currency values
|
14
13
|
email: []
|
@@ -21,7 +20,6 @@ files:
|
|
21
20
|
- bin/console
|
22
21
|
- bin/setup
|
23
22
|
- lib/minting.rb
|
24
|
-
- lib/minting/currency.rb
|
25
23
|
- lib/minting/mint.rb
|
26
24
|
- lib/minting/mint/currency.rb
|
27
25
|
- lib/minting/mint/refinements.rb
|
@@ -45,7 +43,6 @@ metadata:
|
|
45
43
|
source_code_uri: https://github.com/gferraz/minting
|
46
44
|
allowed_push_host: https://rubygems.org
|
47
45
|
rubygems_mfa_required: 'true'
|
48
|
-
post_install_message:
|
49
46
|
rdoc_options: []
|
50
47
|
require_paths:
|
51
48
|
- lib
|
@@ -53,15 +50,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
50
|
requirements:
|
54
51
|
- - ">="
|
55
52
|
- !ruby/object:Gem::Version
|
56
|
-
version: 3.
|
53
|
+
version: 3.2.0
|
57
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
55
|
requirements:
|
59
56
|
- - ">="
|
60
57
|
- !ruby/object:Gem::Version
|
61
58
|
version: '0'
|
62
59
|
requirements: []
|
63
|
-
rubygems_version: 3.
|
64
|
-
signing_key:
|
60
|
+
rubygems_version: 3.7.1
|
65
61
|
specification_version: 4
|
66
62
|
summary: Library to manipulate currency values
|
67
63
|
test_files: []
|
data/lib/minting/currency.rb
DELETED