ruby-ulid 0.0.19 → 0.1.4
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 +48 -11
- data/lib/ulid.rb +118 -51
- data/lib/ulid/crockford_base32.rb +17 -0
- data/lib/ulid/monotonic_generator.rb +41 -20
- data/lib/ulid/uuid.rb +1 -1
- data/lib/ulid/version.rb +1 -1
- data/sig/ulid.rbs +445 -24
- metadata +198 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a390455a27395fd77ef398b0613e72b321b96d44e8454c8ab9346ae15eddfdc
|
4
|
+
data.tar.gz: 1150658ae26cc591f3946898ccb356ce1c6135292de0600ef0604aceb7a00554
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ffb5c69ec1327bacbb54b4afdd6718f8cc9b5afd450f739588d8e414c645eca9c1090c77efb464293f0e8e1b960cf4df75289785d3cd7b489404e31c24ecc8a
|
7
|
+
data.tar.gz: bf6a08216e0745d0727c356a5a239126bac8518f8fbb57f295b417e14ed0878c8f0c32774b83137b1feb58d8230140492cb469bb18dc9d5df5db5aa088410ffb
|
data/README.md
CHANGED
@@ -10,7 +10,7 @@ Also providing [ruby/rbs](https://github.com/ruby/rbs) signature files.
|
|
10
10
|
|
11
11
|

|
12
12
|
|
13
|
-

|
14
14
|
[](http://badge.fury.io/rb/ruby-ulid)
|
15
15
|
|
16
16
|
## Universally Unique Lexicographically Sortable Identifier
|
@@ -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](https://www.crockford.com/base32.html) 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)
|
32
32
|
- Case insensitive
|
33
33
|
- No special characters (URL safe)
|
34
34
|
- Monotonic sort order (correctly detects and handles the same millisecond)
|
@@ -49,7 +49,7 @@ Should be installed!
|
|
49
49
|
Add this line to your application/library's `Gemfile` is needed in basic use-case
|
50
50
|
|
51
51
|
```ruby
|
52
|
-
gem 'ruby-ulid', '0.0.
|
52
|
+
gem 'ruby-ulid', '>= 0.1.4', '< 0.2.0'
|
53
53
|
```
|
54
54
|
|
55
55
|
### Generator and Parser
|
@@ -146,6 +146,8 @@ sample_ulids_by_the_time.take(5) #=>
|
|
146
146
|
ulids.sort == ulids #=> true
|
147
147
|
```
|
148
148
|
|
149
|
+
Same generator does not generate duplicated ULIDs even in multi threads environment. It is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255)
|
150
|
+
|
149
151
|
### Filtering IDs with `Time`
|
150
152
|
|
151
153
|
`ULID` can be element of the `Range`. If you generated the IDs in monotonic generator, ID based filtering is easy and reliable
|
@@ -168,7 +170,7 @@ exclude_end = ULID.range(time1...time2) #=> The end of `Range[ULID]` will be the
|
|
168
170
|
|
169
171
|
# Below patterns are acceptable
|
170
172
|
pinpointing = ULID.range(time1..time1) #=> This will match only for all IDs in `time1`
|
171
|
-
until_the_end = ULID.range(..time1) #=> This will match only for all IDs upto `time1` (The `nil` starting `Range` can be used since Ruby 2.7)
|
173
|
+
# until_the_end = ULID.range(..time1) #=> This will match only for all IDs upto `time1` (The `nil` starting `Range` can be used since Ruby 2.7)
|
172
174
|
until_the_end = ULID.range(ULID.min.to_time..time1) #=> This is same as above for Ruby 2.6
|
173
175
|
until_the_ulid_limit = ULID.range(time1..) # This will match only for all IDs from `time1` to max value of the ULID limit
|
174
176
|
|
@@ -243,13 +245,15 @@ ULID.parse('01F4GNBXW1AM2KWW52PVT3ZY9X').patterns
|
|
243
245
|
|
244
246
|
`ULID.min` and `ULID.max` return termination values for ULID spec.
|
245
247
|
|
248
|
+
It can take `Time` instance as an optional argument. Then returns min/max ID that has limit of randomness part in the time.
|
249
|
+
|
246
250
|
```ruby
|
247
251
|
ULID.min #=> ULID(1970-01-01 00:00:00.000 UTC: 00000000000000000000000000)
|
248
252
|
ULID.max #=> ULID(10889-08-02 05:31:50.655 UTC: 7ZZZZZZZZZZZZZZZZZZZZZZZZZ)
|
249
253
|
|
250
254
|
time = Time.at(946684800, Rational('123456.789')).utc #=> 2000-01-01 00:00:00.123456789 UTC
|
251
|
-
ULID.min(
|
252
|
-
ULID.max(
|
255
|
+
ULID.min(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
|
256
|
+
ULID.max(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
|
253
257
|
```
|
254
258
|
|
255
259
|
`ULID#next` and `ULID#succ` returns next(successor) ULID.
|
@@ -309,7 +313,7 @@ ulids.take(10)
|
|
309
313
|
# ULID(2021-04-29 03:18:24.152 UTC: 01F4DT4Z4RA0QV8WFQGRAG63EH),
|
310
314
|
# ULID(2021-05-02 13:27:16.394 UTC: 01F4PM605ABF5SDVMEHBH8JJ9R)]
|
311
315
|
ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
|
312
|
-
#=>
|
316
|
+
#=>
|
313
317
|
# [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
|
314
318
|
# ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
|
315
319
|
# ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
|
@@ -322,6 +326,34 @@ ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
|
|
322
326
|
# ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
|
323
327
|
```
|
324
328
|
|
329
|
+
### ULID specification ambiguity around orthographical variants of the format
|
330
|
+
|
331
|
+
I'm afraid so, we should consider [Current ULID spec](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#universally-unique-lexicographically-sortable-identifier) has `orthographical variants of the format` possibilities.
|
332
|
+
|
333
|
+
>Uses Crockford's base32 for better efficiency and readability (5 bits per character)
|
334
|
+
|
335
|
+
The original `Crockford's base32` maps `I`, `L` to `1`, `O` to `0`.
|
336
|
+
And accepts freestyle inserting `Hyphens (-)`.
|
337
|
+
To consider this patterns or not is different in each implementations.
|
338
|
+
|
339
|
+
Current parser/validator/matcher aims to cover `subset of Crockford's base32`.
|
340
|
+
I have suggested it would be clarified in [ulid/spec#57](https://github.com/ulid/spec/pull/57).
|
341
|
+
|
342
|
+
>Case insensitive
|
343
|
+
|
344
|
+
I can understand it might be considered in actual use-case.
|
345
|
+
But it is a controversial point, discussing in [ulid/spec#3](https://github.com/ulid/spec/issues/3).
|
346
|
+
|
347
|
+
Be that as it may, this gem provides API for handling the nasty possibilities.
|
348
|
+
|
349
|
+
`ULID.normalize` and `ULID.normalized?`
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV"
|
353
|
+
ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false
|
354
|
+
ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true
|
355
|
+
```
|
356
|
+
|
325
357
|
### UUIDv4 converter for migration use-cases
|
326
358
|
|
327
359
|
`ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
|
@@ -331,7 +363,7 @@ The imported timestamp is meaningless. So ULID's benefit will lost.
|
|
331
363
|
# Currently experimental feature, so needed to load the extension.
|
332
364
|
require 'ulid/uuid'
|
333
365
|
|
334
|
-
# Basically reversible
|
366
|
+
# Basically reversible
|
335
367
|
ulid = ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39') #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
|
336
368
|
ulid.to_uuidv4 #=> "0983d0a2-ff15-4d83-8f37-7dd945b5aa39"
|
337
369
|
|
@@ -388,9 +420,9 @@ Major methods can be replaced as below.
|
|
388
420
|
-ULID.time(string)
|
389
421
|
+ULID.parse(string).to_time
|
390
422
|
-ULID.min_ulid_at(time)
|
391
|
-
+ULID.min(
|
423
|
+
+ULID.min(time).to_s
|
392
424
|
-ULID.max_ulid_at(time)
|
393
|
-
+ULID.max(
|
425
|
+
+ULID.max(time).to_s
|
394
426
|
```
|
395
427
|
|
396
428
|
NOTE: It is still having precision issue similar as `ulid gem` in the both generator and parser. I sent PRs.
|
@@ -399,6 +431,12 @@ NOTE: It is still having precision issue similar as `ulid gem` in the both gener
|
|
399
431
|
1. [Fix to handle timestamp precision in parser](https://github.com/abachman/ulid-ruby/pull/5)
|
400
432
|
1. [Fix to handle timestamp precision in generator](https://github.com/abachman/ulid-ruby/pull/4)
|
401
433
|
|
434
|
+
### Compare performance with them
|
435
|
+
|
436
|
+
See [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
|
437
|
+
|
438
|
+
The results are not something to be proud of.
|
439
|
+
|
402
440
|
## References
|
403
441
|
|
404
442
|
- [Repository](https://github.com/kachick/ruby-ulid)
|
@@ -408,4 +446,3 @@ NOTE: It is still having precision issue similar as `ulid gem` in the both gener
|
|
408
446
|
## Note
|
409
447
|
|
410
448
|
- 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)
|
411
|
-
- 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
@@ -15,6 +15,7 @@ class ULID
|
|
15
15
|
class Error < StandardError; end
|
16
16
|
class OverflowError < Error; end
|
17
17
|
class ParserError < Error; end
|
18
|
+
class UnexpectedError < Error; end
|
18
19
|
|
19
20
|
# Excluded I, L, O, U, -.
|
20
21
|
# This is the encoding patterns.
|
@@ -51,7 +52,7 @@ class ULID
|
|
51
52
|
# @param [Integer] entropy
|
52
53
|
# @return [ULID]
|
53
54
|
def self.generate(moment: current_milliseconds, entropy: reasonable_entropy)
|
54
|
-
|
55
|
+
from_milliseconds_and_entropy(milliseconds: milliseconds_from_moment(moment), entropy: entropy)
|
55
56
|
end
|
56
57
|
|
57
58
|
# Short hand of `ULID.generate(moment: time)`
|
@@ -59,18 +60,18 @@ class ULID
|
|
59
60
|
# @return [ULID]
|
60
61
|
def self.at(time)
|
61
62
|
raise ArgumentError, 'ULID.at takes only `Time` instance' unless Time === time
|
62
|
-
|
63
|
+
from_milliseconds_and_entropy(milliseconds: milliseconds_from_time(time), entropy: reasonable_entropy)
|
63
64
|
end
|
64
65
|
|
65
|
-
# @param [
|
66
|
+
# @param [Time, Integer] moment
|
66
67
|
# @return [ULID]
|
67
|
-
def self.min(moment
|
68
|
+
def self.min(moment=0)
|
68
69
|
0.equal?(moment) ? MIN : generate(moment: moment, entropy: 0)
|
69
70
|
end
|
70
71
|
|
71
|
-
# @param [
|
72
|
+
# @param [Time, Integer] moment
|
72
73
|
# @return [ULID]
|
73
|
-
def self.max(moment
|
74
|
+
def self.max(moment=MAX_MILLISECONDS)
|
74
75
|
MAX_MILLISECONDS.equal?(moment) ? MAX : generate(moment: moment, entropy: MAX_ENTROPY)
|
75
76
|
end
|
76
77
|
|
@@ -112,11 +113,11 @@ class ULID
|
|
112
113
|
raise ArgumentError, 'accepts no argument or integer only' unless Integer === number
|
113
114
|
|
114
115
|
if number > MAX_INTEGER || number.negative?
|
115
|
-
raise ArgumentError, "given number
|
116
|
+
raise ArgumentError, "given number `#{number}` is larger than ULID limit `#{MAX_INTEGER}` or negative"
|
116
117
|
end
|
117
118
|
|
118
119
|
if period && (number > possibilities)
|
119
|
-
raise ArgumentError, "given number
|
120
|
+
raise ArgumentError, "given number `#{number}` is larger than given possibilities `#{possibilities}`"
|
120
121
|
end
|
121
122
|
|
122
123
|
Array.new(number) { from_integer(int_generator.call) }
|
@@ -139,12 +140,12 @@ class ULID
|
|
139
140
|
self
|
140
141
|
end
|
141
142
|
|
142
|
-
# @param [Integer
|
143
|
+
# @param [Integer] integer
|
143
144
|
# @return [ULID]
|
144
145
|
# @raise [OverflowError] if the given integer is larger than the ULID limit
|
145
146
|
# @raise [ArgumentError] if the given integer is negative number
|
146
147
|
def self.from_integer(integer)
|
147
|
-
|
148
|
+
raise ArgumentError, 'ULID.from_integer takes only `Integer`' unless Integer === integer
|
148
149
|
raise OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}" unless integer <= MAX_INTEGER
|
149
150
|
raise ArgumentError, "integer should not be negative: given: #{integer}" if integer.negative?
|
150
151
|
|
@@ -160,29 +161,29 @@ class ULID
|
|
160
161
|
|
161
162
|
# @param [Range<Time>, Range<nil>, Range[ULID]] period
|
162
163
|
# @return [Range<ULID>]
|
163
|
-
# @raise [ArgumentError] if the given period is not a `Range[Time]` or `Range[
|
164
|
+
# @raise [ArgumentError] if the given period is not a `Range[Time]`, `Range[nil]` or `Range[ULID]`
|
164
165
|
def self.range(period)
|
165
|
-
raise ArgumentError, 'ULID.range takes only `Range[Time]` or `Range[
|
166
|
+
raise ArgumentError, 'ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`' unless Range === period
|
166
167
|
begin_element, end_element, exclude_end = period.begin, period.end, period.exclude_end?
|
167
168
|
return period if self === begin_element && self === end_element
|
168
169
|
|
169
170
|
case begin_element
|
170
171
|
when Time
|
171
|
-
begin_ulid = min(
|
172
|
+
begin_ulid = min(begin_element)
|
172
173
|
when nil
|
173
174
|
begin_ulid = MIN
|
174
175
|
when self
|
175
176
|
begin_ulid = begin_element
|
176
177
|
else
|
177
|
-
raise ArgumentError, "ULID.range takes only `Range[Time]` or `Range[
|
178
|
+
raise ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}"
|
178
179
|
end
|
179
180
|
|
180
181
|
case end_element
|
181
182
|
when Time
|
182
183
|
if exclude_end
|
183
|
-
end_ulid = min(
|
184
|
+
end_ulid = min(end_element)
|
184
185
|
else
|
185
|
-
end_ulid = max(
|
186
|
+
end_ulid = max(end_element)
|
186
187
|
end
|
187
188
|
when nil
|
188
189
|
# The end should be max and include end, because nil end means to cover endless ULIDs until the limit
|
@@ -191,7 +192,7 @@ class ULID
|
|
191
192
|
when self
|
192
193
|
end_ulid = end_element
|
193
194
|
else
|
194
|
-
raise ArgumentError, "ULID.range takes only `Range[Time]` or `Range[
|
195
|
+
raise ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}"
|
195
196
|
end
|
196
197
|
|
197
198
|
begin_ulid.freeze
|
@@ -218,6 +219,7 @@ class ULID
|
|
218
219
|
milliseconds_from_time(Time.now)
|
219
220
|
end
|
220
221
|
|
222
|
+
# @api private
|
221
223
|
# @param [Time] time
|
222
224
|
# @return [Integer]
|
223
225
|
private_class_method def self.milliseconds_from_time(time)
|
@@ -238,9 +240,8 @@ class ULID
|
|
238
240
|
end
|
239
241
|
end
|
240
242
|
|
241
|
-
# @api private
|
242
243
|
# @return [Integer]
|
243
|
-
def self.reasonable_entropy
|
244
|
+
private_class_method def self.reasonable_entropy
|
244
245
|
SecureRandom.random_number(MAX_ENTROPY)
|
245
246
|
end
|
246
247
|
|
@@ -259,18 +260,82 @@ class ULID
|
|
259
260
|
end
|
260
261
|
|
261
262
|
# @param [String, #to_str] string
|
262
|
-
# @return [
|
263
|
-
|
263
|
+
# @return [String]
|
264
|
+
# @raise [ParserError] if the given format is not correct for ULID specs, even if ignored `orthographical variants of the format`
|
265
|
+
def self.normalize(string)
|
264
266
|
string = String.try_convert(string)
|
267
|
+
raise ArgumentError, 'ULID.normalize takes only strings' unless string
|
268
|
+
|
269
|
+
normalized_in_crockford = CrockfordBase32.normalize(string)
|
270
|
+
# Ensure the ULID correctness, because CrockfordBase32 does not always mean to satisfy ULID format
|
271
|
+
parse(normalized_in_crockford).to_s
|
272
|
+
end
|
273
|
+
|
274
|
+
# @return [Boolean]
|
275
|
+
def self.normalized?(object)
|
276
|
+
normalized = normalize(object)
|
277
|
+
rescue Exception
|
278
|
+
false
|
279
|
+
else
|
280
|
+
normalized == object
|
281
|
+
end
|
282
|
+
|
283
|
+
# @return [Boolean]
|
284
|
+
def self.valid?(object)
|
285
|
+
string = String.try_convert(object)
|
265
286
|
string ? STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string) : false
|
266
287
|
end
|
267
288
|
|
289
|
+
# @param [ULID, #to_ulid] object
|
290
|
+
# @return [ULID, nil]
|
291
|
+
# @raise [TypeError] if `object.to_ulid` did not return ULID instance
|
292
|
+
def self.try_convert(object)
|
293
|
+
begin
|
294
|
+
converted = object.to_ulid
|
295
|
+
rescue NoMethodError
|
296
|
+
nil
|
297
|
+
else
|
298
|
+
if ULID === converted
|
299
|
+
converted
|
300
|
+
else
|
301
|
+
object_class_name = safe_get_class_name(object)
|
302
|
+
converted_class_name = safe_get_class_name(converted)
|
303
|
+
raise TypeError, "can't convert #{object_class_name} to ULID (#{object_class_name}#to_ulid gives #{converted_class_name})"
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# @param [BasicObject] object
|
309
|
+
# @return [String]
|
310
|
+
private_class_method def self.safe_get_class_name(object)
|
311
|
+
fallback = 'UnknownObject'
|
312
|
+
|
313
|
+
begin
|
314
|
+
name = String.try_convert(object.class.name)
|
315
|
+
rescue Exception
|
316
|
+
fallback
|
317
|
+
else
|
318
|
+
name || fallback
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
268
322
|
# @api private
|
269
|
-
# @param [
|
323
|
+
# @param [Integer] milliseconds
|
324
|
+
# @param [Integer] entropy
|
270
325
|
# @return [ULID]
|
271
|
-
|
272
|
-
|
273
|
-
|
326
|
+
# @raise [OverflowError] if the given value is larger than the ULID limit
|
327
|
+
# @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
|
328
|
+
def self.from_milliseconds_and_entropy(milliseconds:, entropy:)
|
329
|
+
raise ArgumentError, 'milliseconds and entropy should be an `Integer`' unless Integer === milliseconds && Integer === entropy
|
330
|
+
raise OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}" unless milliseconds <= MAX_MILLISECONDS
|
331
|
+
raise OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}" unless entropy <= MAX_ENTROPY
|
332
|
+
raise ArgumentError, 'milliseconds and entropy should not be negative' if milliseconds.negative? || entropy.negative?
|
333
|
+
|
334
|
+
n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
|
335
|
+
n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
|
336
|
+
integer = (n32encoded_timestamp + n32encoded_randomness).to_i(32)
|
337
|
+
|
338
|
+
new milliseconds: milliseconds, entropy: entropy, integer: integer
|
274
339
|
end
|
275
340
|
|
276
341
|
attr_reader :milliseconds, :entropy
|
@@ -280,42 +345,27 @@ class ULID
|
|
280
345
|
# @param [Integer] entropy
|
281
346
|
# @param [Integer] integer
|
282
347
|
# @return [void]
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
if integer
|
287
|
-
@integer = integer
|
288
|
-
else
|
289
|
-
milliseconds = milliseconds.to_int
|
290
|
-
entropy = entropy.to_int
|
291
|
-
|
292
|
-
raise OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}" unless milliseconds <= MAX_MILLISECONDS
|
293
|
-
raise OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}" unless entropy <= MAX_ENTROPY
|
294
|
-
raise ArgumentError, 'milliseconds and entropy should not be negative' if milliseconds.negative? || entropy.negative?
|
295
|
-
end
|
296
|
-
|
348
|
+
def initialize(milliseconds:, entropy:, integer:)
|
349
|
+
# All arguments check should be done with each constructors, not here
|
350
|
+
@integer = integer
|
297
351
|
@milliseconds = milliseconds
|
298
352
|
@entropy = entropy
|
299
353
|
end
|
300
354
|
|
301
355
|
# @return [String]
|
302
356
|
def to_s
|
303
|
-
@string ||= CrockfordBase32.encode(
|
357
|
+
@string ||= CrockfordBase32.encode(@integer).freeze
|
304
358
|
end
|
305
359
|
|
306
360
|
# @return [Integer]
|
307
361
|
def to_i
|
308
|
-
@integer
|
309
|
-
n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
|
310
|
-
n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
|
311
|
-
(n32encoded_timestamp + n32encoded_randomness).to_i(32)
|
312
|
-
end
|
362
|
+
@integer
|
313
363
|
end
|
314
364
|
alias_method :hash, :to_i
|
315
365
|
|
316
366
|
# @return [Integer, nil]
|
317
367
|
def <=>(other)
|
318
|
-
(ULID === other) ? (
|
368
|
+
(ULID === other) ? (@integer <=> other.to_i) : nil
|
319
369
|
end
|
320
370
|
|
321
371
|
# @return [String]
|
@@ -325,7 +375,7 @@ class ULID
|
|
325
375
|
|
326
376
|
# @return [Boolean]
|
327
377
|
def eql?(other)
|
328
|
-
equal?(other) || (ULID === other &&
|
378
|
+
equal?(other) || (ULID === other && @integer == other.to_i)
|
329
379
|
end
|
330
380
|
alias_method :==, :eql?
|
331
381
|
|
@@ -333,7 +383,7 @@ class ULID
|
|
333
383
|
def ===(other)
|
334
384
|
case other
|
335
385
|
when ULID
|
336
|
-
|
386
|
+
@integer == other.to_i
|
337
387
|
when String
|
338
388
|
to_s == other.upcase
|
339
389
|
else
|
@@ -354,7 +404,7 @@ class ULID
|
|
354
404
|
|
355
405
|
# @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
|
356
406
|
def octets
|
357
|
-
digits =
|
407
|
+
digits = @integer.digits(256)
|
358
408
|
(OCTETS_LENGTH - digits.size).times do
|
359
409
|
digits.push 0
|
360
410
|
end
|
@@ -393,7 +443,7 @@ class ULID
|
|
393
443
|
|
394
444
|
# @return [ULID, nil] when called on ULID as `7ZZZZZZZZZZZZZZZZZZZZZZZZZ`, returns `nil` instead of ULID
|
395
445
|
def succ
|
396
|
-
succ_int =
|
446
|
+
succ_int = @integer.succ
|
397
447
|
if succ_int >= MAX_INTEGER
|
398
448
|
if succ_int == MAX_INTEGER
|
399
449
|
MAX
|
@@ -408,7 +458,7 @@ class ULID
|
|
408
458
|
|
409
459
|
# @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
|
410
460
|
def pred
|
411
|
-
pred_int =
|
461
|
+
pred_int = @integer.pred
|
412
462
|
if pred_int <= 0
|
413
463
|
if pred_int == 0
|
414
464
|
MIN
|
@@ -427,6 +477,23 @@ class ULID
|
|
427
477
|
super
|
428
478
|
end
|
429
479
|
|
480
|
+
# @return [self]
|
481
|
+
def to_ulid
|
482
|
+
self
|
483
|
+
end
|
484
|
+
|
485
|
+
# @return [self]
|
486
|
+
def dup
|
487
|
+
self
|
488
|
+
end
|
489
|
+
|
490
|
+
# @return [self]
|
491
|
+
def clone(freeze: true)
|
492
|
+
self
|
493
|
+
end
|
494
|
+
|
495
|
+
undef_method :instance_variable_set
|
496
|
+
|
430
497
|
private
|
431
498
|
|
432
499
|
# @return [void]
|