ruby-ulid 0.1.6 → 0.3.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: 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: []