ruby-ulid 0.7.0 → 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: 637c2b95fc9d18a59e1bc6d23a3791bc80362b146c52ff962c407894c29c782e
4
- data.tar.gz: 74a9e3ea4be5d9a541d4cb6dd012997406ef3cb68a08f7a1a42959f40f7ab4e5
3
+ metadata.gz: 323b750a4e11157bd492b31d74833df2042192775c8627917880ef386dee4c1c
4
+ data.tar.gz: 2dc8a91cbed7b473d6f9e28480dd227ab3469828ab0833f48a754111bd6e926c
5
5
  SHA512:
6
- metadata.gz: c39c42f8da0aac22356cb26fefb7d23855d118f68148be90c1e253577847b91135f8a04ef1fdca1af59075bd673e61cf2f492f046f30616f7c6b493e28332174
7
- data.tar.gz: 623f7f9b4d46e46c2a47a143330b3d1c2cf83a033e83c8e68ab3af13e56f1ff5a082b3a15f57efcf4a3180fcc5786e18e94a0221fd03c44343322d641b69e31a
6
+ metadata.gz: e78a5289863c1300a3f4c186b72a0f1d97cd709ce6043b69729d851f5e6ffcedac001e3bb691397d761ee497d609b5292c300db7f2ec3f8cf5be0ee5c095af80
7
+ data.tar.gz: e19197709d7a896a79c6ebdc40fa919c2e7876284fa1dbf8895498ad7b80e989634cdd3e651362db5cf9f7c627fe17439ccc21e75c8e6598b002da6b19858aa0
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,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.7.0')
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.7.0"
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.7.0).
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
@@ -118,15 +110,15 @@ ULID.decode_time('00VHNCZB00SYG7RCEXZC9DA4E1', in: '+09:00') #=> 2000-01-01 09:0
118
110
 
119
111
  This project does not prioritize on the speed. However it actually works faster than others! :zap:
120
112
 
121
- Snapshot on 0.7.0 is below
113
+ Snapshot on v0.8.0 with Ruby 3.2.1 is below
122
114
 
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)
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
  ```
@@ -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.
@@ -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,22 +313,16 @@ 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
@@ -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)
@@ -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
@@ -14,19 +14,19 @@ class ULID
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)
data/lib/ulid/uuid.rb CHANGED
@@ -1,45 +1,75 @@
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
  require_relative('errors')
8
+ require_relative('utils')
7
9
 
8
- # Extracted features around UUID from some reasons
9
- # ref:
10
- # * https://github.com/kachick/ruby-ulid/issues/105
11
- # * https://github.com/kachick/ruby-ulid/issues/76
12
10
  class ULID
13
- # Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
14
- 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
15
- Utils.make_sharable_value(UUIDV4_PATTERN)
16
- private_constant(:UUIDV4_PATTERN)
17
-
18
- # @param [String, #to_str] uuid
19
- # @return [ULID]
20
- # @raise [ParserError] if the given format is not correct for UUIDv4 specs
21
- def self.from_uuidv4(uuid)
22
- uuid = String.try_convert(uuid)
23
- raise(ArgumentError, 'ULID.from_uuidv4 takes only strings') unless uuid
24
-
25
- prefix_trimmed = uuid.delete_prefix('urn:uuid:')
26
- unless UUIDV4_PATTERN.match?(prefix_trimmed)
27
- raise(ParserError, "given `#{uuid}` does not match to `#{UUIDV4_PATTERN.inspect}`")
11
+ module UUID
12
+ BASE_PATTERN = /\A[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\z/i
13
+ # Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
14
+ V4_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
15
+
16
+ def self.parse_any_to_int(uuidish)
17
+ encoded = String.try_convert(uuidish)
18
+ raise(ArgumentError, 'should pass a string for UUID parser') unless encoded
19
+
20
+ prefix_trimmed = encoded.delete_prefix('urn:uuid:')
21
+ unless BASE_PATTERN.match?(prefix_trimmed)
22
+ raise(ParserError, "given `#{encoded}` does not match to `#{BASE_PATTERN.inspect}`")
23
+ end
24
+
25
+ normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
26
+ Integer(normalized, 16, exception: true)
28
27
  end
29
28
 
30
- normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
31
- from_integer(normalized.to_i(16))
32
- end
29
+ def self.parse_v4_to_int(uuid)
30
+ encoded = String.try_convert(uuid)
31
+ raise(ArgumentError, 'should pass a string for UUID parser') unless encoded
32
+
33
+ prefix_trimmed = encoded.delete_prefix('urn:uuid:')
34
+ unless V4_PATTERN.match?(prefix_trimmed)
35
+ raise(ParserError, "given `#{encoded}` does not match to `#{V4_PATTERN.inspect}`")
36
+ end
33
37
 
34
- # @return [String]
35
- def to_uuidv4
36
- # This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
37
- array = octets.pack('C*').unpack('NnnnnN')
38
- ref2, ref3 = array[2], array[3]
39
- raise unless Integer === ref2 && Integer === ref3
38
+ parse_any_to_int(encoded)
39
+ end
40
40
 
41
- array[2] = (ref2 & 0x0fff) | 0x4000
42
- array[3] = (ref3 & 0x3fff) | 0x8000
43
- ('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
41
+ # @see https://www.rfc-editor.org/rfc/rfc4122#section-4.1.2
42
+ # @todo Replace to Data class after dropped Ruby 3.1
43
+ # @note Using `Fields = Struct.new` syntax made https://github.com/kachick/ruby-ulid/issues/233 again. So use class syntax instead
44
+ class Fields < Struct.new(:time_low, :time_mid, :time_hi_and_version, :clock_seq_hi_and_res, :clk_seq_low, :node, keyword_init: true)
45
+ def self.raw_from_octets(octets)
46
+ case octets.pack('C*').unpack('NnnnnN')
47
+ in [Integer => time_low, Integer => time_mid, Integer => time_hi_and_version, Integer => clock_seq_hi_and_res, Integer => clk_seq_low, Integer => node]
48
+ new(time_low:, time_mid:, time_hi_and_version:, clock_seq_hi_and_res:, clk_seq_low:, node:).freeze
49
+ end
50
+ end
51
+
52
+ def self.forced_v4_from_octets(octets)
53
+ case octets.pack('C*').unpack('NnnnnN')
54
+ in [Integer => time_low, Integer => time_mid, Integer => time_hi_and_version, Integer => clock_seq_hi_and_res, Integer => clk_seq_low, Integer => node]
55
+ new(
56
+ time_low:,
57
+ time_mid:,
58
+ time_hi_and_version: (time_hi_and_version & 0x0fff) | 0x4000,
59
+ clock_seq_hi_and_res: (clock_seq_hi_and_res & 0x3fff) | 0x8000,
60
+ clk_seq_low:,
61
+ node:
62
+ ).freeze
63
+ end
64
+ end
65
+
66
+ def to_s
67
+ '%08x-%04x-%04x-%04x-%04x%08x' % values
68
+ end
69
+ end
44
70
  end
71
+
72
+ Ractor.make_shareable(UUID)
73
+
74
+ private_constant(:UUID)
45
75
  end
data/lib/ulid/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  # shareable_constant_value: literal
4
4
 
5
5
  class ULID
6
- VERSION = '0.7.0'
6
+ VERSION = '0.8.0'
7
7
  end