ruby-ulid 0.2.0 → 0.4.0

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: fd21fa1c7af3acea07d2f4e6e7842927f1b33367e26b84bc5eef9a65a64c5582
4
- data.tar.gz: da03f786abbbffa4411a4eb8b4ea189aecb3a920fb37026f1ae7f66398c5661f
3
+ metadata.gz: 7b50cb3fee0b304a62ebd460e7d08b932d3c58c1f7810a1dc24b5166df10ceaf
4
+ data.tar.gz: b84a2ad766640fa7c87e4bbb5b0fdd637bf831407f235989cdd043ea5b1189aa
5
5
  SHA512:
6
- metadata.gz: 41143d43ef0f41f7441d0f2570bd73cad4ac6e382ea531c5f8aa30fccd5a4ff99790a356c6563c577153857b56eca8545d1b561f66b1ccdb5baa2aa281f2b692
7
- data.tar.gz: 8c3bca9d754e6003af6af000bcb0d63fddb779586f3022cb33771182efa41ee0939c8d0a781ede46807c5cbf106b3389fbbefb684122c3d5ba704a8921b9f9a5
6
+ metadata.gz: e44eadac5ad4c83f1c8da74fbfa502a3b3b7293dda8eb44855bb477070687afd815d46237a9cc15266223f7cd0981b69670165767b366156d23728cdb76c878e
7
+ data.tar.gz: 349b2e8c2b16d5c9ae58270fdb5e87a3c364eef4b3ae2972680cb5de119ba737ed035db36bb7283fc96ff7c7a54663be5de8c0725e0de1975d8f66de34dd052c
data/README.md CHANGED
@@ -1,18 +1,18 @@
1
1
  # ruby-ulid
2
2
 
3
+ [![Build Status](https://github.com/kachick/ruby-ulid/actions/workflows/test_behaviors.yml/badge.svg?branch=main)](https://github.com/kachick/ruby-ulid/actions/workflows/test_behaviors.yml/?branch=main)
4
+ [![Gem Version](https://badge.fury.io/rb/ruby-ulid.svg)](http://badge.fury.io/rb/ruby-ulid)
5
+
3
6
  ## Overview
4
7
 
5
- The `ULID` spec is defined on [ulid/spec](https://github.com/ulid/spec). It has useful specs for applications (e.g. `Database key`), especially possess all `uniqueness`, `randomness`, `extractable timestamps` and `sortable` features.
6
- This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around the ULID.
7
- Also providing [ruby/rbs](https://github.com/ruby/rbs) signature files.
8
+ [ulid/spec](https://github.com/ulid/spec) is useful.
9
+ Especially possess all `uniqueness`, `randomness`, `extractable timestamps` and `sortable` features.
10
+ This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around ULID.
11
+ Also providing [RBS](https://github.com/ruby/rbs) signatures.
8
12
 
9
13
  ---
10
14
 
11
- ![ULIDlogo](https://raw.githubusercontent.com/kachick/ruby-ulid/main/logo.png)
12
-
13
- [![Build Status](https://github.com/kachick/ruby-ulid/actions/workflows/test_behaviors.yml/badge.svg?branch=main)](https://github.com/kachick/ruby-ulid/actions/workflows/test_behaviors.yml/?branch=main)
14
- [![Gem Version](https://badge.fury.io/rb/ruby-ulid.png)](http://badge.fury.io/rb/ruby-ulid)
15
- [![Visual Studio Code](https://img.shields.io/badge/Visual%20Studio%20Code-0078d7.svg?style=for-the-badge&logo=visual-studio-code&logoColor=white)](https://github.dev/kachick/ruby-ulid)
15
+ ![ULIDlogo](./assets/logo.png)
16
16
 
17
17
  ## Universally Unique Lexicographically Sortable Identifier
18
18
 
@@ -47,21 +47,43 @@ $ gem install ruby-ulid
47
47
  Should be installed!
48
48
  ```
49
49
 
50
- Add this line to your application/library's `Gemfile` is needed in basic use-case
50
+ Add this line in your Gemfile.
51
51
 
52
52
  ```ruby
53
- gem 'ruby-ulid', '~> 0.2.0'
53
+ gem('ruby-ulid', '~> 0.4.0')
54
54
  ```
55
55
 
56
- ### Generator and Parser
57
-
58
- The generated `ULID` is an object not just a string.
59
- It means easily get the timestamps and binary formats.
56
+ ### How to use
60
57
 
61
58
  ```ruby
62
59
  require 'ulid'
63
60
 
61
+ defined? ULID
62
+ # => "constant"
63
+ ```
64
+
65
+ ### Basic Generator
66
+
67
+ The generated `ULID` is an object not just a string.
68
+
69
+ ```ruby
64
70
  ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
71
+ ```
72
+
73
+ ### Parser
74
+
75
+ You can get the objects from exists encoded ULIDs.
76
+
77
+ ```ruby
78
+ ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV') #=> ULID(2016-07-30 23:54:10.259 UTC: 01ARZ3NDEKTSV4RRFFQ69G5FAV)
79
+ ```
80
+
81
+ ### ULID object
82
+
83
+ You can extract timestamps and binary formats.
84
+
85
+ ```ruby
86
+ ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
65
87
  ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
66
88
  ulid.milliseconds #=> 1619544442826
67
89
  ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
@@ -71,16 +93,9 @@ ulid.to_i #=> 1957909092946624190749577070267409738
71
93
  ulid.octets #=> [1, 121, 20, 95, 7, 202, 187, 60, 175, 51, 76, 60, 49, 73, 37, 74]
72
94
  ```
73
95
 
74
- You can get the objects from exists encoded ULIDs
75
-
76
- ```ruby
77
- ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV') #=> ULID(2016-07-30 23:54:10.259 UTC: 01ARZ3NDEKTSV4RRFFQ69G5FAV)
78
- ulid.to_time #=> 2016-07-30 23:54:10.259 UTC
79
- ```
80
-
81
96
  ### Sortable with the timestamp
82
97
 
83
- ULIDs are sortable when they are generated in different timestamp with milliseconds precision
98
+ ULIDs are sortable when they are generated in different timestamp with milliseconds precision.
84
99
 
85
100
  ```ruby
86
101
  ulids = 1000.times.map do
@@ -91,7 +106,7 @@ ulids.uniq(&:to_time).size #=> 1000
91
106
  ulids.sort == ulids #=> true
92
107
  ```
93
108
 
94
- `ULID.generate` can take fixed `Time` instance. The shorthand is `ULID.at`
109
+ `ULID.generate` can take fixed `Time` instance. The shorthand is `ULID.at`.
95
110
 
96
111
  ```ruby
97
112
  time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
@@ -147,11 +162,11 @@ sample_ulids_by_the_time.take(5) #=>
147
162
  ulids.sort == ulids #=> true
148
163
  ```
149
164
 
150
- Same generator does not generate duplicated ULIDs even in multi threads environment. It is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255)
165
+ Same generator does not generate duplicated ULIDs even in multi threads environment. It is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255).
151
166
 
152
167
  ### Filtering IDs with `Time`
153
168
 
154
- `ULID` can be element of the `Range`. If you generated the IDs in monotonic generator, ID based filtering is easy and reliable
169
+ `ULID` can be element of the `Range`. If they were generated with monotonic generator, ID based filtering is easy and reliable.
155
170
 
156
171
  ```ruby
157
172
  include_end = ulid1..ulid2
@@ -187,7 +202,9 @@ time = Time.at(946684800, Rational('123456.789')).utc #=> 2000-01-01 00:00:00.12
187
202
  ULID.floor(time) #=> 2000-01-01 00:00:00.123 UTC
188
203
  ```
189
204
 
190
- ### Scanner for string (e.g. `JSON`)
205
+ ### Some methods to help manipulations
206
+
207
+ #### Scanner for string (e.g. `JSON`)
191
208
 
192
209
  For rough operations, `ULID.scan` might be useful.
193
210
 
@@ -230,7 +247,7 @@ ULID.scan(json).to_a
230
247
  ```
231
248
 
232
249
  `ULID#patterns` is a util for text based operations.
233
- The results and spec are not fixed. Should not be used except snippets/console operation
250
+ The results and spec are not fixed. Should not be used except snippets/console operation.
234
251
 
235
252
  ```ruby
236
253
  ULID.parse('01F4GNBXW1AM2KWW52PVT3ZY9X').patterns
@@ -241,7 +258,7 @@ ULID.parse('01F4GNBXW1AM2KWW52PVT3ZY9X').patterns
241
258
  }
242
259
  ```
243
260
 
244
- ### Some methods to help manipulations
261
+ #### Get boundary ULIDs
245
262
 
246
263
  `ULID.min` and `ULID.max` return termination values for ULID spec.
247
264
 
@@ -256,10 +273,12 @@ ULID.min(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
256
273
  ULID.max(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
257
274
  ```
258
275
 
276
+ #### As element in Enumerable
277
+
259
278
  `ULID#next` and `ULID#succ` returns next(successor) ULID.
260
279
  Especially `ULID#succ` makes it possible `Range[ULID]#each`.
261
280
 
262
- NOTE: But basically `Range[ULID]#each` should not be used, incrementing 128 bits IDs are not reasonable operation in most case
281
+ NOTE: However basically `Range[ULID]#each` should not be used. Iincrementing 128 bits IDs are not reasonable operation in most cases.
263
282
 
264
283
  ```ruby
265
284
  ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZY').next.to_s #=> "01BX5ZZKBKZZZZZZZZZZZZZZZZ"
@@ -267,7 +286,7 @@ ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZZ').next.to_s #=> "01BX5ZZKBM00000000000000
267
286
  ULID.parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').next #=> nil
268
287
  ```
269
288
 
270
- `ULID#pred` returns predecessor ULID
289
+ `ULID#pred` returns predecessor ULID.
271
290
 
272
291
  ```ruby
273
292
  ULID.parse('01BX5ZZKBK0000000000000001').pred.to_s #=> "01BX5ZZKBK0000000000000000"
@@ -275,6 +294,8 @@ ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZ
275
294
  ULID.parse('00000000000000000000000000').pred #=> nil
276
295
  ```
277
296
 
297
+ #### Test helpers
298
+
278
299
  `ULID.sample` returns random ULIDs.
279
300
 
280
301
  Basically ignores generating time.
@@ -300,33 +321,23 @@ ulid1 = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.82
300
321
  ulid2 = ULID.parse('01F4PTVCSN9ZPFKYTY2DDJVRK4') #=> ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4)
301
322
  ulids = ULID.sample(1000, period: ulid1..ulid2)
302
323
  ulids.uniq.size #=> 1000
303
- ulids.take(10)
324
+ ulids.take(5)
304
325
  #=>
305
326
  #[ULID(2021-05-02 06:57:19.954 UTC: 01F4NXW02JNB8H0J0TK48JD39X),
306
327
  # ULID(2021-05-02 07:06:07.458 UTC: 01F4NYC372GVP7NS0YAYQGT4VZ),
307
328
  # ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW),
308
329
  # ULID(2021-04-27 22:17:37.844 UTC: 01F4APHGSMFJZQTGXKZBFFBPJP),
309
- # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3),
310
- # ULID(2021-04-30 07:18:54.307 UTC: 01F4GTA2332AS2VPHC4FMKC7R5),
311
- # ULID(2021-05-02 12:26:03.480 UTC: 01F4PGNXARG554Y3HYVBDW4T9S),
312
- # ULID(2021-04-29 09:52:15.107 UTC: 01F4EGP483ZX2747FQPWQNPPMW),
313
- # ULID(2021-04-29 03:18:24.152 UTC: 01F4DT4Z4RA0QV8WFQGRAG63EH),
314
- # ULID(2021-05-02 13:27:16.394 UTC: 01F4PM605ABF5SDVMEHBH8JJ9R)]
315
- ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
330
+ # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3)]
331
+ ULID.sample(5, period: ulid1.to_time..ulid2.to_time)
316
332
  #=>
317
333
  # [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
318
334
  # ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
319
335
  # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
320
336
  # ULID(2021-05-01 03:06:09.130 UTC: 01F4JY7ZBABCBMX16XH2Q4JW4W),
321
- # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G),
322
- # ULID(2021-04-29 17:14:14.116 UTC: 01F4F9ZDQ449BE8BBZFEHYQWG2),
323
- # ULID(2021-04-30 16:18:08.205 UTC: 01F4HS5DPD1HWDVJNJ6YKJXKSK),
324
- # ULID(2021-04-30 10:31:33.602 UTC: 01F4H5ATF2A1CSQF0XV5NKZ288),
325
- # ULID(2021-04-28 16:49:06.484 UTC: 01F4CP4PDM214Q6H3KJP7DYJRR),
326
- # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
337
+ # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G)]
327
338
  ```
328
339
 
329
- ### ULID specification ambiguity around orthographical variants of the format
340
+ #### Variants of format
330
341
 
331
342
  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
343
 
@@ -354,7 +365,7 @@ ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false
354
365
  ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true
355
366
  ```
356
367
 
357
- ### UUIDv4 converter for migration use-cases
368
+ #### UUIDv4 converter for migration use-cases
358
369
 
359
370
  `ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
360
371
  The imported timestamp is meaningless. So ULID's benefit will lost.
@@ -438,6 +449,18 @@ See [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
438
449
 
439
450
  The results are not something to be proud of.
440
451
 
452
+ ## How to use rbs
453
+
454
+ See structure at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox)
455
+
456
+ I have checked the behavior with [ruby/rbs@2.6.0](https://github.com/ruby/rbs) + [soutaro/steep@1.0.1](https://github.com/soutaro/steep) + [soutaro/steep-vscode](https://github.com/soutaro/steep-vscode).
457
+
458
+ * ![rbs overview](./assets/ulid-rbs-overview.png?raw=true.png)
459
+ * ![rbs mix](./assets/ulid-rbs-mix.png?raw=true.png)
460
+ * ![rbs ng-to_str](./assets/ulid-rbs-ng-to_str.png?raw=true.png)
461
+ * ![rbs ok-at-time](./assets/ulid-rbs-ok-at-time.png?raw=true.png)
462
+ * ![rbs ng-at-int](./assets/ulid-rbs-ng-at-int.png?raw=true.png)
463
+
441
464
  ## References
442
465
 
443
466
  - [Repository](https://github.com/kachick/ruby-ulid)
@@ -446,4 +469,5 @@ The results are not something to be proud of.
446
469
 
447
470
  ## Note
448
471
 
449
- - 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)
472
+ - [UUIDv6, UUIDv7, UUIDv8](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-01.html) is another choice for sortable and randomness ID.
473
+ However they are stayed in draft state. ref: [ruby-ulid#37](https://github.com/kachick/ruby-ulid/issues/37)
data/lib/ruby-ulid.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
6
- require_relative 'ulid'
7
+ require_relative('ulid')
@@ -1,5 +1,6 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
@@ -14,10 +15,10 @@ class ULID
14
15
  # * https://github.com/kachick/ruby-ulid/issues/57
15
16
  # * https://github.com/kachick/ruby-ulid/issues/78
16
17
  module CrockfordBase32
17
- class SetupError < ScriptError; end
18
+ class SetupError < UnexpectedError; end
18
19
 
19
20
  n32_chars = [*'0'..'9', *'A'..'V'].map(&:freeze).freeze
20
- raise SetupError, 'obvious bug exists in the mapping algorithm' unless n32_chars.size == 32
21
+ raise(SetupError, 'obvious bug exists in the mapping algorithm') unless n32_chars.size == 32
21
22
 
22
23
  n32_char_by_number = {}
23
24
  n32_chars.each_with_index do |char, index|
@@ -48,7 +49,7 @@ class ULID
48
49
  map[encoding_char] = char_32
49
50
  end
50
51
  end.freeze
51
- raise SetupError, 'obvious bug exists in the mapping algorithm' unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
52
+ raise(SetupError, 'obvious bug exists in the mapping algorithm') unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
52
53
 
53
54
  CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
54
55
 
@@ -1,19 +1,23 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
6
7
  class ULID
7
8
  class MonotonicGenerator
8
- include MonitorMixin
9
+ # @note When use https://github.com/ko1/ractor-tvar might realize Ractor based thread safe monotonic generator.
10
+ # However it is a C extention, I'm pending to use it for now.
11
+ include(MonitorMixin)
9
12
 
13
+ # @dynamic prev
10
14
  # @return [ULID, nil]
11
- attr_reader :prev
15
+ attr_reader(:prev)
12
16
 
13
- undef_method :instance_variable_set
17
+ undef_method(:instance_variable_set)
14
18
 
15
19
  def initialize
16
- super()
20
+ super
17
21
  @prev = nil
18
22
  end
19
23
 
@@ -21,7 +25,8 @@ class ULID
21
25
  def inspect
22
26
  "ULID::MonotonicGenerator(prev: #{@prev.inspect})"
23
27
  end
24
- alias_method :to_s, :inspect
28
+ # @dynamic to_s
29
+ alias_method(:to_s, :inspect)
25
30
 
26
31
  # @param [Time, Integer] moment
27
32
  # @return [ULID]
@@ -30,23 +35,25 @@ class ULID
30
35
  # Basically will not happen. Just means this feature prefers error rather than invalid value.
31
36
  def generate(moment: ULID.current_milliseconds)
32
37
  synchronize do
33
- unless @prev
34
- @prev = ULID.generate(moment: moment)
35
- return @prev
38
+ prev_ulid = @prev
39
+ unless prev_ulid
40
+ ret = ULID.generate(moment: moment)
41
+ @prev = ret
42
+ return ret
36
43
  end
37
44
 
38
45
  milliseconds = ULID.milliseconds_from_moment(moment)
39
46
 
40
47
  ulid = (
41
- if @prev.milliseconds < milliseconds
48
+ if prev_ulid.milliseconds < milliseconds
42
49
  ULID.generate(moment: milliseconds)
43
50
  else
44
- ULID.from_milliseconds_and_entropy(milliseconds: @prev.milliseconds, entropy: @prev.entropy.succ)
51
+ ULID.from_milliseconds_and_entropy(milliseconds: prev_ulid.milliseconds, entropy: prev_ulid.entropy.succ)
45
52
  end
46
53
  )
47
54
 
48
- unless ulid > @prev
49
- base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{@prev.inspect}"
55
+ unless ulid > prev_ulid
56
+ base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{prev_ulid.inspect}"
50
57
  additional_information = (
51
58
  if Thread.list == [Thread.main]
52
59
  '# NOTE: looks single thread only exist'
@@ -55,7 +62,7 @@ class ULID
55
62
  end
56
63
  )
57
64
 
58
- raise UnexpectedError, base_message + additional_information
65
+ raise(UnexpectedError, base_message + additional_information)
59
66
  end
60
67
 
61
68
  @prev = ulid
@@ -63,12 +70,12 @@ class ULID
63
70
  end
64
71
  end
65
72
 
66
- undef_method :freeze
73
+ undef_method(:freeze)
67
74
 
68
75
  # @raise [TypeError] always raises exception and does not freeze self
69
76
  # @return [void]
70
77
  def freeze
71
- raise TypeError, "cannot freeze #{self.class}"
78
+ raise(TypeError, "cannot freeze #{self.class}")
72
79
  end
73
80
  end
74
81
  end
@@ -0,0 +1,12 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+
4
+ class ULID
5
+ min = parse('00000000000000000000000000').freeze
6
+ max = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
7
+
8
+ ractor_can_make_shareable_time = RUBY_VERSION >= '3.1'
9
+
10
+ MIN = ractor_can_make_shareable_time ? Ractor.make_shareable(min) : min
11
+ MAX = ractor_can_make_shareable_time ? Ractor.make_shareable(max) : max
12
+ end
data/lib/ulid/uuid.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
@@ -10,18 +11,18 @@
10
11
  class ULID
11
12
  # Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
12
13
  UUIDV4_PATTERN = /\A[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i.freeze
13
- private_constant :UUIDV4_PATTERN
14
+ private_constant(:UUIDV4_PATTERN)
14
15
 
15
16
  # @param [String, #to_str] uuid
16
17
  # @return [ULID]
17
18
  # @raise [ParserError] if the given format is not correct for UUIDv4 specs
18
19
  def self.from_uuidv4(uuid)
19
20
  uuid = String.try_convert(uuid)
20
- raise ArgumentError, 'ULID.from_uuidv4 takes only strings' unless uuid
21
+ raise(ArgumentError, 'ULID.from_uuidv4 takes only strings') unless uuid
21
22
 
22
23
  prefix_trimmed = uuid.delete_prefix('urn:uuid:')
23
24
  unless UUIDV4_PATTERN.match?(prefix_trimmed)
24
- raise ParserError, "given `#{uuid}` does not match to `#{UUIDV4_PATTERN.inspect}`"
25
+ raise(ParserError, "given `#{uuid}` does not match to `#{UUIDV4_PATTERN.inspect}`")
25
26
  end
26
27
 
27
28
  normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
@@ -32,8 +33,11 @@ class ULID
32
33
  def to_uuidv4
33
34
  # This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
34
35
  array = octets.pack('C*').unpack('NnnnnN')
35
- array[2] = (array[2] & 0x0fff) | 0x4000
36
- array[3] = (array[3] & 0x3fff) | 0x8000
36
+ ref2, ref3 = array[2], array[3]
37
+ raise unless Integer === ref2 && Integer === ref3
38
+
39
+ array[2] = (ref2 & 0x0fff) | 0x4000
40
+ array[3] = (ref3 & 0x3fff) | 0x8000
37
41
  ('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
38
42
  end
39
43
  end
data/lib/ulid/version.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  class ULID
5
- VERSION = '0.2.0'
6
+ VERSION = '0.4.0'
6
7
  end
data/lib/ulid.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: experimental_everything
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
6
- require 'securerandom'
7
+ require('securerandom')
7
8
 
8
9
  # @see https://github.com/ulid/spec
9
10
  # @!attribute [r] milliseconds
@@ -11,7 +12,7 @@ require 'securerandom'
11
12
  # @!attribute [r] entropy
12
13
  # @return [Integer]
13
14
  class ULID
14
- include Comparable
15
+ include(Comparable)
15
16
 
16
17
  class Error < StandardError; end
17
18
  class OverflowError < Error; end
@@ -25,10 +26,12 @@ class ULID
25
26
 
26
27
  TIMESTAMP_ENCODED_LENGTH = 10
27
28
  RANDOMNESS_ENCODED_LENGTH = 16
28
- ENCODED_LENGTH = TIMESTAMP_ENCODED_LENGTH + RANDOMNESS_ENCODED_LENGTH
29
+ ENCODED_LENGTH = 26
30
+
29
31
  TIMESTAMP_OCTETS_LENGTH = 6
30
32
  RANDOMNESS_OCTETS_LENGTH = 10
31
- OCTETS_LENGTH = TIMESTAMP_OCTETS_LENGTH + RANDOMNESS_OCTETS_LENGTH
33
+ OCTETS_LENGTH = 16
34
+
32
35
  MAX_MILLISECONDS = 281474976710655
33
36
  MAX_ENTROPY = 1208925819614629174706175
34
37
  MAX_INTEGER = 340282366920938463463374607431768211455
@@ -40,14 +43,15 @@ class ULID
40
43
  STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET = /\A#{PATTERN_WITH_CROCKFORD_BASE32_SUBSET.source}\z/i.freeze
41
44
 
42
45
  # Optimized for `ULID.scan`, might be changed the definition with gathered `ULID.scan` spec changed.
43
- # This can't contain `\b` for considering UTF-8 (e.g. Japanese), so intentional `false negative` definition.
44
- SCANNING_PATTERN = /[0-7][#{CROCKFORD_BASE32_ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CROCKFORD_BASE32_ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}/i.freeze
46
+ SCANNING_PATTERN = /\b[0-7][#{CROCKFORD_BASE32_ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CROCKFORD_BASE32_ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}\b/i.freeze
45
47
 
46
- # Same as Time#inspect since Ruby 2.7, just to keep backward compatibility
48
+ # Similar as Time#inspect since Ruby 2.7, however it is NOT same.
49
+ # Time#inspect trancates needless digits. Keeping full milliseconds with "%3N" will fit for ULID.
47
50
  # @see https://bugs.ruby-lang.org/issues/15958
51
+ # @see https://github.com/ruby/ruby/blob/744d17ff6c33b09334508e8110007ea2a82252f5/time.c#L4026-L4078
48
52
  TIME_FORMAT_IN_INSPECT = '%Y-%m-%d %H:%M:%S.%3N %Z'
49
53
 
50
- private_class_method :new
54
+ private_class_method(:new)
51
55
 
52
56
  # @param [Integer, Time] moment
53
57
  # @param [Integer] entropy
@@ -60,7 +64,7 @@ class ULID
60
64
  # @param [Time] time
61
65
  # @return [ULID]
62
66
  def self.at(time)
63
- raise ArgumentError, 'ULID.at takes only `Time` instance' unless Time === time
67
+ raise(ArgumentError, 'ULID.at takes only `Time` instance') unless Time === time
64
68
 
65
69
  from_milliseconds_and_entropy(milliseconds: milliseconds_from_time(time), entropy: reasonable_entropy)
66
70
  end
@@ -79,7 +83,7 @@ class ULID
79
83
 
80
84
  RANDOM_INTEGER_GENERATOR = -> {
81
85
  SecureRandom.random_number(MAX_INTEGER)
82
- }
86
+ }.freeze
83
87
 
84
88
  # @param [Range<Time>, Range<nil>, Range[ULID], nil] period
85
89
  # @overload sample(number, period: nil)
@@ -92,14 +96,14 @@ class ULID
92
96
  # * Do not ensure the uniqueness
93
97
  # * Do not take random generator for the arguments
94
98
  # * Raising error instead of truncating elements for the given number
95
- def self.sample(*args, period: nil)
99
+ def self.sample(number=nil, period: nil)
96
100
  int_generator = (
97
101
  if period
98
102
  ulid_range = range(period)
99
103
  min, max, exclude_end = ulid_range.begin.to_i, ulid_range.end.to_i, ulid_range.exclude_end?
100
104
 
101
105
  possibilities = (max - min) + (exclude_end ? 0 : 1)
102
- raise ArgumentError, "given range `#{ulid_range.inspect}` does not have possibilities" unless possibilities.positive?
106
+ raise(ArgumentError, "given range `#{ulid_range.inspect}` does not have possibilities") unless possibilities.positive?
103
107
 
104
108
  -> {
105
109
  SecureRandom.random_number(possibilities) + min
@@ -109,24 +113,21 @@ class ULID
109
113
  end
110
114
  )
111
115
 
112
- case args.size
113
- when 0
116
+ case number
117
+ when nil
114
118
  from_integer(int_generator.call)
115
- when 1
116
- number = args.first
117
- raise ArgumentError, 'accepts no argument or integer only' unless Integer === number
118
-
119
+ when Integer
119
120
  if number > MAX_INTEGER || number.negative?
120
- raise ArgumentError, "given number `#{number}` is larger than ULID limit `#{MAX_INTEGER}` or negative"
121
+ raise(ArgumentError, "given number `#{number}` is larger than ULID limit `#{MAX_INTEGER}` or negative")
121
122
  end
122
123
 
123
- if period && (number > possibilities)
124
- raise ArgumentError, "given number `#{number}` is larger than given possibilities `#{possibilities}`"
124
+ if period && possibilities && (number > possibilities)
125
+ raise(ArgumentError, "given number `#{number}` is larger than given possibilities `#{possibilities}`")
125
126
  end
126
127
 
127
128
  Array.new(number) { from_integer(int_generator.call) }
128
129
  else
129
- raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..1)"
130
+ raise(ArgumentError, 'accepts no argument or integer only')
130
131
  end
131
132
  end
132
133
 
@@ -136,11 +137,13 @@ class ULID
136
137
  # @yieldreturn [self]
137
138
  def self.scan(string)
138
139
  string = String.try_convert(string)
139
- raise ArgumentError, 'ULID.scan takes only strings' unless string
140
- return to_enum(__callee__, string) unless block_given?
140
+ raise(ArgumentError, 'ULID.scan takes only strings') unless string
141
+ return to_enum(:scan, string) unless block_given?
141
142
 
142
143
  string.scan(SCANNING_PATTERN) do |matched|
143
- yield parse(matched)
144
+ if String === matched
145
+ yield(parse(matched))
146
+ end
144
147
  end
145
148
  self
146
149
  end
@@ -150,14 +153,16 @@ class ULID
150
153
  # @raise [OverflowError] if the given integer is larger than the ULID limit
151
154
  # @raise [ArgumentError] if the given integer is negative number
152
155
  def self.from_integer(integer)
153
- raise ArgumentError, 'ULID.from_integer takes only `Integer`' unless Integer === integer
154
- raise OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}" unless integer <= MAX_INTEGER
155
- raise ArgumentError, "integer should not be negative: given: #{integer}" if integer.negative?
156
+ raise(ArgumentError, 'ULID.from_integer takes only `Integer`') unless Integer === integer
157
+ raise(OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}") unless integer <= MAX_INTEGER
158
+ raise(ArgumentError, "integer should not be negative: given: #{integer}") if integer.negative?
156
159
 
157
160
  n32encoded = integer.to_s(32).rjust(ENCODED_LENGTH, '0')
158
161
  n32encoded_timestamp = n32encoded.slice(0, TIMESTAMP_ENCODED_LENGTH)
159
162
  n32encoded_randomness = n32encoded.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH)
160
163
 
164
+ raise(UnexpectedError) unless n32encoded_timestamp && n32encoded_randomness
165
+
161
166
  milliseconds = n32encoded_timestamp.to_i(32)
162
167
  entropy = n32encoded_randomness.to_i(32)
163
168
 
@@ -168,37 +173,43 @@ class ULID
168
173
  # @return [Range<ULID>]
169
174
  # @raise [ArgumentError] if the given period is not a `Range[Time]`, `Range[nil]` or `Range[ULID]`
170
175
  def self.range(period)
171
- raise ArgumentError, 'ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`' unless Range === period
176
+ raise(ArgumentError, 'ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`') unless Range === period
172
177
 
173
178
  begin_element, end_element, exclude_end = period.begin, period.end, period.exclude_end?
174
- return period if self === begin_element && self === end_element
175
-
176
- case begin_element
177
- when Time
178
- begin_ulid = min(begin_element)
179
- when nil
180
- begin_ulid = MIN
181
- when self
182
- begin_ulid = begin_element
183
- else
184
- raise ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}"
185
- end
179
+ new_begin, new_end = false, false
180
+
181
+ begin_ulid = (
182
+ case begin_element
183
+ when Time
184
+ new_begin = true
185
+ min(begin_element)
186
+ when nil
187
+ MIN
188
+ when self
189
+ begin_element
190
+ else
191
+ raise(ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}")
192
+ end
193
+ )
186
194
 
187
- case end_element
188
- when Time
189
- end_ulid = exclude_end ? min(end_element) : max(end_element)
190
- when nil
191
- # The end should be max and include end, because nil end means to cover endless ULIDs until the limit
192
- end_ulid = MAX
193
- exclude_end = false
194
- when self
195
- end_ulid = end_element
196
- else
197
- raise ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}"
198
- end
195
+ end_ulid = (
196
+ case end_element
197
+ when Time
198
+ new_end = true
199
+ exclude_end ? min(end_element) : max(end_element)
200
+ when nil
201
+ exclude_end = false
202
+ # The end should be max and include end, because nil end means to cover endless ULIDs until the limit
203
+ MAX
204
+ when self
205
+ end_element
206
+ else
207
+ raise(ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}")
208
+ end
209
+ )
199
210
 
200
- begin_ulid.freeze
201
- end_ulid.freeze
211
+ begin_ulid.freeze if new_begin
212
+ end_ulid.freeze if new_end
202
213
 
203
214
  Range.new(begin_ulid, end_ulid, exclude_end)
204
215
  end
@@ -206,7 +217,7 @@ class ULID
206
217
  # @param [Time] time
207
218
  # @return [Time]
208
219
  def self.floor(time)
209
- raise ArgumentError, 'ULID.floor takes only `Time` instance' unless Time === time
220
+ raise(ArgumentError, 'ULID.floor takes only `Time` instance') unless Time === time
210
221
 
211
222
  time.floor(3)
212
223
  end
@@ -234,7 +245,7 @@ class ULID
234
245
  when Time
235
246
  milliseconds_from_time(moment)
236
247
  else
237
- raise ArgumentError, '`moment` should be a `Time` or `Integer as milliseconds`'
248
+ raise(ArgumentError, '`moment` should be a `Time` or `Integer as milliseconds`')
238
249
  end
239
250
  end
240
251
 
@@ -248,10 +259,10 @@ class ULID
248
259
  # @raise [ParserError] if the given format is not correct for ULID specs
249
260
  def self.parse(string)
250
261
  string = String.try_convert(string)
251
- raise ArgumentError, 'ULID.parse takes only strings' unless string
262
+ raise(ArgumentError, 'ULID.parse takes only strings') unless string
252
263
 
253
264
  unless STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string)
254
- raise ParserError, "given `#{string}` does not match to `#{STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.inspect}`"
265
+ raise(ParserError, "given `#{string}` does not match to `#{STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.inspect}`")
255
266
  end
256
267
 
257
268
  from_integer(CrockfordBase32.decode(string))
@@ -262,7 +273,7 @@ class ULID
262
273
  # @raise [ParserError] if the given format is not correct for ULID specs, even if ignored `orthographical variants of the format`
263
274
  def self.normalize(string)
264
275
  string = String.try_convert(string)
265
- raise ArgumentError, 'ULID.normalize takes only strings' unless string
276
+ raise(ArgumentError, 'ULID.normalize takes only strings') unless string
266
277
 
267
278
  normalized_in_crockford = CrockfordBase32.normalize(string)
268
279
  # Ensure the ULID correctness, because CrockfordBase32 does not always mean to satisfy ULID format
@@ -298,7 +309,7 @@ class ULID
298
309
  else
299
310
  object_class_name = safe_get_class_name(object)
300
311
  converted_class_name = safe_get_class_name(converted)
301
- raise TypeError, "can't convert #{object_class_name} to ULID (#{object_class_name}#to_ulid gives #{converted_class_name})"
312
+ raise(TypeError, "can't convert #{object_class_name} to ULID (#{object_class_name}#to_ulid gives #{converted_class_name})")
302
313
  end
303
314
  end
304
315
  end
@@ -314,8 +325,12 @@ class ULID
314
325
  begin
315
326
  object.class
316
327
  rescue NoMethodError
328
+ # steep can't correctly handle singleton class assign. See https://github.com/soutaro/steep/pull/586 for further detail
329
+ # So this annotation is hack for the type infer.
330
+ # @type var object: BasicObject
331
+ # @type var singleton_class: untyped
317
332
  singleton_class = class << object; self; end
318
- singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) }
333
+ (Class === singleton_class) ? singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) } : fallback
319
334
  end
320
335
  )
321
336
 
@@ -335,10 +350,10 @@ class ULID
335
350
  # @raise [OverflowError] if the given value is larger than the ULID limit
336
351
  # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
337
352
  def self.from_milliseconds_and_entropy(milliseconds:, entropy:)
338
- raise ArgumentError, 'milliseconds and entropy should be an `Integer`' unless Integer === milliseconds && Integer === entropy
339
- raise OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}" unless milliseconds <= MAX_MILLISECONDS
340
- raise OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}" unless entropy <= MAX_ENTROPY
341
- raise ArgumentError, 'milliseconds and entropy should not be negative' if milliseconds.negative? || entropy.negative?
353
+ raise(ArgumentError, 'milliseconds and entropy should be an `Integer`') unless Integer === milliseconds && Integer === entropy
354
+ raise(OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}") unless milliseconds <= MAX_MILLISECONDS
355
+ raise(OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}") unless entropy <= MAX_ENTROPY
356
+ raise(ArgumentError, 'milliseconds and entropy should not be negative') if milliseconds.negative? || entropy.negative?
342
357
 
343
358
  n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
344
359
  n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
@@ -347,7 +362,8 @@ class ULID
347
362
  new(milliseconds: milliseconds, entropy: entropy, integer: integer)
348
363
  end
349
364
 
350
- attr_reader :milliseconds, :entropy
365
+ # @dynamic milliseconds, entropy
366
+ attr_reader(:milliseconds, :entropy)
351
367
 
352
368
  # @api private
353
369
  # @param [Integer] milliseconds
@@ -370,7 +386,11 @@ class ULID
370
386
  def to_i
371
387
  @integer
372
388
  end
373
- alias_method :hash, :to_i
389
+
390
+ # @return [Integer]
391
+ def hash
392
+ [ULID, @integer].hash
393
+ end
374
394
 
375
395
  # @return [Integer, nil]
376
396
  def <=>(other)
@@ -386,15 +406,29 @@ class ULID
386
406
  def eql?(other)
387
407
  equal?(other) || (ULID === other && @integer == other.to_i)
388
408
  end
389
- alias_method :==, :eql?
409
+ # @dynamic ==
410
+ alias_method(:==, :eql?)
390
411
 
412
+ # Return `true` for same value of ULID, variant formats of strings, same Time in ULID precision(msec).
413
+ # Do not consider integer, octets and partial strings, then returns `false`.
414
+ #
391
415
  # @return [Boolean]
416
+ # @see .normalize
417
+ # @see .floor
392
418
  def ===(other)
393
419
  case other
394
420
  when ULID
395
421
  @integer == other.to_i
396
422
  when String
397
- to_s == other.upcase
423
+ begin
424
+ normalized = ULID.normalize(other)
425
+ rescue Exception
426
+ false
427
+ else
428
+ to_s == normalized
429
+ end
430
+ when Time
431
+ to_time == ULID.floor(other)
398
432
  else
399
433
  false
400
434
  end
@@ -416,22 +450,22 @@ class ULID
416
450
 
417
451
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer)]
418
452
  def timestamp_octets
419
- octets.slice(0, TIMESTAMP_OCTETS_LENGTH)
453
+ octets.slice(0, TIMESTAMP_OCTETS_LENGTH) || raise(UnexpectedError)
420
454
  end
421
455
 
422
456
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
423
457
  def randomness_octets
424
- octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH)
458
+ octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH) || raise(UnexpectedError)
425
459
  end
426
460
 
427
461
  # @return [String]
428
462
  def timestamp
429
- @timestamp ||= to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze
463
+ @timestamp ||= (to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
430
464
  end
431
465
 
432
466
  # @return [String]
433
467
  def randomness
434
- @randomness ||= to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze
468
+ @randomness ||= (to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze || raise(UnexpectedError))
435
469
  end
436
470
 
437
471
  # @note Providing for rough operations. The keys and values is not fixed.
@@ -457,7 +491,8 @@ class ULID
457
491
  ULID.from_integer(succ_int)
458
492
  end
459
493
  end
460
- alias_method :next, :succ
494
+ # @dynamic next
495
+ alias_method(:next, :succ)
461
496
 
462
497
  # @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
463
498
  def pred
@@ -509,7 +544,7 @@ class ULID
509
544
  self
510
545
  end
511
546
 
512
- undef_method :instance_variable_set
547
+ undef_method(:instance_variable_set)
513
548
 
514
549
  private
515
550
 
@@ -521,13 +556,12 @@ class ULID
521
556
  end
522
557
  end
523
558
 
524
- require_relative 'ulid/version'
525
- require_relative 'ulid/crockford_base32'
526
- require_relative 'ulid/monotonic_generator'
559
+ require_relative('ulid/version')
560
+ require_relative('ulid/crockford_base32')
561
+ require_relative('ulid/monotonic_generator')
562
+ require_relative('ulid/ractor_unshareable_constants')
527
563
 
528
564
  class ULID
529
- MIN = parse('00000000000000000000000000').freeze
530
- MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
531
-
532
- private_constant :TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR
565
+ # Do not write as `ULID.private_constant` for avoiding YARD warnings `[warn]: in YARD::Handlers::Ruby::PrivateConstantHandler: Undocumentable private constants:`
566
+ private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR, :CROCKFORD_BASE32_ENCODING_STRING)
533
567
  end
data/sig/ulid.rbs CHANGED
@@ -36,7 +36,7 @@ class ULID < Object
36
36
  end
37
37
 
38
38
  module CrockfordBase32
39
- class SetupError < ScriptError
39
+ class SetupError < UnexpectedError
40
40
  end
41
41
 
42
42
  N32_CHAR_BY_CROCKFORD_BASE32_CHAR: Hash[String, String]
@@ -97,9 +97,9 @@ class ULID < Object
97
97
  def to_ulid: () -> ULID
98
98
  end
99
99
 
100
- type octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
101
- type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
102
- type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
100
+ type full_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
101
+ type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
102
+ type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
103
103
  type period = Range[Time] | Range[nil] | Range[ULID]
104
104
 
105
105
  @string: String?
@@ -292,33 +292,23 @@ class ULID < Object
292
292
  # ulid2 = ULID.parse('01F4PTVCSN9ZPFKYTY2DDJVRK4') #=> ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4)
293
293
  # ulids = ULID.sample(1000, period: ulid1..ulid2)
294
294
  # ulids.uniq.size #=> 1000
295
- # ulids.take(10)
295
+ # ulids.take(5)
296
296
  # #=>
297
297
  # #[ULID(2021-05-02 06:57:19.954 UTC: 01F4NXW02JNB8H0J0TK48JD39X),
298
298
  # # ULID(2021-05-02 07:06:07.458 UTC: 01F4NYC372GVP7NS0YAYQGT4VZ),
299
299
  # # ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW),
300
300
  # # ULID(2021-04-27 22:17:37.844 UTC: 01F4APHGSMFJZQTGXKZBFFBPJP),
301
- # # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3),
302
- # # ULID(2021-04-30 07:18:54.307 UTC: 01F4GTA2332AS2VPHC4FMKC7R5),
303
- # # ULID(2021-05-02 12:26:03.480 UTC: 01F4PGNXARG554Y3HYVBDW4T9S),
304
- # # ULID(2021-04-29 09:52:15.107 UTC: 01F4EGP483ZX2747FQPWQNPPMW),
305
- # # ULID(2021-04-29 03:18:24.152 UTC: 01F4DT4Z4RA0QV8WFQGRAG63EH),
306
- # # ULID(2021-05-02 13:27:16.394 UTC: 01F4PM605ABF5SDVMEHBH8JJ9R)]
301
+ # # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3)]
307
302
  # ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
308
303
  # #=>
309
304
  # # [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
310
305
  # # ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
311
306
  # # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
312
307
  # # ULID(2021-05-01 03:06:09.130 UTC: 01F4JY7ZBABCBMX16XH2Q4JW4W),
313
- # # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G),
314
- # # ULID(2021-04-29 17:14:14.116 UTC: 01F4F9ZDQ449BE8BBZFEHYQWG2),
315
- # # ULID(2021-04-30 16:18:08.205 UTC: 01F4HS5DPD1HWDVJNJ6YKJXKSK),
316
- # # ULID(2021-04-30 10:31:33.602 UTC: 01F4H5ATF2A1CSQF0XV5NKZ288),
317
- # # ULID(2021-04-28 16:49:06.484 UTC: 01F4CP4PDM214Q6H3KJP7DYJRR),
318
- # # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
308
+ # # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G)]
319
309
  # ```
320
310
  def self.sample: (?period: period) -> ULID
321
- | (Integer number, ?period: period) -> Array[ULID]
311
+ | (Integer number, ?period: period?) -> Array[ULID]
322
312
  def self.valid?: (untyped) -> bool
323
313
 
324
314
  # Returns normalized string
@@ -409,7 +399,9 @@ class ULID < Object
409
399
  # ulid.to_i #=> 1957909092946624190749577070267409738
410
400
  # ```
411
401
  def to_i: -> Integer
412
- alias hash to_i
402
+
403
+ # Returns integer for making as a hash key use-case
404
+ def hash: -> Integer
413
405
 
414
406
  # Basically same as String based sort.
415
407
  #
@@ -434,9 +426,28 @@ class ULID < Object
434
426
  # ULID.parse('4NNB20D9C1ME2NGMTX51ERZJX0') == ULID.parse('4nnb20d9c1me2ngmtx51erzjx0')
435
427
  # #=> true
436
428
  # ```
437
- def eql?: (untyped other) -> bool
429
+ def eql?: (ULID other) -> bool
430
+ | (untyped other) -> false
438
431
  alias == eql?
439
- def ===: (untyped other) -> bool
432
+
433
+ # Return `true` for same value of ULID, variant formats of strings, same Time in ULID precision(msec).
434
+ # Do not consider integer, octets and partial strings, then returns `false`.
435
+ #
436
+ # ```ruby
437
+ # ulid = ULID.parse('01G6Z7Q4RSH97E6QHAC7VK19G2')
438
+ # ulid === ULID.parse(ulid.to_s)
439
+ # #=> true
440
+ # ulid === ulid.to_s.downcase
441
+ # #=> true
442
+ # ulid === ulid.to_time
443
+ # #=> true
444
+ # ulid === ulid.to_i
445
+ # #=> false
446
+ # ulid === ulid.next
447
+ # #=> false
448
+ # ```
449
+ def ===: (ULID | String | Time other) -> bool
450
+ | (untyped other) -> false
440
451
 
441
452
  # ```ruby
442
453
  # ulid = ULID.generate
@@ -462,7 +473,7 @@ class ULID < Object
462
473
  # Intentionally avoiding to use `Record type` ref: https://github.com/ruby/rbs/blob/4fb4c33b2325d1a73d79ff7aaeb49f21cec1e0e5/docs/syntax.md#record-type
463
474
  # Because the returning values are not fixed.
464
475
  def patterns: -> Hash[Symbol, Regexp | String]
465
- def octets: -> octets
476
+ def octets: -> full_octets
466
477
  def timestamp_octets: -> timestamp_octets
467
478
  def randomness_octets: -> randomness_octets
468
479
 
@@ -525,7 +536,7 @@ class ULID < Object
525
536
  private
526
537
 
527
538
  # A private API. Should not be used in your code.
528
- def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> self
539
+ def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> ULID
529
540
 
530
541
  # A private API. Should not be used in your code.
531
542
  def self.reasonable_entropy: -> Integer
metadata CHANGED
@@ -1,19 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-ulid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenichi Kamiya
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-13 00:00:00.000000000 Z
11
+ date: 2022-07-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: |2
14
- The ULID(Universally Unique Lexicographically Sortable Identifier) has useful specs for applications (e.g. `Database key`), especially possess all `uniqueness`, `randomness`, `extractable timestamps` and `sortable` features.
15
- This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around the ULID.
16
- Also providing `ruby/rbs` signature files.
13
+ description: " generator, monotonic generator, parser and manipulations for ULID
14
+ (RBS included)\n"
17
15
  email:
18
16
  - kachick1+ruby@gmail.com
19
17
  executables: []
@@ -26,6 +24,7 @@ files:
26
24
  - lib/ulid.rb
27
25
  - lib/ulid/crockford_base32.rb
28
26
  - lib/ulid/monotonic_generator.rb
27
+ - lib/ulid/ractor_unshareable_constants.rb
29
28
  - lib/ulid/uuid.rb
30
29
  - lib/ulid/version.rb
31
30
  - sig/ulid.rbs
@@ -46,7 +45,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
46
45
  requirements:
47
46
  - - ">="
48
47
  - !ruby/object:Gem::Version
49
- version: 2.7.0
48
+ version: 2.7.2
50
49
  required_rubygems_version: !ruby/object:Gem::Requirement
51
50
  requirements:
52
51
  - - ">="