sec_id 4.4.1 → 5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20409e751f968646b7679c2b252f4e1271be9bfd718594623cf2ac7d45c11db3
4
- data.tar.gz: c65e3bcb5f6196543873c19ed845c29b77e4caec3c473bb74c7457eb42ab0369
3
+ metadata.gz: 48989505724d7895a9a0bd0308602f4e8a1f5870c91df5802e32f2a26af2c7c4
4
+ data.tar.gz: 2e810ceadd4d02b47dc3a7cc6eab754e305a44ce83f883a0afdac7cef2bd4740
5
5
  SHA512:
6
- metadata.gz: ea451044dbeee4c685ea90300cdb4b3f4704b58a9966cdae579992b969bf79bab5a0781693ec4f59a7ec32050a2b197827f6b932eb460b7cfc5e145636815d43
7
- data.tar.gz: e7c691adedcdc482580adc6ccd11fa445dcd6414a56ed5b0f31c1496fc54af0eece6259791a791b9c44682b0204b6e4c7d01befe664fa79f63d00ff11efa9e22
6
+ metadata.gz: 8f4897e3de16457e2206fcae937316119a1c824b224bbf84d41f61c1d66f6cd5b48e8662bccfca007e68da6e0e059a130dd04ea82778ae2208a9d2aacfc34a75
7
+ data.tar.gz: b807ce12ec66636016262686b71666d9c84a9e2649c5816dcd97772a02b648a64a26a6f86691969d97f76583652347bb2c627407b674773630764cffe4e3e216
data/CHANGELOG.md CHANGED
@@ -8,6 +8,52 @@ and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [5.0.0] - 2026-02-17
12
+
13
+ ### Added
14
+
15
+ - `Validatable`, `Normalizable`, and `IdentifierMetadata` concerns extracted from `Base`, making each responsibility independently includable
16
+ - `#validate` and `.validate` methods on all identifier types that eagerly trigger + cache errors and return `self`/instance
17
+ - `#validate!` and `.validate!` methods that raise `InvalidFormatError`, `InvalidCheckDigitError`, or `InvalidStructureError` on validation failure, returning self/instance on success
18
+ - Rails-like `#errors` API returning `Errors` with `details`, `messages`, `none?`, `any?`, `empty?`, `size`, `each`, and `to_a` on all identifier classes, with type-specific error detection for check digits, FIGI prefixes, CFI categories/groups, IBAN BBAN format, and OCC dates
19
+ - `#restore` instance method on check-digit identifiers returning the full identifier string without mutation
20
+ - `.restore` class method on check-digit identifiers returning the full identifier string
21
+ - `SecID.parse(str, types: nil)` and `SecID.parse!(str, types: nil)` methods that return a typed identifier instance for the most specific match, with optional type filtering
22
+ - `SecID.valid?(str, types: nil)` method for quick boolean validation against all or specific identifier types
23
+ - `SecID.detect(str)` method that identifies all matching identifier types for a given string, returning symbols sorted by specificity
24
+ - Metadata registry: `SecID.identifiers` returns all identifier classes, `SecID[:isin]` looks up by symbol key
25
+ - Metadata class methods on all identifiers: `short_name`, `full_name`, `id_length`, `example`, `has_check_digit?`
26
+ - `#normalized` and `#normalize` instance methods on all identifier types returning the canonical string form
27
+ - `#normalize!` instance method on all identifier types that mutates `full_id` to canonical form and returns `self`
28
+ - `.normalize(id)` class method on all identifier types that strips separators, upcases, validates, and returns the canonical string
29
+ - `SEPARATORS` constant in `Normalizable` (`/[\s-]/`) included in `Base`, with type-specific overrides for OCC and FISN (`/-/`)
30
+
31
+ ### Changed
32
+
33
+ - **BREAKING:** Minimum Ruby version raised from 3.1 to 3.2 (Ruby 3.1 reached EOL on 2025-03-31)
34
+ - **BREAKING:** `#restore!` now returns `self` instead of a string; use `#restore` for the string return value
35
+ - **BREAKING:** `.restore!` now returns the restored instance instead of a string; use `.restore` for the string return value
36
+ - **BREAKING:** `#normalize!` on CIK, OCC, and Valoren now returns `self` instead of a string; use `#normalized` to get the canonical string
37
+ - **BREAKING:** Class-level `.normalize!` on CIK, OCC, and Valoren replaced by `.normalize` (non-bang) which returns the canonical string
38
+ - **BREAKING:** `Base#parse` always upcases input; the `upcase` keyword parameter is removed
39
+ - **BREAKING:** `#full_number` renamed to `#full_id` on all identifier types
40
+ - **BREAKING:** Ruby module renamed from `SecId` to `SecID` (e.g. `SecId::ISIN` → `SecID::ISIN`)
41
+ - Luhn helper methods in Checkable are now private (implementation detail)
42
+
43
+ ### Removed
44
+
45
+ - Class-level `.normalize!` on CIK, OCC, and Valoren — replaced by `.normalize`
46
+ - `upcase` keyword parameter from `Base#parse`
47
+ - `#valid_format?` instance method (now private) and `.valid_format?` class method
48
+ - `OCC#full_symbol` method — use `#full_id` instead
49
+
50
+ ### Fixed
51
+
52
+ - `to_str` now always returns the same value as `to_s` across all identifier types — previously LEI, IBAN, and Checkable identifiers could return divergent strings due to Ruby `alias` resolving to the parent class method
53
+ - OCC `#date` memoization for invalid dates — previously re-attempted parsing on every call instead of caching `nil`
54
+ - LEI `restore` and `to_s` now correctly pad single-digit check digits to 2 characters
55
+ - `Valoren#to_isin` no longer mutates the source instance
56
+
11
57
  ## [4.4.1] - 2026-02-05
12
58
 
13
59
  ### Fixed
@@ -115,7 +161,7 @@ and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
115
161
 
116
162
  ### Added
117
163
 
118
- - SEDOL numbers support: `SecId::SEDOL`
164
+ - SEDOL numbers support: `SecID::SEDOL`
119
165
 
120
166
  ### Changed
121
167
 
@@ -135,7 +181,7 @@ and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
135
181
 
136
182
  ### Added
137
183
 
138
- - CUSIP numbers support: `SecId::CUSIP`
184
+ - CUSIP numbers support: `SecID::CUSIP`
139
185
  - CHANGELOG.md file added
140
186
 
141
187
  ### Changed
@@ -146,4 +192,4 @@ and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
146
192
 
147
193
  ### Added
148
194
 
149
- - ISIN numbers support: `SecId::ISIN`
195
+ - ISIN numbers support: `SecID::ISIN`
data/MIGRATION.md ADDED
@@ -0,0 +1,257 @@
1
+ # Upgrading to SecID 5.0
2
+
3
+ This guide covers all breaking changes when upgrading from SecID 4.x to 5.0.
4
+
5
+ ## Requirements
6
+
7
+ - **Ruby 3.2+** (was 3.1+)
8
+
9
+ ## Quick Reference
10
+
11
+ | What changed | Search pattern | Replacement |
12
+ |---|---|---|
13
+ | Module rename | `SecId::` | `SecID::` |
14
+ | Attribute rename | `.full_number` | `.full_id` |
15
+ | OCC method removed | `.full_symbol` | `.full_id` |
16
+ | ValidationResult rename | `SecID::ValidationResult` | `SecID::Errors` |
17
+ | `ValidationResult#valid?` | `result.valid?` | `result.none?` |
18
+ | `.validate` return type | `Klass.validate(id)` returns `ValidationResult` | `Klass.validate(id)` returns instance; use `.errors` for errors |
19
+ | `EXCEPTION_MAP` rename | `Base::EXCEPTION_MAP` | `Validatable::ERROR_MAP` |
20
+ | `.exception_for_error` rename | `.exception_for_error(code)` | `.error_class_for(code)` |
21
+ | `format_errors` rename (private) | `def format_errors` | `def detect_errors` |
22
+ | `validation_errors` rename (private) | `def validation_errors` | `def error_codes` |
23
+ | Instance `restore!` return | `= obj.restore!` | `obj.restore!` (returns `self`) or `= obj.restore` (returns string) |
24
+ | Class `restore!` return | `= Klass.restore!(id)` | `Klass.restore!(id)` (returns instance) or `= Klass.restore(id)` (returns string) |
25
+ | Instance `normalize!` return | `= obj.normalize!` | `obj.normalize!` (returns `self`) or `= obj.normalized` (returns string) |
26
+ | Class `normalize!` removed | `.normalize!(id)` | `.normalize(id)` |
27
+ | `valid_format?` removed | `.valid_format?` | `.valid?` or `.validate` |
28
+ | `parse` upcase param | `parse(id, upcase: false)` | `parse(id)` (always upcases) |
29
+
30
+ ## Step-by-Step
31
+
32
+ ### 1. Update Gemfile
33
+
34
+ ```ruby
35
+ gem 'sec_id', '~> 5.0'
36
+ ```
37
+
38
+ Then run `bundle update sec_id`.
39
+
40
+ ### 2. Rename module: SecId → SecID
41
+
42
+ The module was renamed to match the conventional acronym casing.
43
+
44
+ ```ruby
45
+ # Before
46
+ SecId::ISIN.valid?('US5949181045')
47
+
48
+ # After
49
+ SecID::ISIN.valid?('US5949181045')
50
+ ```
51
+
52
+ Find all occurrences:
53
+
54
+ ```bash
55
+ grep -r 'SecId[^A-Z]' app/ lib/ spec/
56
+ ```
57
+
58
+ ### 3. Rename attribute: full_number → full_id
59
+
60
+ ```ruby
61
+ # Before
62
+ isin.full_number # => 'US5949181045'
63
+
64
+ # After
65
+ isin.full_id # => 'US5949181045'
66
+ ```
67
+
68
+ If you used `OCC#full_symbol`, replace it with `#full_id` as well.
69
+
70
+ Find all occurrences:
71
+
72
+ ```bash
73
+ grep -rE '\.(full_number|full_symbol)\b' app/ lib/ spec/
74
+ ```
75
+
76
+ ### 4. Update restore! usage (returns self now)
77
+
78
+ In v4, both instance and class `restore!` returned a string. In v5, they return `self`/instance for chaining. Use `restore` (without bang) to get the string.
79
+
80
+ ```ruby
81
+ # Before (v4) — restore! returned a string
82
+ id_string = isin.restore!
83
+
84
+ # After (v5) — choose one:
85
+ id_string = isin.restore # string return (new method)
86
+ isin.restore! # mutates and returns self
87
+
88
+ # Class-level
89
+ id_string = SecID::ISIN.restore('US594918104') # returns string
90
+ instance = SecID::ISIN.restore!('US594918104') # returns instance
91
+ ```
92
+
93
+ ### 5. Update normalize! usage (returns self now)
94
+
95
+ Applies to CIK, OCC, and Valoren — the only types with normalization in v4.
96
+
97
+ ```ruby
98
+ # Before (v4) — normalize! returned a string
99
+ normalized = cik.normalize!
100
+
101
+ # After (v5) — choose one:
102
+ normalized = cik.normalized # string return (new method)
103
+ cik.normalize! # mutates and returns self
104
+
105
+ # Class-level .normalize! removed — use .normalize
106
+ # Before
107
+ SecId::CIK.normalize!('1094517')
108
+
109
+ # After
110
+ SecID::CIK.normalize('1094517') # => '0001094517'
111
+ ```
112
+
113
+ Note: In v5, all identifier types support normalization, not just CIK/OCC/Valoren.
114
+
115
+ ### 6. Remove calls to deleted methods
116
+
117
+ **`valid_format?` / `.valid_format?`** — removed from public API. Use `valid?` for boolean checks or `errors` for details:
118
+
119
+ ```ruby
120
+ # Before
121
+ SecId::ISIN.valid_format?('US5949181045')
122
+
123
+ # After
124
+ SecID::ISIN.valid?('US5949181045')
125
+ # or for detailed errors:
126
+ SecID::ISIN.validate('US5949181045').messages
127
+ ```
128
+
129
+ **`OCC#full_symbol`** — use `#full_id`:
130
+
131
+ ```ruby
132
+ # Before
133
+ occ.full_symbol
134
+
135
+ # After
136
+ occ.full_id
137
+ ```
138
+
139
+ ### 7. Update parse calls in custom subclasses
140
+
141
+ If you subclass `SecID::Base` and call `parse` with `upcase: false`, remove that parameter. `parse` now always upcases input.
142
+
143
+ ```ruby
144
+ # Before
145
+ parse(id, upcase: false)
146
+
147
+ # After
148
+ parse(id)
149
+ ```
150
+
151
+ ### 8. Handle now-private Luhn helpers
152
+
153
+ If you subclass `SecID::Base` with `Checkable` and call Luhn helper methods directly (`luhn_sum_double_add_double`, `luhn_sum_indexed`, `luhn_sum_standard`, etc.), these are now private. Use `calculate_check_digit` instead.
154
+
155
+ ### 9. Update exception handling
156
+
157
+ The module rename affects exception class references:
158
+
159
+ ```ruby
160
+ # Before
161
+ rescue SecId::InvalidFormatError
162
+
163
+ # After
164
+ rescue SecID::InvalidFormatError
165
+ ```
166
+
167
+ v5 adds two new exception types for more granular error handling:
168
+
169
+ - `SecID::InvalidCheckDigitError` — raised when check digit doesn't match
170
+ - `SecID::InvalidStructureError` — raised for type-specific structural errors (FIGI prefix, CFI category/group, IBAN BBAN, OCC date)
171
+
172
+ Both inherit from `SecID::Error`, so existing `rescue SecID::Error` blocks still work. Optionally catch the specific types:
173
+
174
+ ```ruby
175
+ begin
176
+ SecID::ISIN.new('US5949181040').validate!
177
+ rescue SecID::InvalidCheckDigitError => e
178
+ # handle bad check digit specifically
179
+ rescue SecID::InvalidFormatError => e
180
+ # handle format errors
181
+ end
182
+ ```
183
+
184
+ ### 10. Rename ValidationResult to Errors
185
+
186
+ `ValidationResult` has been renamed to `Errors`. The `valid?` method is replaced by `none?` for clearer semantics.
187
+
188
+ ```ruby
189
+ # Before
190
+ result = SecId::ISIN.new('US5949181045').errors
191
+ result.valid? # => true
192
+
193
+ # After
194
+ result = SecID::ISIN.new('US5949181045').errors
195
+ result.none? # => true
196
+ ```
197
+
198
+ Find all occurrences:
199
+
200
+ ```bash
201
+ grep -rE 'ValidationResult|\.valid\?' app/ lib/ spec/
202
+ ```
203
+
204
+ ### 11. Update .validate usage (returns instance now)
205
+
206
+ `.validate` now returns the identifier instance (with errors cached) instead of a `ValidationResult`/`Errors` object.
207
+
208
+ ```ruby
209
+ # Before (v4)
210
+ result = SecId::ISIN.validate('US5949181045') # => #<SecId::ValidationResult>
211
+ result.valid?
212
+
213
+ # After (v5)
214
+ instance = SecID::ISIN.validate('US5949181045') # => #<SecID::ISIN>
215
+ instance.errors.none?
216
+ ```
217
+
218
+ ### 12. Update subclass overrides (private API)
219
+
220
+ If you subclass `SecID::Base` and override private validation methods:
221
+
222
+ ```ruby
223
+ # Before
224
+ def format_errors # renamed
225
+ def validation_errors # renamed
226
+
227
+ # After
228
+ def detect_errors
229
+ def error_codes
230
+ ```
231
+
232
+ If you reference `EXCEPTION_MAP` or `exception_for_error`:
233
+
234
+ ```ruby
235
+ # Before
236
+ Base::EXCEPTION_MAP
237
+ Klass.exception_for_error(:code)
238
+
239
+ # After
240
+ Validatable::ERROR_MAP
241
+ Klass.error_class_for(:code)
242
+ ```
243
+
244
+ ### 13. Explore new features (optional)
245
+
246
+ v5 adds several features beyond the breaking changes:
247
+
248
+ - **Structured validation** — `#errors` returns an `Errors` object with `details`, `messages`, `none?`, `any?`, `empty?`, `size`, `each`
249
+ - **Eager validation** — `#validate` / `.validate` triggers validation and returns the instance
250
+ - **Fail-fast validation** — `#validate!` raises descriptive exceptions
251
+ - **Type detection** — `SecID.detect('US5949181045')` returns `[:isin]`
252
+ - **Universal parsing** — `SecID.parse('US5949181045')` returns a typed instance
253
+ - **Quick validation** — `SecID.valid?('US5949181045')` validates against all types
254
+ - **Metadata registry** — `SecID.identifiers`, `SecID[:isin]`, `SecID::ISIN.full_name`
255
+ - **Universal normalization** — all types now support `#normalized`, `#normalize!`, `.normalize`
256
+
257
+ See [README.md](README.md) for full usage examples.