ruby-ulid 0.0.14 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +39 -13
- data/lib/ulid.rb +43 -15
- data/lib/ulid/version.rb +1 -1
- data/sig/ulid.rbs +8 -1
- metadata +3 -4
- data/Steepfile +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a620e80f82e91c778329ccf7da8cca96bda4b12e047610b9543768c5ec85d22
|
4
|
+
data.tar.gz: ea28469d63348758f52e1460b5330ce177ad2281e63060800a0669054758f3ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: acb07ae797c3a06a6626d34e15dc1056a30586e8bc151a3773770259d2f4047d0708593e702fbd67a7609758165f20a15f208fede068717a5555733535ed4bde
|
7
|
+
data.tar.gz: 57c9819d86f2a0f002cf3b9a7b76b3a36bc412fa874d50136ec296f1217fb905fa19feb52de2c9d5250547da384580cf882121f0e331054cd665bd113d1f10e3
|
data/README.md
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
A handy `ULID` library
|
4
4
|
|
5
|
-
The `ULID` spec is defined on [ulid/spec](https://github.com/ulid/spec).
|
5
|
+
The `ULID` spec is defined on [ulid/spec](https://github.com/ulid/spec). It has useful specs for applications (e.g. `Database key`), especially possess all `uniqueness`, `randomness`, `extractable timestamps` and `sortable` features.
|
6
6
|
This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around the ULID.
|
7
|
-
Also providing rbs signature files.
|
7
|
+
Also providing [ruby/rbs](https://github.com/ruby/rbs) signature files.
|
8
8
|
|
9
9
|
---
|
10
10
|
|
@@ -28,7 +28,7 @@ Instead, herein is proposed ULID:
|
|
28
28
|
- 1.21e+24 unique ULIDs per millisecond
|
29
29
|
- Lexicographically sortable!
|
30
30
|
- Canonically encoded as a 26 character string, as opposed to the 36 character UUID
|
31
|
-
- Uses Crockford's base32 for better efficiency and readability (5 bits per character)
|
31
|
+
- Uses [Crockford's base32](https://www.crockford.com/base32.html) for better efficiency and readability (5 bits per character) # See also exists issues in [Note](#note)
|
32
32
|
- Case insensitive
|
33
33
|
- No special characters (URL safe)
|
34
34
|
- Monotonic sort order (correctly detects and handles the same millisecond)
|
@@ -98,19 +98,19 @@ ulids.uniq(&:to_time).size #=> 35 (the size is not fixed, might be changed in en
|
|
98
98
|
ulids.sort == ulids #=> false
|
99
99
|
```
|
100
100
|
|
101
|
-
If you want to
|
101
|
+
If you want to ensure `sortable`, Use `MonotonicGenerator` instead. It is called as [Monotonicity](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#monotonicity) on the spec.
|
102
102
|
(Though it starts with new random value when changed the timestamp)
|
103
103
|
|
104
104
|
```ruby
|
105
105
|
monotonic_generator = ULID::MonotonicGenerator.new
|
106
|
-
|
106
|
+
ulids = 10000.times.map do
|
107
107
|
monotonic_generator.generate
|
108
108
|
end
|
109
|
-
sample_ulids_by_the_time =
|
109
|
+
sample_ulids_by_the_time = ulids.uniq(&:to_time)
|
110
110
|
sample_ulids_by_the_time.size #=> 32 (the size is not fixed, might be changed in environment)
|
111
111
|
|
112
112
|
# In same milliseconds creation, it just increments the end of randomness part
|
113
|
-
|
113
|
+
ulids.take(5) #=>
|
114
114
|
# [ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4),
|
115
115
|
# ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK5),
|
116
116
|
# ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK6),
|
@@ -125,7 +125,7 @@ sample_ulids_by_the_time.take(5) #=>
|
|
125
125
|
# ULID(2021-05-02 15:23:48.920 UTC: 01F4PTVCSRBXN2H4P1EYWZ27AK),
|
126
126
|
# ULID(2021-05-02 15:23:48.921 UTC: 01F4PTVCSSK0ASBBZARV7013F8)]
|
127
127
|
|
128
|
-
|
128
|
+
ulids.sort == ulids #=> true
|
129
129
|
```
|
130
130
|
|
131
131
|
When filtering ULIDs by `Time`, we should consider to handle the precision.
|
@@ -209,11 +209,34 @@ ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZ
|
|
209
209
|
ULID.parse('00000000000000000000000000').pred #=> nil
|
210
210
|
```
|
211
211
|
|
212
|
-
UUIDv4 converter for migration use-cases
|
212
|
+
### UUIDv4 converter for migration use-cases
|
213
|
+
|
214
|
+
`ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
|
215
|
+
The imported timestamp is meaningless. So ULID's benefit will lost
|
213
216
|
|
214
217
|
```ruby
|
215
|
-
|
216
|
-
#=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
|
218
|
+
# Basically reversible
|
219
|
+
ulid = ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39') #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
|
220
|
+
ulid.to_uuidv4 #=> "0983d0a2-ff15-4d83-8f37-7dd945b5aa39"
|
221
|
+
|
222
|
+
uuid_v4s = 10000.times.map { SecureRandom.uuid }
|
223
|
+
uuid_v4s.uniq.size == 10000 #=> Probably `true`
|
224
|
+
|
225
|
+
ulids = uuid_v4s.map { |uuid_v4| ULID.from_uuidv4(uuid_v4) }
|
226
|
+
ulids.map(&:to_uuidv4) == uuid_v4s #=> **Probably** `true` except below examples.
|
227
|
+
|
228
|
+
# NOTE: Some boundary values are not reversible. See below.
|
229
|
+
|
230
|
+
ULID.min.to_uuidv4 #=> "00000000-0000-4000-8000-000000000000"
|
231
|
+
ULID.max.to_uuidv4 #=> "ffffffff-ffff-4fff-bfff-ffffffffffff"
|
232
|
+
|
233
|
+
# These importing results are same as https://github.com/ahawker/ulid/tree/96bdb1daad7ce96f6db8c91ac0410b66d2e1c4c1 on CPython 3.9.4
|
234
|
+
reversed_min = ULID.from_uuidv4('00000000-0000-4000-8000-000000000000') #=> ULID(1970-01-01 00:00:00.000 UTC: 00000000008008000000000000)
|
235
|
+
reversed_max = ULID.from_uuidv4('ffffffff-ffff-4fff-bfff-ffffffffffff') #=> ULID(10889-08-02 05:31:50.655 UTC: 7ZZZZZZZZZ9ZZVZZZZZZZZZZZZ)
|
236
|
+
|
237
|
+
# But they are not reversible! Need to consider this issue in https://github.com/kachick/ruby-ulid/issues/76
|
238
|
+
ULID.min == reversed_min #=> false
|
239
|
+
ULID.max == reversed_max #=> false
|
217
240
|
```
|
218
241
|
|
219
242
|
## References
|
@@ -221,5 +244,8 @@ ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
|
|
221
244
|
- [Repository](https://github.com/kachick/ruby-ulid)
|
222
245
|
- [API documents](https://kachick.github.io/ruby-ulid/)
|
223
246
|
- [ulid/spec](https://github.com/ulid/spec)
|
224
|
-
|
225
|
-
|
247
|
+
|
248
|
+
## Note
|
249
|
+
|
250
|
+
- Another choices for sortable and randomness IDs, [UUIDv6, UUIDv7, UUIDv8 might be the one. (But they are still in draft state)](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-01.html), I will track them in [ruby-ulid#37](https://github.com/kachick/ruby-ulid/issues/37)
|
251
|
+
- Current parser/validator/matcher aims to cover `subset of Crockford's base32`. Suggesting it in [ulid/spec#57](https://github.com/ulid/spec/pull/57). Be that as it may, I might provide special handler or converter for the exception in [ruby-ulid#57](https://github.com/kachick/ruby-ulid/issues/57) and/or [ruby-ulid#78](https://github.com/kachick/ruby-ulid/issues/78)
|
data/lib/ulid.rb
CHANGED
@@ -35,7 +35,7 @@ class ULID
|
|
35
35
|
STRICT_PATTERN = /\A#{PATTERN.source}\z/i.freeze
|
36
36
|
|
37
37
|
# Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
|
38
|
-
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
|
38
|
+
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
|
39
39
|
|
40
40
|
# Same as Time#inspect since Ruby 2.7, just to keep backward compatibility
|
41
41
|
# @see https://bugs.ruby-lang.org/issues/15958
|
@@ -51,13 +51,13 @@ class ULID
|
|
51
51
|
# @param [Integer, Time] moment
|
52
52
|
# @return [ULID]
|
53
53
|
def self.min(moment: 0)
|
54
|
-
generate(moment: moment, entropy: 0)
|
54
|
+
0.equal?(moment) ? MIN : generate(moment: moment, entropy: 0)
|
55
55
|
end
|
56
56
|
|
57
57
|
# @param [Integer, Time] moment
|
58
58
|
# @return [ULID]
|
59
59
|
def self.max(moment: MAX_MILLISECONDS)
|
60
|
-
generate(moment: moment, entropy: MAX_ENTROPY)
|
60
|
+
MAX_MILLISECONDS.equal?(moment) ? MAX : generate(moment: moment, entropy: MAX_ENTROPY)
|
61
61
|
end
|
62
62
|
|
63
63
|
# @deprecated This method actually changes class state. Use {ULID::MonotonicGenerator} instead.
|
@@ -121,10 +121,11 @@ class ULID
|
|
121
121
|
new milliseconds: milliseconds, entropy: entropy
|
122
122
|
end
|
123
123
|
|
124
|
-
# @param [Range<Time>] time_range
|
124
|
+
# @param [Range<Time>, Range<nil>] time_range
|
125
125
|
# @return [Range<ULID>]
|
126
|
+
# @raise [ArgumentError] if the given time_range is not a `Range[Time]` or `Range[nil]`
|
126
127
|
def self.range(time_range)
|
127
|
-
raise
|
128
|
+
raise argument_error_for_range_building(time_range) unless time_range.kind_of?(Range)
|
128
129
|
begin_time, end_time, exclude_end = time_range.begin, time_range.end, time_range.exclude_end?
|
129
130
|
|
130
131
|
case begin_time
|
@@ -133,7 +134,7 @@ class ULID
|
|
133
134
|
when nil
|
134
135
|
begin_ulid = min
|
135
136
|
else
|
136
|
-
raise
|
137
|
+
raise argument_error_for_range_building(time_range)
|
137
138
|
end
|
138
139
|
|
139
140
|
case end_time
|
@@ -148,9 +149,12 @@ class ULID
|
|
148
149
|
end_ulid = max
|
149
150
|
exclude_end = false
|
150
151
|
else
|
151
|
-
raise
|
152
|
+
raise argument_error_for_range_building(time_range)
|
152
153
|
end
|
153
154
|
|
155
|
+
begin_ulid.freeze
|
156
|
+
end_ulid.freeze
|
157
|
+
|
154
158
|
Range.new(begin_ulid, end_ulid, exclude_end)
|
155
159
|
end
|
156
160
|
|
@@ -241,6 +245,11 @@ class ULID
|
|
241
245
|
num
|
242
246
|
end
|
243
247
|
|
248
|
+
# @return [ArgumentError]
|
249
|
+
private_class_method def self.argument_error_for_range_building(argument)
|
250
|
+
ArgumentError.new "ULID.range takes only `Range[Time]` or `Range[nil]`, given: #{argument.inspect}"
|
251
|
+
end
|
252
|
+
|
244
253
|
attr_reader :milliseconds, :entropy
|
245
254
|
|
246
255
|
# @api private
|
@@ -364,15 +373,21 @@ class ULID
|
|
364
373
|
@pred ||= self.class.from_integer(pre_int)
|
365
374
|
end
|
366
375
|
|
376
|
+
# @return [String]
|
377
|
+
def to_uuidv4
|
378
|
+
@uuidv4 ||= begin
|
379
|
+
# This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
|
380
|
+
array = octets.pack('C*').unpack('NnnnnN')
|
381
|
+
array[2] = (array[2] & 0x0fff) | 0x4000
|
382
|
+
array[3] = (array[3] & 0x3fff) | 0x8000
|
383
|
+
('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
367
387
|
# @return [self]
|
368
388
|
def freeze
|
369
|
-
#
|
370
|
-
|
371
|
-
octets
|
372
|
-
to_i
|
373
|
-
succ
|
374
|
-
pred
|
375
|
-
strict_pattern
|
389
|
+
# Need to cache before freezing, because frozen objects can't assign instance variables
|
390
|
+
cache_all_instance_variables
|
376
391
|
super
|
377
392
|
end
|
378
393
|
|
@@ -382,13 +397,26 @@ class ULID
|
|
382
397
|
def matchdata
|
383
398
|
@matchdata ||= STRICT_PATTERN.match(to_s).freeze
|
384
399
|
end
|
400
|
+
|
401
|
+
# @return [void]
|
402
|
+
def cache_all_instance_variables
|
403
|
+
inspect
|
404
|
+
octets
|
405
|
+
to_i
|
406
|
+
succ
|
407
|
+
pred
|
408
|
+
strict_pattern
|
409
|
+
to_uuidv4
|
410
|
+
end
|
385
411
|
end
|
386
412
|
|
387
413
|
require_relative 'ulid/version'
|
388
414
|
require_relative 'ulid/monotonic_generator'
|
389
415
|
|
390
416
|
class ULID
|
417
|
+
MIN = parse('00000000000000000000000000').freeze
|
418
|
+
MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
|
391
419
|
MONOTONIC_GENERATOR = MonotonicGenerator.new
|
392
420
|
|
393
|
-
private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN
|
421
|
+
private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN, :MIN, :MAX
|
394
422
|
end
|
data/lib/ulid/version.rb
CHANGED
data/sig/ulid.rbs
CHANGED
@@ -16,8 +16,11 @@ class ULID
|
|
16
16
|
STRICT_PATTERN: Regexp
|
17
17
|
UUIDV4_PATTERN: Regexp
|
18
18
|
MONOTONIC_GENERATOR: MonotonicGenerator
|
19
|
+
MIN: ULID
|
20
|
+
MAX: ULID
|
19
21
|
include Comparable
|
20
22
|
|
23
|
+
# The `moment` is a `Time` or `Intger of the milliseconds`
|
21
24
|
type moment = Time | Integer
|
22
25
|
|
23
26
|
class Error < StandardError
|
@@ -56,6 +59,7 @@ class ULID
|
|
56
59
|
@next: ULID?
|
57
60
|
@pattern: Regexp?
|
58
61
|
@strict_pattern: Regexp?
|
62
|
+
@uuidv4: String?
|
59
63
|
@matchdata: MatchData?
|
60
64
|
|
61
65
|
def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
|
@@ -63,7 +67,7 @@ class ULID
|
|
63
67
|
def self.current_milliseconds: -> Integer
|
64
68
|
def self.milliseconds_from_time: (Time time) -> Integer
|
65
69
|
def self.milliseconds_from_moment: (moment moment) -> Integer
|
66
|
-
def self.range: (Range[Time] time_range) -> Range[ULID]
|
70
|
+
def self.range: (Range[Time] | Range[nil] time_range) -> Range[ULID]
|
67
71
|
def self.floor: (Time time) -> Time
|
68
72
|
def self.reasonable_entropy: -> Integer
|
69
73
|
def self.parse: (String string) -> ULID
|
@@ -96,11 +100,14 @@ class ULID
|
|
96
100
|
def octets: -> octets
|
97
101
|
def timestamp_octets: -> timestamp_octets
|
98
102
|
def randomness_octets: -> randomness_octets
|
103
|
+
def to_uuidv4: -> String
|
99
104
|
def next: -> ULID?
|
100
105
|
alias succ next
|
101
106
|
def pred: -> ULID?
|
102
107
|
def freeze: -> self
|
103
108
|
|
104
109
|
private
|
110
|
+
def self.argument_error_for_range_building: (untyped argument) -> ArgumentError
|
105
111
|
def matchdata: -> MatchData
|
112
|
+
def cache_all_instance_variables: -> void
|
106
113
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-ulid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenichi Kamiya
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
11
|
+
date: 2021-05-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: integer-base
|
@@ -96,7 +96,6 @@ extra_rdoc_files: []
|
|
96
96
|
files:
|
97
97
|
- LICENSE
|
98
98
|
- README.md
|
99
|
-
- Steepfile
|
100
99
|
- lib/ulid.rb
|
101
100
|
- lib/ulid/monotonic_generator.rb
|
102
101
|
- lib/ulid/version.rb
|
@@ -123,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
122
|
- !ruby/object:Gem::Version
|
124
123
|
version: '0'
|
125
124
|
requirements: []
|
126
|
-
rubygems_version: 3.
|
125
|
+
rubygems_version: 3.2.15
|
127
126
|
signing_key:
|
128
127
|
specification_version: 4
|
129
128
|
summary: A handy ULID library
|