ruby-ulid 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +79 -1
- data/lib/ulid.rb +40 -2
- data/lib/ulid/monotonic_generator.rb +38 -19
- data/lib/ulid/version.rb +1 -1
- data/sig/ulid.rbs +16 -6
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9517a4c0fe5e9feec91b2d8e19366baed6b8f1c1ccb42640389c1ad11066854
|
4
|
+
data.tar.gz: 24344ffd2549f7eae76f6f344169eb74baf3168e05499a9d7577a18fea63fc4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5420da3cddc622a02a9d84ba398c17f59c733eea62d122aef0e5fb39dfade8e76cb3e4608814a0afb5c2e0251c6ebbbb2a10c6b27ea68b4ff8e32ad9f2e31ee3
|
7
|
+
data.tar.gz: 7bb4f8f941ed3f3ec2d5ebef03b4559b1ba2b07eaf5a9970ee3936ba6157bf5ff818eabc63847ba973536910bd6e8b8c65ae3382b74a368af85e228fec6524dd
|
data/README.md
CHANGED
@@ -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.1.
|
52
|
+
gem 'ruby-ulid', '>= 0.1.1', '< 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 [Thread::Mutex](https://github.com/ruby/ruby/blob/5f8bca32571fa9c651f6903d36f66082363f8879/thread_sync.c#L1572-L1582)
|
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
|
@@ -401,6 +403,82 @@ NOTE: It is still having precision issue similar as `ulid gem` in the both gener
|
|
401
403
|
1. [Fix to handle timestamp precision in parser](https://github.com/abachman/ulid-ruby/pull/5)
|
402
404
|
1. [Fix to handle timestamp precision in generator](https://github.com/abachman/ulid-ruby/pull/4)
|
403
405
|
|
406
|
+
### Generating benchmarks
|
407
|
+
|
408
|
+
This runs rough benchmarks
|
409
|
+
|
410
|
+
```console
|
411
|
+
$ rake benchmark_with_other_gems
|
412
|
+
(Do not use `bundle exec`!)
|
413
|
+
```
|
414
|
+
|
415
|
+
<details>
|
416
|
+
<summary>One of the result at 2021/05/10 on my machine</summary>
|
417
|
+
|
418
|
+
```plaintext
|
419
|
+
#### rafaelsales - ulid
|
420
|
+
cd ./benchmark/compare_with_othergems/rafaelsales && bundle install --quiet && bundle exec ruby -v ./generate.rb
|
421
|
+
ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x86_64-darwin20]
|
422
|
+
Warming up --------------------------------------
|
423
|
+
ULID.generate 5.560k i/100ms
|
424
|
+
Calculating -------------------------------------
|
425
|
+
ULID.generate 52.655k (±11.0%) i/s - 261.320k in 5.029719s
|
426
|
+
"`ulid gem - 1.3.0` generated products: 371927 - sample: [\"01F59Y97807D2S67KE6X7ATK7Z\", \"01F59Y9AVRQJFAT5M2N7Z72BVF\", \"01F59Y95Z1042X4Z1K9729BSE3\", \"01F59Y95ZMVDFKD63Y8TT145GQ\", \"01F59Y94YQEZ3PH5STZ8PS1JPG\"]"
|
427
|
+
------------------------------------------------------------------------
|
428
|
+
#### abachman - ulid-ruby
|
429
|
+
cd ./benchmark/compare_with_othergems/abachman && bundle install --quiet && bundle exec ruby -v ./generate.rb
|
430
|
+
ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x86_64-darwin20]
|
431
|
+
Warming up --------------------------------------
|
432
|
+
ULID.generate 3.862k i/100ms
|
433
|
+
Calculating -------------------------------------
|
434
|
+
ULID.generate 38.415k (±13.1%) i/s - 189.238k in 5.025788s
|
435
|
+
"`ulid-ruby gem - 1.0.0` generated products: 260625 - sample: [\"01F59Y9H9V17EPXTYNZDCXB9EZ\", \"01F59Y9J4S4XZ68MF5DJDWHTAC\", \"01F59Y9J8887VC8E850QSBDCDX\", \"01F59Y9JEJPD088EYXVHB86W3N\", \"01F59Y9GGAZFXGCB92EQD695CZ\"]"
|
436
|
+
------------------------------------------------------------------------
|
437
|
+
#### kachick - ruby-ulid(This one)
|
438
|
+
cd ./benchmark/compare_with_othergems/kachick && bundle install --quiet && bundle exec ruby -v ./generate.rb
|
439
|
+
ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x86_64-darwin20]
|
440
|
+
Warming up --------------------------------------
|
441
|
+
ULID.generate.to_s 3.185k i/100ms
|
442
|
+
Calculating -------------------------------------
|
443
|
+
ULID.generate.to_s 31.934k (± 9.1%) i/s - 159.250k in 5.030707s
|
444
|
+
"`ruby-ulid gem (this one) - 0.1.0` generated products: 223867 - sample: [\"01F59Y9SPZHM6JCTYP50CHGVAX\", \"01F59Y9VB7X0SX32MMKF78KJR3\", \"01F59Y9W0C83RYCNYVH84R4JG3\", \"01F59Y9V218Q3D4YP3W74ET3EW\", \"01F59Y9X6DD8NX99WBGCR7RNXF\"]"
|
445
|
+
```
|
446
|
+
|
447
|
+
In another execution, Changed as below. So there doesn't seem to be a big difference.
|
448
|
+
|
449
|
+
```plaintext
|
450
|
+
#### rafaelsales - ulid
|
451
|
+
cd ./benchmark/compare_with_othergems/rafaelsales && bundle install --quiet && bundle exec ruby -v ./generate.rb
|
452
|
+
ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x86_64-darwin20]
|
453
|
+
Warming up --------------------------------------
|
454
|
+
ULID.generate 2.473k i/100ms
|
455
|
+
Calculating -------------------------------------
|
456
|
+
ULID.generate 24.101k (±15.9%) i/s - 118.704k in 5.066190s
|
457
|
+
"`ulid gem - 1.3.0` generated products: 164763 - sample: [\"01F59YEGPFMXXZWC1YQ49TSK8Y\", \"01F59YEFF7VX5WAW91VTCSE2N9\", \"01F59YEEZ5P9428SDYEDYW8D27\", \"01F59YEHVK56DZBJSNSQK6V1W6\", \"01F59YEHE07M98PVV97ABBAKHM\"]"
|
458
|
+
------------------------------------------------------------------------
|
459
|
+
#### abachman - ulid-ruby
|
460
|
+
cd ./benchmark/compare_with_othergems/abachman && bundle install --quiet && bundle exec ruby -v ./generate.rb
|
461
|
+
ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x86_64-darwin20]
|
462
|
+
Warming up --------------------------------------
|
463
|
+
ULID.generate 2.620k i/100ms
|
464
|
+
Calculating -------------------------------------
|
465
|
+
ULID.generate 27.571k (±14.2%) i/s - 136.240k in 5.056272s
|
466
|
+
"`ulid-ruby gem - 1.0.0` generated products: 186683 - sample: [\"01F59YEVX6GC9TC0RCZ74RC6Z3\", \"01F59YESJXWYGZ61TXHKRVKS97\", \"01F59YEVQ4QQKBED5T49RTV1MA\", \"01F59YEPJ6MMZY1N63DNW7C4SN\", \"01F59YEQK52K8TKTP1ESC6VC5X\"]"
|
467
|
+
------------------------------------------------------------------------
|
468
|
+
#### kachick - ruby-ulid(This one)
|
469
|
+
cd ./benchmark/compare_with_othergems/kachick && bundle install --quiet && bundle exec ruby -v ./generate.rb
|
470
|
+
ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x86_64-darwin20]
|
471
|
+
Warming up --------------------------------------
|
472
|
+
ULID.generate.to_s 3.014k i/100ms
|
473
|
+
Calculating -------------------------------------
|
474
|
+
ULID.generate.to_s 31.612k (±10.1%) i/s - 156.728k in 5.013432s
|
475
|
+
"`ruby-ulid gem (this one) - 0.1.0` generated products: 212293 - sample: [\"01F59YF1WP49TT4GQPDN3E9JTJ\", \"01F59YF1MW1ZDQW93NX4J6RG4G\", \"01F59YF0KRX2CZKHDQQSN5HXHW\", \"01F59YEZVNH8YHP4ZHDK2ZRWSR\", \"01F59YF1J0FV3CVV099SHA2Q9A\"]"
|
476
|
+
```
|
477
|
+
|
478
|
+
I have an excuse, This gem does not aim `faster than other`.
|
479
|
+
So I think the results are acceptable.
|
480
|
+
</details>
|
481
|
+
|
404
482
|
## References
|
405
483
|
|
406
484
|
- [Repository](https://github.com/kachick/ruby-ulid)
|
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.
|
@@ -239,9 +240,8 @@ class ULID
|
|
239
240
|
end
|
240
241
|
end
|
241
242
|
|
242
|
-
# @api private
|
243
243
|
# @return [Integer]
|
244
|
-
def self.reasonable_entropy
|
244
|
+
private_class_method def self.reasonable_entropy
|
245
245
|
SecureRandom.random_number(MAX_ENTROPY)
|
246
246
|
end
|
247
247
|
|
@@ -266,6 +266,39 @@ class ULID
|
|
266
266
|
string ? STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string) : false
|
267
267
|
end
|
268
268
|
|
269
|
+
# @param [ULID, #to_ulid] object
|
270
|
+
# @return [ULID, nil]
|
271
|
+
# @raise [TypeError] if `object.to_ulid` did not return ULID instance
|
272
|
+
def self.try_convert(object)
|
273
|
+
begin
|
274
|
+
converted = object.to_ulid
|
275
|
+
rescue NoMethodError
|
276
|
+
nil
|
277
|
+
else
|
278
|
+
if ULID === converted
|
279
|
+
converted
|
280
|
+
else
|
281
|
+
object_class_name = safe_get_class_name(object)
|
282
|
+
converted_class_name = safe_get_class_name(converted)
|
283
|
+
raise TypeError, "can't convert #{object_class_name} to ULID (#{object_class_name}#to_ulid gives #{converted_class_name})"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# @param [BasicObject] object
|
289
|
+
# @return [String]
|
290
|
+
private_class_method def self.safe_get_class_name(object)
|
291
|
+
fallback = 'UnknownObject'
|
292
|
+
|
293
|
+
begin
|
294
|
+
name = String.try_convert(object.class.name)
|
295
|
+
rescue Exception
|
296
|
+
fallback
|
297
|
+
else
|
298
|
+
name || fallback
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
269
302
|
# @api private
|
270
303
|
# @param [Integer] milliseconds
|
271
304
|
# @param [Integer] entropy
|
@@ -424,6 +457,11 @@ class ULID
|
|
424
457
|
super
|
425
458
|
end
|
426
459
|
|
460
|
+
# @return [self]
|
461
|
+
def to_ulid
|
462
|
+
self
|
463
|
+
end
|
464
|
+
|
427
465
|
# @return [self]
|
428
466
|
def dup
|
429
467
|
self
|
@@ -4,40 +4,59 @@
|
|
4
4
|
|
5
5
|
class ULID
|
6
6
|
class MonotonicGenerator
|
7
|
-
# @
|
8
|
-
|
7
|
+
# @return [ULID, nil]
|
8
|
+
attr_reader :prev
|
9
|
+
|
10
|
+
undef_method :instance_variable_set
|
9
11
|
|
10
12
|
def initialize
|
11
13
|
@mutex = Thread::Mutex.new
|
12
|
-
|
14
|
+
@prev = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String]
|
18
|
+
def inspect
|
19
|
+
"ULID::MonotonicGenerator(prev: #{@prev.inspect})"
|
13
20
|
end
|
21
|
+
alias_method :to_s, :inspect
|
14
22
|
|
15
23
|
# @param [Time, Integer] moment
|
16
24
|
# @return [ULID]
|
17
25
|
# @raise [OverflowError] if the entropy part is larger than the ULID limit in same milliseconds
|
18
|
-
# @raise [
|
26
|
+
# @raise [UnexpectedError] if the generated ULID is an invalid value in monotonicity spec.
|
27
|
+
# Basically will not happen. Just means this feature prefers error rather than invalid value.
|
19
28
|
def generate(moment: ULID.current_milliseconds)
|
20
|
-
milliseconds = ULID.milliseconds_from_moment(moment)
|
21
|
-
raise ArgumentError, "milliseconds should not be negative: given: #{milliseconds}" if milliseconds.negative?
|
22
|
-
|
23
29
|
@mutex.synchronize do
|
24
|
-
|
25
|
-
@
|
26
|
-
@
|
30
|
+
unless @prev
|
31
|
+
@prev = ULID.generate(moment: moment)
|
32
|
+
return @prev
|
33
|
+
end
|
34
|
+
|
35
|
+
milliseconds = ULID.milliseconds_from_moment(moment)
|
36
|
+
|
37
|
+
ulid = if @prev.milliseconds < milliseconds
|
38
|
+
ULID.generate(moment: milliseconds)
|
27
39
|
else
|
28
|
-
@
|
40
|
+
ULID.from_milliseconds_and_entropy(milliseconds: @prev.milliseconds, entropy: @prev.entropy.succ)
|
29
41
|
end
|
30
|
-
|
42
|
+
|
43
|
+
unless ulid > @prev
|
44
|
+
base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{@prev.inspect}"
|
45
|
+
additional_information = if Thread.list == [Thread.main]
|
46
|
+
'# NOTE: looks single thread only exist'
|
47
|
+
else
|
48
|
+
'# NOTE: ran on multi threads, so this might from concurrency issue'
|
49
|
+
end
|
50
|
+
|
51
|
+
raise UnexpectedError, base_message + additional_information
|
52
|
+
end
|
53
|
+
|
54
|
+
@prev = ulid
|
55
|
+
ulid
|
31
56
|
end
|
32
57
|
end
|
33
58
|
|
34
|
-
|
35
|
-
# @return [void]
|
36
|
-
def reset
|
37
|
-
@latest_milliseconds = 0
|
38
|
-
@latest_entropy = ULID.reasonable_entropy
|
39
|
-
nil
|
40
|
-
end
|
59
|
+
undef_method :freeze
|
41
60
|
|
42
61
|
# @raise [TypeError] always raises exception and does not freeze self
|
43
62
|
# @return [void]
|
data/lib/ulid/version.rb
CHANGED
data/sig/ulid.rbs
CHANGED
@@ -33,6 +33,9 @@ class ULID
|
|
33
33
|
class ParserError < Error
|
34
34
|
end
|
35
35
|
|
36
|
+
class UnexpectedError < Error
|
37
|
+
end
|
38
|
+
|
36
39
|
module CrockfordBase32
|
37
40
|
class SetupError < ScriptError
|
38
41
|
end
|
@@ -47,21 +50,24 @@ class ULID
|
|
47
50
|
end
|
48
51
|
|
49
52
|
class MonotonicGenerator
|
50
|
-
|
51
|
-
|
53
|
+
@mutex: Thread::Mutex
|
54
|
+
attr_reader prev: ULID | nil
|
52
55
|
def initialize: -> void
|
53
56
|
def generate: (?moment: moment) -> ULID
|
54
|
-
def
|
57
|
+
def inspect: -> String
|
58
|
+
alias to_s inspect
|
55
59
|
def freeze: -> void
|
56
60
|
end
|
57
61
|
|
62
|
+
interface _ULID
|
63
|
+
def to_ulid: () -> ULID
|
64
|
+
end
|
65
|
+
|
58
66
|
type octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
|
59
67
|
type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
|
60
68
|
type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
|
61
69
|
type period = Range[Time] | Range[nil] | Range[ULID]
|
62
70
|
|
63
|
-
@milliseconds: Integer
|
64
|
-
@entropy: Integer
|
65
71
|
@string: String?
|
66
72
|
@integer: Integer
|
67
73
|
@timestamp: String?
|
@@ -75,7 +81,6 @@ class ULID
|
|
75
81
|
def self.milliseconds_from_moment: (moment moment) -> Integer
|
76
82
|
def self.range: (period period) -> Range[ULID]
|
77
83
|
def self.floor: (Time time) -> Time
|
78
|
-
def self.reasonable_entropy: -> Integer
|
79
84
|
def self.parse: (String string) -> self
|
80
85
|
def self.from_uuidv4: (String uuid) -> ULID
|
81
86
|
def self.from_integer: (Integer integer) -> self
|
@@ -87,6 +92,8 @@ class ULID
|
|
87
92
|
def self.scan: (String string) -> Enumerator[self, singleton(ULID)]
|
88
93
|
| (String string) { (self ulid) -> void } -> singleton(ULID)
|
89
94
|
def self.from_milliseconds_and_entropy: (milliseconds: Integer, entropy: Integer) -> self
|
95
|
+
def self.try_convert: (_ULID) -> self
|
96
|
+
| (untyped) -> nil
|
90
97
|
attr_reader milliseconds: Integer
|
91
98
|
attr_reader entropy: Integer
|
92
99
|
def initialize: (milliseconds: Integer, entropy: Integer, integer: Integer) -> void
|
@@ -111,11 +118,14 @@ class ULID
|
|
111
118
|
alias succ next
|
112
119
|
def pred: -> ULID?
|
113
120
|
def freeze: -> self
|
121
|
+
def to_ulid: -> self
|
114
122
|
def dup: -> self
|
115
123
|
# Same as https://github.com/ruby/rbs/blob/4fb4c33b2325d1a73d79ff7aaeb49f21cec1e0e5/core/object.rbs#L79
|
116
124
|
def clone: (?freeze: bool) -> self
|
117
125
|
|
118
126
|
private
|
127
|
+
def self.reasonable_entropy: -> Integer
|
119
128
|
def self.milliseconds_from_time: (Time time) -> Integer
|
129
|
+
def self.safe_get_class_name: (untyped object) -> String
|
120
130
|
def cache_all_instance_variables: -> void
|
121
131
|
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.1.
|
4
|
+
version: 0.1.1
|
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-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rbs
|