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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0aff39d0f8044f373d8dcdea149f26501e0118dfc0876f060b1b757a4d2a49e
4
- data.tar.gz: 02df0ea7a5fe5f8dabd1179988cfe4c2574debf3ad7200d11445eef932dd7443
3
+ metadata.gz: d9517a4c0fe5e9feec91b2d8e19366baed6b8f1c1ccb42640389c1ad11066854
4
+ data.tar.gz: 24344ffd2549f7eae76f6f344169eb74baf3168e05499a9d7577a18fea63fc4d
5
5
  SHA512:
6
- metadata.gz: ab036ed40e4f2740de9d37e44d2c6d4d233fa0071f95383accb2334c4d1f85b30b24eea902e0e29c34fefc8749b8caf5d1f9df6c52f93fe2e76c89aa0912d11e
7
- data.tar.gz: d8ac11f63e5b301ed8b1eacd0d1211fd4b3a0bd79ccae8fe87050833cf305a7345da1dc136b95ecccfcafc6110cf9a281afb0f15db1fe2e01984bf320f82b7e5
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.0', '< 0.2.0'
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
- # @api private
8
- attr_accessor :latest_milliseconds, :latest_entropy
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
- reset
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 [ArgumentError] if the given moment(milliseconds) is negative number
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
- if @latest_milliseconds < milliseconds
25
- @latest_milliseconds = milliseconds
26
- @latest_entropy = ULID.reasonable_entropy
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
- @latest_entropy += 1
40
+ ULID.from_milliseconds_and_entropy(milliseconds: @prev.milliseconds, entropy: @prev.entropy.succ)
29
41
  end
30
- ULID.from_milliseconds_and_entropy(milliseconds: @latest_milliseconds, entropy: @latest_entropy)
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
- # @api private
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
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class ULID
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.1'
6
6
  end
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
- attr_accessor latest_milliseconds: Integer
51
- attr_accessor latest_entropy: Integer
53
+ @mutex: Thread::Mutex
54
+ attr_reader prev: ULID | nil
52
55
  def initialize: -> void
53
56
  def generate: (?moment: moment) -> ULID
54
- def reset: -> void
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.0
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-09 00:00:00.000000000 Z
11
+ date: 2021-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbs