ruby-ulid 0.2.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +80 -105
- data/lib/ruby-ulid.rb +2 -1
- data/lib/ulid/crockford_base32.rb +4 -3
- data/lib/ulid/monotonic_generator.rb +22 -15
- data/lib/ulid/ractor_unshareable_constants.rb +12 -0
- data/lib/ulid/uuid.rb +9 -5
- data/lib/ulid/version.rb +2 -1
- data/lib/ulid.rb +148 -90
- data/sig/ulid.rbs +88 -31
- metadata +6 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d7356366d0184815a725d1947f4f685b3abba0320ae60800b5a015dc7c649e9
|
4
|
+
data.tar.gz: 04b55b6d92dbf02865a7803be352f5cd385f3417c061269f2ad197474202154d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa826d204cbdc1e6850fbeab278dc72e5b683926db9af7ed477ec2e74837d32829dbfb468c1156d6dcf5be604f9fdeb8fd0a282999b47a627884152b2a2cd5d4
|
7
|
+
data.tar.gz: 0b1208e76af50f9952c8c4835d1410c7e1fd114313cc71d39a143c9bc04ac4f1a35ba49be88a49406606c441e4f4fa7cad5fc938d74fcecde0d612319a6890b3
|
data/README.md
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
# ruby-ulid
|
2
2
|
|
3
|
+
[](https://github.com/kachick/ruby-ulid/actions/workflows/test_behaviors.yml/?branch=main)
|
4
|
+
[](http://badge.fury.io/rb/ruby-ulid)
|
5
|
+
|
3
6
|
## Overview
|
4
7
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
+
[ulid/spec](https://github.com/ulid/spec) is useful.
|
9
|
+
Especially possess all `uniqueness`, `randomness`, `extractable timestamps` and `sortable` features.
|
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).
|
8
11
|
|
9
12
|
---
|
10
13
|
|
11
|
-
](https://github.com/kachick/ruby-ulid/actions/workflows/test_behaviors.yml/?branch=main)
|
14
|
-
[](http://badge.fury.io/rb/ruby-ulid)
|
15
|
-
[](https://github.dev/kachick/ruby-ulid)
|
14
|
+

|
16
15
|
|
17
16
|
## Universally Unique Lexicographically Sortable Identifier
|
18
17
|
|
@@ -47,21 +46,43 @@ $ gem install ruby-ulid
|
|
47
46
|
Should be installed!
|
48
47
|
```
|
49
48
|
|
50
|
-
Add this line
|
49
|
+
Add this line in your Gemfile.
|
51
50
|
|
52
51
|
```ruby
|
53
|
-
gem
|
52
|
+
gem('ruby-ulid', '~> 0.5.0')
|
54
53
|
```
|
55
54
|
|
56
|
-
###
|
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.5.0"
|
62
|
+
```
|
63
|
+
|
64
|
+
### Basic Generator
|
65
|
+
|
66
|
+
The generated `ULID` is an object not just a string.
|
67
|
+
|
68
|
+
```ruby
|
64
69
|
ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
|
70
|
+
```
|
71
|
+
|
72
|
+
### Parser
|
73
|
+
|
74
|
+
Get the objects from exists encoded ULIDs.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV') #=> ULID(2016-07-30 23:54:10.259 UTC: 01ARZ3NDEKTSV4RRFFQ69G5FAV)
|
78
|
+
```
|
79
|
+
|
80
|
+
### ULID object
|
81
|
+
|
82
|
+
Extract timestamps and binary formats.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
|
65
86
|
ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
|
66
87
|
ulid.milliseconds #=> 1619544442826
|
67
88
|
ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
|
@@ -71,16 +92,9 @@ ulid.to_i #=> 1957909092946624190749577070267409738
|
|
71
92
|
ulid.octets #=> [1, 121, 20, 95, 7, 202, 187, 60, 175, 51, 76, 60, 49, 73, 37, 74]
|
72
93
|
```
|
73
94
|
|
74
|
-
You can get the objects from exists encoded ULIDs
|
75
|
-
|
76
|
-
```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
|
79
|
-
```
|
80
|
-
|
81
95
|
### Sortable with the timestamp
|
82
96
|
|
83
|
-
ULIDs are sortable when they are generated in different timestamp with milliseconds precision
|
97
|
+
ULIDs are sortable when they are generated in different timestamp with milliseconds precision.
|
84
98
|
|
85
99
|
```ruby
|
86
100
|
ulids = 1000.times.map do
|
@@ -91,7 +105,7 @@ ulids.uniq(&:to_time).size #=> 1000
|
|
91
105
|
ulids.sort == ulids #=> true
|
92
106
|
```
|
93
107
|
|
94
|
-
`ULID.generate` can take fixed `Time` instance. The shorthand is `ULID.at
|
108
|
+
`ULID.generate` can take fixed `Time` instance. The shorthand is `ULID.at`.
|
95
109
|
|
96
110
|
```ruby
|
97
111
|
time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
|
@@ -105,7 +119,7 @@ end
|
|
105
119
|
ulids.sort == ulids #=> true
|
106
120
|
```
|
107
121
|
|
108
|
-
|
122
|
+
Basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
|
109
123
|
|
110
124
|
```ruby
|
111
125
|
ulids = 10000.times.map do
|
@@ -147,11 +161,11 @@ sample_ulids_by_the_time.take(5) #=>
|
|
147
161
|
ulids.sort == ulids #=> true
|
148
162
|
```
|
149
163
|
|
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)
|
164
|
+
Same generator does not generate duplicated ULIDs even in multi threads environment. It is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255).
|
151
165
|
|
152
166
|
### Filtering IDs with `Time`
|
153
167
|
|
154
|
-
`ULID` can be element of the `Range`. If
|
168
|
+
`ULID` can be element of the `Range`. If they were generated with monotonic generator, ID based filtering is easy and reliable.
|
155
169
|
|
156
170
|
```ruby
|
157
171
|
include_end = ulid1..ulid2
|
@@ -187,7 +201,9 @@ time = Time.at(946684800, Rational('123456.789')).utc #=> 2000-01-01 00:00:00.12
|
|
187
201
|
ULID.floor(time) #=> 2000-01-01 00:00:00.123 UTC
|
188
202
|
```
|
189
203
|
|
190
|
-
###
|
204
|
+
### Tools
|
205
|
+
|
206
|
+
#### Scanner for string (e.g. `JSON`)
|
191
207
|
|
192
208
|
For rough operations, `ULID.scan` might be useful.
|
193
209
|
|
@@ -230,7 +246,7 @@ ULID.scan(json).to_a
|
|
230
246
|
```
|
231
247
|
|
232
248
|
`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
|
249
|
+
The results and spec are not fixed. Should not be used except snippets/console operation.
|
234
250
|
|
235
251
|
```ruby
|
236
252
|
ULID.parse('01F4GNBXW1AM2KWW52PVT3ZY9X').patterns
|
@@ -241,7 +257,7 @@ ULID.parse('01F4GNBXW1AM2KWW52PVT3ZY9X').patterns
|
|
241
257
|
}
|
242
258
|
```
|
243
259
|
|
244
|
-
|
260
|
+
#### Get boundary ULIDs
|
245
261
|
|
246
262
|
`ULID.min` and `ULID.max` return termination values for ULID spec.
|
247
263
|
|
@@ -256,10 +272,12 @@ ULID.min(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
|
|
256
272
|
ULID.max(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
|
257
273
|
```
|
258
274
|
|
275
|
+
#### As element in Enumerable
|
276
|
+
|
259
277
|
`ULID#next` and `ULID#succ` returns next(successor) ULID.
|
260
278
|
Especially `ULID#succ` makes it possible `Range[ULID]#each`.
|
261
279
|
|
262
|
-
NOTE:
|
280
|
+
NOTE: However basically `Range[ULID]#each` should not be used. Iincrementing 128 bits IDs are not reasonable operation in most cases.
|
263
281
|
|
264
282
|
```ruby
|
265
283
|
ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZY').next.to_s #=> "01BX5ZZKBKZZZZZZZZZZZZZZZZ"
|
@@ -267,7 +285,7 @@ ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZZ').next.to_s #=> "01BX5ZZKBM00000000000000
|
|
267
285
|
ULID.parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').next #=> nil
|
268
286
|
```
|
269
287
|
|
270
|
-
`ULID#pred` returns predecessor ULID
|
288
|
+
`ULID#pred` returns predecessor ULID.
|
271
289
|
|
272
290
|
```ruby
|
273
291
|
ULID.parse('01BX5ZZKBK0000000000000001').pred.to_s #=> "01BX5ZZKBK0000000000000000"
|
@@ -275,6 +293,8 @@ ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZ
|
|
275
293
|
ULID.parse('00000000000000000000000000').pred #=> nil
|
276
294
|
```
|
277
295
|
|
296
|
+
#### Test helpers
|
297
|
+
|
278
298
|
`ULID.sample` returns random ULIDs.
|
279
299
|
|
280
300
|
Basically ignores generating time.
|
@@ -300,61 +320,52 @@ ulid1 = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.82
|
|
300
320
|
ulid2 = ULID.parse('01F4PTVCSN9ZPFKYTY2DDJVRK4') #=> ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4)
|
301
321
|
ulids = ULID.sample(1000, period: ulid1..ulid2)
|
302
322
|
ulids.uniq.size #=> 1000
|
303
|
-
ulids.take(
|
323
|
+
ulids.take(5)
|
304
324
|
#=>
|
305
325
|
#[ULID(2021-05-02 06:57:19.954 UTC: 01F4NXW02JNB8H0J0TK48JD39X),
|
306
326
|
# ULID(2021-05-02 07:06:07.458 UTC: 01F4NYC372GVP7NS0YAYQGT4VZ),
|
307
327
|
# ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW),
|
308
328
|
# ULID(2021-04-27 22:17:37.844 UTC: 01F4APHGSMFJZQTGXKZBFFBPJP),
|
309
|
-
# ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3)
|
310
|
-
|
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)
|
329
|
+
# ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3)]
|
330
|
+
ULID.sample(5, period: ulid1.to_time..ulid2.to_time)
|
316
331
|
#=>
|
317
332
|
# [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
|
318
333
|
# ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
|
319
334
|
# ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
|
320
335
|
# 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)]
|
336
|
+
# ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G)]
|
327
337
|
```
|
328
338
|
|
329
|
-
|
339
|
+
#### Variants of format
|
330
340
|
|
331
341
|
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
342
|
|
343
|
+
>Case insensitive
|
344
|
+
|
345
|
+
I can understand it might be considered in actual use-case. So `ULID.parse` accepts upcase and downcase.
|
346
|
+
However it is a controversial point, discussing in [ulid/spec#3](https://github.com/ulid/spec/issues/3).
|
347
|
+
|
333
348
|
>Uses Crockford's base32 for better efficiency and readability (5 bits per character)
|
334
349
|
|
335
350
|
The original `Crockford's base32` maps `I`, `L` to `1`, `O` to `0`.
|
336
351
|
And accepts freestyle inserting `Hyphens (-)`.
|
337
352
|
To consider this patterns or not is different in each implementations.
|
338
353
|
|
339
|
-
|
340
|
-
I have suggested it would be clarified in [ulid/spec#57](https://github.com/ulid/spec/pull/57).
|
341
|
-
|
342
|
-
>Case insensitive
|
354
|
+
I have suggested to clarify `subset of Crockford's base32` in [ulid/spec#57](https://github.com/ulid/spec/pull/57).
|
343
355
|
|
344
|
-
|
345
|
-
But it is a controversial point, discussing in [ulid/spec#3](https://github.com/ulid/spec/issues/3).
|
356
|
+
This gem provides some methods to handle the nasty possibilities.
|
346
357
|
|
347
|
-
|
348
|
-
|
349
|
-
`ULID.normalize` and `ULID.normalized?`
|
358
|
+
`ULID.normalize`, `ULID.normalized?`, `ULID.valid_as_variant_format?` and `ULID.parse_variant_format`
|
350
359
|
|
351
360
|
```ruby
|
352
|
-
ULID.normalize('-
|
353
|
-
ULID.normalized?('-
|
354
|
-
ULID.normalized?('
|
361
|
+
ULID.normalize('01g70y0y7g-z1xwdarexergsddd') #=> "01G70Y0Y7GZ1XWDAREXERGSDDD"
|
362
|
+
ULID.normalized?('01g70y0y7g-z1xwdarexergsddd') #=> false
|
363
|
+
ULID.normalized?('01G70Y0Y7GZ1XWDAREXERGSDDD') #=> true
|
364
|
+
ULID.valid_as_variant_format?('01g70y0y7g-z1xwdarexergsddd') #=> true
|
365
|
+
ULID.parse_variant_format('01G70Y0Y7G-ZLXWDIREXERGSDoD') #=> ULID(2022-07-03 02:25:22.672 UTC: 01G70Y0Y7GZ1XWD1REXERGSD0D)
|
355
366
|
```
|
356
367
|
|
357
|
-
|
368
|
+
#### UUIDv4 converter (experimental)
|
358
369
|
|
359
370
|
`ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
|
360
371
|
The imported timestamp is meaningless. So ULID's benefit will lost.
|
@@ -387,56 +398,19 @@ ULID.min == reversed_min #=> false
|
|
387
398
|
ULID.max == reversed_max #=> false
|
388
399
|
```
|
389
400
|
|
390
|
-
##
|
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
|
-
```
|
401
|
+
## Migration from other gems
|
427
402
|
|
428
|
-
|
403
|
+
See [wiki page for gem migration](https://github.com/kachick/ruby-ulid/wiki/Gem-migration).
|
429
404
|
|
430
|
-
|
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)
|
405
|
+
## RBS
|
434
406
|
|
435
|
-
|
407
|
+
Try at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox).
|
436
408
|
|
437
|
-
|
409
|
+
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).
|
438
410
|
|
439
|
-
|
411
|
+
* 
|
412
|
+
* 
|
413
|
+
* 
|
440
414
|
|
441
415
|
## References
|
442
416
|
|
@@ -446,4 +420,5 @@ The results are not something to be proud of.
|
|
446
420
|
|
447
421
|
## Note
|
448
422
|
|
449
|
-
-
|
423
|
+
- [UUIDv6, UUIDv7, UUIDv8](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-01.html) is another choice for sortable and randomness ID.
|
424
|
+
However they are stayed in draft state. ref: [ruby-ulid#37](https://github.com/kachick/ruby-ulid/issues/37)
|
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
|
|
@@ -14,10 +15,10 @@ 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 <
|
18
|
+
class SetupError < UnexpectedError; end
|
18
19
|
|
19
20
|
n32_chars = [*'0'..'9', *'A'..'V'].map(&:freeze).freeze
|
20
|
-
raise
|
21
|
+
raise(SetupError, 'obvious bug exists in the mapping algorithm') unless n32_chars.size == 32
|
21
22
|
|
22
23
|
n32_char_by_number = {}
|
23
24
|
n32_chars.each_with_index do |char, index|
|
@@ -48,7 +49,7 @@ class ULID
|
|
48
49
|
map[encoding_char] = char_32
|
49
50
|
end
|
50
51
|
end.freeze
|
51
|
-
raise
|
52
|
+
raise(SetupError, 'obvious bug exists in the mapping algorithm') unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
|
52
53
|
|
53
54
|
CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
|
54
55
|
|
@@ -1,19 +1,23 @@
|
|
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
|
8
|
-
|
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.
|
11
|
+
include(MonitorMixin)
|
9
12
|
|
13
|
+
# @dynamic prev
|
10
14
|
# @return [ULID, nil]
|
11
|
-
attr_reader
|
15
|
+
attr_reader(:prev)
|
12
16
|
|
13
|
-
undef_method
|
17
|
+
undef_method(:instance_variable_set)
|
14
18
|
|
15
19
|
def initialize
|
16
|
-
super
|
20
|
+
super
|
17
21
|
@prev = nil
|
18
22
|
end
|
19
23
|
|
@@ -21,7 +25,8 @@ class ULID
|
|
21
25
|
def inspect
|
22
26
|
"ULID::MonotonicGenerator(prev: #{@prev.inspect})"
|
23
27
|
end
|
24
|
-
|
28
|
+
# @dynamic to_s
|
29
|
+
alias_method(:to_s, :inspect)
|
25
30
|
|
26
31
|
# @param [Time, Integer] moment
|
27
32
|
# @return [ULID]
|
@@ -30,23 +35,25 @@ class ULID
|
|
30
35
|
# Basically will not happen. Just means this feature prefers error rather than invalid value.
|
31
36
|
def generate(moment: ULID.current_milliseconds)
|
32
37
|
synchronize do
|
33
|
-
|
34
|
-
|
35
|
-
|
38
|
+
prev_ulid = @prev
|
39
|
+
unless prev_ulid
|
40
|
+
ret = ULID.generate(moment: moment)
|
41
|
+
@prev = ret
|
42
|
+
return ret
|
36
43
|
end
|
37
44
|
|
38
45
|
milliseconds = ULID.milliseconds_from_moment(moment)
|
39
46
|
|
40
47
|
ulid = (
|
41
|
-
if
|
48
|
+
if prev_ulid.milliseconds < milliseconds
|
42
49
|
ULID.generate(moment: milliseconds)
|
43
50
|
else
|
44
|
-
ULID.from_milliseconds_and_entropy(milliseconds:
|
51
|
+
ULID.from_milliseconds_and_entropy(milliseconds: prev_ulid.milliseconds, entropy: prev_ulid.entropy.succ)
|
45
52
|
end
|
46
53
|
)
|
47
54
|
|
48
|
-
unless ulid >
|
49
|
-
base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{
|
55
|
+
unless ulid > prev_ulid
|
56
|
+
base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{prev_ulid.inspect}"
|
50
57
|
additional_information = (
|
51
58
|
if Thread.list == [Thread.main]
|
52
59
|
'# NOTE: looks single thread only exist'
|
@@ -55,7 +62,7 @@ class ULID
|
|
55
62
|
end
|
56
63
|
)
|
57
64
|
|
58
|
-
raise
|
65
|
+
raise(UnexpectedError, base_message + additional_information)
|
59
66
|
end
|
60
67
|
|
61
68
|
@prev = ulid
|
@@ -63,12 +70,12 @@ class ULID
|
|
63
70
|
end
|
64
71
|
end
|
65
72
|
|
66
|
-
undef_method
|
73
|
+
undef_method(:freeze)
|
67
74
|
|
68
75
|
# @raise [TypeError] always raises exception and does not freeze self
|
69
76
|
# @return [void]
|
70
77
|
def freeze
|
71
|
-
raise
|
78
|
+
raise(TypeError, "cannot freeze #{self.class}")
|
72
79
|
end
|
73
80
|
end
|
74
81
|
end
|
@@ -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
|
|
@@ -10,18 +11,18 @@
|
|
10
11
|
class ULID
|
11
12
|
# Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
|
12
13
|
UUIDV4_PATTERN = /\A[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i.freeze
|
13
|
-
private_constant
|
14
|
+
private_constant(:UUIDV4_PATTERN)
|
14
15
|
|
15
16
|
# @param [String, #to_str] uuid
|
16
17
|
# @return [ULID]
|
17
18
|
# @raise [ParserError] if the given format is not correct for UUIDv4 specs
|
18
19
|
def self.from_uuidv4(uuid)
|
19
20
|
uuid = String.try_convert(uuid)
|
20
|
-
raise
|
21
|
+
raise(ArgumentError, 'ULID.from_uuidv4 takes only strings') unless uuid
|
21
22
|
|
22
23
|
prefix_trimmed = uuid.delete_prefix('urn:uuid:')
|
23
24
|
unless UUIDV4_PATTERN.match?(prefix_trimmed)
|
24
|
-
raise
|
25
|
+
raise(ParserError, "given `#{uuid}` does not match to `#{UUIDV4_PATTERN.inspect}`")
|
25
26
|
end
|
26
27
|
|
27
28
|
normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
|
@@ -32,8 +33,11 @@ class ULID
|
|
32
33
|
def to_uuidv4
|
33
34
|
# This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
|
34
35
|
array = octets.pack('C*').unpack('NnnnnN')
|
35
|
-
array[2]
|
36
|
-
|
36
|
+
ref2, ref3 = array[2], array[3]
|
37
|
+
raise unless Integer === ref2 && Integer === ref3
|
38
|
+
|
39
|
+
array[2] = (ref2 & 0x0fff) | 0x4000
|
40
|
+
array[3] = (ref3 & 0x3fff) | 0x8000
|
37
41
|
('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
|
38
42
|
end
|
39
43
|
end
|
data/lib/ulid/version.rb
CHANGED
data/lib/ulid.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
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
|
-
require
|
7
|
+
require('securerandom')
|
7
8
|
|
8
9
|
# @see https://github.com/ulid/spec
|
9
10
|
# @!attribute [r] milliseconds
|
@@ -11,7 +12,7 @@ require 'securerandom'
|
|
11
12
|
# @!attribute [r] entropy
|
12
13
|
# @return [Integer]
|
13
14
|
class ULID
|
14
|
-
include
|
15
|
+
include(Comparable)
|
15
16
|
|
16
17
|
class Error < StandardError; end
|
17
18
|
class OverflowError < Error; end
|
@@ -25,10 +26,12 @@ class ULID
|
|
25
26
|
|
26
27
|
TIMESTAMP_ENCODED_LENGTH = 10
|
27
28
|
RANDOMNESS_ENCODED_LENGTH = 16
|
28
|
-
ENCODED_LENGTH =
|
29
|
+
ENCODED_LENGTH = 26
|
30
|
+
|
29
31
|
TIMESTAMP_OCTETS_LENGTH = 6
|
30
32
|
RANDOMNESS_OCTETS_LENGTH = 10
|
31
|
-
OCTETS_LENGTH =
|
33
|
+
OCTETS_LENGTH = 16
|
34
|
+
|
32
35
|
MAX_MILLISECONDS = 281474976710655
|
33
36
|
MAX_ENTROPY = 1208925819614629174706175
|
34
37
|
MAX_INTEGER = 340282366920938463463374607431768211455
|
@@ -40,14 +43,15 @@ class ULID
|
|
40
43
|
STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET = /\A#{PATTERN_WITH_CROCKFORD_BASE32_SUBSET.source}\z/i.freeze
|
41
44
|
|
42
45
|
# Optimized for `ULID.scan`, might be changed the definition with gathered `ULID.scan` spec changed.
|
43
|
-
|
44
|
-
SCANNING_PATTERN = /[0-7][#{CROCKFORD_BASE32_ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CROCKFORD_BASE32_ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}/i.freeze
|
46
|
+
SCANNING_PATTERN = /\b[0-7][#{CROCKFORD_BASE32_ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CROCKFORD_BASE32_ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}\b/i.freeze
|
45
47
|
|
46
|
-
#
|
48
|
+
# Similar as Time#inspect since Ruby 2.7, however it is NOT same.
|
49
|
+
# Time#inspect trancates needless digits. Keeping full milliseconds with "%3N" will fit for ULID.
|
47
50
|
# @see https://bugs.ruby-lang.org/issues/15958
|
51
|
+
# @see https://github.com/ruby/ruby/blob/744d17ff6c33b09334508e8110007ea2a82252f5/time.c#L4026-L4078
|
48
52
|
TIME_FORMAT_IN_INSPECT = '%Y-%m-%d %H:%M:%S.%3N %Z'
|
49
53
|
|
50
|
-
private_class_method
|
54
|
+
private_class_method(:new)
|
51
55
|
|
52
56
|
# @param [Integer, Time] moment
|
53
57
|
# @param [Integer] entropy
|
@@ -60,7 +64,7 @@ class ULID
|
|
60
64
|
# @param [Time] time
|
61
65
|
# @return [ULID]
|
62
66
|
def self.at(time)
|
63
|
-
raise
|
67
|
+
raise(ArgumentError, 'ULID.at takes only `Time` instance') unless Time === time
|
64
68
|
|
65
69
|
from_milliseconds_and_entropy(milliseconds: milliseconds_from_time(time), entropy: reasonable_entropy)
|
66
70
|
end
|
@@ -79,7 +83,7 @@ class ULID
|
|
79
83
|
|
80
84
|
RANDOM_INTEGER_GENERATOR = -> {
|
81
85
|
SecureRandom.random_number(MAX_INTEGER)
|
82
|
-
}
|
86
|
+
}.freeze
|
83
87
|
|
84
88
|
# @param [Range<Time>, Range<nil>, Range[ULID], nil] period
|
85
89
|
# @overload sample(number, period: nil)
|
@@ -92,14 +96,14 @@ class ULID
|
|
92
96
|
# * Do not ensure the uniqueness
|
93
97
|
# * Do not take random generator for the arguments
|
94
98
|
# * Raising error instead of truncating elements for the given number
|
95
|
-
def self.sample(
|
99
|
+
def self.sample(number=nil, period: nil)
|
96
100
|
int_generator = (
|
97
101
|
if period
|
98
102
|
ulid_range = range(period)
|
99
103
|
min, max, exclude_end = ulid_range.begin.to_i, ulid_range.end.to_i, ulid_range.exclude_end?
|
100
104
|
|
101
105
|
possibilities = (max - min) + (exclude_end ? 0 : 1)
|
102
|
-
raise
|
106
|
+
raise(ArgumentError, "given range `#{ulid_range.inspect}` does not have possibilities") unless possibilities.positive?
|
103
107
|
|
104
108
|
-> {
|
105
109
|
SecureRandom.random_number(possibilities) + min
|
@@ -109,24 +113,21 @@ class ULID
|
|
109
113
|
end
|
110
114
|
)
|
111
115
|
|
112
|
-
case
|
113
|
-
when
|
116
|
+
case number
|
117
|
+
when nil
|
114
118
|
from_integer(int_generator.call)
|
115
|
-
when
|
116
|
-
number = args.first
|
117
|
-
raise ArgumentError, 'accepts no argument or integer only' unless Integer === number
|
118
|
-
|
119
|
+
when Integer
|
119
120
|
if number > MAX_INTEGER || number.negative?
|
120
|
-
raise
|
121
|
+
raise(ArgumentError, "given number `#{number}` is larger than ULID limit `#{MAX_INTEGER}` or negative")
|
121
122
|
end
|
122
123
|
|
123
|
-
if period && (number > possibilities)
|
124
|
-
raise
|
124
|
+
if period && possibilities && (number > possibilities)
|
125
|
+
raise(ArgumentError, "given number `#{number}` is larger than given possibilities `#{possibilities}`")
|
125
126
|
end
|
126
127
|
|
127
128
|
Array.new(number) { from_integer(int_generator.call) }
|
128
129
|
else
|
129
|
-
raise
|
130
|
+
raise(ArgumentError, 'accepts no argument or integer only')
|
130
131
|
end
|
131
132
|
end
|
132
133
|
|
@@ -136,11 +137,13 @@ class ULID
|
|
136
137
|
# @yieldreturn [self]
|
137
138
|
def self.scan(string)
|
138
139
|
string = String.try_convert(string)
|
139
|
-
raise
|
140
|
-
return to_enum(
|
140
|
+
raise(ArgumentError, 'ULID.scan takes only strings') unless string
|
141
|
+
return to_enum(:scan, string) unless block_given?
|
141
142
|
|
142
143
|
string.scan(SCANNING_PATTERN) do |matched|
|
143
|
-
|
144
|
+
if String === matched
|
145
|
+
yield(parse(matched))
|
146
|
+
end
|
144
147
|
end
|
145
148
|
self
|
146
149
|
end
|
@@ -150,14 +153,16 @@ class ULID
|
|
150
153
|
# @raise [OverflowError] if the given integer is larger than the ULID limit
|
151
154
|
# @raise [ArgumentError] if the given integer is negative number
|
152
155
|
def self.from_integer(integer)
|
153
|
-
raise
|
154
|
-
raise
|
155
|
-
raise
|
156
|
+
raise(ArgumentError, 'ULID.from_integer takes only `Integer`') unless Integer === integer
|
157
|
+
raise(OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}") unless integer <= MAX_INTEGER
|
158
|
+
raise(ArgumentError, "integer should not be negative: given: #{integer}") if integer.negative?
|
156
159
|
|
157
160
|
n32encoded = integer.to_s(32).rjust(ENCODED_LENGTH, '0')
|
158
161
|
n32encoded_timestamp = n32encoded.slice(0, TIMESTAMP_ENCODED_LENGTH)
|
159
162
|
n32encoded_randomness = n32encoded.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH)
|
160
163
|
|
164
|
+
raise(UnexpectedError) unless n32encoded_timestamp && n32encoded_randomness
|
165
|
+
|
161
166
|
milliseconds = n32encoded_timestamp.to_i(32)
|
162
167
|
entropy = n32encoded_randomness.to_i(32)
|
163
168
|
|
@@ -168,37 +173,43 @@ class ULID
|
|
168
173
|
# @return [Range<ULID>]
|
169
174
|
# @raise [ArgumentError] if the given period is not a `Range[Time]`, `Range[nil]` or `Range[ULID]`
|
170
175
|
def self.range(period)
|
171
|
-
raise
|
176
|
+
raise(ArgumentError, 'ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`') unless Range === period
|
172
177
|
|
173
178
|
begin_element, end_element, exclude_end = period.begin, period.end, period.exclude_end?
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
179
|
+
new_begin, new_end = false, false
|
180
|
+
|
181
|
+
begin_ulid = (
|
182
|
+
case begin_element
|
183
|
+
when Time
|
184
|
+
new_begin = true
|
185
|
+
min(begin_element)
|
186
|
+
when nil
|
187
|
+
MIN
|
188
|
+
when self
|
189
|
+
begin_element
|
190
|
+
else
|
191
|
+
raise(ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}")
|
192
|
+
end
|
193
|
+
)
|
186
194
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
195
|
+
end_ulid = (
|
196
|
+
case end_element
|
197
|
+
when Time
|
198
|
+
new_end = true
|
199
|
+
exclude_end ? min(end_element) : max(end_element)
|
200
|
+
when nil
|
201
|
+
exclude_end = false
|
202
|
+
# The end should be max and include end, because nil end means to cover endless ULIDs until the limit
|
203
|
+
MAX
|
204
|
+
when self
|
205
|
+
end_element
|
206
|
+
else
|
207
|
+
raise(ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}")
|
208
|
+
end
|
209
|
+
)
|
199
210
|
|
200
|
-
begin_ulid.freeze
|
201
|
-
end_ulid.freeze
|
211
|
+
begin_ulid.freeze if new_begin
|
212
|
+
end_ulid.freeze if new_end
|
202
213
|
|
203
214
|
Range.new(begin_ulid, end_ulid, exclude_end)
|
204
215
|
end
|
@@ -206,7 +217,7 @@ class ULID
|
|
206
217
|
# @param [Time] time
|
207
218
|
# @return [Time]
|
208
219
|
def self.floor(time)
|
209
|
-
raise
|
220
|
+
raise(ArgumentError, 'ULID.floor takes only `Time` instance') unless Time === time
|
210
221
|
|
211
222
|
time.floor(3)
|
212
223
|
end
|
@@ -234,7 +245,7 @@ class ULID
|
|
234
245
|
when Time
|
235
246
|
milliseconds_from_time(moment)
|
236
247
|
else
|
237
|
-
raise
|
248
|
+
raise(ArgumentError, '`moment` should be a `Time` or `Integer as milliseconds`')
|
238
249
|
end
|
239
250
|
end
|
240
251
|
|
@@ -248,39 +259,67 @@ class ULID
|
|
248
259
|
# @raise [ParserError] if the given format is not correct for ULID specs
|
249
260
|
def self.parse(string)
|
250
261
|
string = String.try_convert(string)
|
251
|
-
raise
|
262
|
+
raise(ArgumentError, 'ULID.parse takes only strings') unless string
|
252
263
|
|
253
264
|
unless STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string)
|
254
|
-
raise
|
265
|
+
raise(ParserError, "given `#{string}` does not match to `#{STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.inspect}`")
|
255
266
|
end
|
256
267
|
|
257
268
|
from_integer(CrockfordBase32.decode(string))
|
258
269
|
end
|
259
270
|
|
271
|
+
# @param [String, #to_str] string
|
272
|
+
# @return [ULID]
|
273
|
+
# @raise [ParserError] if the given format is not correct for ULID specs
|
274
|
+
def self.parse_variant_format(string)
|
275
|
+
string = String.try_convert(string)
|
276
|
+
raise(ArgumentError, 'ULID.parse_variant_format takes only strings') unless string
|
277
|
+
|
278
|
+
normalized_in_crockford = CrockfordBase32.normalize(string)
|
279
|
+
parse(normalized_in_crockford)
|
280
|
+
end
|
281
|
+
|
260
282
|
# @param [String, #to_str] string
|
261
283
|
# @return [String]
|
262
284
|
# @raise [ParserError] if the given format is not correct for ULID specs, even if ignored `orthographical variants of the format`
|
263
285
|
def self.normalize(string)
|
264
286
|
string = String.try_convert(string)
|
265
|
-
raise
|
287
|
+
raise(ArgumentError, 'ULID.normalize takes only strings') unless string
|
266
288
|
|
267
|
-
normalized_in_crockford = CrockfordBase32.normalize(string)
|
268
289
|
# Ensure the ULID correctness, because CrockfordBase32 does not always mean to satisfy ULID format
|
269
|
-
|
290
|
+
parse_variant_format(string).to_s
|
270
291
|
end
|
271
292
|
|
293
|
+
# @param [String, #to_str] string
|
272
294
|
# @return [Boolean]
|
273
|
-
def self.normalized?(
|
274
|
-
normalized = normalize(
|
295
|
+
def self.normalized?(string)
|
296
|
+
normalized = normalize(string)
|
275
297
|
rescue Exception
|
276
298
|
false
|
277
299
|
else
|
278
|
-
normalized ==
|
300
|
+
normalized == string
|
279
301
|
end
|
280
302
|
|
303
|
+
# @param [String, #to_str] string
|
281
304
|
# @return [Boolean]
|
282
|
-
def self.
|
283
|
-
string
|
305
|
+
def self.valid_as_variant_format?(string)
|
306
|
+
normalize(string)
|
307
|
+
rescue Exception
|
308
|
+
false
|
309
|
+
else
|
310
|
+
true
|
311
|
+
end
|
312
|
+
|
313
|
+
# @deprecated Use [.valid_as_variant_format?] or [.normalized?] instead
|
314
|
+
#
|
315
|
+
# Returns `true` if it is normalized string.
|
316
|
+
# Basically the difference of normalized? is to accept downcase or not. This returns true for downcased ULIDs.
|
317
|
+
#
|
318
|
+
# @return [Boolean]
|
319
|
+
def self.valid?(string)
|
320
|
+
warn_kwargs = (RUBY_VERSION >= '3.0') ? { category: :deprecated } : {}
|
321
|
+
Warning.warn('ULID.valid? is deprecated. Use ULID.valid_as_variant_format? or ULID.normalized? instead.', **warn_kwargs)
|
322
|
+
string = String.try_convert(string)
|
284
323
|
string ? STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string) : false
|
285
324
|
end
|
286
325
|
|
@@ -298,7 +337,7 @@ class ULID
|
|
298
337
|
else
|
299
338
|
object_class_name = safe_get_class_name(object)
|
300
339
|
converted_class_name = safe_get_class_name(converted)
|
301
|
-
raise
|
340
|
+
raise(TypeError, "can't convert #{object_class_name} to ULID (#{object_class_name}#to_ulid gives #{converted_class_name})")
|
302
341
|
end
|
303
342
|
end
|
304
343
|
end
|
@@ -314,8 +353,12 @@ class ULID
|
|
314
353
|
begin
|
315
354
|
object.class
|
316
355
|
rescue NoMethodError
|
356
|
+
# steep can't correctly handle singleton class assign. See https://github.com/soutaro/steep/pull/586 for further detail
|
357
|
+
# So this annotation is hack for the type infer.
|
358
|
+
# @type var object: BasicObject
|
359
|
+
# @type var singleton_class: untyped
|
317
360
|
singleton_class = class << object; self; end
|
318
|
-
singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) }
|
361
|
+
(Class === singleton_class) ? singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) } : fallback
|
319
362
|
end
|
320
363
|
)
|
321
364
|
|
@@ -335,10 +378,10 @@ class ULID
|
|
335
378
|
# @raise [OverflowError] if the given value is larger than the ULID limit
|
336
379
|
# @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
|
337
380
|
def self.from_milliseconds_and_entropy(milliseconds:, entropy:)
|
338
|
-
raise
|
339
|
-
raise
|
340
|
-
raise
|
341
|
-
raise
|
381
|
+
raise(ArgumentError, 'milliseconds and entropy should be an `Integer`') unless Integer === milliseconds && Integer === entropy
|
382
|
+
raise(OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}") unless milliseconds <= MAX_MILLISECONDS
|
383
|
+
raise(OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}") unless entropy <= MAX_ENTROPY
|
384
|
+
raise(ArgumentError, 'milliseconds and entropy should not be negative') if milliseconds.negative? || entropy.negative?
|
342
385
|
|
343
386
|
n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
|
344
387
|
n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
|
@@ -347,7 +390,8 @@ class ULID
|
|
347
390
|
new(milliseconds: milliseconds, entropy: entropy, integer: integer)
|
348
391
|
end
|
349
392
|
|
350
|
-
|
393
|
+
# @dynamic milliseconds, entropy
|
394
|
+
attr_reader(:milliseconds, :entropy)
|
351
395
|
|
352
396
|
# @api private
|
353
397
|
# @param [Integer] milliseconds
|
@@ -376,7 +420,7 @@ class ULID
|
|
376
420
|
[ULID, @integer].hash
|
377
421
|
end
|
378
422
|
|
379
|
-
# @return [
|
423
|
+
# @return [-1, 0, 1, nil]
|
380
424
|
def <=>(other)
|
381
425
|
(ULID === other) ? (@integer <=> other.to_i) : nil
|
382
426
|
end
|
@@ -390,15 +434,29 @@ class ULID
|
|
390
434
|
def eql?(other)
|
391
435
|
equal?(other) || (ULID === other && @integer == other.to_i)
|
392
436
|
end
|
393
|
-
|
437
|
+
# @dynamic ==
|
438
|
+
alias_method(:==, :eql?)
|
394
439
|
|
440
|
+
# Return `true` for same value of ULID, variant formats of strings, same Time in ULID precision(msec).
|
441
|
+
# Do not consider integer, octets and partial strings, then returns `false`.
|
442
|
+
#
|
395
443
|
# @return [Boolean]
|
444
|
+
# @see .normalize
|
445
|
+
# @see .floor
|
396
446
|
def ===(other)
|
397
447
|
case other
|
398
448
|
when ULID
|
399
449
|
@integer == other.to_i
|
400
450
|
when String
|
401
|
-
|
451
|
+
begin
|
452
|
+
normalized = ULID.normalize(other)
|
453
|
+
rescue Exception
|
454
|
+
false
|
455
|
+
else
|
456
|
+
to_s == normalized
|
457
|
+
end
|
458
|
+
when Time
|
459
|
+
to_time == ULID.floor(other)
|
402
460
|
else
|
403
461
|
false
|
404
462
|
end
|
@@ -420,22 +478,22 @@ class ULID
|
|
420
478
|
|
421
479
|
# @return [Array(Integer, Integer, Integer, Integer, Integer, Integer)]
|
422
480
|
def timestamp_octets
|
423
|
-
octets.slice(0, TIMESTAMP_OCTETS_LENGTH)
|
481
|
+
octets.slice(0, TIMESTAMP_OCTETS_LENGTH) || raise(UnexpectedError)
|
424
482
|
end
|
425
483
|
|
426
484
|
# @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
|
427
485
|
def randomness_octets
|
428
|
-
octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH)
|
486
|
+
octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH) || raise(UnexpectedError)
|
429
487
|
end
|
430
488
|
|
431
489
|
# @return [String]
|
432
490
|
def timestamp
|
433
|
-
@timestamp ||= to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze
|
491
|
+
@timestamp ||= (to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
|
434
492
|
end
|
435
493
|
|
436
494
|
# @return [String]
|
437
495
|
def randomness
|
438
|
-
@randomness ||= to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze
|
496
|
+
@randomness ||= (to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze || raise(UnexpectedError))
|
439
497
|
end
|
440
498
|
|
441
499
|
# @note Providing for rough operations. The keys and values is not fixed.
|
@@ -461,7 +519,8 @@ class ULID
|
|
461
519
|
ULID.from_integer(succ_int)
|
462
520
|
end
|
463
521
|
end
|
464
|
-
|
522
|
+
# @dynamic next
|
523
|
+
alias_method(:next, :succ)
|
465
524
|
|
466
525
|
# @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
|
467
526
|
def pred
|
@@ -513,7 +572,7 @@ class ULID
|
|
513
572
|
self
|
514
573
|
end
|
515
574
|
|
516
|
-
undef_method
|
575
|
+
undef_method(:instance_variable_set)
|
517
576
|
|
518
577
|
private
|
519
578
|
|
@@ -525,13 +584,12 @@ class ULID
|
|
525
584
|
end
|
526
585
|
end
|
527
586
|
|
528
|
-
require_relative
|
529
|
-
require_relative
|
530
|
-
require_relative
|
587
|
+
require_relative('ulid/version')
|
588
|
+
require_relative('ulid/crockford_base32')
|
589
|
+
require_relative('ulid/monotonic_generator')
|
590
|
+
require_relative('ulid/ractor_unshareable_constants')
|
531
591
|
|
532
592
|
class ULID
|
533
|
-
|
534
|
-
MAX
|
535
|
-
|
536
|
-
private_constant :TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR
|
593
|
+
# Do not write as `ULID.private_constant` for avoiding YARD warnings `[warn]: in YARD::Handlers::Ruby::PrivateConstantHandler: Undocumentable private constants:`
|
594
|
+
private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR, :CROCKFORD_BASE32_ENCODING_STRING)
|
537
595
|
end
|
data/sig/ulid.rbs
CHANGED
@@ -36,7 +36,7 @@ class ULID < Object
|
|
36
36
|
end
|
37
37
|
|
38
38
|
module CrockfordBase32
|
39
|
-
class SetupError <
|
39
|
+
class SetupError < UnexpectedError
|
40
40
|
end
|
41
41
|
|
42
42
|
N32_CHAR_BY_CROCKFORD_BASE32_CHAR: Hash[String, String]
|
@@ -97,9 +97,9 @@ class ULID < Object
|
|
97
97
|
def to_ulid: () -> ULID
|
98
98
|
end
|
99
99
|
|
100
|
-
type
|
101
|
-
type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
|
102
|
-
type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
|
100
|
+
type full_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
|
101
|
+
type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
|
102
|
+
type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
|
103
103
|
type period = Range[Time] | Range[nil] | Range[ULID]
|
104
104
|
|
105
105
|
@string: String?
|
@@ -213,6 +213,21 @@ class ULID < Object
|
|
213
213
|
# ```
|
214
214
|
def self.parse: (_ToStr string) -> ULID
|
215
215
|
|
216
|
+
# Get ULID instance from unnormalized String that encoded in Crockford's base32.
|
217
|
+
#
|
218
|
+
# http://www.crockford.com/base32.html
|
219
|
+
#
|
220
|
+
# * Ignore Hyphens (-)
|
221
|
+
# * Mapping 0 O o => 0, 1 I i L l => 1
|
222
|
+
#
|
223
|
+
# ```ruby
|
224
|
+
# ulid = ULID.parse_variant_format('01G70Y0Y7G-ZLXWDIREXERGSDoD')
|
225
|
+
# #=> ULID(2022-07-03 02:25:22.672 UTC: 01G70Y0Y7GZ1XWD1REXERGSD0D)
|
226
|
+
# ```
|
227
|
+
#
|
228
|
+
# See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
|
229
|
+
def self.parse_variant_format: (_ToStr string) -> ULID
|
230
|
+
|
216
231
|
# ```ruby
|
217
232
|
# # Currently experimental feature, so needed to load the extension.
|
218
233
|
# require 'ulid/uuid'
|
@@ -292,41 +307,30 @@ class ULID < Object
|
|
292
307
|
# ulid2 = ULID.parse('01F4PTVCSN9ZPFKYTY2DDJVRK4') #=> ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4)
|
293
308
|
# ulids = ULID.sample(1000, period: ulid1..ulid2)
|
294
309
|
# ulids.uniq.size #=> 1000
|
295
|
-
# ulids.take(
|
310
|
+
# ulids.take(5)
|
296
311
|
# #=>
|
297
312
|
# #[ULID(2021-05-02 06:57:19.954 UTC: 01F4NXW02JNB8H0J0TK48JD39X),
|
298
313
|
# # ULID(2021-05-02 07:06:07.458 UTC: 01F4NYC372GVP7NS0YAYQGT4VZ),
|
299
314
|
# # ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW),
|
300
315
|
# # 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)]
|
316
|
+
# # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3)]
|
307
317
|
# ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
|
308
318
|
# #=>
|
309
319
|
# # [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
|
310
320
|
# # ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
|
311
321
|
# # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
|
312
322
|
# # 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)]
|
323
|
+
# # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G)]
|
319
324
|
# ```
|
320
325
|
def self.sample: (?period: period) -> ULID
|
321
|
-
| (Integer number, ?period: period) -> Array[ULID]
|
322
|
-
def self.valid?: (untyped) -> bool
|
326
|
+
| (Integer number, ?period: period?) -> Array[ULID]
|
323
327
|
|
324
328
|
# Returns normalized string
|
325
329
|
#
|
326
330
|
# ```ruby
|
331
|
+
# ULID.normalize('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> "01G70Y0Y7GZ1XWDAREXERGSDDD"
|
327
332
|
# ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV"
|
328
|
-
# ULID.
|
329
|
-
# ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true
|
333
|
+
# ULID.normalize('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> ULID::ParserError
|
330
334
|
# ```
|
331
335
|
#
|
332
336
|
# 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 +339,40 @@ class ULID < Object
|
|
335
339
|
# Returns `true` if it is normalized string
|
336
340
|
#
|
337
341
|
# ```ruby
|
338
|
-
# ULID.
|
339
|
-
# ULID.normalized?('-
|
340
|
-
# ULID.normalized?(
|
342
|
+
# ULID.normalized?('01G70Y0Y7GZ1XWDAREXERGSDDD') #=> true
|
343
|
+
# ULID.normalized?('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> false
|
344
|
+
# ULID.normalized?(ULID.generate.to_s.downcase) #=> false
|
345
|
+
# ULID.normalized?('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> false (Not raising ULID::ParserError)
|
346
|
+
# ```
|
347
|
+
#
|
348
|
+
# See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
|
349
|
+
def self.normalized?: (_ToStr string) -> bool
|
350
|
+
| (untyped) -> false
|
351
|
+
|
352
|
+
# Returns `true` if it is valid in ULID format variants
|
353
|
+
#
|
354
|
+
# ```ruby
|
355
|
+
# ULID.valid_as_variant_format?(ULID.generate.to_s.downcase) #=> true
|
356
|
+
# ULID.valid_as_variant_format?('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> true
|
357
|
+
# ULID.valid_as_variant_format?('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> false
|
341
358
|
# ```
|
342
359
|
#
|
343
360
|
# 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.
|
361
|
+
def self.valid_as_variant_format?: (_ToStr string) -> bool
|
362
|
+
| (untyped) -> false
|
363
|
+
|
364
|
+
# DEPRECATED Use valid_as_variant_format? instead
|
365
|
+
#
|
366
|
+
# Returns `true` if it is normalized string.
|
367
|
+
# Basically the difference of normalized? is to accept downcase or not. This returns true for downcased ULIDs.
|
368
|
+
#
|
369
|
+
# ```ruby
|
370
|
+
# ULID.valid?(ULID.generate.to_s.downcase) #=> true
|
371
|
+
# ```
|
372
|
+
#
|
373
|
+
# See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
|
374
|
+
def self.valid?: (_ToStr string) -> bool
|
375
|
+
| (untyped) -> false
|
345
376
|
|
346
377
|
# Returns parsed ULIDs from given String for rough operations.
|
347
378
|
#
|
@@ -409,7 +440,9 @@ class ULID < Object
|
|
409
440
|
# ulid.to_i #=> 1957909092946624190749577070267409738
|
410
441
|
# ```
|
411
442
|
def to_i: -> Integer
|
412
|
-
|
443
|
+
|
444
|
+
# Returns integer for making as a hash key use-case
|
445
|
+
def hash: -> Integer
|
413
446
|
|
414
447
|
# Basically same as String based sort.
|
415
448
|
#
|
@@ -419,7 +452,12 @@ class ULID < Object
|
|
419
452
|
# #=> true
|
420
453
|
# ```
|
421
454
|
#
|
422
|
-
# 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.
|
455
|
+
# 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.
|
456
|
+
# So preferable than `lexicographically sortable` in actual case.
|
457
|
+
#
|
458
|
+
# This returns -1 | 0 | 1 for ULIDs. However defined as returning Integer. It is caused on ruby/rbs current definition.
|
459
|
+
# https://github.com/ruby/ruby/blob/cd34f56d450f2310cceaf4c5f34d23eddfda58e8/numeric.c#L4646-L4660
|
460
|
+
# https://github.com/ruby/rbs/blob/14abbbae8885a09a2ed82de2ef31d67a9c0a108d/core/integer.rbs#L461-L462
|
423
461
|
#
|
424
462
|
def <=>: (ULID other) -> Integer
|
425
463
|
| (untyped other) -> nil
|
@@ -434,9 +472,28 @@ class ULID < Object
|
|
434
472
|
# ULID.parse('4NNB20D9C1ME2NGMTX51ERZJX0') == ULID.parse('4nnb20d9c1me2ngmtx51erzjx0')
|
435
473
|
# #=> true
|
436
474
|
# ```
|
437
|
-
def eql?: (
|
475
|
+
def eql?: (ULID other) -> bool
|
476
|
+
| (untyped other) -> false
|
438
477
|
alias == eql?
|
439
|
-
|
478
|
+
|
479
|
+
# Return `true` for same value of ULID, variant formats of strings, same Time in ULID precision(msec).
|
480
|
+
# Do not consider integer, octets and partial strings, then returns `false`.
|
481
|
+
#
|
482
|
+
# ```ruby
|
483
|
+
# ulid = ULID.parse('01G6Z7Q4RSH97E6QHAC7VK19G2')
|
484
|
+
# ulid === ULID.parse(ulid.to_s)
|
485
|
+
# #=> true
|
486
|
+
# ulid === ulid.to_s.downcase
|
487
|
+
# #=> true
|
488
|
+
# ulid === ulid.to_time
|
489
|
+
# #=> true
|
490
|
+
# ulid === ulid.to_i
|
491
|
+
# #=> false
|
492
|
+
# ulid === ulid.next
|
493
|
+
# #=> false
|
494
|
+
# ```
|
495
|
+
def ===: (ULID | String | Time other) -> bool
|
496
|
+
| (untyped other) -> false
|
440
497
|
|
441
498
|
# ```ruby
|
442
499
|
# ulid = ULID.generate
|
@@ -462,7 +519,7 @@ class ULID < Object
|
|
462
519
|
# Intentionally avoiding to use `Record type` ref: https://github.com/ruby/rbs/blob/4fb4c33b2325d1a73d79ff7aaeb49f21cec1e0e5/docs/syntax.md#record-type
|
463
520
|
# Because the returning values are not fixed.
|
464
521
|
def patterns: -> Hash[Symbol, Regexp | String]
|
465
|
-
def octets: ->
|
522
|
+
def octets: -> full_octets
|
466
523
|
def timestamp_octets: -> timestamp_octets
|
467
524
|
def randomness_octets: -> randomness_octets
|
468
525
|
|
@@ -525,7 +582,7 @@ class ULID < Object
|
|
525
582
|
private
|
526
583
|
|
527
584
|
# A private API. Should not be used in your code.
|
528
|
-
def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) ->
|
585
|
+
def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> ULID
|
529
586
|
|
530
587
|
# A private API. Should not be used in your code.
|
531
588
|
def self.reasonable_entropy: -> Integer
|
metadata
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-ulid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.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-
|
11
|
+
date: 2022-07-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
14
|
-
|
15
|
-
This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around the ULID.
|
16
|
-
Also providing `ruby/rbs` signature files.
|
13
|
+
description: " generator, optional monotonicity, parser and tools for ULID (RBS
|
14
|
+
included)\n"
|
17
15
|
email:
|
18
16
|
- kachick1+ruby@gmail.com
|
19
17
|
executables: []
|
@@ -26,6 +24,7 @@ files:
|
|
26
24
|
- lib/ulid.rb
|
27
25
|
- lib/ulid/crockford_base32.rb
|
28
26
|
- lib/ulid/monotonic_generator.rb
|
27
|
+
- lib/ulid/ractor_unshareable_constants.rb
|
29
28
|
- lib/ulid/uuid.rb
|
30
29
|
- lib/ulid/version.rb
|
31
30
|
- sig/ulid.rbs
|
@@ -46,7 +45,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
46
45
|
requirements:
|
47
46
|
- - ">="
|
48
47
|
- !ruby/object:Gem::Version
|
49
|
-
version: 2.7.
|
48
|
+
version: 2.7.2
|
50
49
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
50
|
requirements:
|
52
51
|
- - ">="
|