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 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