ruby-ulid 0.2.0 → 0.4.0

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: 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
  - - ">="