ruby-ulid 0.1.6 → 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: 6e67f6449c010a7c85ff8050a081b21e824ed876d7695a4fa22beb25f2a62534
4
- data.tar.gz: f0b0607131f8b04dd57c587746cd992a9a01070cacb53857fca6efb7dd11fe45
3
+ metadata.gz: 358b03a503f8a12c87f3f7daaf3b0acf6e55dc44f1aba7a38b9f9a38985c2c68
4
+ data.tar.gz: 15d25d443f4826b07fc9a74b092e35b9cd60dc34cc3615f03fee619d4fc39445
5
5
  SHA512:
6
- metadata.gz: 5c1e3540e8e4f75c92fbc8347d2898fe8f8f233830a878c67462f8f9c9f065c969a9df8dd93e11bd27baa7a5cb7c1cb1eaf2fdf1e7dba5f5833e6295cd9d083f
7
- data.tar.gz: cd70d28d9cba7270ddf61dcf46bba1c4a04328d828ba7ed32a39c68c01f460b36f9f85da74fdb1d1b742309fdffb29d118cc8bc238c1744b61dbdc3a32255475
6
+ metadata.gz: 5b18d13597bd2f3dcd85a15a26d9086fb9a80b91775292c9da82d93144b6abd37585996f1bf64dfc4ac12b6d6d49683012dfea8e04624005358459667864114c
7
+ data.tar.gz: d987aa9b60717577fae96eef68fca76b2395f11a580df4863a69dd0931d955e4cf68608c101959db18f5e3a26031fbe284ff90a0d107fa84bbebb63949bba66b
data/README.md CHANGED
@@ -1,17 +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)
14
- [![Gem Version](https://badge.fury.io/rb/ruby-ulid.png)](http://badge.fury.io/rb/ruby-ulid)
15
+ ![ULIDlogo](./assets/logo.png)
15
16
 
16
17
  ## Universally Unique Lexicographically Sortable Identifier
17
18
 
@@ -37,7 +38,7 @@ Instead, herein is proposed ULID:
37
38
 
38
39
  ### Install
39
40
 
40
- Require Ruby 2.6 or later
41
+ Require Ruby 2.7 or later
41
42
 
42
43
  This command will install the latest version into your environment
43
44
 
@@ -46,10 +47,10 @@ $ gem install ruby-ulid
46
47
  Should be installed!
47
48
  ```
48
49
 
49
- Add this line to your application/library's `Gemfile` is needed in basic use-case
50
+ Add this line to your Gemfile`.
50
51
 
51
52
  ```ruby
52
- gem 'ruby-ulid', '>= 0.1.6', '< 0.2.0'
53
+ gem('ruby-ulid', '~> 0.2.2')
53
54
  ```
54
55
 
55
56
  ### Generator and Parser
@@ -170,8 +171,7 @@ exclude_end = ULID.range(time1...time2) #=> The end of `Range[ULID]` will be the
170
171
 
171
172
  # Below patterns are acceptable
172
173
  pinpointing = ULID.range(time1..time1) #=> This will match only for all IDs in `time1`
173
- # until_the_end = ULID.range(..time1) #=> This will match only for all IDs upto `time1` (The `nil` starting `Range` can be used since Ruby 2.7)
174
- until_the_end = ULID.range(ULID.min.to_time..time1) #=> This is same as above for Ruby 2.6
174
+ until_the_end = ULID.range(..time1) #=> This will match only for all IDs upto `time1`
175
175
  until_the_ulid_limit = ULID.range(time1..) # This will match only for all IDs from `time1` to max value of the ULID limit
176
176
 
177
177
  # So you can use the generated range objects as below
@@ -401,7 +401,7 @@ So you can replace the code as below
401
401
  +ULID.generate.to_s
402
402
  ```
403
403
 
404
- NOTE: It had crucial issue for handling precision, in version before `1.3.0`, when you extract timestamps from old generated ULIDs, it might be not accurate value.
404
+ NOTE: In version before `1.3.0`, timestamps might not be correct value.
405
405
 
406
406
  1. [Sort order does not respect millisecond ordering](https://github.com/rafaelsales/ulid/issues/22)
407
407
  1. [Fixed in this PR](https://github.com/rafaelsales/ulid/pull/23)
@@ -425,11 +425,12 @@ Major methods can be replaced as below.
425
425
  +ULID.max(time).to_s
426
426
  ```
427
427
 
428
- NOTE: It is still having precision issue similar as `ulid gem` in the both generator and parser. I sent PRs.
428
+ NOTE: In version before `1.0.2`, timestamps might not be correct value.
429
429
 
430
430
  1. [Parsed time object has more than milliseconds](https://github.com/abachman/ulid-ruby/issues/3)
431
431
  1. [Fix to handle timestamp precision in parser](https://github.com/abachman/ulid-ruby/pull/5)
432
432
  1. [Fix to handle timestamp precision in generator](https://github.com/abachman/ulid-ruby/pull/4)
433
+ 1. [Released in 1.0.2](https://github.com/abachman/ulid-ruby/compare/v1.0.0...v1.0.2)
433
434
 
434
435
  ### Compare performance with them
435
436
 
@@ -437,6 +438,18 @@ See [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
437
438
 
438
439
  The results are not something to be proud of.
439
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
+
440
453
  ## References
441
454
 
442
455
  - [Repository](https://github.com/kachick/ruby-ulid)
@@ -445,4 +458,5 @@ The results are not something to be proud of.
445
458
 
446
459
  ## Note
447
460
 
448
- - 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.1.6'
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,13 +217,9 @@ 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
- if RUBY_VERSION >= '2.7'
212
- time.floor(3)
213
- else
214
- Time.at(0, milliseconds_from_time(time), :millisecond)
215
- end
222
+ time.floor(3)
216
223
  end
217
224
 
218
225
  # @api private
@@ -224,9 +231,9 @@ class ULID
224
231
  # @api private
225
232
  # @param [Time] time
226
233
  # @return [Integer]
227
- private_class_method def self.milliseconds_from_time(time)
234
+ private_class_method(def self.milliseconds_from_time(time)
228
235
  (time.to_r * 1000).to_i
229
- end
236
+ end)
230
237
 
231
238
  # @api private
232
239
  # @param [Time, Integer] moment
@@ -238,24 +245,24 @@ class ULID
238
245
  when Time
239
246
  milliseconds_from_time(moment)
240
247
  else
241
- raise ArgumentError, '`moment` should be a `Time` or `Integer as milliseconds`'
248
+ raise(ArgumentError, '`moment` should be a `Time` or `Integer as milliseconds`')
242
249
  end
243
250
  end
244
251
 
245
252
  # @return [Integer]
246
- private_class_method def self.reasonable_entropy
253
+ private_class_method(def self.reasonable_entropy
247
254
  SecureRandom.random_number(MAX_ENTROPY)
248
- end
255
+ end)
249
256
 
250
257
  # @param [String, #to_str] string
251
258
  # @return [ULID]
252
259
  # @raise [ParserError] if the given format is not correct for ULID specs
253
260
  def self.parse(string)
254
261
  string = String.try_convert(string)
255
- raise ArgumentError, 'ULID.parse takes only strings' unless string
262
+ raise(ArgumentError, 'ULID.parse takes only strings') unless string
256
263
 
257
264
  unless STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string)
258
- 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}`")
259
266
  end
260
267
 
261
268
  from_integer(CrockfordBase32.decode(string))
@@ -266,7 +273,7 @@ class ULID
266
273
  # @raise [ParserError] if the given format is not correct for ULID specs, even if ignored `orthographical variants of the format`
267
274
  def self.normalize(string)
268
275
  string = String.try_convert(string)
269
- raise ArgumentError, 'ULID.normalize takes only strings' unless string
276
+ raise(ArgumentError, 'ULID.normalize takes only strings') unless string
270
277
 
271
278
  normalized_in_crockford = CrockfordBase32.normalize(string)
272
279
  # Ensure the ULID correctness, because CrockfordBase32 does not always mean to satisfy ULID format
@@ -302,14 +309,14 @@ class ULID
302
309
  else
303
310
  object_class_name = safe_get_class_name(object)
304
311
  converted_class_name = safe_get_class_name(converted)
305
- 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})")
306
313
  end
307
314
  end
308
315
  end
309
316
 
310
317
  # @param [BasicObject] object
311
318
  # @return [String]
312
- private_class_method def self.safe_get_class_name(object)
319
+ private_class_method(def self.safe_get_class_name(object)
313
320
  fallback = 'UnknownObject'
314
321
 
315
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!
@@ -318,8 +325,12 @@ class ULID
318
325
  begin
319
326
  object.class
320
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
321
332
  singleton_class = class << object; self; end
322
- singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) }
333
+ (Class === singleton_class) ? singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) } : fallback
323
334
  end
324
335
  )
325
336
 
@@ -330,7 +341,7 @@ class ULID
330
341
  else
331
342
  name || fallback
332
343
  end
333
- end
344
+ end)
334
345
 
335
346
  # @api private
336
347
  # @param [Integer] milliseconds
@@ -339,10 +350,10 @@ class ULID
339
350
  # @raise [OverflowError] if the given value is larger than the ULID limit
340
351
  # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
341
352
  def self.from_milliseconds_and_entropy(milliseconds:, entropy:)
342
- raise ArgumentError, 'milliseconds and entropy should be an `Integer`' unless Integer === milliseconds && Integer === entropy
343
- raise OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}" unless milliseconds <= MAX_MILLISECONDS
344
- raise OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}" unless entropy <= MAX_ENTROPY
345
- 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?
346
357
 
347
358
  n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
348
359
  n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
@@ -351,7 +362,8 @@ class ULID
351
362
  new(milliseconds: milliseconds, entropy: entropy, integer: integer)
352
363
  end
353
364
 
354
- attr_reader :milliseconds, :entropy
365
+ # @dynamic milliseconds, entropy
366
+ attr_reader(:milliseconds, :entropy)
355
367
 
356
368
  # @api private
357
369
  # @param [Integer] milliseconds
@@ -374,7 +386,11 @@ class ULID
374
386
  def to_i
375
387
  @integer
376
388
  end
377
- alias_method :hash, :to_i
389
+
390
+ # @return [Integer]
391
+ def hash
392
+ [ULID, @integer].hash
393
+ end
378
394
 
379
395
  # @return [Integer, nil]
380
396
  def <=>(other)
@@ -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)
@@ -406,13 +423,7 @@ class ULID
406
423
 
407
424
  # @return [Time]
408
425
  def to_time
409
- @time ||= begin
410
- if RUBY_VERSION >= '2.7'
411
- Time.at(0, @milliseconds, :millisecond, in: 'UTC').freeze
412
- else
413
- Time.at(0, @milliseconds, :millisecond).utc.freeze
414
- end
415
- end
426
+ @time ||= Time.at(0, @milliseconds, :millisecond, in: 'UTC').freeze
416
427
  end
417
428
 
418
429
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
@@ -426,22 +437,22 @@ class ULID
426
437
 
427
438
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer)]
428
439
  def timestamp_octets
429
- octets.slice(0, TIMESTAMP_OCTETS_LENGTH)
440
+ octets.slice(0, TIMESTAMP_OCTETS_LENGTH) || raise(UnexpectedError)
430
441
  end
431
442
 
432
443
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
433
444
  def randomness_octets
434
- octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH)
445
+ octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH) || raise(UnexpectedError)
435
446
  end
436
447
 
437
448
  # @return [String]
438
449
  def timestamp
439
- @timestamp ||= to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze
450
+ @timestamp ||= (to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
440
451
  end
441
452
 
442
453
  # @return [String]
443
454
  def randomness
444
- @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))
445
456
  end
446
457
 
447
458
  # @note Providing for rough operations. The keys and values is not fixed.
@@ -467,7 +478,8 @@ class ULID
467
478
  ULID.from_integer(succ_int)
468
479
  end
469
480
  end
470
- alias_method :next, :succ
481
+ # @dynamic next
482
+ alias_method(:next, :succ)
471
483
 
472
484
  # @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
473
485
  def pred
@@ -519,7 +531,7 @@ class ULID
519
531
  self
520
532
  end
521
533
 
522
- undef_method :instance_variable_set
534
+ undef_method(:instance_variable_set)
523
535
 
524
536
  private
525
537
 
@@ -531,13 +543,13 @@ class ULID
531
543
  end
532
544
  end
533
545
 
534
- require_relative 'ulid/version'
535
- require_relative 'ulid/crockford_base32'
536
- require_relative 'ulid/monotonic_generator'
546
+ require_relative('ulid/version')
547
+ require_relative('ulid/crockford_base32')
548
+ require_relative('ulid/monotonic_generator')
537
549
 
538
550
  class ULID
539
551
  MIN = parse('00000000000000000000000000').freeze
540
552
  MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
541
553
 
542
- 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)
543
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?
@@ -185,8 +185,7 @@ class ULID < Object
185
185
  #
186
186
  # # Below patterns are acceptable
187
187
  # pinpointing = ULID.range(time1..time1) #=> This will match only for all IDs in `time1`
188
- # until_the_end = ULID.range(..time1) #=> This will match only for all IDs upto `time1` (The `nil` starting `Range` can be used since Ruby 2.7)
189
- # until_the_end = ULID.range(ULID.min.to_time..time1) #=> This is same as above for Ruby 2.6
188
+ # until_the_end = ULID.range(..time1) #=> This will match only for all IDs upto `time1`
190
189
  # until_the_ulid_limit = ULID.range(time1..) # This will match only for all IDs from `time1` to max value of the ULID limit
191
190
  #
192
191
  # # So you can use the generated range objects as below
@@ -319,7 +318,7 @@ class ULID < Object
319
318
  # # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
320
319
  # ```
321
320
  def self.sample: (?period: period) -> ULID
322
- | (Integer number, ?period: period) -> Array[ULID]
321
+ | (Integer number, ?period: period?) -> Array[ULID]
323
322
  def self.valid?: (untyped) -> bool
324
323
 
325
324
  # Returns normalized string
@@ -410,7 +409,9 @@ class ULID < Object
410
409
  # ulid.to_i #=> 1957909092946624190749577070267409738
411
410
  # ```
412
411
  def to_i: -> Integer
413
- alias hash to_i
412
+
413
+ # Returns integer for making as a hash key use-case
414
+ def hash: -> Integer
414
415
 
415
416
  # Basically same as String based sort.
416
417
  #
@@ -463,7 +464,7 @@ class ULID < Object
463
464
  # Intentionally avoiding to use `Record type` ref: https://github.com/ruby/rbs/blob/4fb4c33b2325d1a73d79ff7aaeb49f21cec1e0e5/docs/syntax.md#record-type
464
465
  # Because the returning values are not fixed.
465
466
  def patterns: -> Hash[Symbol, Regexp | String]
466
- def octets: -> octets
467
+ def octets: -> full_octets
467
468
  def timestamp_octets: -> timestamp_octets
468
469
  def randomness_octets: -> randomness_octets
469
470
 
@@ -526,7 +527,7 @@ class ULID < Object
526
527
  private
527
528
 
528
529
  # A private API. Should not be used in your code.
529
- def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> self
530
+ def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> ULID
530
531
 
531
532
  # A private API. Should not be used in your code.
532
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.1.6
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenichi Kamiya
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-05 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: []
@@ -37,7 +35,8 @@ metadata:
37
35
  homepage_uri: https://github.com/kachick/ruby-ulid
38
36
  source_code_uri: https://github.com/kachick/ruby-ulid
39
37
  bug_tracker_uri: https://github.com/kachick/ruby-ulid/issues
40
- post_install_message:
38
+ rubygems_mfa_required: 'true'
39
+ post_install_message:
41
40
  rdoc_options: []
42
41
  require_paths:
43
42
  - lib
@@ -45,15 +44,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
45
44
  requirements:
46
45
  - - ">="
47
46
  - !ruby/object:Gem::Version
48
- version: 2.6.0
47
+ version: 2.7.2
49
48
  required_rubygems_version: !ruby/object:Gem::Requirement
50
49
  requirements:
51
50
  - - ">="
52
51
  - !ruby/object:Gem::Version
53
52
  version: '0'
54
53
  requirements: []
55
- rubygems_version: 3.2.15
56
- signing_key:
54
+ rubygems_version: 3.3.7
55
+ signing_key:
57
56
  specification_version: 4
58
57
  summary: ULID manipulation library
59
58
  test_files: []