ruby-ulid 0.6.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c7cf89034d0f74026278e2a4c113d57f8fc28b861b8bcf1d2210dbb76688e52
4
- data.tar.gz: 340bfbea8fb1ff3ec0e49339e2a5a388a81552f8cd73a65ad9ff049448fc0c3f
3
+ metadata.gz: 323b750a4e11157bd492b31d74833df2042192775c8627917880ef386dee4c1c
4
+ data.tar.gz: 2dc8a91cbed7b473d6f9e28480dd227ab3469828ab0833f48a754111bd6e926c
5
5
  SHA512:
6
- metadata.gz: 74d26cb5dc49a8d79a5c933817c1c8913954d675d996a91057f29859fd1fdf0a32c99d8121ef954e1d7d400110fa1737be17a9084bfc03655082de96c9614392
7
- data.tar.gz: 96f19838c4c90ee313722857f88693e88f247e5380293291b362eadc834f6adb1613c01034a0cfe430f24576b4fea55f252b79f689b00c3e05b739118db8906e
6
+ metadata.gz: e78a5289863c1300a3f4c186b72a0f1d97cd709ce6043b69729d851f5e6ffcedac001e3bb691397d761ee497d609b5292c300db7f2ec3f8cf5be0ee5c095af80
7
+ data.tar.gz: e19197709d7a896a79c6ebdc40fa919c2e7876284fa1dbf8895498ad7b80e989634cdd3e651362db5cf9f7c627fe17439ccc21e75c8e6598b002da6b19858aa0
data/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  # ruby-ulid
2
2
 
3
- [![Build Status](https://github.com/kachick/ruby-ulid/actions/workflows/test_behaviors.yml/badge.svg?branch=main)](https://github.com/kachick/ruby-ulid/actions/workflows/test_behaviors.yml/?branch=main)
3
+ [![Build Status](https://github.com/kachick/ruby-ulid/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/kachick/ruby-ulid/actions/workflows/ci.yml?query=branch%3Amain)
4
4
  [![Gem Version](https://badge.fury.io/rb/ruby-ulid.svg)](http://badge.fury.io/rb/ruby-ulid)
5
5
 
6
6
  ## Overview
7
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
+ [ulid/spec](https://github.com/ulid/spec) defines some useful features.\
9
+ In particular, it has uniqueness, randomness, extractable timestamps, and sortability.\
10
+ This gem aims to provide the generator, optional monotonicity, parser, and other manipulations around ULID.\
11
+ [RBS](https://github.com/ruby/rbs) definitions are also included.
11
12
 
12
13
  ---
13
14
 
@@ -37,31 +38,22 @@ Instead, herein is proposed ULID:
37
38
 
38
39
  ### Install
39
40
 
40
- Require Ruby 2.7 or later
41
+ Tested only in the last 2 Rubies. So you need Ruby 3.1 or higher.
41
42
 
42
- This command will install the latest version into your environment
43
-
44
- ```console
45
- $ gem install ruby-ulid
46
- Should be installed!
47
- ```
48
-
49
- Add this line in your Gemfile.
43
+ Add this line to your `Gemfile`.
50
44
 
51
45
  ```ruby
52
- gem('ruby-ulid', '~> 0.6.1')
46
+ gem('ruby-ulid', '~> 0.8.0')
53
47
  ```
54
48
 
55
- ### How to use
49
+ And load it.
56
50
 
57
51
  ```ruby
58
52
  require 'ulid'
59
-
60
- ULID::VERSION
61
- # => "0.6.1"
62
53
  ```
63
54
 
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.1).
55
+ NOTE: This README contains information about the development version.\
56
+ If you would like to see released version's one. [Look at the ref](https://github.com/kachick/ruby-ulid/tree/v0.8.0).
65
57
 
66
58
  ### Generator and Parser
67
59
 
@@ -77,7 +69,7 @@ ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GR
77
69
  ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
78
70
  ```
79
71
 
80
- It is helpful to inspect.
72
+ It has inspector methods.
81
73
 
82
74
  ```ruby
83
75
  ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
@@ -99,9 +91,9 @@ ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT
99
91
  ULID.at(time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB002W5BGWWKN76N22H6)
100
92
  ```
101
93
 
102
- Also `ULID.encode` and `ULID.decode_time` can be used to get primitive values for most usecases.
94
+ Also `ULID.encode` and `ULID.decode_time` can be used to get primitive values for most usecases.
103
95
 
104
- `ULID.encode` returns [normalized](#variants-of-format) String without ULID object creation.
96
+ `ULID.encode` returns [normalized](#variants-of-format) String without ULID object creation.\
105
97
  It can take same arguments as `ULID.generate`.
106
98
 
107
99
  ```ruby
@@ -116,17 +108,17 @@ ULID.decode_time('00VHNCZB00SYG7RCEXZC9DA4E1') #=> 2000-01-01 00:00:00 UTC
116
108
  ULID.decode_time('00VHNCZB00SYG7RCEXZC9DA4E1', in: '+09:00') #=> 2000-01-01 09:00:00 +0900
117
109
  ```
118
110
 
119
- This project does not prioritize the speed. However it actually works faster than others! :zap:
111
+ This project does not prioritize on the speed. However it actually works faster than others! :zap:
120
112
 
121
- Snapshot on 0.6.1 is below
113
+ Snapshot on v0.8.0 with Ruby 3.2.1 is below
122
114
 
123
- * Generator is 1.5x faster than - [ulid gem](https://github.com/rafaelsales/ulid)
124
- * Generator is 1.9x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
125
- * Parser is 2.7x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
115
+ - Generator is 1.9x faster than - [ulid gem - v1.4.0](https://github.com/rafaelsales/ulid)
116
+ - Generator is 2.0x faster than - [ulid-ruby gem - v1.0.2](https://github.com/abachman/ulid-ruby)
117
+ - Parser is 3.1x faster than - [ulid-ruby gem - v1.0.2](https://github.com/abachman/ulid-ruby)
126
118
 
127
119
  You can see further detail at [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
128
120
 
129
- ### Sortable with the timestamp
121
+ ### Sortable by timestamp
130
122
 
131
123
  ULIDs are sortable when they are generated in different timestamp with milliseconds precision.
132
124
 
@@ -139,7 +131,7 @@ ulids.uniq(&:to_time).size #=> 1000
139
131
  ulids.sort == ulids #=> true
140
132
  ```
141
133
 
142
- Basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
134
+ The basic generator prefers `randomness`, the results in the same milliseconds are not sortable.
143
135
 
144
136
  ```ruby
145
137
  ulids = 10000.times.map do
@@ -151,8 +143,9 @@ ulids.sort == ulids #=> false
151
143
 
152
144
  ### How to keep `Sortable` even if in same timestamp
153
145
 
154
- If you want to prefer `sortable`, Use `MonotonicGenerator` instead. It is called as [Monotonicity](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#monotonicity) on the spec.
155
- (Though it starts with new random value when changed the timestamp)
146
+ If you prefer `sortability`, you can use `MonotonicGenerator` instead.\
147
+ It is referred to as [Monotonicity](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#monotonicity) in the spec.\
148
+ (Although it starts with a new random value when the timestamp is changed)
156
149
 
157
150
  ```ruby
158
151
  monotonic_generator = ULID::MonotonicGenerator.new
@@ -163,20 +156,16 @@ sample_ulids_by_the_time = ulids.uniq(&:to_time)
163
156
  sample_ulids_by_the_time.size #=> 32 (the size is not fixed, might be changed in environment)
164
157
 
165
158
  # In same milliseconds creation, it just increments the end of randomness part
166
- ulids.take(5) #=>
159
+ ulids.take(3) #=>
167
160
  # [ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4),
168
161
  # ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK5),
169
- # ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK6),
170
- # ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK7),
171
- # ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK8)]
162
+ # ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK6)]
172
163
 
173
164
  # When the milliseconds is updated, it starts with new randomness
174
- sample_ulids_by_the_time.take(5) #=>
165
+ sample_ulids_by_the_time.take(3) #=>
175
166
  # [ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4),
176
167
  # ULID(2021-05-02 15:23:48.918 UTC: 01F4PTVCSPF2KXG4ABT7CK3204),
177
- # ULID(2021-05-02 15:23:48.919 UTC: 01F4PTVCSQF1GERBPCQV6TCX2K),
178
- # ULID(2021-05-02 15:23:48.920 UTC: 01F4PTVCSRBXN2H4P1EYWZ27AK),
179
- # ULID(2021-05-02 15:23:48.921 UTC: 01F4PTVCSSK0ASBBZARV7013F8)]
168
+ # ULID(2021-05-02 15:23:48.919 UTC: 01F4PTVCSQF1GERBPCQV6TCX2K)]
180
169
 
181
170
  ulids.sort == ulids #=> true
182
171
  ```
@@ -195,7 +184,7 @@ ulids.grep(one_of_the_above)
195
184
  ulids.grep_v(one_of_the_above)
196
185
  ```
197
186
 
198
- When want to filter ULIDs with `Time`, we should consider to handle the precision.
187
+ When want to filter ULIDs with `Time`, we should consider to handle the precision.\
199
188
  So this gem provides `ULID.range` to generate reasonable `Range[ULID]` from `Range[Time]`
200
189
 
201
190
  ```ruby
@@ -265,18 +254,6 @@ ULID.scan(json).to_a
265
254
  # ULID(2021-04-30 05:53:12.478 UTC: 01F4GND4RYYSKNAADHQ9BNXAWJ)]
266
255
  ```
267
256
 
268
- `ULID#patterns` is a util for text based operations.
269
- The results and spec are not fixed. Should not be used except snippets/console operation.
270
-
271
- ```ruby
272
- ULID.parse('01F4GNBXW1AM2KWW52PVT3ZY9X').patterns
273
- #=> returns like a fallowing Hash
274
- {
275
- named_captures: /(?<timestamp>01F4GNBXW1)(?<randomness>AM2KWW52PVT3ZY9X)/i,
276
- strict_named_captures: /\A(?<timestamp>01F4GNBXW1)(?<randomness>AM2KWW52PVT3ZY9X)\z/i
277
- }
278
- ```
279
-
280
257
  #### Get boundary ULIDs
281
258
 
282
259
  `ULID.min` and `ULID.max` return termination values for ULID spec.
@@ -294,7 +271,7 @@ ULID.max(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
294
271
 
295
272
  #### As element in Enumerable
296
273
 
297
- `ULID#next` and `ULID#succ` returns next(successor) ULID.
274
+ `ULID#next` and `ULID#succ` returns next(successor) ULID.\
298
275
  Especially `ULID#succ` makes it possible `Range[ULID]#each`.
299
276
 
300
277
  NOTE: However basically `Range[ULID]#each` should not be used. Incrementing 128 bits IDs are not reasonable operation in most cases.
@@ -324,13 +301,11 @@ ULID.sample #=> ULID(2545-07-26 06:51:20.085 UTC: 0GGKQ45GMNMZR6N8A8GFG0ZXST)
324
301
  ULID.sample #=> ULID(5098-07-26 21:31:06.946 UTC: 2SSBNGGYA272J7BMDCG4Z6EEM5)
325
302
  ULID.sample(0) #=> []
326
303
  ULID.sample(1) #=> [ULID(2241-04-16 03:31:18.440 UTC: 07S52YWZ98AZ8T565MD9VRYMQH)]
327
- ULID.sample(5)
304
+ ULID.sample(3)
328
305
  #=>
329
306
  #[ULID(5701-04-29 12:41:19.647 UTC: 3B2YH2DV0ZYDDATGTYSKMM1CMT),
330
307
  # ULID(2816-08-01 01:21:46.612 UTC: 0R9GT6RZKMK3RG02Q2HAFVKEY2),
331
- # ULID(10408-10-05 17:06:27.848 UTC: 7J6CPTEEC86Y24EQ4F1Y93YYN0),
332
- # ULID(2741-09-02 16:24:18.803 UTC: 0P4Q4V34KKAJW46QW47WQB5463),
333
- # ULID(2665-03-16 14:50:22.724 UTC: 0KYFW9DWM4CEGFNTAC6YFAVVJ6)]
308
+ # ULID(10408-10-05 17:06:27.848 UTC: 7J6CPTEEC86Y24EQ4F1Y93YYN0)]
334
309
  ```
335
310
 
336
311
  You can specify a range object for the timestamp restriction, see also `ULID.range`.
@@ -338,37 +313,31 @@ You can specify a range object for the timestamp restriction, see also `ULID.ran
338
313
  ```ruby
339
314
  ulid1 = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
340
315
  ulid2 = ULID.parse('01F4PTVCSN9ZPFKYTY2DDJVRK4') #=> ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4)
341
- ulids = ULID.sample(1000, period: ulid1..ulid2)
342
- ulids.uniq.size #=> 1000
343
- ulids.take(5)
316
+ ulids = ULID.sample(3, period: ulid1..ulid2)
344
317
  #=>
345
318
  #[ULID(2021-05-02 06:57:19.954 UTC: 01F4NXW02JNB8H0J0TK48JD39X),
346
319
  # ULID(2021-05-02 07:06:07.458 UTC: 01F4NYC372GVP7NS0YAYQGT4VZ),
347
- # ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW),
348
- # ULID(2021-04-27 22:17:37.844 UTC: 01F4APHGSMFJZQTGXKZBFFBPJP),
349
- # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3)]
350
- ULID.sample(5, period: ulid1.to_time..ulid2.to_time)
320
+ # ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW)]
321
+ ULID.sample(3, period: ulid1.to_time..ulid2.to_time)
351
322
  #=>
352
323
  # [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
353
324
  # ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
354
- # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
355
- # ULID(2021-05-01 03:06:09.130 UTC: 01F4JY7ZBABCBMX16XH2Q4JW4W),
356
- # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G)]
325
+ # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW)]
357
326
  ```
358
327
 
359
328
  #### Variants of format
360
329
 
361
330
  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.
362
331
 
363
- >Case insensitive
332
+ > Case insensitive
364
333
 
365
- I can understand it might be considered in actual use-case. So `ULID.parse` accepts upcase and downcase.
334
+ I can understand it might be considered in actual use-case. So `ULID.parse` accepts upcase and downcase.\
366
335
  However it is a controversial point, discussing in [ulid/spec#3](https://github.com/ulid/spec/issues/3).
367
336
 
368
- >Uses Crockford's base32 for better efficiency and readability (5 bits per character)
337
+ > Uses Crockford's base32 for better efficiency and readability (5 bits per character)
369
338
 
370
- The original `Crockford's base32` maps `I`, `L` to `1`, `O` to `0`.
371
- And accepts freestyle inserting `Hyphens (-)`.
339
+ The original `Crockford's base32` maps `I`, `L` to `1`, `O` to `0`.\
340
+ And accepts freestyle inserting `Hyphens (-)`.\
372
341
  To consider this patterns or not is different in each implementations.
373
342
 
374
343
  I have suggested to clarify `subset of Crockford's base32` in [ulid/spec#57](https://github.com/ulid/spec/pull/57).
@@ -385,37 +354,40 @@ ULID.valid_as_variant_format?('01g70y0y7g-z1xwdarexergsddd') #=> true
385
354
  ULID.parse_variant_format('01G70Y0Y7G-ZLXWDIREXERGSDoD') #=> ULID(2022-07-03 02:25:22.672 UTC: 01G70Y0Y7GZ1XWD1REXERGSD0D)
386
355
  ```
387
356
 
388
- #### UUIDv4 converter (experimental)
389
-
390
- `ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
391
- The imported timestamp is meaningless. So ULID's benefit will lost.
392
-
393
- ```ruby
394
- # Currently experimental feature, so needed to load the extension.
395
- require 'ulid/uuid'
396
-
397
- # Basically reversible
398
- ulid = ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39') #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
399
- ulid.to_uuidv4 #=> "0983d0a2-ff15-4d83-8f37-7dd945b5aa39"
357
+ #### UUID
400
358
 
401
- uuid_v4s = 10000.times.map { SecureRandom.uuid }
402
- uuid_v4s.uniq.size == 10000 #=> Probably `true`
359
+ Both ULID and UUID are 128-bit IDs. But with different specs. Especially UUID has some versions probably UUIDv4.
403
360
 
404
- ulids = uuid_v4s.map { |uuid_v4| ULID.from_uuidv4(uuid_v4) }
405
- ulids.map(&:to_uuidv4) == uuid_v4s #=> **Probably** `true` except below examples.
361
+ All UUIDv4s can be converted to ULID, but this will not have the correct "timestamp".\
362
+ Most ULIDs cannot be converted to UUIDv4 while maintaining reversibility, because UUIDv4 requires version and variants in the fields.
406
363
 
407
- # NOTE: Some boundary values are not reversible. See below.
364
+ See also [ulid/spec#64](https://github.com/ulid/spec/issues/64) for further detail.
408
365
 
409
- ULID.min.to_uuidv4 #=> "00000000-0000-4000-8000-000000000000"
410
- ULID.max.to_uuidv4 #=> "ffffffff-ffff-4fff-bfff-ffffffffffff"
366
+ For now, this gem provides 4 methods for UUIDs.
411
367
 
412
- # These importing results are same as https://github.com/ahawker/ulid/tree/96bdb1daad7ce96f6db8c91ac0410b66d2e1c4c1 on CPython 3.9.4
413
- reversed_min = ULID.from_uuidv4('00000000-0000-4000-8000-000000000000') #=> ULID(1970-01-01 00:00:00.000 UTC: 00000000008008000000000000)
414
- reversed_max = ULID.from_uuidv4('ffffffff-ffff-4fff-bfff-ffffffffffff') #=> ULID(10889-08-02 05:31:50.655 UTC: 7ZZZZZZZZZ9ZZVZZZZZZZZZZZZ)
368
+ - Reversibility is preferred: `ULID.from_uuidish`, `ULID.to_uuidish`
369
+ - Prefer UUIDv4 specification: `ULID.from_uuidv4`, `ULID.to_uuidv4`
415
370
 
416
- # But they are not reversible! Need to consider this issue in https://github.com/kachick/ruby-ulid/issues/76
417
- ULID.min == reversed_min #=> false
418
- ULID.max == reversed_max #=> false
371
+ ```ruby
372
+ # All UUIDv4 IDs can be reversible even if converted to ULID
373
+ uuid = SecureRandom.uuid
374
+ ULID.from_uuidish(uuid) == ULID.from_uuidv4(uuid) #=> true
375
+ ULID.from_uuidish(uuid).to_uuidish == ULID.from_uuidv4(uuid).to_uuidv4 #=> true
376
+
377
+ # But most ULIDs cannot be converted to UUIDv4
378
+ ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA')
379
+ ulid.to_uuidv4 #=> ULID::IrreversibleUUIDError
380
+ # So 2 ways to get substitute strings that might satisfy the use case
381
+ ulid.to_uuidv4(force: true) #=> "0179145f-07ca-4b3c-af33-4c3c3149254a" this cannot be reverse to source ULID
382
+ ulid == ULID.from_uuidv4(ulid.to_uuidv4(force: true)) #=> false
383
+ ulid.to_uuidish #=> "0179145f-07ca-bb3c-af33-4c3c3149254a" does not satisfy UUIDv4 spec
384
+ ulid == ULID.from_uuidish(ulid.to_uuidish) #=> true
385
+
386
+ # Seeing boundary IDs makes it easier to understand
387
+ ULID.min.to_uuidish #=> "00000000-0000-0000-0000-000000000000"
388
+ ULID.min.to_uuidv4(force: true) #=> "00000000-0000-4000-8000-000000000000"
389
+ ULID.max.to_uuidish #=> "ffffffff-ffff-ffff-ffff-ffffffffffff"
390
+ ULID.max.to_uuidv4(force: true) #=> "ffffffff-ffff-4fff-bfff-ffffffffffff"
419
391
  ```
420
392
 
421
393
  ## Migration from other gems
@@ -424,13 +396,8 @@ See [wiki page for gem migration](https://github.com/kachick/ruby-ulid/wiki/Gem-
424
396
 
425
397
  ## RBS
426
398
 
427
- Try at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox).
428
-
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).
430
-
431
- * ![rbs overview](./assets/ulid-rbs-overview.png?raw=true.png)
432
- * ![rbs mix](./assets/ulid-rbs-mix.png?raw=true.png)
433
- * ![rbs ng-to_str](./assets/ulid-rbs-ng-to_str.png?raw=true.png)
399
+ - Try at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox).
400
+ - See the overview in [our wiki page for RBS](https://github.com/kachick/ruby-ulid/wiki/RBS)
434
401
 
435
402
  ## References
436
403
 
@@ -440,5 +407,6 @@ I have checked the behavior with [ruby/rbs@2.6.0](https://github.com/ruby/rbs) +
440
407
 
441
408
  ## Note
442
409
 
443
- - [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.
444
- However they are stayed in draft state. ref: [ruby-ulid#37](https://github.com/kachick/ruby-ulid/issues/37)
410
+ - [UUIDv6, UUIDv7, UUIDv8](https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-02.html) is another choice for sortable and randomness ID.
411
+ \
412
+ However they remain in draft state. Our tracker is: [ruby-ulid#37](https://github.com/kachick/ruby-ulid/issues/37)
@@ -1,11 +1,12 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
- # shareable_constant_value: literal
4
3
 
5
4
  # Copyright (C) 2021 Kenichi Kamiya
6
5
 
6
+ require_relative('utils')
7
+
7
8
  class ULID
8
- # @see https://www.crockford.com/base32.html
9
+ # @see https://www.crockford.com/base32.html and https://www.rfc-editor.org/rfc/rfc4648
9
10
  #
10
11
  # This module supporting only `subset of original crockford for actual use-case` in ULID context.
11
12
  # Original decoding spec allows other characters.
@@ -37,7 +38,7 @@ class ULID
37
38
  }.freeze
38
39
 
39
40
  # Excluded I, L, O, U, - from Base32
40
- base32_to_crockford = {
41
+ base32hex_to_crockford = {
41
42
  'I' => 'J',
42
43
  'J' => 'K',
43
44
  'K' => 'M',
@@ -53,8 +54,8 @@ class ULID
53
54
  'U' => 'Y',
54
55
  'V' => 'Z'
55
56
  }.freeze
56
- BASE32_TR_PATTERN = base32_to_crockford.keys.join.freeze
57
- CROCKFORD_TR_PATTERN = base32_to_crockford.values.join.freeze
57
+ BASE32HEX_TR_PATTERN = base32hex_to_crockford.keys.join.freeze
58
+ CROCKFORD_TR_PATTERN = base32hex_to_crockford.values.join.freeze
58
59
  ENCODING_STRING = "#{same_definitions.values.join}#{CROCKFORD_TR_PATTERN}".freeze
59
60
 
60
61
  variant_to_normarized = {
@@ -68,36 +69,36 @@ class ULID
68
69
  VARIANT_TR_PATTERN = variant_to_normarized.keys.join.freeze
69
70
  NORMALIZED_TR_PATTERN = variant_to_normarized.values.join.freeze
70
71
 
72
+ Utils.make_sharable_constants(self)
73
+
71
74
  # @note Avoid to depend regex as possible. `tr(string, string)` is almost 2x Faster than `gsub(regex, hash)` in Ruby 3.1
72
75
 
73
- # @api private
74
76
  # @param [String] string
75
77
  # @return [Integer]
76
78
  def self.decode(string)
77
- n32encoded = string.upcase.tr(CROCKFORD_TR_PATTERN, BASE32_TR_PATTERN)
78
- n32encoded.to_i(32)
79
+ base32hex = string.upcase.tr(CROCKFORD_TR_PATTERN, BASE32HEX_TR_PATTERN)
80
+ Integer(base32hex, 32, exception: true)
79
81
  end
80
82
 
81
- # @api private
82
83
  # @param [Integer] integer
83
84
  # @return [String]
84
85
  def self.encode(integer)
85
- n32encoded = integer.to_s(32)
86
- from_n32(n32encoded).rjust(ENCODED_LENGTH, '0')
86
+ base32hex = integer.to_s(32)
87
+ from_base32hex(base32hex).rjust(ENCODED_LENGTH, '0')
87
88
  end
88
89
 
89
- # @api private
90
90
  # @param [String] string
91
91
  # @return [String]
92
92
  def self.normalize(string)
93
93
  string.delete('-').tr(VARIANT_TR_PATTERN, NORMALIZED_TR_PATTERN)
94
94
  end
95
95
 
96
- # @api private
97
- # @param [String] n32encoded
96
+ # @param [String] base32hex
98
97
  # @return [String]
99
- def self.from_n32(n32encoded)
100
- n32encoded.upcase.tr(BASE32_TR_PATTERN, CROCKFORD_TR_PATTERN)
98
+ def self.from_base32hex(base32hex)
99
+ base32hex.upcase.tr(BASE32HEX_TR_PATTERN, CROCKFORD_TR_PATTERN)
101
100
  end
102
101
  end
102
+
103
+ private_constant(:CrockfordBase32)
103
104
  end
data/lib/ulid/errors.rb CHANGED
@@ -7,4 +7,5 @@ class ULID
7
7
  class OverflowError < Error; end
8
8
  class ParserError < Error; end
9
9
  class UnexpectedError < Error; end
10
+ class IrreversibleUUIDError < Error; end
10
11
  end
@@ -4,6 +4,9 @@
4
4
 
5
5
  # Copyright (C) 2021 Kenichi Kamiya
6
6
 
7
+ require_relative('errors')
8
+ require_relative('utils')
9
+
7
10
  class ULID
8
11
  class MonotonicGenerator
9
12
  # @note When use https://github.com/ko1/ractor-tvar might realize Ractor based thread safe monotonic generator.
@@ -11,18 +14,19 @@ class ULID
11
14
  include(MonitorMixin)
12
15
 
13
16
  # @return [ULID, nil]
14
- attr_reader(:prev)
17
+ attr_accessor(:last)
18
+ private(:last=)
15
19
 
16
20
  undef_method(:instance_variable_set)
17
21
 
18
22
  def initialize
19
23
  super
20
- @prev = nil
24
+ @last = nil
21
25
  end
22
26
 
23
27
  # @return [String]
24
28
  def inspect
25
- "ULID::MonotonicGenerator(prev: #{@prev.inspect})"
29
+ "ULID::MonotonicGenerator(last: #{@last.inspect})"
26
30
  end
27
31
  alias_method(:to_s, :inspect)
28
32
 
@@ -31,27 +35,27 @@ class ULID
31
35
  # @raise [OverflowError] if the entropy part is larger than the ULID limit in same milliseconds
32
36
  # @raise [UnexpectedError] if the generated ULID is an invalid value in monotonicity spec.
33
37
  # Basically will not happen. Just means this feature prefers error rather than invalid value.
34
- def generate(moment: ULID.current_milliseconds)
38
+ def generate(moment: Utils.current_milliseconds)
35
39
  synchronize do
36
- prev_ulid = @prev
37
- unless prev_ulid
38
- ret = ULID.generate(moment: moment)
39
- @prev = ret
40
+ prev = @last
41
+ unless prev
42
+ ret = ULID.generate(moment:)
43
+ @last = ret
40
44
  return ret
41
45
  end
42
46
 
43
- milliseconds = ULID.milliseconds_from_moment(moment)
47
+ milliseconds = Utils.milliseconds_from_moment(moment)
44
48
 
45
49
  ulid = (
46
- if prev_ulid.milliseconds < milliseconds
50
+ if prev.milliseconds < milliseconds
47
51
  ULID.generate(moment: milliseconds)
48
52
  else
49
- ULID.from_milliseconds_and_entropy(milliseconds: prev_ulid.milliseconds, entropy: prev_ulid.entropy.succ)
53
+ ULID.generate(moment: prev.milliseconds, entropy: prev.entropy.succ)
50
54
  end
51
55
  )
52
56
 
53
- unless ulid > prev_ulid
54
- base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{prev_ulid.inspect}"
57
+ unless ulid > prev
58
+ base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{prev.inspect}"
55
59
  additional_information = (
56
60
  if Thread.list == [Thread.main]
57
61
  '# NOTE: looks single thread only exist'
@@ -63,15 +67,18 @@ class ULID
63
67
  raise(UnexpectedError, base_message + additional_information)
64
68
  end
65
69
 
66
- @prev = ulid
70
+ @last = ulid
67
71
  ulid
68
72
  end
69
73
  end
70
74
 
75
+ # Just providing similar api as `ULID.generate` and `ULID.encode` relation. No performance benefit exists in monotonic generator's one.
76
+ #
77
+ # @see https://github.com/kachick/ruby-ulid/pull/220
71
78
  # @param [Time, Integer] moment
72
79
  # @return [String]
73
- def encode(moment: ULID.current_milliseconds)
74
- generate(moment: moment).encode
80
+ def encode(moment: Utils.current_milliseconds)
81
+ generate(moment:).encode
75
82
  end
76
83
 
77
84
  undef_method(:freeze)
data/lib/ulid/utils.rb ADDED
@@ -0,0 +1,99 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+ # shareable_constant_value: literal
4
+
5
+ # Copyright (C) 2021 Kenichi Kamiya
6
+
7
+ require('securerandom')
8
+
9
+ class ULID
10
+ # @note I don't have confidence for the naming of `Utils`. However some standard libraries have same name.
11
+ # https://github.com/ruby/webrick/blob/14612a7540fdd7373344461851c4bfff64985b3e/lib/webrick/utils.rb#L17
12
+ # https://docs.ruby-lang.org/ja/latest/class/ERB=3a=3aUtil.html
13
+ # https://github.com/ruby/rss/blob/af1c3c9c9630ec0a48abec48ed1ef348ba82aa13/lib/rss/utils.rb#L9
14
+ module Utils
15
+ # @return [Integer]
16
+ def self.current_milliseconds
17
+ milliseconds_from_time(Time.now)
18
+ end
19
+
20
+ # @param [Time] time
21
+ # @return [Integer]
22
+ def self.milliseconds_from_time(time)
23
+ (time.to_r * 1000).to_i
24
+ end
25
+
26
+ # @param [Time, Integer] moment
27
+ # @return [Integer]
28
+ def self.milliseconds_from_moment(moment)
29
+ case moment
30
+ when Integer
31
+ moment
32
+ when Time
33
+ milliseconds_from_time(moment)
34
+ else
35
+ raise(ArgumentError, '`moment` should be a `Time` or `Integer as milliseconds`')
36
+ end
37
+ end
38
+
39
+ # @return [Integer]
40
+ def self.reasonable_entropy
41
+ SecureRandom.random_number(MAX_ENTROPY)
42
+ end
43
+
44
+ # @param [Integer] milliseconds
45
+ # @param [Integer] entropy
46
+ # @return [String]
47
+ # @raise [OverflowError] if the given value is larger than the ULID limit
48
+ # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
49
+ def self.encode_base32hex(milliseconds:, entropy:)
50
+ raise(ArgumentError, 'milliseconds and entropy should be an `Integer`') unless Integer === milliseconds && Integer === entropy
51
+ raise(OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}") unless milliseconds <= MAX_MILLISECONDS
52
+ raise(OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}") unless entropy <= MAX_ENTROPY
53
+ raise(ArgumentError, 'milliseconds and entropy should not be negative') if milliseconds.negative? || entropy.negative?
54
+
55
+ base32hex_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
56
+ base32hex_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
57
+ "#{base32hex_timestamp}#{base32hex_randomness}"
58
+ end
59
+
60
+ # @param [BasicObject] object
61
+ # @return [String]
62
+ def self.safe_get_class_name(object)
63
+ fallback = 'UnknownObject'
64
+
65
+ # This class getter implementation used https://github.com/rspec/rspec-support/blob/4ad8392d0787a66f9c351d9cf6c7618e18b3d0f2/lib/rspec/support.rb#L83-L89 as a reference, thank you!
66
+ # ref: https://twitter.com/_kachick/status/1400064896759304196
67
+ klass = (
68
+ begin
69
+ object.class
70
+ rescue NoMethodError
71
+ # steep can't correctly handle singleton class assign. See https://github.com/soutaro/steep/pull/586 for further detail
72
+ # So this annotation is hack for the type infer.
73
+ # @type var object: BasicObject
74
+ # @type var singleton_class: untyped
75
+ singleton_class = class << object; self; end
76
+ (Class === singleton_class) ? singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) } : fallback
77
+ end
78
+ )
79
+
80
+ begin
81
+ name = String.try_convert(klass.name)
82
+ rescue Exception
83
+ fallback
84
+ else
85
+ name || fallback
86
+ end
87
+ end
88
+
89
+ # @note Call before Module#private_constant
90
+ def self.make_sharable_constants(mod)
91
+ mod.constants.each do |const_name|
92
+ value = mod.const_get(const_name)
93
+ Ractor.make_shareable(value)
94
+ end
95
+ end
96
+ end
97
+
98
+ private_constant(:Utils)
99
+ end