ruby-ulid 0.2.2 → 0.3.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: cea320f797b834d5a0f246db9920a9453a6d229728b428590e2f99dd275c8fe9
4
- data.tar.gz: e703ca1896a7ee4155b1362cffdfaf9499e4ae32073cd440c50750d4e9cc9c1a
3
+ metadata.gz: 358b03a503f8a12c87f3f7daaf3b0acf6e55dc44f1aba7a38b9f9a38985c2c68
4
+ data.tar.gz: 15d25d443f4826b07fc9a74b092e35b9cd60dc34cc3615f03fee619d4fc39445
5
5
  SHA512:
6
- metadata.gz: b9d0436d98095dd0084675d87a6605848f6c7ee14d65724ce5bd9229945ffc17a3b7e1efaf9a295baeaeac11782c9fc63bc901149cfa58d96046e334d5d19067
7
- data.tar.gz: f7e016a4ca3d859f8469b5281d9f5581ffe6c0fdde737c554c627f7862461701844e46e4513ce09748e1cdf3fa4b8da2607a8d3267f74c64ae4a257ae54d6e78
6
+ metadata.gz: 5b18d13597bd2f3dcd85a15a26d9086fb9a80b91775292c9da82d93144b6abd37585996f1bf64dfc4ac12b6d6d49683012dfea8e04624005358459667864114c
7
+ data.tar.gz: d987aa9b60717577fae96eef68fca76b2395f11a580df4863a69dd0931d955e4cf68608c101959db18f5e3a26031fbe284ff90a0d107fa84bbebb63949bba66b
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 [ruby/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.svg)](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,10 +47,10 @@ $ 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 to your Gemfile`.
51
51
 
52
52
  ```ruby
53
- gem 'ruby-ulid', '~> 0.2.2'
53
+ gem('ruby-ulid', '~> 0.2.2')
54
54
  ```
55
55
 
56
56
  ### Generator and Parser
@@ -438,6 +438,18 @@ See [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
438
438
 
439
439
  The results are not something to be proud of.
440
440
 
441
+ ## How to use rbs
442
+
443
+ See structure at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox)
444
+
445
+ 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).
446
+
447
+ * ![rbs overview](./assets/ulid-rbs-overview.png?raw=true.png)
448
+ * ![rbs mix](./assets/ulid-rbs-mix.png?raw=true.png)
449
+ * ![rbs ng-to_str](./assets/ulid-rbs-ng-to_str.png?raw=true.png)
450
+ * ![rbs ok-at-time](./assets/ulid-rbs-ok-at-time.png?raw=true.png)
451
+ * ![rbs ng-at-int](./assets/ulid-rbs-ng-at-int.png?raw=true.png)
452
+
441
453
  ## References
442
454
 
443
455
  - [Repository](https://github.com/kachick/ruby-ulid)
@@ -446,4 +458,5 @@ The results are not something to be proud of.
446
458
 
447
459
  ## Note
448
460
 
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)
461
+ - [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.
462
+ 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
@@ -3,4 +3,4 @@
3
3
 
4
4
  # Copyright (C) 2021 Kenichi Kamiya
5
5
 
6
- require_relative 'ulid'
6
+ require_relative('ulid')
@@ -14,10 +14,10 @@ class ULID
14
14
  # * https://github.com/kachick/ruby-ulid/issues/57
15
15
  # * https://github.com/kachick/ruby-ulid/issues/78
16
16
  module CrockfordBase32
17
- class SetupError < ScriptError; end
17
+ class SetupError < UnexpectedError; end
18
18
 
19
19
  n32_chars = [*'0'..'9', *'A'..'V'].map(&:freeze).freeze
20
- raise SetupError, 'obvious bug exists in the mapping algorithm' unless n32_chars.size == 32
20
+ raise(SetupError, 'obvious bug exists in the mapping algorithm') unless n32_chars.size == 32
21
21
 
22
22
  n32_char_by_number = {}
23
23
  n32_chars.each_with_index do |char, index|
@@ -48,7 +48,7 @@ class ULID
48
48
  map[encoding_char] = char_32
49
49
  end
50
50
  end.freeze
51
- raise SetupError, 'obvious bug exists in the mapping algorithm' unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
51
+ raise(SetupError, 'obvious bug exists in the mapping algorithm') unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
52
52
 
53
53
  CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
54
54
 
@@ -5,15 +5,16 @@
5
5
 
6
6
  class ULID
7
7
  class MonotonicGenerator
8
- include MonitorMixin
8
+ include(MonitorMixin)
9
9
 
10
+ # @dynamic prev
10
11
  # @return [ULID, nil]
11
- attr_reader :prev
12
+ attr_reader(:prev)
12
13
 
13
- undef_method :instance_variable_set
14
+ undef_method(:instance_variable_set)
14
15
 
15
16
  def initialize
16
- super()
17
+ super
17
18
  @prev = nil
18
19
  end
19
20
 
@@ -21,7 +22,8 @@ class ULID
21
22
  def inspect
22
23
  "ULID::MonotonicGenerator(prev: #{@prev.inspect})"
23
24
  end
24
- alias_method :to_s, :inspect
25
+ # @dynamic to_s
26
+ alias_method(:to_s, :inspect)
25
27
 
26
28
  # @param [Time, Integer] moment
27
29
  # @return [ULID]
@@ -30,23 +32,25 @@ class ULID
30
32
  # Basically will not happen. Just means this feature prefers error rather than invalid value.
31
33
  def generate(moment: ULID.current_milliseconds)
32
34
  synchronize do
33
- unless @prev
34
- @prev = ULID.generate(moment: moment)
35
- return @prev
35
+ prev_ulid = @prev
36
+ unless prev_ulid
37
+ ret = ULID.generate(moment: moment)
38
+ @prev = ret
39
+ return ret
36
40
  end
37
41
 
38
42
  milliseconds = ULID.milliseconds_from_moment(moment)
39
43
 
40
44
  ulid = (
41
- if @prev.milliseconds < milliseconds
45
+ if prev_ulid.milliseconds < milliseconds
42
46
  ULID.generate(moment: milliseconds)
43
47
  else
44
- ULID.from_milliseconds_and_entropy(milliseconds: @prev.milliseconds, entropy: @prev.entropy.succ)
48
+ ULID.from_milliseconds_and_entropy(milliseconds: prev_ulid.milliseconds, entropy: prev_ulid.entropy.succ)
45
49
  end
46
50
  )
47
51
 
48
- unless ulid > @prev
49
- base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{@prev.inspect}"
52
+ unless ulid > prev_ulid
53
+ base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{prev_ulid.inspect}"
50
54
  additional_information = (
51
55
  if Thread.list == [Thread.main]
52
56
  '# NOTE: looks single thread only exist'
@@ -55,7 +59,7 @@ class ULID
55
59
  end
56
60
  )
57
61
 
58
- raise UnexpectedError, base_message + additional_information
62
+ raise(UnexpectedError, base_message + additional_information)
59
63
  end
60
64
 
61
65
  @prev = ulid
@@ -63,12 +67,12 @@ class ULID
63
67
  end
64
68
  end
65
69
 
66
- undef_method :freeze
70
+ undef_method(:freeze)
67
71
 
68
72
  # @raise [TypeError] always raises exception and does not freeze self
69
73
  # @return [void]
70
74
  def freeze
71
- raise TypeError, "cannot freeze #{self.class}"
75
+ raise(TypeError, "cannot freeze #{self.class}")
72
76
  end
73
77
  end
74
78
  end
data/lib/ulid/uuid.rb CHANGED
@@ -10,18 +10,18 @@
10
10
  class ULID
11
11
  # Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
12
12
  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
13
+ private_constant(:UUIDV4_PATTERN)
14
14
 
15
15
  # @param [String, #to_str] uuid
16
16
  # @return [ULID]
17
17
  # @raise [ParserError] if the given format is not correct for UUIDv4 specs
18
18
  def self.from_uuidv4(uuid)
19
19
  uuid = String.try_convert(uuid)
20
- raise ArgumentError, 'ULID.from_uuidv4 takes only strings' unless uuid
20
+ raise(ArgumentError, 'ULID.from_uuidv4 takes only strings') unless uuid
21
21
 
22
22
  prefix_trimmed = uuid.delete_prefix('urn:uuid:')
23
23
  unless UUIDV4_PATTERN.match?(prefix_trimmed)
24
- raise ParserError, "given `#{uuid}` does not match to `#{UUIDV4_PATTERN.inspect}`"
24
+ raise(ParserError, "given `#{uuid}` does not match to `#{UUIDV4_PATTERN.inspect}`")
25
25
  end
26
26
 
27
27
  normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
@@ -32,8 +32,11 @@ class ULID
32
32
  def to_uuidv4
33
33
  # This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
34
34
  array = octets.pack('C*').unpack('NnnnnN')
35
- array[2] = (array[2] & 0x0fff) | 0x4000
36
- array[3] = (array[3] & 0x3fff) | 0x8000
35
+ ref2, ref3 = array[2], array[3]
36
+ raise unless Integer === ref2 && Integer === ref3
37
+
38
+ array[2] = (ref2 & 0x0fff) | 0x4000
39
+ array[3] = (ref3 & 0x3fff) | 0x8000
37
40
  ('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
38
41
  end
39
42
  end
data/lib/ulid/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class ULID
5
- VERSION = '0.2.2'
5
+ VERSION = '0.3.0'
6
6
  end
data/lib/ulid.rb CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # Copyright (C) 2021 Kenichi Kamiya
5
5
 
6
- require 'securerandom'
6
+ require('securerandom')
7
7
 
8
8
  # @see https://github.com/ulid/spec
9
9
  # @!attribute [r] milliseconds
@@ -11,7 +11,7 @@ require 'securerandom'
11
11
  # @!attribute [r] entropy
12
12
  # @return [Integer]
13
13
  class ULID
14
- include Comparable
14
+ include(Comparable)
15
15
 
16
16
  class Error < StandardError; end
17
17
  class OverflowError < Error; end
@@ -25,10 +25,12 @@ class ULID
25
25
 
26
26
  TIMESTAMP_ENCODED_LENGTH = 10
27
27
  RANDOMNESS_ENCODED_LENGTH = 16
28
- ENCODED_LENGTH = TIMESTAMP_ENCODED_LENGTH + RANDOMNESS_ENCODED_LENGTH
28
+ ENCODED_LENGTH = 26
29
+
29
30
  TIMESTAMP_OCTETS_LENGTH = 6
30
31
  RANDOMNESS_OCTETS_LENGTH = 10
31
- OCTETS_LENGTH = TIMESTAMP_OCTETS_LENGTH + RANDOMNESS_OCTETS_LENGTH
32
+ OCTETS_LENGTH = 16
33
+
32
34
  MAX_MILLISECONDS = 281474976710655
33
35
  MAX_ENTROPY = 1208925819614629174706175
34
36
  MAX_INTEGER = 340282366920938463463374607431768211455
@@ -43,11 +45,13 @@ class ULID
43
45
  # This can't contain `\b` for considering UTF-8 (e.g. Japanese), so intentional `false negative` definition.
44
46
  SCANNING_PATTERN = /[0-7][#{CROCKFORD_BASE32_ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CROCKFORD_BASE32_ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}/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
@@ -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
@@ -220,9 +231,9 @@ class ULID
220
231
  # @api private
221
232
  # @param [Time] time
222
233
  # @return [Integer]
223
- private_class_method def self.milliseconds_from_time(time)
234
+ private_class_method(def self.milliseconds_from_time(time)
224
235
  (time.to_r * 1000).to_i
225
- end
236
+ end)
226
237
 
227
238
  # @api private
228
239
  # @param [Time, Integer] moment
@@ -234,24 +245,24 @@ 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
 
241
252
  # @return [Integer]
242
- private_class_method def self.reasonable_entropy
253
+ private_class_method(def self.reasonable_entropy
243
254
  SecureRandom.random_number(MAX_ENTROPY)
244
- end
255
+ end)
245
256
 
246
257
  # @param [String, #to_str] string
247
258
  # @return [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,14 +309,14 @@ 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
305
316
 
306
317
  # @param [BasicObject] object
307
318
  # @return [String]
308
- private_class_method def self.safe_get_class_name(object)
319
+ private_class_method(def self.safe_get_class_name(object)
309
320
  fallback = 'UnknownObject'
310
321
 
311
322
  # This class getter implementation used https://github.com/rspec/rspec-support/blob/4ad8392d0787a66f9c351d9cf6c7618e18b3d0f2/lib/rspec/support.rb#L83-L89 as a reference, thank you!
@@ -314,8 +325,12 @@ class ULID
314
325
  begin
315
326
  object.class
316
327
  rescue NoMethodError
328
+ # steep can't correctly handle singeton 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
 
@@ -326,7 +341,7 @@ class ULID
326
341
  else
327
342
  name || fallback
328
343
  end
329
- end
344
+ end)
330
345
 
331
346
  # @api private
332
347
  # @param [Integer] milliseconds
@@ -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
@@ -390,7 +406,8 @@ class ULID
390
406
  def eql?(other)
391
407
  equal?(other) || (ULID === other && @integer == other.to_i)
392
408
  end
393
- alias_method :==, :eql?
409
+ # @dynamic ==
410
+ alias_method(:==, :eql?)
394
411
 
395
412
  # @return [Boolean]
396
413
  def ===(other)
@@ -420,22 +437,22 @@ class ULID
420
437
 
421
438
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer)]
422
439
  def timestamp_octets
423
- octets.slice(0, TIMESTAMP_OCTETS_LENGTH)
440
+ octets.slice(0, TIMESTAMP_OCTETS_LENGTH) || raise(UnexpectedError)
424
441
  end
425
442
 
426
443
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
427
444
  def randomness_octets
428
- octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH)
445
+ octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH) || raise(UnexpectedError)
429
446
  end
430
447
 
431
448
  # @return [String]
432
449
  def timestamp
433
- @timestamp ||= to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze
450
+ @timestamp ||= (to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
434
451
  end
435
452
 
436
453
  # @return [String]
437
454
  def randomness
438
- @randomness ||= to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze
455
+ @randomness ||= (to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze || raise(UnexpectedError))
439
456
  end
440
457
 
441
458
  # @note Providing for rough operations. The keys and values is not fixed.
@@ -461,7 +478,8 @@ class ULID
461
478
  ULID.from_integer(succ_int)
462
479
  end
463
480
  end
464
- alias_method :next, :succ
481
+ # @dynamic next
482
+ alias_method(:next, :succ)
465
483
 
466
484
  # @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
467
485
  def pred
@@ -513,7 +531,7 @@ class ULID
513
531
  self
514
532
  end
515
533
 
516
- undef_method :instance_variable_set
534
+ undef_method(:instance_variable_set)
517
535
 
518
536
  private
519
537
 
@@ -525,13 +543,13 @@ class ULID
525
543
  end
526
544
  end
527
545
 
528
- require_relative 'ulid/version'
529
- require_relative 'ulid/crockford_base32'
530
- require_relative 'ulid/monotonic_generator'
546
+ require_relative('ulid/version')
547
+ require_relative('ulid/crockford_base32')
548
+ require_relative('ulid/monotonic_generator')
531
549
 
532
550
  class ULID
533
551
  MIN = parse('00000000000000000000000000').freeze
534
552
  MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
535
553
 
536
- private_constant :TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR
554
+ private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR, :CROCKFORD_BASE32_ENCODING_STRING)
537
555
  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?
@@ -318,7 +318,7 @@ class ULID < Object
318
318
  # # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
319
319
  # ```
320
320
  def self.sample: (?period: period) -> ULID
321
- | (Integer number, ?period: period) -> Array[ULID]
321
+ | (Integer number, ?period: period?) -> Array[ULID]
322
322
  def self.valid?: (untyped) -> bool
323
323
 
324
324
  # Returns normalized string
@@ -409,7 +409,9 @@ class ULID < Object
409
409
  # ulid.to_i #=> 1957909092946624190749577070267409738
410
410
  # ```
411
411
  def to_i: -> Integer
412
- alias hash to_i
412
+
413
+ # Returns integer for making as a hash key use-case
414
+ def hash: -> Integer
413
415
 
414
416
  # Basically same as String based sort.
415
417
  #
@@ -462,7 +464,7 @@ class ULID < Object
462
464
  # Intentionally avoiding to use `Record type` ref: https://github.com/ruby/rbs/blob/4fb4c33b2325d1a73d79ff7aaeb49f21cec1e0e5/docs/syntax.md#record-type
463
465
  # Because the returning values are not fixed.
464
466
  def patterns: -> Hash[Symbol, Regexp | String]
465
- def octets: -> octets
467
+ def octets: -> full_octets
466
468
  def timestamp_octets: -> timestamp_octets
467
469
  def randomness_octets: -> randomness_octets
468
470
 
@@ -525,7 +527,7 @@ class ULID < Object
525
527
  private
526
528
 
527
529
  # A private API. Should not be used in your code.
528
- def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> self
530
+ def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> ULID
529
531
 
530
532
  # A private API. Should not be used in your code.
531
533
  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.2
4
+ version: 0.3.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-26 00:00:00.000000000 Z
11
+ date: 2022-06-22 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
+ (ruby/rbs signatures included)\n"
17
15
  email:
18
16
  - kachick1+ruby@gmail.com
19
17
  executables: []
@@ -46,7 +44,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
46
44
  requirements:
47
45
  - - ">="
48
46
  - !ruby/object:Gem::Version
49
- version: 2.7.0
47
+ version: 2.7.2
50
48
  required_rubygems_version: !ruby/object:Gem::Requirement
51
49
  requirements:
52
50
  - - ">="