ruby-ulid 0.0.14 → 0.0.15

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: fa89ecbb2a09940666b57e690d43a985872bb85f40b2b1175c79a762d79c8e45
4
- data.tar.gz: 2404fe5e1efa77899262fbf8979529fc474e44ffa0a8572ee3cf71eb1caf941a
3
+ metadata.gz: 6a620e80f82e91c778329ccf7da8cca96bda4b12e047610b9543768c5ec85d22
4
+ data.tar.gz: ea28469d63348758f52e1460b5330ce177ad2281e63060800a0669054758f3ee
5
5
  SHA512:
6
- metadata.gz: 1a86224846b27985d641bf485f952583c73dafc87a0ca8f65744cfdfa35371a6f5cfedb4246e7839da6e51fdcfd53632f20057c399038f3399f2986a9bf20085
7
- data.tar.gz: 24daff089a2b0564a2e7a93c34fef4d44420d1e0bbc6a054c5bcdfac02986c66204515bc085a7dc6499257692682217a888d8a04e04a7ae8de36e884cc182965
6
+ metadata.gz: acb07ae797c3a06a6626d34e15dc1056a30586e8bc151a3773770259d2f4047d0708593e702fbd67a7609758165f20a15f208fede068717a5555733535ed4bde
7
+ data.tar.gz: 57c9819d86f2a0f002cf3b9a7b76b3a36bc412fa874d50136ec296f1217fb905fa19feb52de2c9d5250547da384580cf882121f0e331054cd665bd113d1f10e3
data/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  A handy `ULID` library
4
4
 
5
- The `ULID` spec is defined on [ulid/spec](https://github.com/ulid/spec).
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
6
  This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around the ULID.
7
- Also providing rbs signature files.
7
+ Also providing [ruby/rbs](https://github.com/ruby/rbs) signature files.
8
8
 
9
9
  ---
10
10
 
@@ -28,7 +28,7 @@ Instead, herein is proposed ULID:
28
28
  - 1.21e+24 unique ULIDs per millisecond
29
29
  - Lexicographically sortable!
30
30
  - Canonically encoded as a 26 character string, as opposed to the 36 character UUID
31
- - Uses Crockford's base32 for better efficiency and readability (5 bits per character)
31
+ - Uses [Crockford's base32](https://www.crockford.com/base32.html) for better efficiency and readability (5 bits per character) # See also exists issues in [Note](#note)
32
32
  - Case insensitive
33
33
  - No special characters (URL safe)
34
34
  - Monotonic sort order (correctly detects and handles the same millisecond)
@@ -98,19 +98,19 @@ ulids.uniq(&:to_time).size #=> 35 (the size is not fixed, might be changed in en
98
98
  ulids.sort == ulids #=> false
99
99
  ```
100
100
 
101
- If you want to prefer `sortable` rather than the `randomness`, Use `MonotonicGenerator` instead. It is called as [Monotonicity](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#monotonicity) on the spec.
101
+ If you want to ensure `sortable`, Use `MonotonicGenerator` instead. It is called as [Monotonicity](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#monotonicity) on the spec.
102
102
  (Though it starts with new random value when changed the timestamp)
103
103
 
104
104
  ```ruby
105
105
  monotonic_generator = ULID::MonotonicGenerator.new
106
- monotonic_ulids = 10000.times.map do
106
+ ulids = 10000.times.map do
107
107
  monotonic_generator.generate
108
108
  end
109
- sample_ulids_by_the_time = monotonic_ulids.uniq(&:to_time)
109
+ sample_ulids_by_the_time = ulids.uniq(&:to_time)
110
110
  sample_ulids_by_the_time.size #=> 32 (the size is not fixed, might be changed in environment)
111
111
 
112
112
  # In same milliseconds creation, it just increments the end of randomness part
113
- monotonic_ulids.take(5) #=>
113
+ ulids.take(5) #=>
114
114
  # [ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4),
115
115
  # ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK5),
116
116
  # ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK6),
@@ -125,7 +125,7 @@ sample_ulids_by_the_time.take(5) #=>
125
125
  # ULID(2021-05-02 15:23:48.920 UTC: 01F4PTVCSRBXN2H4P1EYWZ27AK),
126
126
  # ULID(2021-05-02 15:23:48.921 UTC: 01F4PTVCSSK0ASBBZARV7013F8)]
127
127
 
128
- monotonic_ulids.sort == monotonic_ulids #=> true
128
+ ulids.sort == ulids #=> true
129
129
  ```
130
130
 
131
131
  When filtering ULIDs by `Time`, we should consider to handle the precision.
@@ -209,11 +209,34 @@ ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZ
209
209
  ULID.parse('00000000000000000000000000').pred #=> nil
210
210
  ```
211
211
 
212
- UUIDv4 converter for migration use-cases. (Of course the timestamp will be useless one. Sortable benefit is lost.)
212
+ ### UUIDv4 converter for migration use-cases
213
+
214
+ `ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
215
+ The imported timestamp is meaningless. So ULID's benefit will lost
213
216
 
214
217
  ```ruby
215
- ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
216
- #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
218
+ # Basically reversible
219
+ ulid = ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39') #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
220
+ ulid.to_uuidv4 #=> "0983d0a2-ff15-4d83-8f37-7dd945b5aa39"
221
+
222
+ uuid_v4s = 10000.times.map { SecureRandom.uuid }
223
+ uuid_v4s.uniq.size == 10000 #=> Probably `true`
224
+
225
+ ulids = uuid_v4s.map { |uuid_v4| ULID.from_uuidv4(uuid_v4) }
226
+ ulids.map(&:to_uuidv4) == uuid_v4s #=> **Probably** `true` except below examples.
227
+
228
+ # NOTE: Some boundary values are not reversible. See below.
229
+
230
+ ULID.min.to_uuidv4 #=> "00000000-0000-4000-8000-000000000000"
231
+ ULID.max.to_uuidv4 #=> "ffffffff-ffff-4fff-bfff-ffffffffffff"
232
+
233
+ # These importing results are same as https://github.com/ahawker/ulid/tree/96bdb1daad7ce96f6db8c91ac0410b66d2e1c4c1 on CPython 3.9.4
234
+ reversed_min = ULID.from_uuidv4('00000000-0000-4000-8000-000000000000') #=> ULID(1970-01-01 00:00:00.000 UTC: 00000000008008000000000000)
235
+ reversed_max = ULID.from_uuidv4('ffffffff-ffff-4fff-bfff-ffffffffffff') #=> ULID(10889-08-02 05:31:50.655 UTC: 7ZZZZZZZZZ9ZZVZZZZZZZZZZZZ)
236
+
237
+ # But they are not reversible! Need to consider this issue in https://github.com/kachick/ruby-ulid/issues/76
238
+ ULID.min == reversed_min #=> false
239
+ ULID.max == reversed_max #=> false
217
240
  ```
218
241
 
219
242
  ## References
@@ -221,5 +244,8 @@ ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
221
244
  - [Repository](https://github.com/kachick/ruby-ulid)
222
245
  - [API documents](https://kachick.github.io/ruby-ulid/)
223
246
  - [ulid/spec](https://github.com/ulid/spec)
224
- - [Another choices are UUIDv6, UUIDv7, UUIDv8. 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)
225
- - Current parser/validator/matcher implementation aims `strict`, It might be changed in [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ruby-ulid#57](https://github.com/kachick/ruby-ulid/issues/57).
247
+
248
+ ## Note
249
+
250
+ - 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)
251
+ - Current parser/validator/matcher aims to cover `subset of Crockford's base32`. Suggesting it in [ulid/spec#57](https://github.com/ulid/spec/pull/57). Be that as it may, I might provide special handler or converter for the exception in [ruby-ulid#57](https://github.com/kachick/ruby-ulid/issues/57) and/or [ruby-ulid#78](https://github.com/kachick/ruby-ulid/issues/78)
data/lib/ulid.rb CHANGED
@@ -35,7 +35,7 @@ class ULID
35
35
  STRICT_PATTERN = /\A#{PATTERN.source}\z/i.freeze
36
36
 
37
37
  # Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
38
- 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
38
+ 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
39
39
 
40
40
  # Same as Time#inspect since Ruby 2.7, just to keep backward compatibility
41
41
  # @see https://bugs.ruby-lang.org/issues/15958
@@ -51,13 +51,13 @@ class ULID
51
51
  # @param [Integer, Time] moment
52
52
  # @return [ULID]
53
53
  def self.min(moment: 0)
54
- generate(moment: moment, entropy: 0)
54
+ 0.equal?(moment) ? MIN : generate(moment: moment, entropy: 0)
55
55
  end
56
56
 
57
57
  # @param [Integer, Time] moment
58
58
  # @return [ULID]
59
59
  def self.max(moment: MAX_MILLISECONDS)
60
- generate(moment: moment, entropy: MAX_ENTROPY)
60
+ MAX_MILLISECONDS.equal?(moment) ? MAX : generate(moment: moment, entropy: MAX_ENTROPY)
61
61
  end
62
62
 
63
63
  # @deprecated This method actually changes class state. Use {ULID::MonotonicGenerator} instead.
@@ -121,10 +121,11 @@ class ULID
121
121
  new milliseconds: milliseconds, entropy: entropy
122
122
  end
123
123
 
124
- # @param [Range<Time>] time_range
124
+ # @param [Range<Time>, Range<nil>] time_range
125
125
  # @return [Range<ULID>]
126
+ # @raise [ArgumentError] if the given time_range is not a `Range[Time]` or `Range[nil]`
126
127
  def self.range(time_range)
127
- raise ArgumentError, 'ULID.range takes only Range[Time]' unless time_range.kind_of?(Range)
128
+ raise argument_error_for_range_building(time_range) unless time_range.kind_of?(Range)
128
129
  begin_time, end_time, exclude_end = time_range.begin, time_range.end, time_range.exclude_end?
129
130
 
130
131
  case begin_time
@@ -133,7 +134,7 @@ class ULID
133
134
  when nil
134
135
  begin_ulid = min
135
136
  else
136
- raise ArgumentError, 'ULID.range takes only Range[Time]'
137
+ raise argument_error_for_range_building(time_range)
137
138
  end
138
139
 
139
140
  case end_time
@@ -148,9 +149,12 @@ class ULID
148
149
  end_ulid = max
149
150
  exclude_end = false
150
151
  else
151
- raise ArgumentError, 'ULID.range takes only Range[Time]'
152
+ raise argument_error_for_range_building(time_range)
152
153
  end
153
154
 
155
+ begin_ulid.freeze
156
+ end_ulid.freeze
157
+
154
158
  Range.new(begin_ulid, end_ulid, exclude_end)
155
159
  end
156
160
 
@@ -241,6 +245,11 @@ class ULID
241
245
  num
242
246
  end
243
247
 
248
+ # @return [ArgumentError]
249
+ private_class_method def self.argument_error_for_range_building(argument)
250
+ ArgumentError.new "ULID.range takes only `Range[Time]` or `Range[nil]`, given: #{argument.inspect}"
251
+ end
252
+
244
253
  attr_reader :milliseconds, :entropy
245
254
 
246
255
  # @api private
@@ -364,15 +373,21 @@ class ULID
364
373
  @pred ||= self.class.from_integer(pre_int)
365
374
  end
366
375
 
376
+ # @return [String]
377
+ def to_uuidv4
378
+ @uuidv4 ||= begin
379
+ # This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
380
+ array = octets.pack('C*').unpack('NnnnnN')
381
+ array[2] = (array[2] & 0x0fff) | 0x4000
382
+ array[3] = (array[3] & 0x3fff) | 0x8000
383
+ ('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
384
+ end
385
+ end
386
+
367
387
  # @return [self]
368
388
  def freeze
369
- # Evaluate all caching
370
- inspect
371
- octets
372
- to_i
373
- succ
374
- pred
375
- strict_pattern
389
+ # Need to cache before freezing, because frozen objects can't assign instance variables
390
+ cache_all_instance_variables
376
391
  super
377
392
  end
378
393
 
@@ -382,13 +397,26 @@ class ULID
382
397
  def matchdata
383
398
  @matchdata ||= STRICT_PATTERN.match(to_s).freeze
384
399
  end
400
+
401
+ # @return [void]
402
+ def cache_all_instance_variables
403
+ inspect
404
+ octets
405
+ to_i
406
+ succ
407
+ pred
408
+ strict_pattern
409
+ to_uuidv4
410
+ end
385
411
  end
386
412
 
387
413
  require_relative 'ulid/version'
388
414
  require_relative 'ulid/monotonic_generator'
389
415
 
390
416
  class ULID
417
+ MIN = parse('00000000000000000000000000').freeze
418
+ MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
391
419
  MONOTONIC_GENERATOR = MonotonicGenerator.new
392
420
 
393
- private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN
421
+ private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN, :MIN, :MAX
394
422
  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.0.14'
5
+ VERSION = '0.0.15'
6
6
  end
data/sig/ulid.rbs CHANGED
@@ -16,8 +16,11 @@ class ULID
16
16
  STRICT_PATTERN: Regexp
17
17
  UUIDV4_PATTERN: Regexp
18
18
  MONOTONIC_GENERATOR: MonotonicGenerator
19
+ MIN: ULID
20
+ MAX: ULID
19
21
  include Comparable
20
22
 
23
+ # The `moment` is a `Time` or `Intger of the milliseconds`
21
24
  type moment = Time | Integer
22
25
 
23
26
  class Error < StandardError
@@ -56,6 +59,7 @@ class ULID
56
59
  @next: ULID?
57
60
  @pattern: Regexp?
58
61
  @strict_pattern: Regexp?
62
+ @uuidv4: String?
59
63
  @matchdata: MatchData?
60
64
 
61
65
  def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
@@ -63,7 +67,7 @@ class ULID
63
67
  def self.current_milliseconds: -> Integer
64
68
  def self.milliseconds_from_time: (Time time) -> Integer
65
69
  def self.milliseconds_from_moment: (moment moment) -> Integer
66
- def self.range: (Range[Time] time_range) -> Range[ULID]
70
+ def self.range: (Range[Time] | Range[nil] time_range) -> Range[ULID]
67
71
  def self.floor: (Time time) -> Time
68
72
  def self.reasonable_entropy: -> Integer
69
73
  def self.parse: (String string) -> ULID
@@ -96,11 +100,14 @@ class ULID
96
100
  def octets: -> octets
97
101
  def timestamp_octets: -> timestamp_octets
98
102
  def randomness_octets: -> randomness_octets
103
+ def to_uuidv4: -> String
99
104
  def next: -> ULID?
100
105
  alias succ next
101
106
  def pred: -> ULID?
102
107
  def freeze: -> self
103
108
 
104
109
  private
110
+ def self.argument_error_for_range_building: (untyped argument) -> ArgumentError
105
111
  def matchdata: -> MatchData
112
+ def cache_all_instance_variables: -> void
106
113
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-ulid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.14
4
+ version: 0.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenichi Kamiya
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-03 00:00:00.000000000 Z
11
+ date: 2021-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: integer-base
@@ -96,7 +96,6 @@ extra_rdoc_files: []
96
96
  files:
97
97
  - LICENSE
98
98
  - README.md
99
- - Steepfile
100
99
  - lib/ulid.rb
101
100
  - lib/ulid/monotonic_generator.rb
102
101
  - lib/ulid/version.rb
@@ -123,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
122
  - !ruby/object:Gem::Version
124
123
  version: '0'
125
124
  requirements: []
126
- rubygems_version: 3.1.4
125
+ rubygems_version: 3.2.15
127
126
  signing_key:
128
127
  specification_version: 4
129
128
  summary: A handy ULID library
data/Steepfile DELETED
@@ -1,7 +0,0 @@
1
- target :lib do
2
- signature 'sig'
3
-
4
- check 'lib'
5
-
6
- library 'securerandom'
7
- end