minting 1.3.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 +31 -3
- data/Rakefile +4 -4
- data/doc/agents/AGENTS.md +25 -0
- data/doc/agents/copilot-instructions.md +75 -0
- data/doc/agents/gemini_gem_evaluation.md +245 -0
- data/doc/agents/recommendations.md +335 -0
- data/doc/agents/rubocop-issues.md +332 -0
- data/lib/minting/mint/currency.rb +9 -8
- data/lib/minting/mint/currency_store.rb +68 -0
- data/lib/minting/mint/refinements.rb +3 -1
- data/lib/minting/mint/registry.rb +21 -61
- 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 +9 -17
- data/lib/minting/money/formatting.rb +42 -8
- data/lib/minting/money/money.rb +60 -34
- 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
- data/minting.gemspec +1 -0
- metadata +23 -2
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# `minting` — Recommendations Roadmap
|
|
2
|
+
|
|
3
|
+
Living document of improvement recommendations for the `minting` gem,
|
|
4
|
+
produced from a full read of `AGENTS.md`, `README.md`, `CHANGELOG.md`,
|
|
5
|
+
and the entire `lib/` + `test/` tree (v1.3.0 baseline).
|
|
6
|
+
|
|
7
|
+
**Conventions**
|
|
8
|
+
|
|
9
|
+
- P0 / P1 / P2 / P3 = priority. P0 = small, high-value, ship now.
|
|
10
|
+
- ✅ = done (committed on `master` or an in-flight branch).
|
|
11
|
+
- 🟡 = partially done.
|
|
12
|
+
- Each item is sized to be a single PR unless marked *[epic]*.
|
|
13
|
+
- File references are relative to repo root.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## TL;DR
|
|
18
|
+
|
|
19
|
+
The gem is in **excellent shape**: ~1,400 LoC, 100% line coverage,
|
|
20
|
+
RuboCop + Reek clean, performance bench suite, immutable `Rational`
|
|
21
|
+
internals, 117 built-in currencies. The recommendations below are
|
|
22
|
+
**polish, hardening, and roadmap** — not "fix the broken thing".
|
|
23
|
+
|
|
24
|
+
Two of the highest-leverage moves are already in flight:
|
|
25
|
+
|
|
26
|
+
- ✅ **README-as-spec** — `test_readme_usage` now asserts the
|
|
27
|
+
README's `to_s` / `to_json` / `to_hash` / `split` / `allocate`
|
|
28
|
+
examples instead of just exercising them silently.
|
|
29
|
+
- ✅ **`Money.from_fractional`** — symmetric inverse of `#fractional`
|
|
30
|
+
with full test + README + CHANGELOG coverage.
|
|
31
|
+
|
|
32
|
+
The remaining work is mostly documentation discipline, API ergonomics,
|
|
33
|
+
and the two roadmap items already advertised in the README (I18n +
|
|
34
|
+
exchange rates).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 🔴 P0 — Quick wins, ship now
|
|
39
|
+
|
|
40
|
+
### P0-1 ✅ Tighten README-as-spec
|
|
41
|
+
|
|
42
|
+
**Status:** Merged in commit `20359dc`.
|
|
43
|
+
|
|
44
|
+
- Turned 14 commented `#=>` examples in `test/minting_test.rb` into
|
|
45
|
+
real `assert_equal` calls.
|
|
46
|
+
- Fixed typo: `conversiob` → `conversion`.
|
|
47
|
+
- Corrected the README's `to_json` example to match the actual output
|
|
48
|
+
(the real string has spaces around the `:`).
|
|
49
|
+
- Softened the "2× faster" headline to point at the Performance
|
|
50
|
+
section where the measured ratios live.
|
|
51
|
+
- Added a one-line `rubocop:disable` on `test_readme_usage` (the spec
|
|
52
|
+
has to stay monolithic to serve as a spec).
|
|
53
|
+
|
|
54
|
+
**Why it mattered:** the test was passing while the README silently
|
|
55
|
+
drifted. Now any example change in the README either stays correct or
|
|
56
|
+
fails CI.
|
|
57
|
+
|
|
58
|
+
### P0-2 🟡 Auto-require `bigdecimal` and clean up `to_d`
|
|
59
|
+
|
|
60
|
+
**Status:** Partial — the dependency was added in commit `33b2c01`
|
|
61
|
+
(`s.add_dependency 'bigdecimal', '>= 4.0'` in `minting.gemspec`).
|
|
62
|
+
The runtime guard in `lib/minting/money/conversion.rb` (lines 10–14)
|
|
63
|
+
is now redundant:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
def to_d
|
|
67
|
+
raise NoMethodError, 'decimal gem required' unless defined?(BigDecimal)
|
|
68
|
+
amount.to_d 0
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Follow-up:** delete the `raise NoMethodError` line. It's dead code
|
|
73
|
+
once `bigdecimal` is a hard dependency. One-line PR.
|
|
74
|
+
|
|
75
|
+
### P0-3 ✅ Fix CHANGELOG typos
|
|
76
|
+
|
|
77
|
+
**Status:** Done in commit `20359dc` — "is no private" →
|
|
78
|
+
"is now private" in the v1.3.0 entry. Watch for new typos as
|
|
79
|
+
items are added under `[Unreleased]`.
|
|
80
|
+
|
|
81
|
+
### P0-4 Add a CI gate on the marketing claims
|
|
82
|
+
|
|
83
|
+
The README's "**2× faster**" / "**10×+ for formatting**" headline and
|
|
84
|
+
the table at the bottom (lines 213–247) are generated from a single
|
|
85
|
+
Qwen run on 2026-05-30 and aren't reproducible from CI.
|
|
86
|
+
|
|
87
|
+
**Concrete action:** in CI, run
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
BENCH=true rake bench:regression
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
and fail the build if any benchmark regresses by more than (e.g.)
|
|
94
|
+
20% vs the stored baseline in `pkg/bench_baseline.json`. The baseline
|
|
95
|
+
file is optional but easy to maintain.
|
|
96
|
+
|
|
97
|
+
This makes the table *defensible* rather than aspirational.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 🟠 P1 — Correctness, clarity, small ergonomics
|
|
102
|
+
|
|
103
|
+
### P1-1 🟡 `Money.from_fractional(integer, currency)`
|
|
104
|
+
|
|
105
|
+
**Status:** Merged on branch `feature/money-from-fractional` (commit
|
|
106
|
+
`150a2d0`).
|
|
107
|
+
|
|
108
|
+
- Inverse of `#fractional`. Uses `Rational(n, fractional_multiplier)`
|
|
109
|
+
so subunit-0 currencies work without a special case.
|
|
110
|
+
- Integer-only contract — rejects `Float` / `String` / `Rational` with
|
|
111
|
+
`ArgumentError` to preserve the exactness guarantee.
|
|
112
|
+
- Accepts `String`, `Symbol`, or `Currency` for the currency arg,
|
|
113
|
+
matching `Mint.money` / `Money.create`.
|
|
114
|
+
- 4 new tests + 3 new README-spec asserts; 100% coverage maintained.
|
|
115
|
+
|
|
116
|
+
**To do** Add benchmark tests
|
|
117
|
+
|
|
118
|
+
### P1-2 Add `Money#clamp(min, max)`, `Money#min(other)`, `Money#max(other)`
|
|
119
|
+
|
|
120
|
+
`Comparable` is already mixed in (`lib/minting/money/comparable.rb`),
|
|
121
|
+
so `min` and `max` work *implicitly* through the `<=>` machinery —
|
|
122
|
+
but only when both operands are the same currency. A first-class
|
|
123
|
+
`#clamp` would be a one-liner and very useful in domain code
|
|
124
|
+
(pricing floors/ceilings, exchange-rate bands, etc.).
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
# Sketch
|
|
128
|
+
def clamp(min, max)
|
|
129
|
+
raise ArgumentError, 'min/max must share currency' unless
|
|
130
|
+
same_currency?(min) && same_currency?(max)
|
|
131
|
+
return min if self < min
|
|
132
|
+
return max if self > max
|
|
133
|
+
self
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Add tests for: in-range, below-min, above-max, equal-to-bound,
|
|
138
|
+
non-matching-currency error.
|
|
139
|
+
|
|
140
|
+
### P1-3 Improve `to_s` format-hash handling
|
|
141
|
+
|
|
142
|
+
`lib/minting/money/formatting.rb` accepts `format:` as either a
|
|
143
|
+
`String` or a `Hash` with `:positive` / `:negative` / `:zero` keys.
|
|
144
|
+
The current code:
|
|
145
|
+
|
|
146
|
+
- Falls through to `'%<symbol>s%<amount>f'` if `:positive` is missing
|
|
147
|
+
and the input is non-zero non-negative (line 58).
|
|
148
|
+
- Has no test coverage for the hash form at all.
|
|
149
|
+
|
|
150
|
+
**Action:** add explicit tests for:
|
|
151
|
+
|
|
152
|
+
- `{ positive: '...', negative: '...', zero: '...' }`
|
|
153
|
+
- `{ positive: '...' }` alone (default for neg/zero)
|
|
154
|
+
- Mixing hash and `decimal:` / `thousand:` kwargs
|
|
155
|
+
|
|
156
|
+
### P1-4 Document and harden registry thread-safety
|
|
157
|
+
|
|
158
|
+
`lib/minting/mint/registry.rb` memoizes `@currencies`,
|
|
159
|
+
`@currency_symbols`, and `@zero` with `||=`. In a multi-threaded
|
|
160
|
+
server (Rails, Sidekiq, Puma) the first call to `Mint.money` from
|
|
161
|
+
two threads can:
|
|
162
|
+
|
|
163
|
+
- Both call `YAML.load_file` and `register` the same currencies.
|
|
164
|
+
- Both call `@currency_symbols ||= ...` and produce a non-frozen copy
|
|
165
|
+
that diverges.
|
|
166
|
+
|
|
167
|
+
**Action:** either
|
|
168
|
+
|
|
169
|
+
1. Wrap the lazy initializers in `Mutex#synchronize` (3 places), or
|
|
170
|
+
2. Switch to `Concurrent::Map` (no new dep), or
|
|
171
|
+
3. Eager-load in a Railtie/loaded hook and treat the registry as
|
|
172
|
+
read-only after boot.
|
|
173
|
+
|
|
174
|
+
Option 3 is the most pragmatic. Document the contract.
|
|
175
|
+
|
|
176
|
+
### P1-5 Make `Mint.currencies` immutable
|
|
177
|
+
|
|
178
|
+
`currencies` returns the live `@currencies` hash. A caller can
|
|
179
|
+
`currencies.delete('USD')` and break invariants. Either:
|
|
180
|
+
|
|
181
|
+
- Return a frozen copy (`@currencies.dup.freeze`), or
|
|
182
|
+
- Document loudly that the returned hash must be treated read-only.
|
|
183
|
+
|
|
184
|
+
### P1-6 Improve `coercion.rb` — add `**` and a clean `==`
|
|
185
|
+
|
|
186
|
+
`lib/minting/money/coercion.rb` defines `CoercedNumber` (used by
|
|
187
|
+
`Money#coerce`) with `+`, `-`, `*`, `/`, `<=>`, but no `**` or `==`.
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
2 ** 0.dollars #=> 1 (works)
|
|
191
|
+
2 ** 5.dollars #=> NoMethodError (silent failure)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`==` falls through to `Comparable`, which raises `ArgumentError`, but
|
|
195
|
+
a custom `==` on `CoercedNumber` would give a more specific error
|
|
196
|
+
message.
|
|
197
|
+
|
|
198
|
+
**Action:** add `**` and `==` to `CoercedNumber`. Tests in
|
|
199
|
+
`test/money/money_coercion_test.rb` (or wherever they live).
|
|
200
|
+
|
|
201
|
+
### P1-7 Add `inspect` round-trip test
|
|
202
|
+
|
|
203
|
+
`Money#inspect` is implemented in `lib/minting/money/money.rb:79`
|
|
204
|
+
as `"[#{currency_code} %0.#{currency.subunit}f]"` — e.g.
|
|
205
|
+
`[USD 9.99]`. The existing parser (`lib/minting/money/parse.rb`)
|
|
206
|
+
can almost read this back, but a clean round-trip would be a nice
|
|
207
|
+
property test:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
m = Mint.money(9.99, 'USD')
|
|
211
|
+
assert_equal m, Mint::Money.parse(m.inspect.delete_prefix('[').delete_suffix(']'))
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Make it a one-liner or a property-based test with 10–20 random
|
|
215
|
+
amounts. Cheap, catches regressions in either direction.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 🟡 P2 — Roadmap items already advertised in README
|
|
220
|
+
|
|
221
|
+
### P2-1 I18n / locale-aware formatting *[epic]*
|
|
222
|
+
|
|
223
|
+
Foundation is already there: `to_s(decimal: ',', thousand: '.')`
|
|
224
|
+
works. What's missing is locale-keyed defaults:
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
Mint.money(9.99, 'USD').to_s(locale: :'pt-BR') #=> "US$ 9,99"
|
|
228
|
+
Mint.money(9.99, 'USD').to_s(locale: :'fr') #=> "9,99 $US"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Action plan (multi-PR):**
|
|
232
|
+
|
|
233
|
+
1. Add `Mint::Locale` value object + a small `locales.yaml` (or
|
|
234
|
+
build off `currencies.yaml` since most formatting info is per-
|
|
235
|
+
currency already).
|
|
236
|
+
2. Add `I18n` integration behind a `require 'minting/i18n'` opt-in.
|
|
237
|
+
3. Default to "no locale" so existing code is unaffected.
|
|
238
|
+
|
|
239
|
+
### P2-2 Exchange rates *[epic]*
|
|
240
|
+
|
|
241
|
+
Mentioned in the README Roadmap. The hard part isn't the math, it's
|
|
242
|
+
the rate source. Suggested shape:
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
Mint.bank = Mint::Bank::MemoryStore.new(
|
|
246
|
+
'USD' => { 'EUR' => 0.92, 'JPY' => 149.50 }
|
|
247
|
+
)
|
|
248
|
+
Mint.money(10, 'USD').exchange('EUR') #=> [EUR 9.20]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Multi-PR epic:
|
|
252
|
+
|
|
253
|
+
1. `Mint::Bank` interface with `#exchange(money, target)`.
|
|
254
|
+
2. `Mint::Bank::MemoryStore` (in-memory, good for tests).
|
|
255
|
+
3. `Mint::Bank::ECB` or `Mint::Bank::OpenExchangeRates` (network).
|
|
256
|
+
4. Update `minting-rails` to expose bank config in the Railtie.
|
|
257
|
+
|
|
258
|
+
### P2-3 Reek / RuboCop tightening
|
|
259
|
+
|
|
260
|
+
`.rubocop_todo.yml` still has 9 pre-existing offenses, all in
|
|
261
|
+
`test/performance/*` and `test/mint_benchmark.rb`. They're
|
|
262
|
+
"`Metrics::BlockLength`" / "`Metrics::MethodLength`" — fixable by
|
|
263
|
+
extracting helper methods or by scoping the cop to non-bench code.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 🟢 P3 — Polish
|
|
268
|
+
|
|
269
|
+
### P3-1 ✅ Move AI-generated docs to `doc/agents/`
|
|
270
|
+
|
|
271
|
+
Done in commit `ff08578`. Consider also moving `pkg/` artifacts to
|
|
272
|
+
a release script that builds on tag rather than committing pre-built
|
|
273
|
+
gems.
|
|
274
|
+
|
|
275
|
+
### P3-2 `Gemfile.lock` policy
|
|
276
|
+
|
|
277
|
+
`Gemfile.lock` is currently committed. The project is a *gem*, not
|
|
278
|
+
an app, so the conventional choice is to *not* commit it (let
|
|
279
|
+
consumers' Bundler resolve). Or, if you prefer reproducibility for
|
|
280
|
+
CI, commit it and document the choice in `CONTRIBUTING.md`.
|
|
281
|
+
|
|
282
|
+
### P3-3 Add `SECURITY.md` and `CODE_OF_CONDUCT.md`
|
|
283
|
+
|
|
284
|
+
Standard hygiene for a public Ruby gem. One PR each, copy-paste
|
|
285
|
+
from the GitHub templates.
|
|
286
|
+
|
|
287
|
+
### P3-4 Document `Money#each_currency` / `Mint.currencies.each_value`
|
|
288
|
+
|
|
289
|
+
The README's "Features" list doesn't mention that you can iterate
|
|
290
|
+
registered currencies. Trivial doc addition; potential ergonomic
|
|
291
|
+
win for tooling authors.
|
|
292
|
+
|
|
293
|
+
### P3-5 Decide on `pkg/` retention
|
|
294
|
+
|
|
295
|
+
`pkg/minting-*.gem` is in `.gitignore`, but old builds may still
|
|
296
|
+
sit there. Run `rake clobber_pkg` (if it exists) or just `rm -rf
|
|
297
|
+
pkg/*` once. Then add a `git clean -nd pkg` check to `Rakefile` if
|
|
298
|
+
you want belt-and-braces.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Suggested next PR (after `from_fractional` merges)
|
|
303
|
+
|
|
304
|
+
**Title:** *Delete dead `to_d` guard, document `bigdecimal` as a hard dependency*
|
|
305
|
+
|
|
306
|
+
**Scope:**
|
|
307
|
+
|
|
308
|
+
- `lib/minting/money/conversion.rb`: remove
|
|
309
|
+
`raise NoMethodError, 'decimal gem required' unless defined?(BigDecimal)`.
|
|
310
|
+
- `CHANGELOG.md`: add a one-liner under `[Unreleased]` → `### Other`.
|
|
311
|
+
- `test/money/money_conversion_test.rb`: add a sanity test that
|
|
312
|
+
`to_d` returns a `BigDecimal` (already covered indirectly, but
|
|
313
|
+
make it explicit).
|
|
314
|
+
|
|
315
|
+
One-line source change, one-line test, one-line changelog. ~10 min.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Metrics to watch going forward
|
|
320
|
+
|
|
321
|
+
| Metric | Current | Target |
|
|
322
|
+
|---|---|---|
|
|
323
|
+
| Line coverage | 100% (284/284) | ≥ 100% — don't regress |
|
|
324
|
+
| Test count | 80 runs, 342 assertions | Grow with each new method |
|
|
325
|
+
| `to_s` Hash format coverage | 0 tests | ≥ 6 (P1-3) |
|
|
326
|
+
| Bench variance vs baseline | unmeasured | ≤ 20% (P0-4) |
|
|
327
|
+
| Public API methods with YARD | ~all | 100% (already achieved) |
|
|
328
|
+
| Open `rubocop_todo` offenses | 9 in `test/performance/*` | 0 (P2-3) |
|
|
329
|
+
| `Gemfile.lock` policy | committed | documented (P3-2) |
|
|
330
|
+
| `to_d` runtime guard | present but dead | removed (suggested next PR) |
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
*Last updated alongside PR #2 (`Money.from_fractional`). Refresh
|
|
335
|
+
after each release.*
|
|
@@ -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
|