ruby-ulid 0.1.0 → 0.1.1
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 +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
|