ruby-ulid 0.1.6 → 0.3.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 +4 -4
- data/README.md +29 -15
- data/lib/ruby-ulid.rb +1 -1
- data/lib/ulid/crockford_base32.rb +3 -3
- data/lib/ulid/monotonic_generator.rb +19 -15
- data/lib/ulid/uuid.rb +8 -5
- data/lib/ulid/version.rb +1 -1
- data/lib/ulid.rb +106 -94
- data/sig/ulid.rbs +11 -10
- metadata +10 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 358b03a503f8a12c87f3f7daaf3b0acf6e55dc44f1aba7a38b9f9a38985c2c68
|
4
|
+
data.tar.gz: 15d25d443f4826b07fc9a74b092e35b9cd60dc34cc3615f03fee619d4fc39445
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b18d13597bd2f3dcd85a15a26d9086fb9a80b91775292c9da82d93144b6abd37585996f1bf64dfc4ac12b6d6d49683012dfea8e04624005358459667864114c
|
7
|
+
data.tar.gz: d987aa9b60717577fae96eef68fca76b2395f11a580df4863a69dd0931d955e4cf68608c101959db18f5e3a26031fbe284ff90a0d107fa84bbebb63949bba66b
|
data/README.md
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
# ruby-ulid
|
2
2
|
|
3
|
+
[](https://github.com/kachick/ruby-ulid/actions/workflows/test_behaviors.yml/?branch=main)
|
4
|
+
[](http://badge.fury.io/rb/ruby-ulid)
|
5
|
+
|
3
6
|
## Overview
|
4
7
|
|
5
|
-
|
6
|
-
|
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, monotonic generator, parser and handy manipulation features around ULID.
|
11
|
+
Also providing [ruby/rbs](https://github.com/ruby/rbs) signatures.
|
8
12
|
|
9
13
|
---
|
10
14
|
|
11
|
-

|
14
|
-
[](http://badge.fury.io/rb/ruby-ulid)
|
15
|
+

|
15
16
|
|
16
17
|
## Universally Unique Lexicographically Sortable Identifier
|
17
18
|
|
@@ -37,7 +38,7 @@ Instead, herein is proposed ULID:
|
|
37
38
|
|
38
39
|
### Install
|
39
40
|
|
40
|
-
Require Ruby 2.
|
41
|
+
Require Ruby 2.7 or later
|
41
42
|
|
42
43
|
This command will install the latest version into your environment
|
43
44
|
|
@@ -46,10 +47,10 @@ $ gem install ruby-ulid
|
|
46
47
|
Should be installed!
|
47
48
|
```
|
48
49
|
|
49
|
-
Add this line to your
|
50
|
+
Add this line to your Gemfile`.
|
50
51
|
|
51
52
|
```ruby
|
52
|
-
gem
|
53
|
+
gem('ruby-ulid', '~> 0.2.2')
|
53
54
|
```
|
54
55
|
|
55
56
|
### Generator and Parser
|
@@ -170,8 +171,7 @@ exclude_end = ULID.range(time1...time2) #=> The end of `Range[ULID]` will be the
|
|
170
171
|
|
171
172
|
# Below patterns are acceptable
|
172
173
|
pinpointing = ULID.range(time1..time1) #=> This will match only for all IDs in `time1`
|
173
|
-
|
174
|
-
until_the_end = ULID.range(ULID.min.to_time..time1) #=> This is same as above for Ruby 2.6
|
174
|
+
until_the_end = ULID.range(..time1) #=> This will match only for all IDs upto `time1`
|
175
175
|
until_the_ulid_limit = ULID.range(time1..) # This will match only for all IDs from `time1` to max value of the ULID limit
|
176
176
|
|
177
177
|
# So you can use the generated range objects as below
|
@@ -401,7 +401,7 @@ So you can replace the code as below
|
|
401
401
|
+ULID.generate.to_s
|
402
402
|
```
|
403
403
|
|
404
|
-
NOTE:
|
404
|
+
NOTE: In version before `1.3.0`, timestamps might not be correct value.
|
405
405
|
|
406
406
|
1. [Sort order does not respect millisecond ordering](https://github.com/rafaelsales/ulid/issues/22)
|
407
407
|
1. [Fixed in this PR](https://github.com/rafaelsales/ulid/pull/23)
|
@@ -425,11 +425,12 @@ Major methods can be replaced as below.
|
|
425
425
|
+ULID.max(time).to_s
|
426
426
|
```
|
427
427
|
|
428
|
-
NOTE:
|
428
|
+
NOTE: In version before `1.0.2`, timestamps might not be correct value.
|
429
429
|
|
430
430
|
1. [Parsed time object has more than milliseconds](https://github.com/abachman/ulid-ruby/issues/3)
|
431
431
|
1. [Fix to handle timestamp precision in parser](https://github.com/abachman/ulid-ruby/pull/5)
|
432
432
|
1. [Fix to handle timestamp precision in generator](https://github.com/abachman/ulid-ruby/pull/4)
|
433
|
+
1. [Released in 1.0.2](https://github.com/abachman/ulid-ruby/compare/v1.0.0...v1.0.2)
|
433
434
|
|
434
435
|
### Compare performance with them
|
435
436
|
|
@@ -437,6 +438,18 @@ See [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
|
|
437
438
|
|
438
439
|
The results are not something to be proud of.
|
439
440
|
|
441
|
+
## How to use rbs
|
442
|
+
|
443
|
+
See structure at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox)
|
444
|
+
|
445
|
+
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).
|
446
|
+
|
447
|
+
* 
|
448
|
+
* 
|
449
|
+
* 
|
450
|
+
* 
|
451
|
+
* 
|
452
|
+
|
440
453
|
## References
|
441
454
|
|
442
455
|
- [Repository](https://github.com/kachick/ruby-ulid)
|
@@ -445,4 +458,5 @@ The results are not something to be proud of.
|
|
445
458
|
|
446
459
|
## Note
|
447
460
|
|
448
|
-
-
|
461
|
+
- [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.
|
462
|
+
However they are stayed in draft state. ref: [ruby-ulid#37](https://github.com/kachick/ruby-ulid/issues/37)
|
data/lib/ruby-ulid.rb
CHANGED
@@ -14,10 +14,10 @@ class ULID
|
|
14
14
|
# * https://github.com/kachick/ruby-ulid/issues/57
|
15
15
|
# * https://github.com/kachick/ruby-ulid/issues/78
|
16
16
|
module CrockfordBase32
|
17
|
-
class SetupError <
|
17
|
+
class SetupError < UnexpectedError; end
|
18
18
|
|
19
19
|
n32_chars = [*'0'..'9', *'A'..'V'].map(&:freeze).freeze
|
20
|
-
raise
|
20
|
+
raise(SetupError, 'obvious bug exists in the mapping algorithm') unless n32_chars.size == 32
|
21
21
|
|
22
22
|
n32_char_by_number = {}
|
23
23
|
n32_chars.each_with_index do |char, index|
|
@@ -48,7 +48,7 @@ class ULID
|
|
48
48
|
map[encoding_char] = char_32
|
49
49
|
end
|
50
50
|
end.freeze
|
51
|
-
raise
|
51
|
+
raise(SetupError, 'obvious bug exists in the mapping algorithm') unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
|
52
52
|
|
53
53
|
CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
|
54
54
|
|
@@ -5,15 +5,16 @@
|
|
5
5
|
|
6
6
|
class ULID
|
7
7
|
class MonotonicGenerator
|
8
|
-
include
|
8
|
+
include(MonitorMixin)
|
9
9
|
|
10
|
+
# @dynamic prev
|
10
11
|
# @return [ULID, nil]
|
11
|
-
attr_reader
|
12
|
+
attr_reader(:prev)
|
12
13
|
|
13
|
-
undef_method
|
14
|
+
undef_method(:instance_variable_set)
|
14
15
|
|
15
16
|
def initialize
|
16
|
-
super
|
17
|
+
super
|
17
18
|
@prev = nil
|
18
19
|
end
|
19
20
|
|
@@ -21,7 +22,8 @@ class ULID
|
|
21
22
|
def inspect
|
22
23
|
"ULID::MonotonicGenerator(prev: #{@prev.inspect})"
|
23
24
|
end
|
24
|
-
|
25
|
+
# @dynamic to_s
|
26
|
+
alias_method(:to_s, :inspect)
|
25
27
|
|
26
28
|
# @param [Time, Integer] moment
|
27
29
|
# @return [ULID]
|
@@ -30,23 +32,25 @@ class ULID
|
|
30
32
|
# Basically will not happen. Just means this feature prefers error rather than invalid value.
|
31
33
|
def generate(moment: ULID.current_milliseconds)
|
32
34
|
synchronize do
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
prev_ulid = @prev
|
36
|
+
unless prev_ulid
|
37
|
+
ret = ULID.generate(moment: moment)
|
38
|
+
@prev = ret
|
39
|
+
return ret
|
36
40
|
end
|
37
41
|
|
38
42
|
milliseconds = ULID.milliseconds_from_moment(moment)
|
39
43
|
|
40
44
|
ulid = (
|
41
|
-
if
|
45
|
+
if prev_ulid.milliseconds < milliseconds
|
42
46
|
ULID.generate(moment: milliseconds)
|
43
47
|
else
|
44
|
-
ULID.from_milliseconds_and_entropy(milliseconds:
|
48
|
+
ULID.from_milliseconds_and_entropy(milliseconds: prev_ulid.milliseconds, entropy: prev_ulid.entropy.succ)
|
45
49
|
end
|
46
50
|
)
|
47
51
|
|
48
|
-
unless ulid >
|
49
|
-
base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{
|
52
|
+
unless ulid > prev_ulid
|
53
|
+
base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{prev_ulid.inspect}"
|
50
54
|
additional_information = (
|
51
55
|
if Thread.list == [Thread.main]
|
52
56
|
'# NOTE: looks single thread only exist'
|
@@ -55,7 +59,7 @@ class ULID
|
|
55
59
|
end
|
56
60
|
)
|
57
61
|
|
58
|
-
raise
|
62
|
+
raise(UnexpectedError, base_message + additional_information)
|
59
63
|
end
|
60
64
|
|
61
65
|
@prev = ulid
|
@@ -63,12 +67,12 @@ class ULID
|
|
63
67
|
end
|
64
68
|
end
|
65
69
|
|
66
|
-
undef_method
|
70
|
+
undef_method(:freeze)
|
67
71
|
|
68
72
|
# @raise [TypeError] always raises exception and does not freeze self
|
69
73
|
# @return [void]
|
70
74
|
def freeze
|
71
|
-
raise
|
75
|
+
raise(TypeError, "cannot freeze #{self.class}")
|
72
76
|
end
|
73
77
|
end
|
74
78
|
end
|
data/lib/ulid/uuid.rb
CHANGED
@@ -10,18 +10,18 @@
|
|
10
10
|
class ULID
|
11
11
|
# Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
|
12
12
|
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
|
13
|
-
private_constant
|
13
|
+
private_constant(:UUIDV4_PATTERN)
|
14
14
|
|
15
15
|
# @param [String, #to_str] uuid
|
16
16
|
# @return [ULID]
|
17
17
|
# @raise [ParserError] if the given format is not correct for UUIDv4 specs
|
18
18
|
def self.from_uuidv4(uuid)
|
19
19
|
uuid = String.try_convert(uuid)
|
20
|
-
raise
|
20
|
+
raise(ArgumentError, 'ULID.from_uuidv4 takes only strings') unless uuid
|
21
21
|
|
22
22
|
prefix_trimmed = uuid.delete_prefix('urn:uuid:')
|
23
23
|
unless UUIDV4_PATTERN.match?(prefix_trimmed)
|
24
|
-
raise
|
24
|
+
raise(ParserError, "given `#{uuid}` does not match to `#{UUIDV4_PATTERN.inspect}`")
|
25
25
|
end
|
26
26
|
|
27
27
|
normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
|
@@ -32,8 +32,11 @@ class ULID
|
|
32
32
|
def to_uuidv4
|
33
33
|
# This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
|
34
34
|
array = octets.pack('C*').unpack('NnnnnN')
|
35
|
-
array[2]
|
36
|
-
|
35
|
+
ref2, ref3 = array[2], array[3]
|
36
|
+
raise unless Integer === ref2 && Integer === ref3
|
37
|
+
|
38
|
+
array[2] = (ref2 & 0x0fff) | 0x4000
|
39
|
+
array[3] = (ref3 & 0x3fff) | 0x8000
|
37
40
|
('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
|
38
41
|
end
|
39
42
|
end
|
data/lib/ulid/version.rb
CHANGED
data/lib/ulid.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
# Copyright (C) 2021 Kenichi Kamiya
|
5
5
|
|
6
|
-
require
|
6
|
+
require('securerandom')
|
7
7
|
|
8
8
|
# @see https://github.com/ulid/spec
|
9
9
|
# @!attribute [r] milliseconds
|
@@ -11,7 +11,7 @@ require 'securerandom'
|
|
11
11
|
# @!attribute [r] entropy
|
12
12
|
# @return [Integer]
|
13
13
|
class ULID
|
14
|
-
include
|
14
|
+
include(Comparable)
|
15
15
|
|
16
16
|
class Error < StandardError; end
|
17
17
|
class OverflowError < Error; end
|
@@ -25,10 +25,12 @@ class ULID
|
|
25
25
|
|
26
26
|
TIMESTAMP_ENCODED_LENGTH = 10
|
27
27
|
RANDOMNESS_ENCODED_LENGTH = 16
|
28
|
-
ENCODED_LENGTH =
|
28
|
+
ENCODED_LENGTH = 26
|
29
|
+
|
29
30
|
TIMESTAMP_OCTETS_LENGTH = 6
|
30
31
|
RANDOMNESS_OCTETS_LENGTH = 10
|
31
|
-
OCTETS_LENGTH =
|
32
|
+
OCTETS_LENGTH = 16
|
33
|
+
|
32
34
|
MAX_MILLISECONDS = 281474976710655
|
33
35
|
MAX_ENTROPY = 1208925819614629174706175
|
34
36
|
MAX_INTEGER = 340282366920938463463374607431768211455
|
@@ -43,11 +45,13 @@ class ULID
|
|
43
45
|
# This can't contain `\b` for considering UTF-8 (e.g. Japanese), so intentional `false negative` definition.
|
44
46
|
SCANNING_PATTERN = /[0-7][#{CROCKFORD_BASE32_ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CROCKFORD_BASE32_ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}/i.freeze
|
45
47
|
|
46
|
-
#
|
48
|
+
# Similar as Time#inspect since Ruby 2.7, however it is NOT same.
|
49
|
+
# Time#inspect trancates needless digits. Keeping full milliseconds with "%3N" will fit for ULID.
|
47
50
|
# @see https://bugs.ruby-lang.org/issues/15958
|
51
|
+
# @see https://github.com/ruby/ruby/blob/744d17ff6c33b09334508e8110007ea2a82252f5/time.c#L4026-L4078
|
48
52
|
TIME_FORMAT_IN_INSPECT = '%Y-%m-%d %H:%M:%S.%3N %Z'
|
49
53
|
|
50
|
-
private_class_method
|
54
|
+
private_class_method(:new)
|
51
55
|
|
52
56
|
# @param [Integer, Time] moment
|
53
57
|
# @param [Integer] entropy
|
@@ -60,7 +64,7 @@ class ULID
|
|
60
64
|
# @param [Time] time
|
61
65
|
# @return [ULID]
|
62
66
|
def self.at(time)
|
63
|
-
raise
|
67
|
+
raise(ArgumentError, 'ULID.at takes only `Time` instance') unless Time === time
|
64
68
|
|
65
69
|
from_milliseconds_and_entropy(milliseconds: milliseconds_from_time(time), entropy: reasonable_entropy)
|
66
70
|
end
|
@@ -92,14 +96,14 @@ class ULID
|
|
92
96
|
# * Do not ensure the uniqueness
|
93
97
|
# * Do not take random generator for the arguments
|
94
98
|
# * Raising error instead of truncating elements for the given number
|
95
|
-
def self.sample(
|
99
|
+
def self.sample(number=nil, period: nil)
|
96
100
|
int_generator = (
|
97
101
|
if period
|
98
102
|
ulid_range = range(period)
|
99
103
|
min, max, exclude_end = ulid_range.begin.to_i, ulid_range.end.to_i, ulid_range.exclude_end?
|
100
104
|
|
101
105
|
possibilities = (max - min) + (exclude_end ? 0 : 1)
|
102
|
-
raise
|
106
|
+
raise(ArgumentError, "given range `#{ulid_range.inspect}` does not have possibilities") unless possibilities.positive?
|
103
107
|
|
104
108
|
-> {
|
105
109
|
SecureRandom.random_number(possibilities) + min
|
@@ -109,24 +113,21 @@ class ULID
|
|
109
113
|
end
|
110
114
|
)
|
111
115
|
|
112
|
-
case
|
113
|
-
when
|
116
|
+
case number
|
117
|
+
when nil
|
114
118
|
from_integer(int_generator.call)
|
115
|
-
when
|
116
|
-
number = args.first
|
117
|
-
raise ArgumentError, 'accepts no argument or integer only' unless Integer === number
|
118
|
-
|
119
|
+
when Integer
|
119
120
|
if number > MAX_INTEGER || number.negative?
|
120
|
-
raise
|
121
|
+
raise(ArgumentError, "given number `#{number}` is larger than ULID limit `#{MAX_INTEGER}` or negative")
|
121
122
|
end
|
122
123
|
|
123
|
-
if period && (number > possibilities)
|
124
|
-
raise
|
124
|
+
if period && possibilities && (number > possibilities)
|
125
|
+
raise(ArgumentError, "given number `#{number}` is larger than given possibilities `#{possibilities}`")
|
125
126
|
end
|
126
127
|
|
127
128
|
Array.new(number) { from_integer(int_generator.call) }
|
128
129
|
else
|
129
|
-
raise
|
130
|
+
raise(ArgumentError, 'accepts no argument or integer only')
|
130
131
|
end
|
131
132
|
end
|
132
133
|
|
@@ -136,11 +137,13 @@ class ULID
|
|
136
137
|
# @yieldreturn [self]
|
137
138
|
def self.scan(string)
|
138
139
|
string = String.try_convert(string)
|
139
|
-
raise
|
140
|
-
return to_enum(
|
140
|
+
raise(ArgumentError, 'ULID.scan takes only strings') unless string
|
141
|
+
return to_enum(:scan, string) unless block_given?
|
141
142
|
|
142
143
|
string.scan(SCANNING_PATTERN) do |matched|
|
143
|
-
|
144
|
+
if String === matched
|
145
|
+
yield(parse(matched))
|
146
|
+
end
|
144
147
|
end
|
145
148
|
self
|
146
149
|
end
|
@@ -150,14 +153,16 @@ class ULID
|
|
150
153
|
# @raise [OverflowError] if the given integer is larger than the ULID limit
|
151
154
|
# @raise [ArgumentError] if the given integer is negative number
|
152
155
|
def self.from_integer(integer)
|
153
|
-
raise
|
154
|
-
raise
|
155
|
-
raise
|
156
|
+
raise(ArgumentError, 'ULID.from_integer takes only `Integer`') unless Integer === integer
|
157
|
+
raise(OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}") unless integer <= MAX_INTEGER
|
158
|
+
raise(ArgumentError, "integer should not be negative: given: #{integer}") if integer.negative?
|
156
159
|
|
157
160
|
n32encoded = integer.to_s(32).rjust(ENCODED_LENGTH, '0')
|
158
161
|
n32encoded_timestamp = n32encoded.slice(0, TIMESTAMP_ENCODED_LENGTH)
|
159
162
|
n32encoded_randomness = n32encoded.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH)
|
160
163
|
|
164
|
+
raise(UnexpectedError) unless n32encoded_timestamp && n32encoded_randomness
|
165
|
+
|
161
166
|
milliseconds = n32encoded_timestamp.to_i(32)
|
162
167
|
entropy = n32encoded_randomness.to_i(32)
|
163
168
|
|
@@ -168,37 +173,43 @@ class ULID
|
|
168
173
|
# @return [Range<ULID>]
|
169
174
|
# @raise [ArgumentError] if the given period is not a `Range[Time]`, `Range[nil]` or `Range[ULID]`
|
170
175
|
def self.range(period)
|
171
|
-
raise
|
176
|
+
raise(ArgumentError, 'ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`') unless Range === period
|
172
177
|
|
173
178
|
begin_element, end_element, exclude_end = period.begin, period.end, period.exclude_end?
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
179
|
+
new_begin, new_end = false, false
|
180
|
+
|
181
|
+
begin_ulid = (
|
182
|
+
case begin_element
|
183
|
+
when Time
|
184
|
+
new_begin = true
|
185
|
+
min(begin_element)
|
186
|
+
when nil
|
187
|
+
MIN
|
188
|
+
when self
|
189
|
+
begin_element
|
190
|
+
else
|
191
|
+
raise(ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}")
|
192
|
+
end
|
193
|
+
)
|
186
194
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
195
|
+
end_ulid = (
|
196
|
+
case end_element
|
197
|
+
when Time
|
198
|
+
new_end = true
|
199
|
+
exclude_end ? min(end_element) : max(end_element)
|
200
|
+
when nil
|
201
|
+
exclude_end = false
|
202
|
+
# The end should be max and include end, because nil end means to cover endless ULIDs until the limit
|
203
|
+
MAX
|
204
|
+
when self
|
205
|
+
end_element
|
206
|
+
else
|
207
|
+
raise(ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}")
|
208
|
+
end
|
209
|
+
)
|
199
210
|
|
200
|
-
begin_ulid.freeze
|
201
|
-
end_ulid.freeze
|
211
|
+
begin_ulid.freeze if new_begin
|
212
|
+
end_ulid.freeze if new_end
|
202
213
|
|
203
214
|
Range.new(begin_ulid, end_ulid, exclude_end)
|
204
215
|
end
|
@@ -206,13 +217,9 @@ class ULID
|
|
206
217
|
# @param [Time] time
|
207
218
|
# @return [Time]
|
208
219
|
def self.floor(time)
|
209
|
-
raise
|
220
|
+
raise(ArgumentError, 'ULID.floor takes only `Time` instance') unless Time === time
|
210
221
|
|
211
|
-
|
212
|
-
time.floor(3)
|
213
|
-
else
|
214
|
-
Time.at(0, milliseconds_from_time(time), :millisecond)
|
215
|
-
end
|
222
|
+
time.floor(3)
|
216
223
|
end
|
217
224
|
|
218
225
|
# @api private
|
@@ -224,9 +231,9 @@ class ULID
|
|
224
231
|
# @api private
|
225
232
|
# @param [Time] time
|
226
233
|
# @return [Integer]
|
227
|
-
private_class_method
|
234
|
+
private_class_method(def self.milliseconds_from_time(time)
|
228
235
|
(time.to_r * 1000).to_i
|
229
|
-
end
|
236
|
+
end)
|
230
237
|
|
231
238
|
# @api private
|
232
239
|
# @param [Time, Integer] moment
|
@@ -238,24 +245,24 @@ class ULID
|
|
238
245
|
when Time
|
239
246
|
milliseconds_from_time(moment)
|
240
247
|
else
|
241
|
-
raise
|
248
|
+
raise(ArgumentError, '`moment` should be a `Time` or `Integer as milliseconds`')
|
242
249
|
end
|
243
250
|
end
|
244
251
|
|
245
252
|
# @return [Integer]
|
246
|
-
private_class_method
|
253
|
+
private_class_method(def self.reasonable_entropy
|
247
254
|
SecureRandom.random_number(MAX_ENTROPY)
|
248
|
-
end
|
255
|
+
end)
|
249
256
|
|
250
257
|
# @param [String, #to_str] string
|
251
258
|
# @return [ULID]
|
252
259
|
# @raise [ParserError] if the given format is not correct for ULID specs
|
253
260
|
def self.parse(string)
|
254
261
|
string = String.try_convert(string)
|
255
|
-
raise
|
262
|
+
raise(ArgumentError, 'ULID.parse takes only strings') unless string
|
256
263
|
|
257
264
|
unless STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string)
|
258
|
-
raise
|
265
|
+
raise(ParserError, "given `#{string}` does not match to `#{STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.inspect}`")
|
259
266
|
end
|
260
267
|
|
261
268
|
from_integer(CrockfordBase32.decode(string))
|
@@ -266,7 +273,7 @@ class ULID
|
|
266
273
|
# @raise [ParserError] if the given format is not correct for ULID specs, even if ignored `orthographical variants of the format`
|
267
274
|
def self.normalize(string)
|
268
275
|
string = String.try_convert(string)
|
269
|
-
raise
|
276
|
+
raise(ArgumentError, 'ULID.normalize takes only strings') unless string
|
270
277
|
|
271
278
|
normalized_in_crockford = CrockfordBase32.normalize(string)
|
272
279
|
# Ensure the ULID correctness, because CrockfordBase32 does not always mean to satisfy ULID format
|
@@ -302,14 +309,14 @@ class ULID
|
|
302
309
|
else
|
303
310
|
object_class_name = safe_get_class_name(object)
|
304
311
|
converted_class_name = safe_get_class_name(converted)
|
305
|
-
raise
|
312
|
+
raise(TypeError, "can't convert #{object_class_name} to ULID (#{object_class_name}#to_ulid gives #{converted_class_name})")
|
306
313
|
end
|
307
314
|
end
|
308
315
|
end
|
309
316
|
|
310
317
|
# @param [BasicObject] object
|
311
318
|
# @return [String]
|
312
|
-
private_class_method
|
319
|
+
private_class_method(def self.safe_get_class_name(object)
|
313
320
|
fallback = 'UnknownObject'
|
314
321
|
|
315
322
|
# This class getter implementation used https://github.com/rspec/rspec-support/blob/4ad8392d0787a66f9c351d9cf6c7618e18b3d0f2/lib/rspec/support.rb#L83-L89 as a reference, thank you!
|
@@ -318,8 +325,12 @@ class ULID
|
|
318
325
|
begin
|
319
326
|
object.class
|
320
327
|
rescue NoMethodError
|
328
|
+
# steep can't correctly handle singeton class assign. See https://github.com/soutaro/steep/pull/586 for further detail
|
329
|
+
# So this annotation is hack for the type infer.
|
330
|
+
# @type var object: BasicObject
|
331
|
+
# @type var singleton_class: untyped
|
321
332
|
singleton_class = class << object; self; end
|
322
|
-
singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) }
|
333
|
+
(Class === singleton_class) ? singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) } : fallback
|
323
334
|
end
|
324
335
|
)
|
325
336
|
|
@@ -330,7 +341,7 @@ class ULID
|
|
330
341
|
else
|
331
342
|
name || fallback
|
332
343
|
end
|
333
|
-
end
|
344
|
+
end)
|
334
345
|
|
335
346
|
# @api private
|
336
347
|
# @param [Integer] milliseconds
|
@@ -339,10 +350,10 @@ class ULID
|
|
339
350
|
# @raise [OverflowError] if the given value is larger than the ULID limit
|
340
351
|
# @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
|
341
352
|
def self.from_milliseconds_and_entropy(milliseconds:, entropy:)
|
342
|
-
raise
|
343
|
-
raise
|
344
|
-
raise
|
345
|
-
raise
|
353
|
+
raise(ArgumentError, 'milliseconds and entropy should be an `Integer`') unless Integer === milliseconds && Integer === entropy
|
354
|
+
raise(OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}") unless milliseconds <= MAX_MILLISECONDS
|
355
|
+
raise(OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}") unless entropy <= MAX_ENTROPY
|
356
|
+
raise(ArgumentError, 'milliseconds and entropy should not be negative') if milliseconds.negative? || entropy.negative?
|
346
357
|
|
347
358
|
n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
|
348
359
|
n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
|
@@ -351,7 +362,8 @@ class ULID
|
|
351
362
|
new(milliseconds: milliseconds, entropy: entropy, integer: integer)
|
352
363
|
end
|
353
364
|
|
354
|
-
|
365
|
+
# @dynamic milliseconds, entropy
|
366
|
+
attr_reader(:milliseconds, :entropy)
|
355
367
|
|
356
368
|
# @api private
|
357
369
|
# @param [Integer] milliseconds
|
@@ -374,7 +386,11 @@ class ULID
|
|
374
386
|
def to_i
|
375
387
|
@integer
|
376
388
|
end
|
377
|
-
|
389
|
+
|
390
|
+
# @return [Integer]
|
391
|
+
def hash
|
392
|
+
[ULID, @integer].hash
|
393
|
+
end
|
378
394
|
|
379
395
|
# @return [Integer, nil]
|
380
396
|
def <=>(other)
|
@@ -390,7 +406,8 @@ class ULID
|
|
390
406
|
def eql?(other)
|
391
407
|
equal?(other) || (ULID === other && @integer == other.to_i)
|
392
408
|
end
|
393
|
-
|
409
|
+
# @dynamic ==
|
410
|
+
alias_method(:==, :eql?)
|
394
411
|
|
395
412
|
# @return [Boolean]
|
396
413
|
def ===(other)
|
@@ -406,13 +423,7 @@ class ULID
|
|
406
423
|
|
407
424
|
# @return [Time]
|
408
425
|
def to_time
|
409
|
-
@time ||=
|
410
|
-
if RUBY_VERSION >= '2.7'
|
411
|
-
Time.at(0, @milliseconds, :millisecond, in: 'UTC').freeze
|
412
|
-
else
|
413
|
-
Time.at(0, @milliseconds, :millisecond).utc.freeze
|
414
|
-
end
|
415
|
-
end
|
426
|
+
@time ||= Time.at(0, @milliseconds, :millisecond, in: 'UTC').freeze
|
416
427
|
end
|
417
428
|
|
418
429
|
# @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
|
@@ -426,22 +437,22 @@ class ULID
|
|
426
437
|
|
427
438
|
# @return [Array(Integer, Integer, Integer, Integer, Integer, Integer)]
|
428
439
|
def timestamp_octets
|
429
|
-
octets.slice(0, TIMESTAMP_OCTETS_LENGTH)
|
440
|
+
octets.slice(0, TIMESTAMP_OCTETS_LENGTH) || raise(UnexpectedError)
|
430
441
|
end
|
431
442
|
|
432
443
|
# @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
|
433
444
|
def randomness_octets
|
434
|
-
octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH)
|
445
|
+
octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH) || raise(UnexpectedError)
|
435
446
|
end
|
436
447
|
|
437
448
|
# @return [String]
|
438
449
|
def timestamp
|
439
|
-
@timestamp ||= to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze
|
450
|
+
@timestamp ||= (to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
|
440
451
|
end
|
441
452
|
|
442
453
|
# @return [String]
|
443
454
|
def randomness
|
444
|
-
@randomness ||= to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze
|
455
|
+
@randomness ||= (to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze || raise(UnexpectedError))
|
445
456
|
end
|
446
457
|
|
447
458
|
# @note Providing for rough operations. The keys and values is not fixed.
|
@@ -467,7 +478,8 @@ class ULID
|
|
467
478
|
ULID.from_integer(succ_int)
|
468
479
|
end
|
469
480
|
end
|
470
|
-
|
481
|
+
# @dynamic next
|
482
|
+
alias_method(:next, :succ)
|
471
483
|
|
472
484
|
# @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
|
473
485
|
def pred
|
@@ -519,7 +531,7 @@ class ULID
|
|
519
531
|
self
|
520
532
|
end
|
521
533
|
|
522
|
-
undef_method
|
534
|
+
undef_method(:instance_variable_set)
|
523
535
|
|
524
536
|
private
|
525
537
|
|
@@ -531,13 +543,13 @@ class ULID
|
|
531
543
|
end
|
532
544
|
end
|
533
545
|
|
534
|
-
require_relative
|
535
|
-
require_relative
|
536
|
-
require_relative
|
546
|
+
require_relative('ulid/version')
|
547
|
+
require_relative('ulid/crockford_base32')
|
548
|
+
require_relative('ulid/monotonic_generator')
|
537
549
|
|
538
550
|
class ULID
|
539
551
|
MIN = parse('00000000000000000000000000').freeze
|
540
552
|
MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
|
541
553
|
|
542
|
-
private_constant
|
554
|
+
private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR, :CROCKFORD_BASE32_ENCODING_STRING)
|
543
555
|
end
|
data/sig/ulid.rbs
CHANGED
@@ -36,7 +36,7 @@ class ULID < Object
|
|
36
36
|
end
|
37
37
|
|
38
38
|
module CrockfordBase32
|
39
|
-
class SetupError <
|
39
|
+
class SetupError < UnexpectedError
|
40
40
|
end
|
41
41
|
|
42
42
|
N32_CHAR_BY_CROCKFORD_BASE32_CHAR: Hash[String, String]
|
@@ -97,9 +97,9 @@ class ULID < Object
|
|
97
97
|
def to_ulid: () -> ULID
|
98
98
|
end
|
99
99
|
|
100
|
-
type
|
101
|
-
type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
|
102
|
-
type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
|
100
|
+
type full_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
|
101
|
+
type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
|
102
|
+
type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
|
103
103
|
type period = Range[Time] | Range[nil] | Range[ULID]
|
104
104
|
|
105
105
|
@string: String?
|
@@ -185,8 +185,7 @@ class ULID < Object
|
|
185
185
|
#
|
186
186
|
# # Below patterns are acceptable
|
187
187
|
# pinpointing = ULID.range(time1..time1) #=> This will match only for all IDs in `time1`
|
188
|
-
# until_the_end = ULID.range(..time1) #=> This will match only for all IDs upto `time1`
|
189
|
-
# until_the_end = ULID.range(ULID.min.to_time..time1) #=> This is same as above for Ruby 2.6
|
188
|
+
# until_the_end = ULID.range(..time1) #=> This will match only for all IDs upto `time1`
|
190
189
|
# until_the_ulid_limit = ULID.range(time1..) # This will match only for all IDs from `time1` to max value of the ULID limit
|
191
190
|
#
|
192
191
|
# # So you can use the generated range objects as below
|
@@ -319,7 +318,7 @@ class ULID < Object
|
|
319
318
|
# # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
|
320
319
|
# ```
|
321
320
|
def self.sample: (?period: period) -> ULID
|
322
|
-
| (Integer number, ?period: period) -> Array[ULID]
|
321
|
+
| (Integer number, ?period: period?) -> Array[ULID]
|
323
322
|
def self.valid?: (untyped) -> bool
|
324
323
|
|
325
324
|
# Returns normalized string
|
@@ -410,7 +409,9 @@ class ULID < Object
|
|
410
409
|
# ulid.to_i #=> 1957909092946624190749577070267409738
|
411
410
|
# ```
|
412
411
|
def to_i: -> Integer
|
413
|
-
|
412
|
+
|
413
|
+
# Returns integer for making as a hash key use-case
|
414
|
+
def hash: -> Integer
|
414
415
|
|
415
416
|
# Basically same as String based sort.
|
416
417
|
#
|
@@ -463,7 +464,7 @@ class ULID < Object
|
|
463
464
|
# Intentionally avoiding to use `Record type` ref: https://github.com/ruby/rbs/blob/4fb4c33b2325d1a73d79ff7aaeb49f21cec1e0e5/docs/syntax.md#record-type
|
464
465
|
# Because the returning values are not fixed.
|
465
466
|
def patterns: -> Hash[Symbol, Regexp | String]
|
466
|
-
def octets: ->
|
467
|
+
def octets: -> full_octets
|
467
468
|
def timestamp_octets: -> timestamp_octets
|
468
469
|
def randomness_octets: -> randomness_octets
|
469
470
|
|
@@ -526,7 +527,7 @@ class ULID < Object
|
|
526
527
|
private
|
527
528
|
|
528
529
|
# A private API. Should not be used in your code.
|
529
|
-
def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) ->
|
530
|
+
def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> ULID
|
530
531
|
|
531
532
|
# A private API. Should not be used in your code.
|
532
533
|
def self.reasonable_entropy: -> Integer
|
metadata
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-ulid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenichi Kamiya
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-22 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
14
|
-
|
15
|
-
This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around the ULID.
|
16
|
-
Also providing `ruby/rbs` signature files.
|
13
|
+
description: " generator, monotonic generator, parser and manipulations for ULID
|
14
|
+
(ruby/rbs signatures included)\n"
|
17
15
|
email:
|
18
16
|
- kachick1+ruby@gmail.com
|
19
17
|
executables: []
|
@@ -37,7 +35,8 @@ metadata:
|
|
37
35
|
homepage_uri: https://github.com/kachick/ruby-ulid
|
38
36
|
source_code_uri: https://github.com/kachick/ruby-ulid
|
39
37
|
bug_tracker_uri: https://github.com/kachick/ruby-ulid/issues
|
40
|
-
|
38
|
+
rubygems_mfa_required: 'true'
|
39
|
+
post_install_message:
|
41
40
|
rdoc_options: []
|
42
41
|
require_paths:
|
43
42
|
- lib
|
@@ -45,15 +44,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
45
44
|
requirements:
|
46
45
|
- - ">="
|
47
46
|
- !ruby/object:Gem::Version
|
48
|
-
version: 2.
|
47
|
+
version: 2.7.2
|
49
48
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
49
|
requirements:
|
51
50
|
- - ">="
|
52
51
|
- !ruby/object:Gem::Version
|
53
52
|
version: '0'
|
54
53
|
requirements: []
|
55
|
-
rubygems_version: 3.
|
56
|
-
signing_key:
|
54
|
+
rubygems_version: 3.3.7
|
55
|
+
signing_key:
|
57
56
|
specification_version: 4
|
58
57
|
summary: ULID manipulation library
|
59
58
|
test_files: []
|