ruby-ulid 0.7.0 → 0.9.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: 637c2b95fc9d18a59e1bc6d23a3791bc80362b146c52ff962c407894c29c782e
4
- data.tar.gz: 74a9e3ea4be5d9a541d4cb6dd012997406ef3cb68a08f7a1a42959f40f7ab4e5
3
+ metadata.gz: 6c0ca476ab05b4b53ce593e893ffdfd6aa72b3c08563116b95f5a22f43a7926b
4
+ data.tar.gz: 7454acd393c8df299696d2907a71f994643dacc45103e2874561d186e1fa85c5
5
5
  SHA512:
6
- metadata.gz: c39c42f8da0aac22356cb26fefb7d23855d118f68148be90c1e253577847b91135f8a04ef1fdca1af59075bd673e61cf2f492f046f30616f7c6b493e28332174
7
- data.tar.gz: 623f7f9b4d46e46c2a47a143330b3d1c2cf83a033e83c8e68ab3af13e56f1ff5a082b3a15f57efcf4a3180fcc5786e18e94a0221fd03c44343322d641b69e31a
6
+ metadata.gz: bfe19e20452a12edd7472ddaf3b9583d2eaa5640e03a081621c35bcfdf218f9ccf5d7c7625b0850312447f61c0592796d333aa4578a6c2f0281e6ff4e8392efd
7
+ data.tar.gz: cdd8dba77214852ea70d07a137f75098558097db686a4409dd14e611d533de33f4c510b9a38ee232e918dd0aae6b028f19a4dd7312ac42172890d726091f282b
data/README.md CHANGED
@@ -5,9 +5,10 @@
5
5
 
6
6
  ## Overview
7
7
 
8
- [ulid/spec](https://github.com/ulid/spec) has some useful features.\
9
- Especially possess all `uniqueness`, `randomness`, `extractable timestamps` and `sortability`.\
10
- This gem aims to provide the generator, optional monotonicity, parser and other manipulation ways 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,33 @@ 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.2 or higher.
41
42
 
42
- This command will install the latest version into your environment
43
+ Add this line to your `Gemfile`.
43
44
 
44
- ```console
45
- $ gem install ruby-ulid
46
- Should be installed!
45
+ ```ruby
46
+ gem('ruby-ulid', '~> 0.9.0')
47
47
  ```
48
48
 
49
- Add this line in your Gemfile.
49
+ And load it.
50
50
 
51
51
  ```ruby
52
- gem('ruby-ulid', '~> 0.7.0')
52
+ require 'ulid'
53
53
  ```
54
54
 
55
- ### How to use
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.9.0).
56
57
 
57
- ```ruby
58
- require 'ulid'
58
+ In [Nix](https://nixos.org/), you can skip the installation steps for both ruby and ruby-ulid to try.
59
59
 
60
- ULID::VERSION
61
- # => "0.7.0"
62
- ```
60
+ ```console
61
+ > nix run github:kachick/ruby-ulid#ruby -- -e 'p ULID.generate'
62
+ ULID(2024-03-03 18:37:06.152 UTC: 01HR2SNY789ZZ027EDJEHAGQ62)
63
63
 
64
- NOTE: This README includes info about development version. If you would see released version's one. [Look at the ref](https://github.com/kachick/ruby-ulid/tree/v0.7.0).
64
+ > nix run github:kachick/ruby-ulid#irb
65
+ irb(main):001:0> ULID.parse('01H66XG2A9WWYRCYGPA62T4AZA')
66
+ => ULID(2023-07-25 16:18:12.937 UTC: 01H66XG2A9WWYRCYGPA62T4AZA)
67
+ ```
65
68
 
66
69
  ### Generator and Parser
67
70
 
@@ -77,7 +80,7 @@ ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GR
77
80
  ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
78
81
  ```
79
82
 
80
- It is helpful to inspect.
83
+ It has inspector methods.
81
84
 
82
85
  ```ruby
83
86
  ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
@@ -118,15 +121,15 @@ ULID.decode_time('00VHNCZB00SYG7RCEXZC9DA4E1', in: '+09:00') #=> 2000-01-01 09:0
118
121
 
119
122
  This project does not prioritize on the speed. However it actually works faster than others! :zap:
120
123
 
121
- Snapshot on 0.7.0 is below
124
+ Snapshot on v0.8.0 with Ruby 3.2.1 is below
122
125
 
123
- - Generator is 1.6x 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.6x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
126
+ - Generator is 1.9x faster than - [ulid gem - v1.4.0](https://github.com/rafaelsales/ulid)
127
+ - Generator is 2.0x faster than - [ulid-ruby gem - v1.0.2](https://github.com/abachman/ulid-ruby)
128
+ - Parser is 3.1x faster than - [ulid-ruby gem - v1.0.2](https://github.com/abachman/ulid-ruby)
126
129
 
127
130
  You can see further detail at [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
128
131
 
129
- ### Sortable with the timestamp
132
+ ### Sortable by timestamp
130
133
 
131
134
  ULIDs are sortable when they are generated in different timestamp with milliseconds precision.
132
135
 
@@ -139,7 +142,7 @@ ulids.uniq(&:to_time).size #=> 1000
139
142
  ulids.sort == ulids #=> true
140
143
  ```
141
144
 
142
- Basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
145
+ The basic generator prefers `randomness`, the results in the same milliseconds are not sortable.
143
146
 
144
147
  ```ruby
145
148
  ulids = 10000.times.map do
@@ -151,8 +154,9 @@ ulids.sort == ulids #=> false
151
154
 
152
155
  ### How to keep `Sortable` even if in same timestamp
153
156
 
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)
157
+ If you prefer `sortability`, you can use `MonotonicGenerator` instead.\
158
+ It is referred to as [Monotonicity](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#monotonicity) in the spec.\
159
+ (Although it starts with a new random value when the timestamp is changed)
156
160
 
157
161
  ```ruby
158
162
  monotonic_generator = ULID::MonotonicGenerator.new
@@ -163,20 +167,16 @@ sample_ulids_by_the_time = ulids.uniq(&:to_time)
163
167
  sample_ulids_by_the_time.size #=> 32 (the size is not fixed, might be changed in environment)
164
168
 
165
169
  # In same milliseconds creation, it just increments the end of randomness part
166
- ulids.take(5) #=>
170
+ ulids.take(3) #=>
167
171
  # [ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4),
168
172
  # 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)]
173
+ # ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK6)]
172
174
 
173
175
  # When the milliseconds is updated, it starts with new randomness
174
- sample_ulids_by_the_time.take(5) #=>
176
+ sample_ulids_by_the_time.take(3) #=>
175
177
  # [ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4),
176
178
  # 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)]
179
+ # ULID(2021-05-02 15:23:48.919 UTC: 01F4PTVCSQF1GERBPCQV6TCX2K)]
180
180
 
181
181
  ulids.sort == ulids #=> true
182
182
  ```
@@ -265,18 +265,6 @@ ULID.scan(json).to_a
265
265
  # ULID(2021-04-30 05:53:12.478 UTC: 01F4GND4RYYSKNAADHQ9BNXAWJ)]
266
266
  ```
267
267
 
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
268
  #### Get boundary ULIDs
281
269
 
282
270
  `ULID.min` and `ULID.max` return termination values for ULID spec.
@@ -292,7 +280,7 @@ ULID.min(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
292
280
  ULID.max(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
293
281
  ```
294
282
 
295
- #### As element in Enumerable
283
+ #### As an element in Enumerable and Range
296
284
 
297
285
  `ULID#next` and `ULID#succ` returns next(successor) ULID.\
298
286
  Especially `ULID#succ` makes it possible `Range[ULID]#each`.
@@ -313,6 +301,17 @@ ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZ
313
301
  ULID.parse('00000000000000000000000000').pred #=> nil
314
302
  ```
315
303
 
304
+ `ULID#+` is also provided to realize `Range#step` since [ruby-3.4.0 spec changes](https://bugs.ruby-lang.org/issues/18368).
305
+
306
+ ```ruby
307
+ # This code works only in ruby-3.4.0dev or later
308
+ (ULID.min...).step(42).take(3)
309
+ # =>
310
+ [ULID(1970-01-01 00:00:00.000 UTC: 00000000000000000000000000),
311
+ ULID(1970-01-01 00:00:00.000 UTC: 0000000000000000000000001A),
312
+ ULID(1970-01-01 00:00:00.000 UTC: 0000000000000000000000002M)]
313
+ ```
314
+
316
315
  #### Test helpers
317
316
 
318
317
  `ULID.sample` returns random ULIDs.
@@ -324,13 +323,11 @@ ULID.sample #=> ULID(2545-07-26 06:51:20.085 UTC: 0GGKQ45GMNMZR6N8A8GFG0ZXST)
324
323
  ULID.sample #=> ULID(5098-07-26 21:31:06.946 UTC: 2SSBNGGYA272J7BMDCG4Z6EEM5)
325
324
  ULID.sample(0) #=> []
326
325
  ULID.sample(1) #=> [ULID(2241-04-16 03:31:18.440 UTC: 07S52YWZ98AZ8T565MD9VRYMQH)]
327
- ULID.sample(5)
326
+ ULID.sample(3)
328
327
  #=>
329
328
  #[ULID(5701-04-29 12:41:19.647 UTC: 3B2YH2DV0ZYDDATGTYSKMM1CMT),
330
329
  # 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)]
330
+ # ULID(10408-10-05 17:06:27.848 UTC: 7J6CPTEEC86Y24EQ4F1Y93YYN0)]
334
331
  ```
335
332
 
336
333
  You can specify a range object for the timestamp restriction, see also `ULID.range`.
@@ -338,22 +335,16 @@ You can specify a range object for the timestamp restriction, see also `ULID.ran
338
335
  ```ruby
339
336
  ulid1 = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
340
337
  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)
338
+ ulids = ULID.sample(3, period: ulid1..ulid2)
344
339
  #=>
345
340
  #[ULID(2021-05-02 06:57:19.954 UTC: 01F4NXW02JNB8H0J0TK48JD39X),
346
341
  # 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)
342
+ # ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW)]
343
+ ULID.sample(3, period: ulid1.to_time..ulid2.to_time)
351
344
  #=>
352
345
  # [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
353
346
  # 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)]
347
+ # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW)]
357
348
  ```
358
349
 
359
350
  #### Variants of format
@@ -385,60 +376,57 @@ ULID.valid_as_variant_format?('01g70y0y7g-z1xwdarexergsddd') #=> true
385
376
  ULID.parse_variant_format('01G70Y0Y7G-ZLXWDIREXERGSDoD') #=> ULID(2022-07-03 02:25:22.672 UTC: 01G70Y0Y7GZ1XWD1REXERGSD0D)
386
377
  ```
387
378
 
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"
379
+ #### UUID
400
380
 
401
- uuid_v4s = 10000.times.map { SecureRandom.uuid }
402
- uuid_v4s.uniq.size == 10000 #=> Probably `true`
381
+ Both ULID and UUID are 128-bit IDs. But with different specs. Especially UUID has some versions probably UUIDv4.
403
382
 
404
- ulids = uuid_v4s.map { |uuid_v4| ULID.from_uuidv4(uuid_v4) }
405
- ulids.map(&:to_uuidv4) == uuid_v4s #=> **Probably** `true` except below examples.
383
+ All UUIDv4s can be converted to ULID, but this will not have the correct "timestamp".\
384
+ Most ULIDs cannot be converted to UUIDv4 while maintaining reversibility, because UUIDv4 requires version and variants in the fields.
406
385
 
407
- # NOTE: Some boundary values are not reversible. See below.
386
+ See also [ulid/spec#64](https://github.com/ulid/spec/issues/64) for further detail.
408
387
 
409
- ULID.min.to_uuidv4 #=> "00000000-0000-4000-8000-000000000000"
410
- ULID.max.to_uuidv4 #=> "ffffffff-ffff-4fff-bfff-ffffffffffff"
388
+ For now, this gem provides 4 methods for UUIDs.
411
389
 
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)
390
+ - Reversibility is preferred: `ULID.from_uuidish`, `ULID.to_uuidish`
391
+ - Prefer UUIDv4 specification: `ULID.from_uuidv4`, `ULID.to_uuidv4`
415
392
 
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
393
+ ```ruby
394
+ # All UUIDv4 IDs can be reversible even if converted to ULID
395
+ uuid = SecureRandom.uuid
396
+ ULID.from_uuidish(uuid) == ULID.from_uuidv4(uuid) #=> true
397
+ ULID.from_uuidish(uuid).to_uuidish == ULID.from_uuidv4(uuid).to_uuidv4 #=> true
398
+
399
+ # But most ULIDs cannot be converted to UUIDv4
400
+ ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA')
401
+ ulid.to_uuidv4 #=> ULID::IrreversibleUUIDError
402
+ # So 2 ways to get substitute strings that might satisfy the use case
403
+ ulid.to_uuidv4(force: true) #=> "0179145f-07ca-4b3c-af33-4c3c3149254a" this cannot be reverse to source ULID
404
+ ulid == ULID.from_uuidv4(ulid.to_uuidv4(force: true)) #=> false
405
+ ulid.to_uuidish #=> "0179145f-07ca-bb3c-af33-4c3c3149254a" does not satisfy UUIDv4 spec
406
+ ulid == ULID.from_uuidish(ulid.to_uuidish) #=> true
407
+
408
+ # Seeing boundary IDs makes it easier to understand
409
+ ULID.min.to_uuidish #=> "00000000-0000-0000-0000-000000000000"
410
+ ULID.min.to_uuidv4(force: true) #=> "00000000-0000-4000-8000-000000000000"
411
+ ULID.max.to_uuidish #=> "ffffffff-ffff-ffff-ffff-ffffffffffff"
412
+ ULID.max.to_uuidv4(force: true) #=> "ffffffff-ffff-4fff-bfff-ffffffffffff"
419
413
  ```
420
414
 
415
+ [UUIDv6, UUIDv7, UUIDv8](https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-02.html) are other candidates for sortable and randomness ID.\
416
+ Latest [ruby/securerandom merged the UUIDv7 generator](https://github.com/ruby/securerandom/pull/19).\
417
+ See [tracker](https://bugs.ruby-lang.org/issues/19735) for further detail.
418
+
421
419
  ## Migration from other gems
422
420
 
423
421
  See [wiki page for gem migration](https://github.com/kachick/ruby-ulid/wiki/Gem-migration).
424
422
 
425
423
  ## RBS
426
424
 
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)
425
+ - Try at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox).
426
+ - See the overview in [our wiki page for RBS](https://github.com/kachick/ruby-ulid/wiki/RBS)
434
427
 
435
428
  ## References
436
429
 
437
430
  - [Repository](https://github.com/kachick/ruby-ulid)
438
431
  - [API documents](https://kachick.github.io/ruby-ulid/)
439
432
  - [ulid/spec](https://github.com/ulid/spec)
440
-
441
- ## Note
442
-
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)
@@ -6,7 +6,7 @@
6
6
  require_relative('utils')
7
7
 
8
8
  class ULID
9
- # @see https://www.crockford.com/base32.html
9
+ # @see https://www.crockford.com/base32.html and https://www.rfc-editor.org/rfc/rfc4648
10
10
  #
11
11
  # This module supporting only `subset of original crockford for actual use-case` in ULID context.
12
12
  # Original decoding spec allows other characters.
@@ -38,7 +38,7 @@ class ULID
38
38
  }.freeze
39
39
 
40
40
  # Excluded I, L, O, U, - from Base32
41
- base32_to_crockford = {
41
+ base32hex_to_crockford = {
42
42
  'I' => 'J',
43
43
  'J' => 'K',
44
44
  'K' => 'M',
@@ -54,8 +54,8 @@ class ULID
54
54
  'U' => 'Y',
55
55
  'V' => 'Z'
56
56
  }.freeze
57
- BASE32_TR_PATTERN = base32_to_crockford.keys.join.freeze
58
- 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
59
59
  ENCODING_STRING = "#{same_definitions.values.join}#{CROCKFORD_TR_PATTERN}".freeze
60
60
 
61
61
  variant_to_normarized = {
@@ -69,22 +69,22 @@ class ULID
69
69
  VARIANT_TR_PATTERN = variant_to_normarized.keys.join.freeze
70
70
  NORMALIZED_TR_PATTERN = variant_to_normarized.values.join.freeze
71
71
 
72
- Utils.make_sharable_constantans(self)
72
+ Utils.make_sharable_constants(self)
73
73
 
74
74
  # @note Avoid to depend regex as possible. `tr(string, string)` is almost 2x Faster than `gsub(regex, hash)` in Ruby 3.1
75
75
 
76
76
  # @param [String] string
77
77
  # @return [Integer]
78
78
  def self.decode(string)
79
- base32encoded = string.upcase.tr(CROCKFORD_TR_PATTERN, BASE32_TR_PATTERN)
80
- Integer(base32encoded, 32, exception: true)
79
+ base32hex = string.upcase.tr(CROCKFORD_TR_PATTERN, BASE32HEX_TR_PATTERN)
80
+ Integer(base32hex, 32, exception: true)
81
81
  end
82
82
 
83
83
  # @param [Integer] integer
84
84
  # @return [String]
85
85
  def self.encode(integer)
86
- base32encoded = integer.to_s(32)
87
- from_base32(base32encoded).rjust(ENCODED_LENGTH, '0')
86
+ base32hex = integer.to_s(32)
87
+ from_base32hex(base32hex).rjust(ENCODED_LENGTH, '0')
88
88
  end
89
89
 
90
90
  # @param [String] string
@@ -93,10 +93,10 @@ class ULID
93
93
  string.delete('-').tr(VARIANT_TR_PATTERN, NORMALIZED_TR_PATTERN)
94
94
  end
95
95
 
96
- # @param [String] base32encoded
96
+ # @param [String] base32hex
97
97
  # @return [String]
98
- def self.from_base32(base32encoded)
99
- base32encoded.upcase.tr(BASE32_TR_PATTERN, CROCKFORD_TR_PATTERN)
98
+ def self.from_base32hex(base32hex)
99
+ base32hex.upcase.tr(BASE32HEX_TR_PATTERN, CROCKFORD_TR_PATTERN)
100
100
  end
101
101
  end
102
102
 
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
@@ -10,23 +10,23 @@ require_relative('utils')
10
10
  class ULID
11
11
  class MonotonicGenerator
12
12
  # @note When use https://github.com/ko1/ractor-tvar might realize Ractor based thread safe monotonic generator.
13
- # However it is a C extention, I'm pending to use it for now.
13
+ # However it is a C extension, I'm pending to use it for now.
14
14
  include(MonitorMixin)
15
15
 
16
16
  # @return [ULID, nil]
17
- attr_accessor(:prev)
18
- private(:prev=)
17
+ attr_accessor(:last)
18
+ private(:last=)
19
19
 
20
20
  undef_method(:instance_variable_set)
21
21
 
22
22
  def initialize
23
23
  super
24
- @prev = nil
24
+ @last = nil
25
25
  end
26
26
 
27
27
  # @return [String]
28
28
  def inspect
29
- "ULID::MonotonicGenerator(prev: #{@prev.inspect})"
29
+ "ULID::MonotonicGenerator(last: #{@last.inspect})"
30
30
  end
31
31
  alias_method(:to_s, :inspect)
32
32
 
@@ -37,25 +37,25 @@ class ULID
37
37
  # Basically will not happen. Just means this feature prefers error rather than invalid value.
38
38
  def generate(moment: Utils.current_milliseconds)
39
39
  synchronize do
40
- prev_ulid = @prev
41
- unless prev_ulid
42
- ret = ULID.generate(moment: moment)
43
- @prev = ret
40
+ prev = @last
41
+ unless prev
42
+ ret = ULID.generate(moment:)
43
+ @last = ret
44
44
  return ret
45
45
  end
46
46
 
47
47
  milliseconds = Utils.milliseconds_from_moment(moment)
48
48
 
49
49
  ulid = (
50
- if prev_ulid.milliseconds < milliseconds
50
+ if prev.milliseconds < milliseconds
51
51
  ULID.generate(moment: milliseconds)
52
52
  else
53
- ULID.generate(moment: prev_ulid.milliseconds, entropy: prev_ulid.entropy.succ)
53
+ ULID.generate(moment: prev.milliseconds, entropy: prev.entropy.succ)
54
54
  end
55
55
  )
56
56
 
57
- unless ulid > prev_ulid
58
- 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}"
59
59
  additional_information = (
60
60
  if Thread.list == [Thread.main]
61
61
  '# NOTE: looks single thread only exist'
@@ -67,7 +67,7 @@ class ULID
67
67
  raise(UnexpectedError, base_message + additional_information)
68
68
  end
69
69
 
70
- @prev = ulid
70
+ @last = ulid
71
71
  ulid
72
72
  end
73
73
  end
@@ -78,7 +78,7 @@ class ULID
78
78
  # @param [Time, Integer] moment
79
79
  # @return [String]
80
80
  def encode(moment: Utils.current_milliseconds)
81
- generate(moment: moment).encode
81
+ generate(moment:).encode
82
82
  end
83
83
 
84
84
  undef_method(:freeze)
data/lib/ulid/utils.rb CHANGED
@@ -46,15 +46,15 @@ class ULID
46
46
  # @return [String]
47
47
  # @raise [OverflowError] if the given value is larger than the ULID limit
48
48
  # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
49
- def self.encode_base32(milliseconds:, entropy:)
49
+ def self.encode_base32hex(milliseconds:, entropy:)
50
50
  raise(ArgumentError, 'milliseconds and entropy should be an `Integer`') unless Integer === milliseconds && Integer === entropy
51
51
  raise(OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}") unless milliseconds <= MAX_MILLISECONDS
52
52
  raise(OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}") unless entropy <= MAX_ENTROPY
53
53
  raise(ArgumentError, 'milliseconds and entropy should not be negative') if milliseconds.negative? || entropy.negative?
54
54
 
55
- base32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
56
- base32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
57
- "#{base32encoded_timestamp}#{base32encoded_randomness}"
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
58
  end
59
59
 
60
60
  # @param [BasicObject] object
@@ -86,31 +86,13 @@ class ULID
86
86
  end
87
87
  end
88
88
 
89
- def self.make_sharable_value(value)
90
- value.freeze
91
- if defined?(Ractor)
92
- case value
93
- when ULID, Time
94
- if ractor_can_make_shareable_time?
95
- Ractor.make_shareable(value)
96
- end
97
- else
98
- Ractor.make_shareable(value)
99
- end
100
- end
101
- end
102
-
103
89
  # @note Call before Module#private_constant
104
- def self.make_sharable_constantans(mod)
90
+ def self.make_sharable_constants(mod)
105
91
  mod.constants.each do |const_name|
106
92
  value = mod.const_get(const_name)
107
- make_sharable_value(value)
93
+ Ractor.make_shareable(value)
108
94
  end
109
95
  end
110
-
111
- def self.ractor_can_make_shareable_time?
112
- RUBY_VERSION >= '3.1'
113
- end
114
96
  end
115
97
 
116
98
  private_constant(:Utils)
@@ -0,0 +1,41 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+ # shareable_constant_value: literal
4
+
5
+ # Copyright (C) 2021 Kenichi Kamiya
6
+
7
+ class ULID
8
+ module UUID
9
+ # @see https://www.rfc-editor.org/rfc/rfc4122#section-4.1.2
10
+ # @note
11
+ # - Using `Fields = Data.define do; end` syntax made https://github.com/kachick/ruby-ulid/issues/233 again. So use class syntax instead
12
+ # - This file is extracted to avoid YARD warnings "Ruby::ClassHandler: Undocumentable superclass" https://github.com/lsegal/yard/issues/737
13
+ # Partially avoiding is hard in YARD, so extracting the code and using exclude filter in yardopts...
14
+ class Fields < Data.define(:time_low, :time_mid, :time_hi_and_version, :clock_seq_hi_and_res, :clk_seq_low, :node)
15
+ def self.raw_from_octets(octets)
16
+ case octets.pack('C*').unpack('NnnnnN')
17
+ in [Integer => time_low, Integer => time_mid, Integer => time_hi_and_version, Integer => clock_seq_hi_and_res, Integer => clk_seq_low, Integer => node]
18
+ new(time_low:, time_mid:, time_hi_and_version:, clock_seq_hi_and_res:, clk_seq_low:, node:).freeze
19
+ end
20
+ end
21
+
22
+ def self.forced_v4_from_octets(octets)
23
+ case octets.pack('C*').unpack('NnnnnN')
24
+ in [Integer => time_low, Integer => time_mid, Integer => time_hi_and_version, Integer => clock_seq_hi_and_res, Integer => clk_seq_low, Integer => node]
25
+ new(
26
+ time_low:,
27
+ time_mid:,
28
+ time_hi_and_version: (time_hi_and_version & 0x0fff) | 0x4000,
29
+ clock_seq_hi_and_res: (clock_seq_hi_and_res & 0x3fff) | 0x8000,
30
+ clk_seq_low:,
31
+ node:
32
+ ).freeze
33
+ end
34
+ end
35
+
36
+ def to_s
37
+ '%08x-%04x-%04x-%04x-%04x%08x' % deconstruct
38
+ end
39
+ end
40
+ end
41
+ end