minting 1.4.0 โ 1.5.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 +14 -1
- data/Rakefile +1 -1
- data/doc/agents/recommendations.md +1 -1
- data/doc/agents/rubocop-issues.md +332 -0
- data/lib/minting/mint/currency.rb +8 -7
- data/lib/minting/mint/currency_store.rb +68 -0
- data/lib/minting/mint/refinements.rb +3 -1
- data/lib/minting/mint/registry.rb +15 -46
- data/lib/minting/mint.rb +4 -1
- data/lib/minting/money/allocation.rb +4 -1
- data/lib/minting/money/arithmetics.rb +25 -10
- data/lib/minting/money/coercion.rb +3 -1
- data/lib/minting/money/comparable.rb +5 -6
- data/lib/minting/money/constructors.rb +66 -0
- data/lib/minting/money/conversion.rb +4 -1
- data/lib/minting/money/formatting.rb +20 -19
- data/lib/minting/money/money.rb +30 -82
- data/lib/minting/money/parse.rb +16 -19
- data/lib/minting/money.rb +3 -0
- data/lib/minting/version.rb +3 -1
- data/lib/minting.rb +2 -0
- metadata +4 -19
- data/doc/Mint/Currency.html +0 -816
- data/doc/Mint/Money.html +0 -3471
- data/doc/Mint.html +0 -953
- data/doc/Minting.html +0 -142
- data/doc/_index.html +0 -136
- data/doc/class_list.html +0 -54
- data/doc/css/common.css +0 -1
- data/doc/css/full_list.css +0 -206
- data/doc/css/style.css +0 -1089
- data/doc/file.README.html +0 -379
- data/doc/file_list.html +0 -59
- data/doc/frames.html +0 -22
- data/doc/index.html +0 -379
- data/doc/js/app.js +0 -801
- data/doc/js/full_list.js +0 -334
- data/doc/js/jquery.js +0 -4
- data/doc/method_list.html +0 -470
- data/doc/top-level-namespace.html +0 -112
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 135843eccae0c9415ad8cd2c941d7afeceb19958fdff5c3d898fbcfd76d51218
|
|
4
|
+
data.tar.gz: 90d8349d74b739d37a3aa37a83002190a3975064cb11b279f8d4c9ef1760e4a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aaaab50cf31d56f8e4f22d0f760803c0c24eef022648df41161c387a6dadbe403d41b6347dc7018758cb0d5e72b8ce9ba2567a3f23df84da9b26fd53c4a9b31e
|
|
7
|
+
data.tar.gz: bed44112191195ea250b6a5b8c809a43df72f79e387bae80881833f1d174ef2f01e450c986431f542b4fa41b55ed202e8ccfd38921355118ecf5004b5237ed99
|
data/README.md
CHANGED
|
@@ -35,7 +35,8 @@ total.currency_code #=> "USD"
|
|
|
35
35
|
- Comparisons: `==`, `<=>`, `zero?`, `nonzero?`, `positive?`, `negative?`
|
|
36
36
|
- Formatting: `to_s` with custom formats, thousand delimiters and decimal separators
|
|
37
37
|
- Serialization: `to_json`, `to_i`, `to_f`, `to_r`, `to_d`
|
|
38
|
-
- Allocation utilities: `split(quantity)`, `allocate([ratios])
|
|
38
|
+
- Allocation utilities: `split(quantity)`, `allocate([ratios])`,
|
|
39
|
+
- Utilities: `clamp(min, max)`
|
|
39
40
|
- Numeric Refinements for ergonomics: `10.dollars`, `3.euros`, `4.to_money('USD')`
|
|
40
41
|
- Currency registry with 117+ currencies and custom registration
|
|
41
42
|
|
|
@@ -116,6 +117,18 @@ Mint::Money.from_fractional(1234, 'JPY') #=> [JPY 1234] # subunit 0 -> no scali
|
|
|
116
117
|
ten.split(3) #=> [[USD 3.34], [USD 3.33], [USD 3.33]]
|
|
117
118
|
ten.allocate([1, 2, 3]) #=> [[USD 1.67], [USD 3.33], [USD 5.00]]
|
|
118
119
|
|
|
120
|
+
# Clamping to a range
|
|
121
|
+
|
|
122
|
+
price = Mint.money(50, 'USD')
|
|
123
|
+
min_price = Mint.money(75, 'USD')
|
|
124
|
+
|
|
125
|
+
price.clamp(0, 100) #=> [USD 50.00] (returns self, no new object)
|
|
126
|
+
price.clamp(0, 25) #=> [USD 25.00] (clamped to max)
|
|
127
|
+
price.clamp(min_price, 100) #=> [USD 75.00] (clamped to min)
|
|
128
|
+
|
|
129
|
+
# Clamp accepts Money bounds or Numeric amounts
|
|
130
|
+
price.clamp(min_price, 100) #=> [USD 75.00]
|
|
131
|
+
|
|
119
132
|
# Ranges and enumeration are supported
|
|
120
133
|
|
|
121
134
|
1.dollar..10.dollars #=> [USD 1.00]..[USD 10.00]
|
data/Rakefile
CHANGED
|
@@ -100,7 +100,7 @@ This makes the table *defensible* rather than aspirational.
|
|
|
100
100
|
|
|
101
101
|
## ๐ P1 โ Correctness, clarity, small ergonomics
|
|
102
102
|
|
|
103
|
-
### P1-1
|
|
103
|
+
### P1-1 ๐ก `Money.from_fractional(integer, currency)`
|
|
104
104
|
|
|
105
105
|
**Status:** Merged on branch `feature/money-from-fractional` (commit
|
|
106
106
|
`150a2d0`).
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# Rubocop Issues Assessment: Minting Gem
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-06-03
|
|
4
|
+
**Total Issues Found:** 93 (88 conventions, 5 warnings)
|
|
5
|
+
**Files Analyzed:** 37
|
|
6
|
+
**Files with Issues:** 23
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Executive Summary
|
|
11
|
+
|
|
12
|
+
The minting gem's rubocop analysis reveals **93 total violations**, primarily concentrated in test code rather than production code. The violations fall into two broad categories:
|
|
13
|
+
|
|
14
|
+
1. **Production Code Issues (9 violations):** Focused on architectural patterns, complexity, and parameter design
|
|
15
|
+
2. **Test Code Issues (84 violations):** Mostly style/complexity issues in test assertions
|
|
16
|
+
|
|
17
|
+
The good news: **Most issues are low-risk conventions** affecting code style and test organization, not correctness or performance. The critical items requiring attention are marked with โ ๏ธ.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Issues by Category
|
|
22
|
+
|
|
23
|
+
### ๐จ Critical Issues (Must Address)
|
|
24
|
+
|
|
25
|
+
#### 1. **Lint/ItWithoutArgumentsInBlock** (Warning)
|
|
26
|
+
- **Severity:** Warning (Ruby 3.4 compatibility)
|
|
27
|
+
- **Location:** `lib/minting/money/allocation.rb:56`
|
|
28
|
+
- **Issue:** `it` calls without arguments will have different semantics in Ruby 3.4
|
|
29
|
+
- **Action:** Use `it()` or `self.it` explicitly
|
|
30
|
+
- **Status:** Actionable, simple fix
|
|
31
|
+
|
|
32
|
+
#### 2. **Lint/EmptyWhen** (Warnings) - 4 occurrences
|
|
33
|
+
- **Severity:** Warning
|
|
34
|
+
- **Locations:** `lib/minting/money/money.rb` (lines 136, 137, 147, 148)
|
|
35
|
+
- **Issue:** Empty `when` branches in case statement (likely fall-through logic)
|
|
36
|
+
- **Action:** Either add `# rubocop:disable` comment if intentional, or refactor the case logic
|
|
37
|
+
- **Status:** Needs clarification on intent
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
### โ ๏ธ High Priority - Production Code (Should Address Soon)
|
|
42
|
+
|
|
43
|
+
#### 1. **ThreadSafety/ClassInstanceVariable** - 4 occurrences
|
|
44
|
+
- **Severity:** Convention
|
|
45
|
+
- **Location:** `lib/minting/mint/registry.rb` (lines 21, 64, 72, 80)
|
|
46
|
+
- **Issue:** Class instance variables used in registry pattern for caching
|
|
47
|
+
- **Current Design:** Registry caches currencies and symbol mappings using `@@` class variables
|
|
48
|
+
- **Trade-offs:**
|
|
49
|
+
- โ
Simple, works well for single-threaded scenarios
|
|
50
|
+
- โ Not thread-safe; could cause race conditions if accessed concurrently
|
|
51
|
+
- โ Rubocop flags this as an anti-pattern for multi-threaded apps
|
|
52
|
+
- **Recommendation:** Current design is acceptable for a gem that caches static data (currencies don't change at runtime). Consider adding thread-safety if multi-threaded use becomes a requirement.
|
|
53
|
+
- **Effort:** Medium (if refactored, would use class-level synchronization or lazy initialization)
|
|
54
|
+
|
|
55
|
+
#### 2. **Metrics/ParameterLists** - 1 occurrence
|
|
56
|
+
- **Severity:** Convention
|
|
57
|
+
- **Location:** `lib/minting/mint/currency.rb:23`
|
|
58
|
+
- **Issue:** Currency constructor has 6 parameters (limit: 5)
|
|
59
|
+
- **Current Signature:** `initialize(code:, subunit:, symbol: nil, priority: 0, minimum_amount: nil, ...)`
|
|
60
|
+
- **Recommendation:** Consider if all parameters are essential. Could use keyword arguments or a builder pattern.
|
|
61
|
+
- **Effort:** Low-to-Medium (API surface change)
|
|
62
|
+
|
|
63
|
+
#### 3. **Metrics/AbcSize (Low Severity)** - 3 occurrences in production code
|
|
64
|
+
- **Severity:** Convention
|
|
65
|
+
- **Locations:**
|
|
66
|
+
- `lib/minting/money/allocation.rb:15` - `allocate` method (18.68/17)
|
|
67
|
+
- `lib/minting/money/formatting.rb:81` - `format_amount` method (22.65/17)
|
|
68
|
+
- `lib/minting/money/money.rb:122` - `clamp` method (20.05/17)
|
|
69
|
+
- **Issue:** These methods slightly exceed the complexity threshold, but are core business logic
|
|
70
|
+
- **Recommendation:** These are acceptable as-is; splitting would reduce clarity. Monitor if they grow further.
|
|
71
|
+
- **Effort:** Low priority; monitor only
|
|
72
|
+
|
|
73
|
+
#### 4. **Metrics/CyclomaticComplexity & Metrics/PerceivedComplexity** - 3 occurrences
|
|
74
|
+
- **Severity:** Convention
|
|
75
|
+
- **Locations:**
|
|
76
|
+
- `lib/minting/money/money.rb:122` - `clamp` method
|
|
77
|
+
- `lib/minting/money/parse.rb:43` - `normalize_separators` method
|
|
78
|
+
- `test/performance/benchmark_helper.rb:71` - test utility
|
|
79
|
+
- **Issue:** Multiple conditional branches increase cyclomatic complexity
|
|
80
|
+
- **Recommendation:** These methods handle multi-condition business logic; acceptable for now. Refactor only if maintainability becomes an issue.
|
|
81
|
+
- **Effort:** Low priority
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### ๐ Medium Priority - Test Code Issues
|
|
86
|
+
|
|
87
|
+
#### 1. **Minitest/MultipleAssertions** - 34 occurrences (37% of total issues)
|
|
88
|
+
- **Severity:** Convention
|
|
89
|
+
- **Affected Files:** Most test files
|
|
90
|
+
- **Issue:** Test methods exceed 3 assertions (max threshold)
|
|
91
|
+
- **Examples:**
|
|
92
|
+
- `test/mint_test.rb:21` - 10 assertions
|
|
93
|
+
- `test/money/money_allocation_test.rb:26` - 12 assertions
|
|
94
|
+
- `test/money/money_arithmetics_test.rb:6` - 7 assertions
|
|
95
|
+
- **Rationale in Minting:** This is a measurement gem where comprehensive verification is necessary. Each operation's correctness depends on multiple properties:
|
|
96
|
+
- Amount correctness
|
|
97
|
+
- Currency preservation
|
|
98
|
+
- Rounding behavior
|
|
99
|
+
- Edge cases (zero, negative, large values)
|
|
100
|
+
- **Recommendation:** Consider documenting this intentional choice in `.rubocop.yml` or refactoring into helper assertions that reduce per-test assertion count while maintaining test coverage.
|
|
101
|
+
- **Effort:** Medium-to-High (would require restructuring test organization)
|
|
102
|
+
|
|
103
|
+
#### 2. **Metrics/AbcSize (Test Context)** - 22 occurrences
|
|
104
|
+
- **Severity:** Convention
|
|
105
|
+
- **Affected Files:** Primarily benchmark and test files
|
|
106
|
+
- **Issue:** Complex test methods with many branches
|
|
107
|
+
- **Recommendation:** These are acceptable in test code; they reflect the complexity of the scenarios being tested. Low priority.
|
|
108
|
+
- **Effort:** Low priority
|
|
109
|
+
|
|
110
|
+
#### 3. **Metrics/ClassLength** - 4 occurrences
|
|
111
|
+
- **Severity:** Convention
|
|
112
|
+
- **Locations:**
|
|
113
|
+
- `test/money/money_format_test.rb:3` - 232 lines
|
|
114
|
+
- `test/performance/algorithm_benchmark.rb:5` - 172 lines
|
|
115
|
+
- `test/performance/competitive_performance_benchmark.rb:5` - 182 lines
|
|
116
|
+
- `test/performance/regression_benchmark.rb:5` - 151 lines
|
|
117
|
+
- **Issue:** Test classes exceed 100-line limit
|
|
118
|
+
- **Recommendation:** These are large test suites for good reason (comprehensive coverage). Could be split into smaller classes, but test organization should prioritize clarity over line counts.
|
|
119
|
+
- **Effort:** Medium (if refactored)
|
|
120
|
+
|
|
121
|
+
#### 4. **Metrics/BlockLength** - 3 occurrences
|
|
122
|
+
- **Severity:** Convention
|
|
123
|
+
- **Affected Files:** Performance benchmark files
|
|
124
|
+
- **Issue:** Block length exceeds 25 lines
|
|
125
|
+
- **Recommendation:** Low priority for benchmarks; these require comprehensive setup and measurement
|
|
126
|
+
- **Effort:** Low priority
|
|
127
|
+
|
|
128
|
+
#### 5. **Metrics/ModuleLength** - 1 occurrence
|
|
129
|
+
- **Severity:** Convention
|
|
130
|
+
- **Location:** `test/performance/benchmark_helper.rb:11` (105 lines)
|
|
131
|
+
- **Issue:** Module slightly exceeds 100-line limit
|
|
132
|
+
- **Recommendation:** This is a utility module for benchmarks; acceptable as-is
|
|
133
|
+
- **Effort:** Low priority
|
|
134
|
+
|
|
135
|
+
#### 6. **Performance/CollectionLiteralInLoop** - 1 occurrence
|
|
136
|
+
- **Severity:** Convention
|
|
137
|
+
- **Location:** `test/performance/algorithm_benchmark.rb:19`
|
|
138
|
+
- **Issue:** Array literal created in loop
|
|
139
|
+
- **Recommendation:** Extract to constant or variable outside loop for performance
|
|
140
|
+
- **Effort:** Low
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Issue Distribution Analysis
|
|
145
|
+
|
|
146
|
+
### By Component
|
|
147
|
+
```
|
|
148
|
+
Production Code Issues:
|
|
149
|
+
โโโ lib/minting/mint/currency.rb: 1
|
|
150
|
+
โโโ lib/minting/mint/registry.rb: 4 (thread safety)
|
|
151
|
+
โโโ lib/minting/money/allocation.rb: 2
|
|
152
|
+
โโโ lib/minting/money/formatting.rb: 1
|
|
153
|
+
โโโ lib/minting/money/money.rb: 7 (complexity)
|
|
154
|
+
โโโ lib/minting/money/parse.rb: 2
|
|
155
|
+
|
|
156
|
+
Test/Benchmark Issues:
|
|
157
|
+
โโโ Unit tests (test/): 43
|
|
158
|
+
โโโ Benchmarks (test/performance/): 42
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### By Severity
|
|
162
|
+
- **Warnings:** 5 (Ruby 3.4 compatibility + empty when branches)
|
|
163
|
+
- **Conventions:** 88 (style, complexity, structure)
|
|
164
|
+
|
|
165
|
+
### By Cop Type
|
|
166
|
+
```
|
|
167
|
+
Minitest/MultipleAssertions 34 (37%) - Test assertion counts
|
|
168
|
+
Metrics/AbcSize 33 (35%) - Complexity of methods
|
|
169
|
+
ThreadSafety/ClassInstanceVar 4 (4%) - Registry caching pattern
|
|
170
|
+
Lint/EmptyWhen 4 (4%) - Incomplete case logic
|
|
171
|
+
Metrics/ClassLength 4 (4%) - Large test classes
|
|
172
|
+
Metrics/CyclomaticComplexity 3 (3%) - Branch complexity
|
|
173
|
+
Metrics/PerceivedComplexity 3 (3%) - Subjective complexity
|
|
174
|
+
Metrics/BlockLength 3 (3%) - Large blocks
|
|
175
|
+
Others 5 (5%) - Various minor issues
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Rubocop Configuration
|
|
181
|
+
|
|
182
|
+
**Current thresholds** (in `.rubocop.yml`):
|
|
183
|
+
- Line length: 120 characters
|
|
184
|
+
- Method length: 30 lines
|
|
185
|
+
- Default complexity thresholds apply
|
|
186
|
+
|
|
187
|
+
**Active plugins:**
|
|
188
|
+
- rubocop-minitest (for test-specific rules)
|
|
189
|
+
- rubocop-packaging (gem packaging rules)
|
|
190
|
+
- rubocop-performance (performance optimizations)
|
|
191
|
+
- rubocop-rake (Rakefile rules)
|
|
192
|
+
- rubocop-thread_safety (concurrency patterns)
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Recommendations by Priority
|
|
197
|
+
|
|
198
|
+
### Priority 1: Fix Immediately
|
|
199
|
+
- [ ] Fix `Lint/ItWithoutArgumentsInBlock` in `lib/minting/money/allocation.rb:56`
|
|
200
|
+
- **Effort:** < 5 minutes
|
|
201
|
+
- **Impact:** Ensures Ruby 3.4+ compatibility
|
|
202
|
+
|
|
203
|
+
- [ ] Clarify or fix `Lint/EmptyWhen` in `lib/minting/money/money.rb` (4 lines)
|
|
204
|
+
- **Effort:** < 10 minutes
|
|
205
|
+
- **Impact:** Clarifies intent of case statement logic
|
|
206
|
+
|
|
207
|
+
### Priority 2: Plan Refactoring
|
|
208
|
+
- [ ] Evaluate thread-safety requirements for `Registry` class
|
|
209
|
+
- **Effort:** Medium
|
|
210
|
+
- **Impact:** Determines if class variable pattern is acceptable
|
|
211
|
+
- **Timeline:** Consider for v2.0 or if multi-threaded use is required
|
|
212
|
+
|
|
213
|
+
- [ ] Decide on parameter design for `Currency` (6 parameters vs. 5 max)
|
|
214
|
+
- **Effort:** Low
|
|
215
|
+
- **Impact:** Could simplify API
|
|
216
|
+
- **Timeline:** Can defer if current design is stable
|
|
217
|
+
|
|
218
|
+
### Priority 3: Test Organization (Optional)
|
|
219
|
+
- [ ] Consider refactoring test assertions to reduce `Minitest/MultipleAssertions` violations
|
|
220
|
+
- **Effort:** High
|
|
221
|
+
- **Impact:** Improves test organization, but current approach is acceptable
|
|
222
|
+
- **Timeline:** Only if test maintainability becomes an issue
|
|
223
|
+
|
|
224
|
+
- [ ] Consider splitting large test/benchmark classes (`money_format_test.rb`, benchmark files)
|
|
225
|
+
- **Effort:** Medium-High
|
|
226
|
+
- **Impact:** Improves file organization
|
|
227
|
+
- **Timeline:** Optional; not critical
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Configuration Adjustments
|
|
232
|
+
|
|
233
|
+
### Option A: Accept Current Baseline (Recommended)
|
|
234
|
+
Keep current `.rubocop.yml` configuration. The violations represent:
|
|
235
|
+
- Acceptable complexity trade-offs for a measurement gem
|
|
236
|
+
- Best practices for test organization given domain requirements
|
|
237
|
+
- Intentional design choices for performance and clarity
|
|
238
|
+
|
|
239
|
+
### Option B: Relax Thresholds
|
|
240
|
+
Add exceptions to `.rubocop.yml` for known violations:
|
|
241
|
+
```yaml
|
|
242
|
+
Minitest/MultipleAssertions:
|
|
243
|
+
Enabled: false # Disable for this gem; multi-assertion tests are necessary
|
|
244
|
+
|
|
245
|
+
Metrics/AbcSize:
|
|
246
|
+
Exclude:
|
|
247
|
+
- test/performance/**/*
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Option C: Strict Compliance (Not Recommended)
|
|
251
|
+
Refactor to meet all thresholds. Would require:
|
|
252
|
+
- Splitting large test classes (disruptive to test organization)
|
|
253
|
+
- Reducing assertions per test (reduces coverage clarity)
|
|
254
|
+
- Architectural changes to reduce method complexity
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Summary Table
|
|
259
|
+
|
|
260
|
+
| Category | Count | Severity | Effort | Recommendation |
|
|
261
|
+
|----------|-------|----------|--------|-----------------|
|
|
262
|
+
| Ruby 3.4 Compatibility | 1 | High | Low | **Fix immediately** |
|
|
263
|
+
| Empty When Branches | 4 | Medium | Low | **Fix/clarify** |
|
|
264
|
+
| Thread Safety | 4 | Medium | Medium | Monitor & plan |
|
|
265
|
+
| Parameter Lists | 1 | Low | Low | Optional refactor |
|
|
266
|
+
| Method Complexity | 8 | Low | Medium | Accept as-is |
|
|
267
|
+
| Test Assertions | 34 | Low | High | Accept/document intent |
|
|
268
|
+
| Test Organization | 37 | Low | High | Optional refactor |
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Next Steps
|
|
273
|
+
|
|
274
|
+
1. **Immediate (This Sprint):**
|
|
275
|
+
- Fix `Lint/ItWithoutArgumentsInBlock` warning
|
|
276
|
+
- Address `Lint/EmptyWhen` warnings (fix or document intent)
|
|
277
|
+
- Run full test suite to verify fixes don't break anything
|
|
278
|
+
|
|
279
|
+
2. **Short-term (This Quarter):**
|
|
280
|
+
- Evaluate thread-safety requirements
|
|
281
|
+
- Decide on currency parameter design
|
|
282
|
+
- Document rationale for test assertion counts
|
|
283
|
+
|
|
284
|
+
3. **Long-term (v2.0 Planning):**
|
|
285
|
+
- Consider architectural improvements to reduce complexity
|
|
286
|
+
- Evaluate thread-safety refactoring if multi-threaded use becomes a requirement
|
|
287
|
+
- Plan test organization improvements if maintainability becomes an issue
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Appendix: Full Issue List
|
|
292
|
+
|
|
293
|
+
### Production Code - All Issues
|
|
294
|
+
|
|
295
|
+
**lib/minting/mint/currency.rb**
|
|
296
|
+
- Line 23: `Metrics/ParameterLists` - 6 parameters vs. 5 max
|
|
297
|
+
|
|
298
|
+
**lib/minting/mint/registry.rb**
|
|
299
|
+
- Line 21, 64, 72, 80: `ThreadSafety/ClassInstanceVariable` - 4 violations (class instance variables for caching)
|
|
300
|
+
|
|
301
|
+
**lib/minting/money/allocation.rb**
|
|
302
|
+
- Line 15: `Metrics/AbcSize` - allocate method (18.68/17)
|
|
303
|
+
- Line 56: `Lint/ItWithoutArgumentsInBlock` - Warning for Ruby 3.4
|
|
304
|
+
|
|
305
|
+
**lib/minting/money/formatting.rb**
|
|
306
|
+
- Line 81: `Metrics/AbcSize` - format_amount method (22.65/17)
|
|
307
|
+
|
|
308
|
+
**lib/minting/money/money.rb**
|
|
309
|
+
- Line 122: `Metrics/AbcSize` - clamp method (20.05/17)
|
|
310
|
+
- Line 122: `Metrics/CyclomaticComplexity` - clamp method (11/7)
|
|
311
|
+
- Line 122: `Metrics/PerceivedComplexity` - clamp method (10/8)
|
|
312
|
+
- Lines 136, 137, 147, 148: `Lint/EmptyWhen` - 4 empty when branches
|
|
313
|
+
|
|
314
|
+
**lib/minting/money/parse.rb**
|
|
315
|
+
- Line 43: `Metrics/CyclomaticComplexity` - normalize_separators (8/7)
|
|
316
|
+
- Line 43: `Metrics/PerceivedComplexity` - normalize_separators (9/8)
|
|
317
|
+
|
|
318
|
+
### Test Code - Summary
|
|
319
|
+
- 34 `Minitest/MultipleAssertions` violations across test files
|
|
320
|
+
- 22 `Metrics/AbcSize` violations in tests
|
|
321
|
+
- 4 `Metrics/ClassLength` violations
|
|
322
|
+
- 3 `Metrics/BlockLength` violations in benchmarks
|
|
323
|
+
- 1 `Metrics/ModuleLength` in benchmark_helper.rb
|
|
324
|
+
- 1 `Performance/CollectionLiteralInLoop` in algorithm_benchmark.rb
|
|
325
|
+
|
|
326
|
+
See detailed output above for specific line numbers and messages.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
**Generated:** 2026-06-03
|
|
331
|
+
**Rubocop Version:** 1.87.0
|
|
332
|
+
**Ruby Version:** 4.0.1
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Mint
|
|
2
4
|
# Represents a specific currency unit, identified by ISO 4217 alphabetic code
|
|
3
5
|
#
|
|
@@ -8,13 +10,12 @@ module Mint
|
|
|
8
10
|
:fractional_multiplier, :minimum_amount,
|
|
9
11
|
:name, :priority
|
|
10
12
|
|
|
11
|
-
def inspect
|
|
12
|
-
"<Currency:(#{code} #{symbol} #{subunit})>"
|
|
13
|
-
end
|
|
13
|
+
def inspect = "<Currency:(#{code} #{symbol} #{subunit} #{name})>"
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
# Normalizes numeric amounts for this currency
|
|
16
|
+
# 1. Converts to Rational
|
|
17
|
+
# 2. Rounds to respect currency subunit
|
|
18
|
+
def normalize_amount(amount) = amount.to_r.round(subunit)
|
|
18
19
|
|
|
19
20
|
private
|
|
20
21
|
|
|
@@ -26,7 +27,7 @@ module Mint
|
|
|
26
27
|
@country = country
|
|
27
28
|
@name = name
|
|
28
29
|
@fractional_multiplier = 10**@subunit
|
|
29
|
-
@minimum_amount =
|
|
30
|
+
@minimum_amount = Rational(1, fractional_multiplier)
|
|
30
31
|
freeze
|
|
31
32
|
end
|
|
32
33
|
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
# Mint currency store (internal)
|
|
6
|
+
module Mint
|
|
7
|
+
# Internal currency storage and loading.
|
|
8
|
+
# Manages the registry cache and currency symbol lookups.
|
|
9
|
+
module CurrencyStore
|
|
10
|
+
# Returns the hash of all registered currencies.
|
|
11
|
+
#
|
|
12
|
+
# @return [Hash{String => Currency}] registered currencies mapped by code
|
|
13
|
+
# @api private
|
|
14
|
+
def self.currencies
|
|
15
|
+
@currencies ||= begin
|
|
16
|
+
registry = { 'XXX' => Currency.new(code: 'XXX', name: 'No currency', symbol: 'ยค') }
|
|
17
|
+
load_currencies(registry)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Registered symbols sorted for detection: longest match wins, then parser priority.
|
|
22
|
+
#
|
|
23
|
+
# @return [Array<Array<String, Currency>>] sorted symbol-to-currency mappings
|
|
24
|
+
# @api private
|
|
25
|
+
def self.currency_symbols
|
|
26
|
+
@currency_symbols ||= begin
|
|
27
|
+
currencies.values
|
|
28
|
+
.reject { |currency| currency.symbol.empty? }
|
|
29
|
+
.map { |currency| [currency.symbol, currency] }
|
|
30
|
+
.sort_by { |symbol, currency| [-symbol.length, -currency.priority] }
|
|
31
|
+
end.freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Clears and refreshes the currency symbol cache.
|
|
35
|
+
# Called when currencies are registered.
|
|
36
|
+
#
|
|
37
|
+
# @api private
|
|
38
|
+
def self.invalidate_symbols_cache
|
|
39
|
+
@currency_symbols = nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Loads currencies from YAML file into the registry.
|
|
43
|
+
#
|
|
44
|
+
# @param registry [Hash] the registry hash to populate
|
|
45
|
+
# @return [Hash] the populated registry
|
|
46
|
+
# @api private
|
|
47
|
+
def self.load_currencies(registry)
|
|
48
|
+
base = File.expand_path('../data', __dir__)
|
|
49
|
+
path = File.join(base, 'currencies.yaml')
|
|
50
|
+
|
|
51
|
+
data = YAML.load_file(path)
|
|
52
|
+
data.each do |entry|
|
|
53
|
+
code = entry['code']
|
|
54
|
+
registry[code] = Currency.new(
|
|
55
|
+
code: code,
|
|
56
|
+
subunit: entry['subunit'],
|
|
57
|
+
symbol: entry['symbol'],
|
|
58
|
+
priority: entry['priority'],
|
|
59
|
+
country: entry['country'],
|
|
60
|
+
name: entry['name']
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
registry
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private_class_method :load_currencies
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Mint currency registration and factory (public API)
|
|
3
4
|
module Mint
|
|
4
5
|
# Creates a new {Money} instance with the given amount and currency code.
|
|
5
6
|
#
|
|
6
7
|
# @param amount [Numeric] the financial value
|
|
7
|
-
# @param currency_code [String
|
|
8
|
+
# @param currency_code [String] the ISO currency code
|
|
8
9
|
# @return [Money] the instantiated Money object
|
|
9
10
|
# @raise [ArgumentError] if the currency code is not registered
|
|
10
11
|
def self.money(amount, currency_code)
|
|
11
12
|
currency = currency(currency_code)
|
|
12
13
|
return Money.create(amount, currency) if currency
|
|
13
14
|
|
|
14
|
-
raise ArgumentError, "[#{currency.inspect}] is not a registered currency.
|
|
15
|
+
raise ArgumentError, "[#{currency.inspect}] is not a registered currency."
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
# Returns default zero, no currency money
|
|
@@ -23,7 +24,7 @@ module Mint
|
|
|
23
24
|
# @param currency [String, Currency] the currency identifier or object
|
|
24
25
|
# @return [Currency, nil] the registered Currency instance or nil if not found
|
|
25
26
|
def self.currency(currency)
|
|
26
|
-
currency.is_a?(Currency) ? currency : currencies[currency]
|
|
27
|
+
currency.is_a?(Currency) ? currency : CurrencyStore.currencies[currency]
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
# Registers a new currency if not already registered.
|
|
@@ -34,8 +35,8 @@ module Mint
|
|
|
34
35
|
# @param priority [Integer] parser precedence priority (defaults to 0)
|
|
35
36
|
# @return [Currency] the registered or existing Currency instance
|
|
36
37
|
# @raise [ArgumentError] if the code layout is invalid or register throws an error
|
|
37
|
-
def self.register_currency(code:, subunit:
|
|
38
|
-
currencies[code] || register_currency!(code:, subunit:, symbol:, priority:)
|
|
38
|
+
def self.register_currency(code:, subunit: 0, symbol: '', priority: 0)
|
|
39
|
+
CurrencyStore.currencies[code] || register_currency!(code:, subunit:, symbol:, priority:)
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
# Strictly registers a new currency, raising a KeyError if already registered.
|
|
@@ -54,52 +55,20 @@ module Mint
|
|
|
54
55
|
"Currency code must only letters or '_' ('USD',, 'MY_COIN')"
|
|
55
56
|
end
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
raise KeyError, "Currency: #{code} already registered" if
|
|
58
|
+
currencies = CurrencyStore.currencies
|
|
59
|
+
raise KeyError, "Currency: #{code} already registered" if currencies[code]
|
|
59
60
|
|
|
60
61
|
currency = currencies[code] = Currency.new(code:, subunit:, symbol:, priority:)
|
|
61
|
-
|
|
62
|
+
CurrencyStore.invalidate_symbols_cache
|
|
62
63
|
currency
|
|
63
64
|
end
|
|
64
65
|
|
|
65
|
-
# Returns the hash of all registered currencies.
|
|
66
|
-
#
|
|
67
|
-
# @return [Hash{String => Currency}] registered currencies mapped by code
|
|
68
|
-
def self.currencies
|
|
69
|
-
@currencies ||= begin
|
|
70
|
-
registry = { 'XXX' => Currency.new(code: 'XXX', name: 'No currency', symbol: 'ยค') }
|
|
71
|
-
load_currencies(registry)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
66
|
# Registered symbols sorted for detection: longest match wins, then parser priority.
|
|
67
|
+
# Internal API - used by Money parser.
|
|
68
|
+
#
|
|
69
|
+
# @return [Array<Array<String, Currency>>] sorted symbol-to-currency mappings
|
|
70
|
+
# @api private
|
|
76
71
|
def self.currency_symbols
|
|
77
|
-
|
|
78
|
-
currencies.values
|
|
79
|
-
.map { |currency| [currency.symbol, currency] }
|
|
80
|
-
.reject { |symbol, _| symbol.empty? }
|
|
81
|
-
.sort_by { |symbol, currency| [-symbol.length, -currency.priority] }
|
|
82
|
-
end.freeze
|
|
72
|
+
CurrencyStore.currency_symbols
|
|
83
73
|
end
|
|
84
|
-
|
|
85
|
-
def self.load_currencies(registry)
|
|
86
|
-
base = File.expand_path('../data', __dir__)
|
|
87
|
-
path = File.join(base, 'currencies.yaml')
|
|
88
|
-
|
|
89
|
-
data = YAML.load_file(path)
|
|
90
|
-
data.each do |entry|
|
|
91
|
-
code = entry['code']
|
|
92
|
-
registry[code] = Currency.new(
|
|
93
|
-
code: code,
|
|
94
|
-
subunit: entry['subunit'],
|
|
95
|
-
symbol: entry['symbol'],
|
|
96
|
-
priority: entry['priority'],
|
|
97
|
-
country: entry['country'],
|
|
98
|
-
name: entry['name']
|
|
99
|
-
)
|
|
100
|
-
end
|
|
101
|
-
registry
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
private_class_method :load_currencies
|
|
105
74
|
end
|
data/lib/minting/mint.rb
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Mint
|
|
4
|
+
# Allocation and splitting
|
|
2
5
|
class Money
|
|
3
6
|
# Proportionally allocates the monetary amount among a list of ratios.
|
|
4
7
|
# Disperses any subunit rounding amounts across the initial slots
|
|
@@ -50,7 +53,7 @@ module Mint
|
|
|
50
53
|
last_slot = (left_over / minimum).to_i - 1
|
|
51
54
|
(0..last_slot).each { |slot| amounts[slot] += minimum }
|
|
52
55
|
end
|
|
53
|
-
amounts.map { Money.new(
|
|
56
|
+
amounts.map { |amount| Money.new(amount, currency) }
|
|
54
57
|
end
|
|
55
58
|
end
|
|
56
59
|
end
|