ruby-ulid 0.6.1 → 0.8.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: 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