ruby-ulid 0.3.0 → 0.6.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: 358b03a503f8a12c87f3f7daaf3b0acf6e55dc44f1aba7a38b9f9a38985c2c68
4
- data.tar.gz: 15d25d443f4826b07fc9a74b092e35b9cd60dc34cc3615f03fee619d4fc39445
3
+ metadata.gz: 82f48c45c124521403b04938ec4498a85bf292645357d75f32173cc2b9613618
4
+ data.tar.gz: 9c6269cbef649b57df980d120116687cdcc33e505cb45585b4566f4b16f3cdf2
5
5
  SHA512:
6
- metadata.gz: 5b18d13597bd2f3dcd85a15a26d9086fb9a80b91775292c9da82d93144b6abd37585996f1bf64dfc4ac12b6d6d49683012dfea8e04624005358459667864114c
7
- data.tar.gz: d987aa9b60717577fae96eef68fca76b2395f11a580df4863a69dd0931d955e4cf68608c101959db18f5e3a26031fbe284ff90a0d107fa84bbebb63949bba66b
6
+ metadata.gz: 817d5589c2967d8d1b787ce20c147483cd12769eba22a0489ade4002285e1edc31e06f9e5d8dd590d3cf2342d2875c388818e55b95cbd1d72921b1848287483f
7
+ data.tar.gz: 6bcacebe7955bb247127ddc64df1fec2cc776954e7c86530750c200da5420fd4cbb73d5fd2f0888cb37ff5f097987dd12f372ee591adccb8b35f35e03e4f0538
data/README.md CHANGED
@@ -7,8 +7,7 @@
7
7
 
8
8
  [ulid/spec](https://github.com/ulid/spec) is useful.
9
9
  Especially possess all `uniqueness`, `randomness`, `extractable timestamps` and `sortable` features.
10
- This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around ULID.
11
- Also providing [ruby/rbs](https://github.com/ruby/rbs) signatures.
10
+ This gem aims to provide the generator, optional monotonicity, parser and other manipulation features around ULID with included [RBS](https://github.com/ruby/rbs).
12
11
 
13
12
  ---
14
13
 
@@ -47,23 +46,43 @@ $ gem install ruby-ulid
47
46
  Should be installed!
48
47
  ```
49
48
 
50
- Add this line to your Gemfile`.
49
+ Add this line in your Gemfile.
51
50
 
52
51
  ```ruby
53
- gem('ruby-ulid', '~> 0.2.2')
52
+ gem('ruby-ulid', '~> 0.6.0')
54
53
  ```
55
54
 
56
- ### Generator and Parser
57
-
58
- The generated `ULID` is an object not just a string.
59
- It means easily get the timestamps and binary formats.
55
+ ### How to use
60
56
 
61
57
  ```ruby
62
58
  require 'ulid'
63
59
 
60
+ ULID::VERSION
61
+ # => "0.6.0"
62
+ ```
63
+
64
+ NOTE: This README includes info about development version. If you would see released version's one. [Look at the ref](https://github.com/kachick/ruby-ulid/tree/v0.6.0).
65
+
66
+ ### Generator and Parser
67
+
68
+ `ULID.generate` returns `ULID` instance. It is not just a string.
69
+
70
+ ```ruby
64
71
  ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
72
+ ```
73
+
74
+ `ULID.parse` returns `ULID` instance from exists encoded ULIDs.
75
+
76
+ ```ruby
77
+ ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
78
+ ```
79
+
80
+ It is helpful to inspect.
81
+
82
+ ```ruby
65
83
  ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
66
84
  ulid.milliseconds #=> 1619544442826
85
+ ulid.encode #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
67
86
  ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
68
87
  ulid.timestamp #=> "01F4A5Y1YA"
69
88
  ulid.randomness #=> "QCYAYCTC7GRMJ9AA"
@@ -71,41 +90,56 @@ ulid.to_i #=> 1957909092946624190749577070267409738
71
90
  ulid.octets #=> [1, 121, 20, 95, 7, 202, 187, 60, 175, 51, 76, 60, 49, 73, 37, 74]
72
91
  ```
73
92
 
74
- You can get the objects from exists encoded ULIDs
93
+ `ULID.generate` can take fixed `Time` instance. `ULID.at` is the shorthand.
75
94
 
76
95
  ```ruby
77
- ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV') #=> ULID(2016-07-30 23:54:10.259 UTC: 01ARZ3NDEKTSV4RRFFQ69G5FAV)
78
- ulid.to_time #=> 2016-07-30 23:54:10.259 UTC
96
+ time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
97
+ ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
98
+ ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
99
+ ULID.at(time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB002W5BGWWKN76N22H6)
79
100
  ```
80
101
 
81
- ### Sortable with the timestamp
102
+ Also `ULID.encode` and `ULID.decode_time` can be used to get primitive values for most usecases.
82
103
 
83
- ULIDs are sortable when they are generated in different timestamp with milliseconds precision
104
+ `ULID.encode` returns [normalized](#variants-of-format) String without ULID object creation.
105
+ It can take same arguments as `ULID.generate`.
84
106
 
85
107
  ```ruby
86
- ulids = 1000.times.map do
87
- sleep(0.001)
88
- ULID.generate
89
- end
90
- ulids.uniq(&:to_time).size #=> 1000
91
- ulids.sort == ulids #=> true
108
+ ULID.encode #=> "01G86M42Q6SJ9XQM2ZRM6JRDSF"
109
+ ULID.encode(moment: Time.at(946684800).utc) #=> "00VHNCZB00SYG7RCEXZC9DA4E1"
92
110
  ```
93
111
 
94
- `ULID.generate` can take fixed `Time` instance. The shorthand is `ULID.at`
112
+ `ULID.decode_time` returns Time. It can take `in` keyarg as same as `Time.at`.
95
113
 
96
114
  ```ruby
97
- time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
98
- ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
99
- ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
100
- ULID.at(time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB002W5BGWWKN76N22H6)
115
+ ULID.decode_time('00VHNCZB00SYG7RCEXZC9DA4E1') #=> 2000-01-01 00:00:00 UTC
116
+ ULID.decode_time('00VHNCZB00SYG7RCEXZC9DA4E1', in: '+09:00') #=> 2000-01-01 09:00:00 +0900
117
+ ```
118
+
119
+ This project does not prioritize the speed. However it actually works faster than others! :zap:
120
+
121
+ Snapshot on 0.6.0 is below
101
122
 
102
- ulids = 1000.times.map do |n|
103
- ULID.at(time + n)
123
+ * Generator is 1.4x faster than - [ulid gem](https://github.com/rafaelsales/ulid)
124
+ * Generator is 1.7x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
125
+ * Parser is 2.6x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
126
+
127
+ You can see further detail at [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
128
+
129
+ ### Sortable with the timestamp
130
+
131
+ ULIDs are sortable when they are generated in different timestamp with milliseconds precision.
132
+
133
+ ```ruby
134
+ ulids = 1000.times.map do
135
+ sleep(0.001)
136
+ ULID.generate
104
137
  end
138
+ ulids.uniq(&:to_time).size #=> 1000
105
139
  ulids.sort == ulids #=> true
106
140
  ```
107
141
 
108
- The basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
142
+ Basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
109
143
 
110
144
  ```ruby
111
145
  ulids = 10000.times.map do
@@ -147,11 +181,11 @@ sample_ulids_by_the_time.take(5) #=>
147
181
  ulids.sort == ulids #=> true
148
182
  ```
149
183
 
150
- Same generator does not generate duplicated ULIDs even in multi threads environment. It is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255)
184
+ Same instance of `ULID::MonotonicGenerator` does not generate duplicated ULIDs even in multi threads environment. It is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255).
151
185
 
152
186
  ### Filtering IDs with `Time`
153
187
 
154
- `ULID` can be element of the `Range`. If you generated the IDs in monotonic generator, ID based filtering is easy and reliable
188
+ `ULID` can be element of the `Range`. If they were generated with monotonic generator, ID based filtering is easy and reliable.
155
189
 
156
190
  ```ruby
157
191
  include_end = ulid1..ulid2
@@ -187,7 +221,9 @@ time = Time.at(946684800, Rational('123456.789')).utc #=> 2000-01-01 00:00:00.12
187
221
  ULID.floor(time) #=> 2000-01-01 00:00:00.123 UTC
188
222
  ```
189
223
 
190
- ### Scanner for string (e.g. `JSON`)
224
+ ### Tools
225
+
226
+ #### Scanner for string (e.g. `JSON`)
191
227
 
192
228
  For rough operations, `ULID.scan` might be useful.
193
229
 
@@ -230,7 +266,7 @@ ULID.scan(json).to_a
230
266
  ```
231
267
 
232
268
  `ULID#patterns` is a util for text based operations.
233
- The results and spec are not fixed. Should not be used except snippets/console operation
269
+ The results and spec are not fixed. Should not be used except snippets/console operation.
234
270
 
235
271
  ```ruby
236
272
  ULID.parse('01F4GNBXW1AM2KWW52PVT3ZY9X').patterns
@@ -241,7 +277,7 @@ ULID.parse('01F4GNBXW1AM2KWW52PVT3ZY9X').patterns
241
277
  }
242
278
  ```
243
279
 
244
- ### Some methods to help manipulations
280
+ #### Get boundary ULIDs
245
281
 
246
282
  `ULID.min` and `ULID.max` return termination values for ULID spec.
247
283
 
@@ -256,10 +292,12 @@ ULID.min(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
256
292
  ULID.max(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
257
293
  ```
258
294
 
295
+ #### As element in Enumerable
296
+
259
297
  `ULID#next` and `ULID#succ` returns next(successor) ULID.
260
298
  Especially `ULID#succ` makes it possible `Range[ULID]#each`.
261
299
 
262
- NOTE: But basically `Range[ULID]#each` should not be used, incrementing 128 bits IDs are not reasonable operation in most case
300
+ NOTE: However basically `Range[ULID]#each` should not be used. Incrementing 128 bits IDs are not reasonable operation in most cases.
263
301
 
264
302
  ```ruby
265
303
  ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZY').next.to_s #=> "01BX5ZZKBKZZZZZZZZZZZZZZZZ"
@@ -267,7 +305,7 @@ ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZZ').next.to_s #=> "01BX5ZZKBM00000000000000
267
305
  ULID.parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').next #=> nil
268
306
  ```
269
307
 
270
- `ULID#pred` returns predecessor ULID
308
+ `ULID#pred` returns predecessor ULID.
271
309
 
272
310
  ```ruby
273
311
  ULID.parse('01BX5ZZKBK0000000000000001').pred.to_s #=> "01BX5ZZKBK0000000000000000"
@@ -275,6 +313,8 @@ ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZ
275
313
  ULID.parse('00000000000000000000000000').pred #=> nil
276
314
  ```
277
315
 
316
+ #### Test helpers
317
+
278
318
  `ULID.sample` returns random ULIDs.
279
319
 
280
320
  Basically ignores generating time.
@@ -300,61 +340,52 @@ ulid1 = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.82
300
340
  ulid2 = ULID.parse('01F4PTVCSN9ZPFKYTY2DDJVRK4') #=> ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4)
301
341
  ulids = ULID.sample(1000, period: ulid1..ulid2)
302
342
  ulids.uniq.size #=> 1000
303
- ulids.take(10)
343
+ ulids.take(5)
304
344
  #=>
305
345
  #[ULID(2021-05-02 06:57:19.954 UTC: 01F4NXW02JNB8H0J0TK48JD39X),
306
346
  # ULID(2021-05-02 07:06:07.458 UTC: 01F4NYC372GVP7NS0YAYQGT4VZ),
307
347
  # ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW),
308
348
  # ULID(2021-04-27 22:17:37.844 UTC: 01F4APHGSMFJZQTGXKZBFFBPJP),
309
- # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3),
310
- # ULID(2021-04-30 07:18:54.307 UTC: 01F4GTA2332AS2VPHC4FMKC7R5),
311
- # ULID(2021-05-02 12:26:03.480 UTC: 01F4PGNXARG554Y3HYVBDW4T9S),
312
- # ULID(2021-04-29 09:52:15.107 UTC: 01F4EGP483ZX2747FQPWQNPPMW),
313
- # ULID(2021-04-29 03:18:24.152 UTC: 01F4DT4Z4RA0QV8WFQGRAG63EH),
314
- # ULID(2021-05-02 13:27:16.394 UTC: 01F4PM605ABF5SDVMEHBH8JJ9R)]
315
- ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
349
+ # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3)]
350
+ ULID.sample(5, period: ulid1.to_time..ulid2.to_time)
316
351
  #=>
317
352
  # [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
318
353
  # ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
319
354
  # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
320
355
  # ULID(2021-05-01 03:06:09.130 UTC: 01F4JY7ZBABCBMX16XH2Q4JW4W),
321
- # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G),
322
- # ULID(2021-04-29 17:14:14.116 UTC: 01F4F9ZDQ449BE8BBZFEHYQWG2),
323
- # ULID(2021-04-30 16:18:08.205 UTC: 01F4HS5DPD1HWDVJNJ6YKJXKSK),
324
- # ULID(2021-04-30 10:31:33.602 UTC: 01F4H5ATF2A1CSQF0XV5NKZ288),
325
- # ULID(2021-04-28 16:49:06.484 UTC: 01F4CP4PDM214Q6H3KJP7DYJRR),
326
- # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
356
+ # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G)]
327
357
  ```
328
358
 
329
- ### ULID specification ambiguity around orthographical variants of the format
359
+ #### Variants of format
330
360
 
331
361
  I'm afraid so, we should consider [Current ULID spec](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#universally-unique-lexicographically-sortable-identifier) has `orthographical variants of the format` possibilities.
332
362
 
363
+ >Case insensitive
364
+
365
+ I can understand it might be considered in actual use-case. So `ULID.parse` accepts upcase and downcase.
366
+ However it is a controversial point, discussing in [ulid/spec#3](https://github.com/ulid/spec/issues/3).
367
+
333
368
  >Uses Crockford's base32 for better efficiency and readability (5 bits per character)
334
369
 
335
370
  The original `Crockford's base32` maps `I`, `L` to `1`, `O` to `0`.
336
371
  And accepts freestyle inserting `Hyphens (-)`.
337
372
  To consider this patterns or not is different in each implementations.
338
373
 
339
- Current parser/validator/matcher aims to cover `subset of Crockford's base32`.
340
- I have suggested it would be clarified in [ulid/spec#57](https://github.com/ulid/spec/pull/57).
341
-
342
- >Case insensitive
343
-
344
- I can understand it might be considered in actual use-case.
345
- But it is a controversial point, discussing in [ulid/spec#3](https://github.com/ulid/spec/issues/3).
374
+ I have suggested to clarify `subset of Crockford's base32` in [ulid/spec#57](https://github.com/ulid/spec/pull/57).
346
375
 
347
- Be that as it may, this gem provides API for handling the nasty possibilities.
376
+ This gem provides some methods to handle the nasty possibilities.
348
377
 
349
- `ULID.normalize` and `ULID.normalized?`
378
+ `ULID.normalize`, `ULID.normalized?`, `ULID.valid_as_variant_format?` and `ULID.parse_variant_format`
350
379
 
351
380
  ```ruby
352
- ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV"
353
- ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false
354
- ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true
381
+ ULID.normalize('01g70y0y7g-z1xwdarexergsddd') #=> "01G70Y0Y7GZ1XWDAREXERGSDDD"
382
+ ULID.normalized?('01g70y0y7g-z1xwdarexergsddd') #=> false
383
+ ULID.normalized?('01G70Y0Y7GZ1XWDAREXERGSDDD') #=> true
384
+ ULID.valid_as_variant_format?('01g70y0y7g-z1xwdarexergsddd') #=> true
385
+ ULID.parse_variant_format('01G70Y0Y7G-ZLXWDIREXERGSDoD') #=> ULID(2022-07-03 02:25:22.672 UTC: 01G70Y0Y7GZ1XWD1REXERGSD0D)
355
386
  ```
356
387
 
357
- ### UUIDv4 converter for migration use-cases
388
+ #### UUIDv4 converter (experimental)
358
389
 
359
390
  `ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
360
391
  The imported timestamp is meaningless. So ULID's benefit will lost.
@@ -387,68 +418,19 @@ ULID.min == reversed_min #=> false
387
418
  ULID.max == reversed_max #=> false
388
419
  ```
389
420
 
390
- ## How to migrate from other gems
391
-
392
- As far as I know, major prior arts are below
393
-
394
- ### [ulid gem](https://rubygems.org/gems/ulid) - [rafaelsales/ulid](https://github.com/rafaelsales/ulid)
395
-
396
- It is just providing basic `String` generator only.
397
- So you can replace the code as below
398
-
399
- ```diff
400
- -ULID.generate
401
- +ULID.generate.to_s
402
- ```
403
-
404
- NOTE: In version before `1.3.0`, timestamps might not be correct value.
405
-
406
- 1. [Sort order does not respect millisecond ordering](https://github.com/rafaelsales/ulid/issues/22)
407
- 1. [Fixed in this PR](https://github.com/rafaelsales/ulid/pull/23)
408
- 1. [Released in 1.3.0](https://github.com/rafaelsales/ulid/compare/1.2.0...v1.3.0)
409
-
410
- ### [ulid-ruby gem](https://rubygems.org/gems/ulid-ruby) - [abachman/ulid-ruby](https://github.com/abachman/ulid-ruby)
411
-
412
- It is providing basic generator(except monotonic generator) and parser.
413
- Major methods can be replaced as below.
414
-
415
- ```diff
416
- -ULID.generate
417
- +ULID.generate.to_s
418
- -ULID.at(time)
419
- +ULID.at(time).to_s
420
- -ULID.time(string)
421
- +ULID.parse(string).to_time
422
- -ULID.min_ulid_at(time)
423
- +ULID.min(time).to_s
424
- -ULID.max_ulid_at(time)
425
- +ULID.max(time).to_s
426
- ```
427
-
428
- NOTE: In version before `1.0.2`, timestamps might not be correct value.
429
-
430
- 1. [Parsed time object has more than milliseconds](https://github.com/abachman/ulid-ruby/issues/3)
431
- 1. [Fix to handle timestamp precision in parser](https://github.com/abachman/ulid-ruby/pull/5)
432
- 1. [Fix to handle timestamp precision in generator](https://github.com/abachman/ulid-ruby/pull/4)
433
- 1. [Released in 1.0.2](https://github.com/abachman/ulid-ruby/compare/v1.0.0...v1.0.2)
434
-
435
- ### Compare performance with them
436
-
437
- See [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
421
+ ## Migration from other gems
438
422
 
439
- The results are not something to be proud of.
423
+ See [wiki page for gem migration](https://github.com/kachick/ruby-ulid/wiki/Gem-migration).
440
424
 
441
- ## How to use rbs
425
+ ## RBS
442
426
 
443
- See structure at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox)
427
+ Try at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox).
444
428
 
445
- I have checked the behavior with [ruby/rbs@2.6.0](https://github.com/ruby/rbs) && [soutaro/steep@1.0.1](https://github.com/soutaro/steep) && [soutaro/steep-vscode](https://github.com/soutaro/steep-vscode).
429
+ I have checked the behavior with [ruby/rbs@2.6.0](https://github.com/ruby/rbs) + [soutaro/steep@1.0.1](https://github.com/soutaro/steep) + [soutaro/steep-vscode](https://github.com/soutaro/steep-vscode).
446
430
 
447
431
  * ![rbs overview](./assets/ulid-rbs-overview.png?raw=true.png)
448
432
  * ![rbs mix](./assets/ulid-rbs-mix.png?raw=true.png)
449
433
  * ![rbs ng-to_str](./assets/ulid-rbs-ng-to_str.png?raw=true.png)
450
- * ![rbs ok-at-time](./assets/ulid-rbs-ok-at-time.png?raw=true.png)
451
- * ![rbs ng-at-int](./assets/ulid-rbs-ng-at-int.png?raw=true.png)
452
434
 
453
435
  ## References
454
436
 
data/lib/ruby-ulid.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
@@ -1,5 +1,6 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
@@ -14,63 +15,62 @@ class ULID
14
15
  # * https://github.com/kachick/ruby-ulid/issues/57
15
16
  # * https://github.com/kachick/ruby-ulid/issues/78
16
17
  module CrockfordBase32
17
- class SetupError < UnexpectedError; end
18
-
19
- n32_chars = [*'0'..'9', *'A'..'V'].map(&:freeze).freeze
20
- raise(SetupError, 'obvious bug exists in the mapping algorithm') unless n32_chars.size == 32
21
-
22
- n32_char_by_number = {}
23
- n32_chars.each_with_index do |char, index|
24
- n32_char_by_number[index] = char
25
- end
26
- n32_char_by_number.freeze
27
-
28
- crockford_base32_mappings = {
29
- 'J' => 18,
30
- 'K' => 19,
31
- 'M' => 20,
32
- 'N' => 21,
33
- 'P' => 22,
34
- 'Q' => 23,
35
- 'R' => 24,
36
- 'S' => 25,
37
- 'T' => 26,
38
- 'V' => 27,
39
- 'W' => 28,
40
- 'X' => 29,
41
- 'Y' => 30,
42
- 'Z' => 31
18
+ # Excluded I, L, O, U, - from Base32
19
+ base32_to_crockford = {
20
+ '0' => '0',
21
+ '1' => '1',
22
+ '2' => '2',
23
+ '3' => '3',
24
+ '4' => '4',
25
+ '5' => '5',
26
+ '6' => '6',
27
+ '7' => '7',
28
+ '8' => '8',
29
+ '9' => '9',
30
+ 'A' => 'A',
31
+ 'B' => 'B',
32
+ 'C' => 'C',
33
+ 'D' => 'D',
34
+ 'E' => 'E',
35
+ 'F' => 'F',
36
+ 'G' => 'G',
37
+ 'H' => 'H',
38
+ 'I' => 'J',
39
+ 'J' => 'K',
40
+ 'K' => 'M',
41
+ 'L' => 'N',
42
+ 'M' => 'P',
43
+ 'N' => 'Q',
44
+ 'O' => 'R',
45
+ 'P' => 'S',
46
+ 'Q' => 'T',
47
+ 'R' => 'V',
48
+ 'S' => 'W',
49
+ 'T' => 'X',
50
+ 'U' => 'Y',
51
+ 'V' => 'Z'
43
52
  }.freeze
53
+ BASE32_TR_PATTERN = base32_to_crockford.keys.join.freeze
54
+ ENCODING_STRING = CROCKFORD_BASE32_TR_PATTERN = base32_to_crockford.values.freeze.join.freeze
44
55
 
45
- N32_CHAR_BY_CROCKFORD_BASE32_CHAR = CROCKFORD_BASE32_ENCODING_STRING.chars.map(&:freeze).each_with_object({}) do |encoding_char, map|
46
- if n = crockford_base32_mappings[encoding_char]
47
- char_32 = n32_char_by_number.fetch(n)
48
- map[encoding_char] = char_32
49
- end
50
- end.freeze
51
- raise(SetupError, 'obvious bug exists in the mapping algorithm') unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
52
-
53
- CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
54
-
55
- CROCKFORD_BASE32_CHAR_BY_N32_CHAR = N32_CHAR_BY_CROCKFORD_BASE32_CHAR.invert.freeze
56
- N32_CHAR_PATTERN = /[#{CROCKFORD_BASE32_CHAR_BY_N32_CHAR.keys.join}]/.freeze
57
-
58
- STANDARD_BY_VARIANT = {
56
+ normarized_by_variant = {
59
57
  'L' => '1',
60
58
  'l' => '1',
61
59
  'I' => '1',
62
60
  'i' => '1',
63
61
  'O' => '0',
64
- 'o' => '0',
65
- '-' => ''
62
+ 'o' => '0'
66
63
  }.freeze
67
- VARIANT_PATTERN = /[#{STANDARD_BY_VARIANT.keys.join}]/.freeze
64
+ VARIANT_TR_PATTERN = normarized_by_variant.keys.join.freeze
65
+ NORMALIZED_TR_PATTERN = normarized_by_variant.values.join.freeze
66
+
67
+ # @note Avoid to depend regex as possible. `tr(string, string)` is almost 2x Faster than `gsub(regex, hash)` in Ruby 3.1
68
68
 
69
69
  # @api private
70
70
  # @param [String] string
71
71
  # @return [Integer]
72
72
  def self.decode(string)
73
- n32encoded = string.upcase.gsub(CROCKFORD_BASE32_CHAR_PATTERN, N32_CHAR_BY_CROCKFORD_BASE32_CHAR)
73
+ n32encoded = string.upcase.tr(CROCKFORD_BASE32_TR_PATTERN, BASE32_TR_PATTERN)
74
74
  n32encoded.to_i(32)
75
75
  end
76
76
 
@@ -79,14 +79,21 @@ class ULID
79
79
  # @return [String]
80
80
  def self.encode(integer)
81
81
  n32encoded = integer.to_s(32)
82
- n32encoded.upcase.gsub(N32_CHAR_PATTERN, CROCKFORD_BASE32_CHAR_BY_N32_CHAR).rjust(ENCODED_LENGTH, '0')
82
+ from_n32(n32encoded).rjust(ENCODED_LENGTH, '0')
83
83
  end
84
84
 
85
85
  # @api private
86
86
  # @param [String] string
87
87
  # @return [String]
88
88
  def self.normalize(string)
89
- string.gsub(VARIANT_PATTERN, STANDARD_BY_VARIANT)
89
+ string.delete('-').tr(VARIANT_TR_PATTERN, NORMALIZED_TR_PATTERN)
90
+ end
91
+
92
+ # @api private
93
+ # @param [String] n32encoded
94
+ # @return [String]
95
+ def self.from_n32(n32encoded)
96
+ n32encoded.upcase.tr(BASE32_TR_PATTERN, CROCKFORD_BASE32_TR_PATTERN)
90
97
  end
91
98
  end
92
99
  end
@@ -0,0 +1,10 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+ # shareable_constant_value: literal
4
+
5
+ class ULID
6
+ class Error < StandardError; end
7
+ class OverflowError < Error; end
8
+ class ParserError < Error; end
9
+ class UnexpectedError < Error; end
10
+ end
@@ -1,13 +1,15 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
6
7
  class ULID
7
8
  class MonotonicGenerator
9
+ # @note When use https://github.com/ko1/ractor-tvar might realize Ractor based thread safe monotonic generator.
10
+ # However it is a C extention, I'm pending to use it for now.
8
11
  include(MonitorMixin)
9
12
 
10
- # @dynamic prev
11
13
  # @return [ULID, nil]
12
14
  attr_reader(:prev)
13
15
 
@@ -22,7 +24,6 @@ class ULID
22
24
  def inspect
23
25
  "ULID::MonotonicGenerator(prev: #{@prev.inspect})"
24
26
  end
25
- # @dynamic to_s
26
27
  alias_method(:to_s, :inspect)
27
28
 
28
29
  # @param [Time, Integer] moment
@@ -67,6 +68,12 @@ class ULID
67
68
  end
68
69
  end
69
70
 
71
+ # @param [Time, Integer] moment
72
+ # @return [String]
73
+ def encode(moment: ULID.current_milliseconds)
74
+ generate(moment: moment).encode
75
+ end
76
+
70
77
  undef_method(:freeze)
71
78
 
72
79
  # @raise [TypeError] always raises exception and does not freeze self
@@ -0,0 +1,12 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+
4
+ class ULID
5
+ min = parse('00000000000000000000000000').freeze
6
+ max = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
7
+
8
+ ractor_can_make_shareable_time = RUBY_VERSION >= '3.1'
9
+
10
+ MIN = ractor_can_make_shareable_time ? Ractor.make_shareable(min) : min
11
+ MAX = ractor_can_make_shareable_time ? Ractor.make_shareable(max) : max
12
+ end
data/lib/ulid/uuid.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
data/lib/ulid/version.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  class ULID
5
- VERSION = '0.3.0'
6
+ VERSION = '0.6.0'
6
7
  end
data/lib/ulid.rb CHANGED
@@ -1,10 +1,16 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: experimental_everything
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
6
7
  require('securerandom')
7
8
 
9
+ require_relative('ulid/version')
10
+ require_relative('ulid/errors')
11
+ require_relative('ulid/crockford_base32')
12
+ require_relative('ulid/monotonic_generator')
13
+
8
14
  # @see https://github.com/ulid/spec
9
15
  # @!attribute [r] milliseconds
10
16
  # @return [Integer]
@@ -13,16 +19,6 @@ require('securerandom')
13
19
  class ULID
14
20
  include(Comparable)
15
21
 
16
- class Error < StandardError; end
17
- class OverflowError < Error; end
18
- class ParserError < Error; end
19
- class UnexpectedError < Error; end
20
-
21
- # Excluded I, L, O, U, -.
22
- # This is the encoding patterns.
23
- # The decoding issue is written in ULID::CrockfordBase32
24
- CROCKFORD_BASE32_ENCODING_STRING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
25
-
26
22
  TIMESTAMP_ENCODED_LENGTH = 10
27
23
  RANDOMNESS_ENCODED_LENGTH = 16
28
24
  ENCODED_LENGTH = 26
@@ -37,13 +33,12 @@ class ULID
37
33
 
38
34
  # @see https://github.com/ulid/spec/pull/57
39
35
  # Currently not used as a constant, but kept as a reference for now.
40
- PATTERN_WITH_CROCKFORD_BASE32_SUBSET = /(?<timestamp>[0-7][#{CROCKFORD_BASE32_ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}})(?<randomness>[#{CROCKFORD_BASE32_ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}})/i.freeze
36
+ PATTERN_WITH_CROCKFORD_BASE32_SUBSET = /(?<timestamp>[0-7][#{CrockfordBase32::ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}})(?<randomness>[#{CrockfordBase32::ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}})/i.freeze
41
37
 
42
38
  STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET = /\A#{PATTERN_WITH_CROCKFORD_BASE32_SUBSET.source}\z/i.freeze
43
39
 
44
40
  # Optimized for `ULID.scan`, might be changed the definition with gathered `ULID.scan` spec changed.
45
- # This can't contain `\b` for considering UTF-8 (e.g. Japanese), so intentional `false negative` definition.
46
- SCANNING_PATTERN = /[0-7][#{CROCKFORD_BASE32_ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CROCKFORD_BASE32_ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}/i.freeze
41
+ SCANNING_PATTERN = /\b[0-7][#{CrockfordBase32::ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CrockfordBase32::ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}\b/i.freeze
47
42
 
48
43
  # Similar as Time#inspect since Ruby 2.7, however it is NOT same.
49
44
  # Time#inspect trancates needless digits. Keeping full milliseconds with "%3N" will fit for ULID.
@@ -60,6 +55,16 @@ class ULID
60
55
  from_milliseconds_and_entropy(milliseconds: milliseconds_from_moment(moment), entropy: entropy)
61
56
  end
62
57
 
58
+ # Almost same as [.generate] except directly returning String without needless object creation
59
+ #
60
+ # @param [Integer, Time] moment
61
+ # @param [Integer] entropy
62
+ # @return [String]
63
+ def self.encode(moment: current_milliseconds, entropy: reasonable_entropy)
64
+ n32_encoded = encode_n32(milliseconds: milliseconds_from_moment(moment), entropy: entropy)
65
+ CrockfordBase32.from_n32(n32_encoded)
66
+ end
67
+
63
68
  # Short hand of `ULID.generate(moment: time)`
64
69
  # @param [Time] time
65
70
  # @return [ULID]
@@ -83,7 +88,7 @@ class ULID
83
88
 
84
89
  RANDOM_INTEGER_GENERATOR = -> {
85
90
  SecureRandom.random_number(MAX_INTEGER)
86
- }
91
+ }.freeze
87
92
 
88
93
  # @param [Range<Time>, Range<nil>, Range[ULID], nil] period
89
94
  # @overload sample(number, period: nil)
@@ -166,7 +171,12 @@ class ULID
166
171
  milliseconds = n32encoded_timestamp.to_i(32)
167
172
  entropy = n32encoded_randomness.to_i(32)
168
173
 
169
- new(milliseconds: milliseconds, entropy: entropy, integer: integer)
174
+ new(
175
+ milliseconds: milliseconds,
176
+ entropy: entropy,
177
+ integer: integer,
178
+ encoded: CrockfordBase32.from_n32(n32encoded).freeze
179
+ )
170
180
  end
171
181
 
172
182
  # @param [Range<Time>, Range<nil>, Range[ULID]] period
@@ -231,9 +241,9 @@ class ULID
231
241
  # @api private
232
242
  # @param [Time] time
233
243
  # @return [Integer]
234
- private_class_method(def self.milliseconds_from_time(time)
244
+ private_class_method def self.milliseconds_from_time(time)
235
245
  (time.to_r * 1000).to_i
236
- end)
246
+ end
237
247
 
238
248
  # @api private
239
249
  # @param [Time, Integer] moment
@@ -250,9 +260,20 @@ class ULID
250
260
  end
251
261
 
252
262
  # @return [Integer]
253
- private_class_method(def self.reasonable_entropy
263
+ private_class_method def self.reasonable_entropy
254
264
  SecureRandom.random_number(MAX_ENTROPY)
255
- end)
265
+ end
266
+
267
+ private_class_method def self.encode_n32(milliseconds:, entropy:)
268
+ raise(ArgumentError, 'milliseconds and entropy should be an `Integer`') unless Integer === milliseconds && Integer === entropy
269
+ raise(OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}") unless milliseconds <= MAX_MILLISECONDS
270
+ raise(OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}") unless entropy <= MAX_ENTROPY
271
+ raise(ArgumentError, 'milliseconds and entropy should not be negative') if milliseconds.negative? || entropy.negative?
272
+
273
+ n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
274
+ n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
275
+ "#{n32encoded_timestamp}#{n32encoded_randomness}"
276
+ end
256
277
 
257
278
  # @param [String, #to_str] string
258
279
  # @return [ULID]
@@ -268,6 +289,36 @@ class ULID
268
289
  from_integer(CrockfordBase32.decode(string))
269
290
  end
270
291
 
292
+ # @param [String, #to_str] string
293
+ # @return [ULID]
294
+ # @raise [ParserError] if the given format is not correct for ULID specs
295
+ def self.parse_variant_format(string)
296
+ string = String.try_convert(string)
297
+ raise(ArgumentError, 'ULID.parse_variant_format takes only strings') unless string
298
+
299
+ normalized_in_crockford = CrockfordBase32.normalize(string)
300
+ parse(normalized_in_crockford)
301
+ end
302
+
303
+ # Almost same as `ULID.parse(string).to_time` except directly returning Time instance without needless object creation
304
+ #
305
+ # @param [String, #to_str] string
306
+ # @return [Time]
307
+ # @raise [ParserError] if the given format is not correct for ULID specs
308
+ def self.decode_time(string, in: 'UTC')
309
+ in_for_time_at = binding.local_variable_get(:in) # Needed because `in` is a reserved word.
310
+ string = String.try_convert(string)
311
+ raise(ArgumentError, 'ULID.decode_time takes only strings') unless string
312
+
313
+ unless STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string)
314
+ raise(ParserError, "given `#{string}` does not match to `#{STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.inspect}`")
315
+ end
316
+
317
+ timestamp = string.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError)
318
+
319
+ Time.at(0, CrockfordBase32.decode(timestamp), :millisecond, in: in_for_time_at)
320
+ end
321
+
271
322
  # @param [String, #to_str] string
272
323
  # @return [String]
273
324
  # @raise [ParserError] if the given format is not correct for ULID specs, even if ignored `orthographical variants of the format`
@@ -275,23 +326,40 @@ class ULID
275
326
  string = String.try_convert(string)
276
327
  raise(ArgumentError, 'ULID.normalize takes only strings') unless string
277
328
 
278
- normalized_in_crockford = CrockfordBase32.normalize(string)
279
329
  # Ensure the ULID correctness, because CrockfordBase32 does not always mean to satisfy ULID format
280
- parse(normalized_in_crockford).to_s
330
+ parse_variant_format(string).to_s
281
331
  end
282
332
 
333
+ # @param [String, #to_str] string
283
334
  # @return [Boolean]
284
- def self.normalized?(object)
285
- normalized = normalize(object)
335
+ def self.normalized?(string)
336
+ normalized = normalize(string)
286
337
  rescue Exception
287
338
  false
288
339
  else
289
- normalized == object
340
+ normalized == string
290
341
  end
291
342
 
343
+ # @param [String, #to_str] string
292
344
  # @return [Boolean]
293
- def self.valid?(object)
294
- string = String.try_convert(object)
345
+ def self.valid_as_variant_format?(string)
346
+ parse_variant_format(string)
347
+ rescue Exception
348
+ false
349
+ else
350
+ true
351
+ end
352
+
353
+ # @deprecated Use [.valid_as_variant_format?] or [.normalized?] instead
354
+ #
355
+ # Returns `true` if it is normalized string.
356
+ # Basically the difference of normalized? is to accept downcase or not. This returns true for downcased ULIDs.
357
+ #
358
+ # @return [Boolean]
359
+ def self.valid?(string)
360
+ warn_kwargs = (RUBY_VERSION >= '3.0') ? { category: :deprecated } : {}
361
+ Warning.warn('ULID.valid? is deprecated. Use ULID.valid_as_variant_format? or ULID.normalized? instead.', **warn_kwargs)
362
+ string = String.try_convert(string)
295
363
  string ? STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string) : false
296
364
  end
297
365
 
@@ -316,7 +384,7 @@ class ULID
316
384
 
317
385
  # @param [BasicObject] object
318
386
  # @return [String]
319
- private_class_method(def self.safe_get_class_name(object)
387
+ private_class_method def self.safe_get_class_name(object)
320
388
  fallback = 'UnknownObject'
321
389
 
322
390
  # This class getter implementation used https://github.com/rspec/rspec-support/blob/4ad8392d0787a66f9c351d9cf6c7618e18b3d0f2/lib/rspec/support.rb#L83-L89 as a reference, thank you!
@@ -325,7 +393,7 @@ class ULID
325
393
  begin
326
394
  object.class
327
395
  rescue NoMethodError
328
- # steep can't correctly handle singeton class assign. See https://github.com/soutaro/steep/pull/586 for further detail
396
+ # steep can't correctly handle singleton class assign. See https://github.com/soutaro/steep/pull/586 for further detail
329
397
  # So this annotation is hack for the type infer.
330
398
  # @type var object: BasicObject
331
399
  # @type var singleton_class: untyped
@@ -341,7 +409,7 @@ class ULID
341
409
  else
342
410
  name || fallback
343
411
  end
344
- end)
412
+ end
345
413
 
346
414
  # @api private
347
415
  # @param [Integer] milliseconds
@@ -350,37 +418,36 @@ class ULID
350
418
  # @raise [OverflowError] if the given value is larger than the ULID limit
351
419
  # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
352
420
  def self.from_milliseconds_and_entropy(milliseconds:, entropy:)
353
- raise(ArgumentError, 'milliseconds and entropy should be an `Integer`') unless Integer === milliseconds && Integer === entropy
354
- raise(OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}") unless milliseconds <= MAX_MILLISECONDS
355
- raise(OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}") unless entropy <= MAX_ENTROPY
356
- raise(ArgumentError, 'milliseconds and entropy should not be negative') if milliseconds.negative? || entropy.negative?
357
-
358
- n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
359
- n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
360
- integer = (n32encoded_timestamp + n32encoded_randomness).to_i(32)
361
-
362
- new(milliseconds: milliseconds, entropy: entropy, integer: integer)
421
+ n32_encoded = encode_n32(milliseconds: milliseconds, entropy: entropy)
422
+ new(
423
+ milliseconds: milliseconds,
424
+ entropy: entropy,
425
+ integer: n32_encoded.to_i(32),
426
+ encoded: CrockfordBase32.from_n32(n32_encoded).upcase.freeze
427
+ )
363
428
  end
364
429
 
365
- # @dynamic milliseconds, entropy
366
430
  attr_reader(:milliseconds, :entropy)
367
431
 
368
432
  # @api private
369
433
  # @param [Integer] milliseconds
370
434
  # @param [Integer] entropy
371
435
  # @param [Integer] integer
436
+ # @param [String] encoded
372
437
  # @return [void]
373
- def initialize(milliseconds:, entropy:, integer:)
438
+ def initialize(milliseconds:, entropy:, integer:, encoded:)
374
439
  # All arguments check should be done with each constructors, not here
375
440
  @integer = integer
441
+ @encoded = encoded
376
442
  @milliseconds = milliseconds
377
443
  @entropy = entropy
378
444
  end
379
445
 
380
446
  # @return [String]
381
- def to_s
382
- @string ||= CrockfordBase32.encode(@integer).freeze
447
+ def encode
448
+ @encoded
383
449
  end
450
+ alias_method(:to_s, :encode)
384
451
 
385
452
  # @return [Integer]
386
453
  def to_i
@@ -392,30 +459,40 @@ class ULID
392
459
  [ULID, @integer].hash
393
460
  end
394
461
 
395
- # @return [Integer, nil]
462
+ # @return [-1, 0, 1, nil]
396
463
  def <=>(other)
397
464
  (ULID === other) ? (@integer <=> other.to_i) : nil
398
465
  end
399
466
 
400
467
  # @return [String]
401
468
  def inspect
402
- @inspect ||= "ULID(#{to_time.strftime(TIME_FORMAT_IN_INSPECT)}: #{to_s})".freeze
469
+ @inspect ||= "ULID(#{to_time.strftime(TIME_FORMAT_IN_INSPECT)}: #{@encoded})".freeze
403
470
  end
404
471
 
405
472
  # @return [Boolean]
406
473
  def eql?(other)
407
474
  equal?(other) || (ULID === other && @integer == other.to_i)
408
475
  end
409
- # @dynamic ==
410
476
  alias_method(:==, :eql?)
411
477
 
478
+ # Return `true` for same value of ULID, variant formats of strings, same Time in ULID precision(msec).
479
+ # Do not consider integer, octets and partial strings, then returns `false`.
480
+ #
412
481
  # @return [Boolean]
482
+ # @see .normalize
483
+ # @see .floor
413
484
  def ===(other)
414
485
  case other
415
486
  when ULID
416
487
  @integer == other.to_i
417
488
  when String
418
- to_s == other.upcase
489
+ begin
490
+ to_i == ULID.parse_variant_format(other).to_i
491
+ rescue Exception
492
+ false
493
+ end
494
+ when Time
495
+ to_time == ULID.floor(other)
419
496
  else
420
497
  false
421
498
  end
@@ -447,12 +524,12 @@ class ULID
447
524
 
448
525
  # @return [String]
449
526
  def timestamp
450
- @timestamp ||= (to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
527
+ @timestamp ||= (@encoded.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
451
528
  end
452
529
 
453
530
  # @return [String]
454
531
  def randomness
455
- @randomness ||= (to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze || raise(UnexpectedError))
532
+ @randomness ||= (@encoded.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze || raise(UnexpectedError))
456
533
  end
457
534
 
458
535
  # @note Providing for rough operations. The keys and values is not fixed.
@@ -478,7 +555,6 @@ class ULID
478
555
  ULID.from_integer(succ_int)
479
556
  end
480
557
  end
481
- # @dynamic next
482
558
  alias_method(:next, :succ)
483
559
 
484
560
  # @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
@@ -513,7 +589,7 @@ class ULID
513
589
  # @return [void]
514
590
  def marshal_load(integer)
515
591
  unmarshaled = ULID.from_integer(integer)
516
- initialize(integer: unmarshaled.to_i, milliseconds: unmarshaled.milliseconds, entropy: unmarshaled.entropy)
592
+ initialize(integer: unmarshaled.to_i, milliseconds: unmarshaled.milliseconds, entropy: unmarshaled.entropy, encoded: unmarshaled.to_s)
517
593
  end
518
594
 
519
595
  # @return [self]
@@ -543,13 +619,9 @@ class ULID
543
619
  end
544
620
  end
545
621
 
546
- require_relative('ulid/version')
547
- require_relative('ulid/crockford_base32')
548
- require_relative('ulid/monotonic_generator')
622
+ require_relative('ulid/ractor_unshareable_constants')
549
623
 
550
624
  class ULID
551
- MIN = parse('00000000000000000000000000').freeze
552
- MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
553
-
554
- private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR, :CROCKFORD_BASE32_ENCODING_STRING)
625
+ # Do not write as `ULID.private_constant` for avoiding YARD warnings `[warn]: in YARD::Handlers::Ruby::PrivateConstantHandler: Undocumentable private constants:`
626
+ private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR)
555
627
  end
data/sig/ulid.rbs CHANGED
@@ -1,6 +1,5 @@
1
1
  class ULID < Object
2
2
  VERSION: String
3
- CROCKFORD_BASE32_ENCODING_STRING: String
4
3
  TIMESTAMP_ENCODED_LENGTH: 10
5
4
  RANDOMNESS_ENCODED_LENGTH: 16
6
5
  ENCODED_LENGTH: 26
@@ -39,12 +38,11 @@ class ULID < Object
39
38
  class SetupError < UnexpectedError
40
39
  end
41
40
 
42
- N32_CHAR_BY_CROCKFORD_BASE32_CHAR: Hash[String, String]
43
- CROCKFORD_BASE32_CHAR_PATTERN: Regexp
44
- CROCKFORD_BASE32_CHAR_BY_N32_CHAR: Hash[String, String]
45
- N32_CHAR_PATTERN: Regexp
46
- STANDARD_BY_VARIANT: Hash[String, String]
47
- VARIANT_PATTERN: Regexp
41
+ ENCODING_STRING: String
42
+ CROCKFORD_BASE32_TR_PATTERN: String
43
+ BASE32_TR_PATTERN: String
44
+ VARIANT_TR_PATTERN: String
45
+ NORMALIZED_TR_PATTERN: String
48
46
 
49
47
  # A private API. Should not be used in your code.
50
48
  def self.encode: (Integer integer) -> String
@@ -54,6 +52,9 @@ class ULID < Object
54
52
 
55
53
  # A private API. Should not be used in your code.
56
54
  def self.normalize: (String string) -> String
55
+
56
+ # A private API. Should not be used in your code.
57
+ def self.from_n32: (String n32encoded) -> String
57
58
  end
58
59
 
59
60
  class MonotonicGenerator
@@ -78,6 +79,9 @@ class ULID < Object
78
79
  # The `Thread-safety` is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255)
79
80
  def generate: (?moment: moment) -> ULID
80
81
 
82
+ # Just providing similar api as `ULID.generate` and `ULID.encode` relation. No performance benefit exists in monotonic generator's one.
83
+ def encode: (?moment: moment) -> String
84
+
81
85
  # Returned value is `basically not` Thread-safety
82
86
  # If you want to keep Thread-safety, keep to call {#generate} only in same {#synchronize} block
83
87
  #
@@ -102,8 +106,8 @@ class ULID < Object
102
106
  type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
103
107
  type period = Range[Time] | Range[nil] | Range[ULID]
104
108
 
105
- @string: String?
106
109
  @integer: Integer
110
+ @encoded: String
107
111
  @timestamp: String?
108
112
  @randomness: String?
109
113
  @inspect: String?
@@ -145,6 +149,16 @@ class ULID < Object
145
149
  #
146
150
  def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
147
151
 
152
+ # Retuns encoded and normalzied String.
153
+ # It has same arguments signatures as `.generate`. So can be used for just ID creation usecases without needless object creation.
154
+ #
155
+ # NOTE: Difference of ULID#encode, returned String is NOT frozen.
156
+ #
157
+ def self.encode: (?moment: moment, ?entropy: Integer) -> String
158
+
159
+ # A private API. Should not be used in your code.
160
+ def self.encode_n32: (milliseconds: Integer, entropy: Integer) -> String
161
+
148
162
  # Shorthand of `ULID.generate(moment: Time)`
149
163
  # See also [ULID.generate](https://kachick.github.io/ruby-ulid/ULID.html#generate-class_method)
150
164
  #
@@ -205,7 +219,7 @@ class ULID < Object
205
219
  # ```
206
220
  def self.floor: (Time time) -> Time
207
221
 
208
- # Get ULID instance from encoded String.
222
+ # Return ULID instance from encoded String.
209
223
  #
210
224
  # ```ruby
211
225
  # ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV')
@@ -213,6 +227,32 @@ class ULID < Object
213
227
  # ```
214
228
  def self.parse: (_ToStr string) -> ULID
215
229
 
230
+ # Return Time instance from encoded String.
231
+ # See also `ULID.encode` for similar purpose.
232
+ #
233
+ # NOTE: Difference of ULID#to_time, returned Time is NOT frozen.
234
+ #
235
+ # ```ruby
236
+ # time = ULID.decode_time('01ARZ3NDEKTSV4RRFFQ69G5FAV')
237
+ # #=> 2016-07-30 23:54:10.259 UTC
238
+ # ```
239
+ def self.decode_time: (_ToStr string, ?in: String | Integer | nil) -> Time
240
+
241
+ # Get ULID instance from unnormalized String that encoded in Crockford's base32.
242
+ #
243
+ # http://www.crockford.com/base32.html
244
+ #
245
+ # * Ignore Hyphens (-)
246
+ # * Mapping 0 O o => 0, 1 I i L l => 1
247
+ #
248
+ # ```ruby
249
+ # ulid = ULID.parse_variant_format('01G70Y0Y7G-ZLXWDIREXERGSDoD')
250
+ # #=> ULID(2022-07-03 02:25:22.672 UTC: 01G70Y0Y7GZ1XWD1REXERGSD0D)
251
+ # ```
252
+ #
253
+ # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
254
+ def self.parse_variant_format: (_ToStr string) -> ULID
255
+
216
256
  # ```ruby
217
257
  # # Currently experimental feature, so needed to load the extension.
218
258
  # require 'ulid/uuid'
@@ -292,41 +332,30 @@ class ULID < Object
292
332
  # ulid2 = ULID.parse('01F4PTVCSN9ZPFKYTY2DDJVRK4') #=> ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4)
293
333
  # ulids = ULID.sample(1000, period: ulid1..ulid2)
294
334
  # ulids.uniq.size #=> 1000
295
- # ulids.take(10)
335
+ # ulids.take(5)
296
336
  # #=>
297
337
  # #[ULID(2021-05-02 06:57:19.954 UTC: 01F4NXW02JNB8H0J0TK48JD39X),
298
338
  # # ULID(2021-05-02 07:06:07.458 UTC: 01F4NYC372GVP7NS0YAYQGT4VZ),
299
339
  # # ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW),
300
340
  # # ULID(2021-04-27 22:17:37.844 UTC: 01F4APHGSMFJZQTGXKZBFFBPJP),
301
- # # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3),
302
- # # ULID(2021-04-30 07:18:54.307 UTC: 01F4GTA2332AS2VPHC4FMKC7R5),
303
- # # ULID(2021-05-02 12:26:03.480 UTC: 01F4PGNXARG554Y3HYVBDW4T9S),
304
- # # ULID(2021-04-29 09:52:15.107 UTC: 01F4EGP483ZX2747FQPWQNPPMW),
305
- # # ULID(2021-04-29 03:18:24.152 UTC: 01F4DT4Z4RA0QV8WFQGRAG63EH),
306
- # # ULID(2021-05-02 13:27:16.394 UTC: 01F4PM605ABF5SDVMEHBH8JJ9R)]
341
+ # # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3)]
307
342
  # ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
308
343
  # #=>
309
344
  # # [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
310
345
  # # ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
311
346
  # # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
312
347
  # # ULID(2021-05-01 03:06:09.130 UTC: 01F4JY7ZBABCBMX16XH2Q4JW4W),
313
- # # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G),
314
- # # ULID(2021-04-29 17:14:14.116 UTC: 01F4F9ZDQ449BE8BBZFEHYQWG2),
315
- # # ULID(2021-04-30 16:18:08.205 UTC: 01F4HS5DPD1HWDVJNJ6YKJXKSK),
316
- # # ULID(2021-04-30 10:31:33.602 UTC: 01F4H5ATF2A1CSQF0XV5NKZ288),
317
- # # ULID(2021-04-28 16:49:06.484 UTC: 01F4CP4PDM214Q6H3KJP7DYJRR),
318
- # # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
348
+ # # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G)]
319
349
  # ```
320
350
  def self.sample: (?period: period) -> ULID
321
351
  | (Integer number, ?period: period?) -> Array[ULID]
322
- def self.valid?: (untyped) -> bool
323
352
 
324
353
  # Returns normalized string
325
354
  #
326
355
  # ```ruby
356
+ # ULID.normalize('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> "01G70Y0Y7GZ1XWDAREXERGSDDD"
327
357
  # ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV"
328
- # ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false
329
- # ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true
358
+ # ULID.normalize('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> ULID::ParserError
330
359
  # ```
331
360
  #
332
361
  # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
@@ -335,13 +364,40 @@ class ULID < Object
335
364
  # Returns `true` if it is normalized string
336
365
  #
337
366
  # ```ruby
338
- # ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV"
339
- # ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false
340
- # ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true
367
+ # ULID.normalized?('01G70Y0Y7GZ1XWDAREXERGSDDD') #=> true
368
+ # ULID.normalized?('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> false
369
+ # ULID.normalized?(ULID.generate.to_s.downcase) #=> false
370
+ # ULID.normalized?('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> false (Not raising ULID::ParserError)
341
371
  # ```
342
372
  #
343
373
  # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
344
- def self.normalized?: (untyped) -> bool
374
+ def self.normalized?: (_ToStr string) -> bool
375
+ | (untyped) -> false
376
+
377
+ # Returns `true` if it is valid in ULID format variants
378
+ #
379
+ # ```ruby
380
+ # ULID.valid_as_variant_format?(ULID.generate.to_s.downcase) #=> true
381
+ # ULID.valid_as_variant_format?('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> true
382
+ # ULID.valid_as_variant_format?('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> false
383
+ # ```
384
+ #
385
+ # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
386
+ def self.valid_as_variant_format?: (_ToStr string) -> bool
387
+ | (untyped) -> false
388
+
389
+ # DEPRECATED Use valid_as_variant_format? instead
390
+ #
391
+ # Returns `true` if it is normalized string.
392
+ # Basically the difference of normalized? is to accept downcase or not. This returns true for downcased ULIDs.
393
+ #
394
+ # ```ruby
395
+ # ULID.valid?(ULID.generate.to_s.downcase) #=> true
396
+ # ```
397
+ #
398
+ # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
399
+ def self.valid?: (_ToStr string) -> bool
400
+ | (untyped) -> false
345
401
 
346
402
  # Returns parsed ULIDs from given String for rough operations.
347
403
  #
@@ -399,9 +455,12 @@ class ULID < Object
399
455
  # ```ruby
400
456
  # ulid = ULID.generate
401
457
  # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
402
- # ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
458
+ # ulid.encode #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
459
+ # ulid.encode.frozen? #=> true
460
+ # ulid.encode.equal?(ulid.to_s) #=> true
403
461
  # ```
404
- def to_s: -> String
462
+ def encode: -> String
463
+ alias to_s encode
405
464
 
406
465
  # ```ruby
407
466
  # ulid = ULID.generate
@@ -421,7 +480,12 @@ class ULID < Object
421
480
  # #=> true
422
481
  # ```
423
482
  #
424
- # To be precise, this sorting unaffected with `case sensitive or not` and might handle [ulid/spec#57](https://github.com/ulid/spec/pull/57) in future. So preferable than `lexicographically sortable` in actual case.
483
+ # To be precise, this sorting unaffected with `case sensitive or not` and might handle [ulid/spec#57](https://github.com/ulid/spec/pull/57) in future.
484
+ # So preferable than `lexicographically sortable` in actual case.
485
+ #
486
+ # This returns -1 | 0 | 1 for ULIDs. However defined as returning Integer. It is caused on ruby/rbs current definition.
487
+ # https://github.com/ruby/ruby/blob/cd34f56d450f2310cceaf4c5f34d23eddfda58e8/numeric.c#L4646-L4660
488
+ # https://github.com/ruby/rbs/blob/14abbbae8885a09a2ed82de2ef31d67a9c0a108d/core/integer.rbs#L461-L462
425
489
  #
426
490
  def <=>: (ULID other) -> Integer
427
491
  | (untyped other) -> nil
@@ -436,9 +500,28 @@ class ULID < Object
436
500
  # ULID.parse('4NNB20D9C1ME2NGMTX51ERZJX0') == ULID.parse('4nnb20d9c1me2ngmtx51erzjx0')
437
501
  # #=> true
438
502
  # ```
439
- def eql?: (untyped other) -> bool
503
+ def eql?: (ULID other) -> bool
504
+ | (untyped other) -> false
440
505
  alias == eql?
441
- def ===: (untyped other) -> bool
506
+
507
+ # Return `true` for same value of ULID, variant formats of strings, same Time in ULID precision(msec).
508
+ # Do not consider integer, octets and partial strings, then returns `false`.
509
+ #
510
+ # ```ruby
511
+ # ulid = ULID.parse('01G6Z7Q4RSH97E6QHAC7VK19G2')
512
+ # ulid === ULID.parse(ulid.to_s)
513
+ # #=> true
514
+ # ulid === ulid.to_s.downcase
515
+ # #=> true
516
+ # ulid === ulid.to_time
517
+ # #=> true
518
+ # ulid === ulid.to_i
519
+ # #=> false
520
+ # ulid === ulid.next
521
+ # #=> false
522
+ # ```
523
+ def ===: (ULID | String | Time other) -> bool
524
+ | (untyped other) -> false
442
525
 
443
526
  # ```ruby
444
527
  # ulid = ULID.generate
@@ -526,9 +609,6 @@ class ULID < Object
526
609
 
527
610
  private
528
611
 
529
- # A private API. Should not be used in your code.
530
- def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> ULID
531
-
532
612
  # A private API. Should not be used in your code.
533
613
  def self.reasonable_entropy: -> Integer
534
614
 
@@ -539,7 +619,7 @@ class ULID < Object
539
619
  def self.safe_get_class_name: (untyped object) -> String
540
620
 
541
621
  # A private API. Should not be used in your code.
542
- def initialize: (milliseconds: Integer, entropy: Integer, integer: Integer) -> void
622
+ def initialize: (milliseconds: Integer, entropy: Integer, integer: Integer, encoded: String) -> void
543
623
 
544
624
  # A private API. Should not be used in your code.
545
625
  def cache_all_instance_variables: -> void
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-ulid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenichi Kamiya
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-22 00:00:00.000000000 Z
11
+ date: 2022-07-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: " generator, monotonic generator, parser and manipulations for ULID
14
- (ruby/rbs signatures included)\n"
13
+ description: " generator, optional monotonicity, parser and tools for ULID (RBS
14
+ included)\n"
15
15
  email:
16
16
  - kachick1+ruby@gmail.com
17
17
  executables: []
@@ -23,7 +23,9 @@ files:
23
23
  - lib/ruby-ulid.rb
24
24
  - lib/ulid.rb
25
25
  - lib/ulid/crockford_base32.rb
26
+ - lib/ulid/errors.rb
26
27
  - lib/ulid/monotonic_generator.rb
28
+ - lib/ulid/ractor_unshareable_constants.rb
27
29
  - lib/ulid/uuid.rb
28
30
  - lib/ulid/version.rb
29
31
  - sig/ulid.rbs