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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55db3e9b61e8ee1c813e313789222cc2e083f59c1070c12801cd3afd5eaf6fe6
4
- data.tar.gz: d2d4420c40bd39e551a79a9b1ad42386f8d8ab4e9560ad8ad287909fc1a5633a
3
+ metadata.gz: 135843eccae0c9415ad8cd2c941d7afeceb19958fdff5c3d898fbcfd76d51218
4
+ data.tar.gz: 90d8349d74b739d37a3aa37a83002190a3975064cb11b279f8d4c9ef1760e4a6
5
5
  SHA512:
6
- metadata.gz: 7aa9f93ea36aeef350308adcec126812a8e5438a95d5e305d79ca6e993f27ffbf7f27908fd4ead9fa0b3659f10f29db263b31b97c5a86fb1f689c401913401eb
7
- data.tar.gz: c9267d2eb5e5329d7af59eca2507663bf85e4716f2dda4089946c86bd0da8f2848e4069e298e4a9a58eba4a89da203fd7116226f546ab2bf531ad0cc04ce3526
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
@@ -3,7 +3,7 @@ require 'rubocop/rake_task'
3
3
  require 'rake/testtask'
4
4
  require 'yard'
5
5
 
6
- CLOBBER.include %w[doc tmp .yardoc]
6
+ CLOBBER.include %w[doc/css doc/js doc/Mint doc/*.html tmp .yardoc]
7
7
 
8
8
  Rake::TestTask.new(:test) do |t|
9
9
  t.libs << 'test'
@@ -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 โœ… `Money.from_fractional(integer, currency)`
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
- def normalize_amount(amount)
16
- amount.to_r.round(subunit)
17
- end
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 = 1r / fractional_multiplier
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,4 +1,6 @@
1
- # Mint is a library to operate with monetary values
1
+ # frozen_string_literal: true
2
+
3
+ # Mint refinements
2
4
  module Mint
3
5
  refine Numeric do
4
6
  def reais
@@ -1,17 +1,18 @@
1
- require 'yaml'
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, Symbol] the ISO currency code or symbol
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. Check Mint.currencies"
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: 2, symbol: '', priority: 0)
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
- currency = currencies[code]
58
- raise KeyError, "Currency: #{code} already registered" if currency
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
- @currency_symbols = nil
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
- @currency_symbols ||= begin
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,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'minting/mint/currency'
2
- require 'minting/mint/refinements'
4
+ require 'minting/mint/currency_store'
3
5
  require 'minting/mint/registry'
6
+ require 'minting/mint/refinements'
@@ -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(it, currency) }
56
+ amounts.map { |amount| Money.new(amount, currency) }
54
57
  end
55
58
  end
56
59
  end