ruby-ulid 0.0.19 → 0.1.4

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: 646d2a4b433ffbe28d4801483d3f1c43cfb5839bf5f80e759447304a1c8dad52
4
- data.tar.gz: 1e4ddc37266eb09f3635ba5509e66e6ff93e15fad3e6574fb3b50e8ad9322b10
3
+ metadata.gz: 2a390455a27395fd77ef398b0613e72b321b96d44e8454c8ab9346ae15eddfdc
4
+ data.tar.gz: 1150658ae26cc591f3946898ccb356ce1c6135292de0600ef0604aceb7a00554
5
5
  SHA512:
6
- metadata.gz: 9a564dbad8c3c88b729353826c599e618cd8963806759ce8a6bf041a95aef60044c8f75873560129bcef7550eec226c3bf04bcfec339c4b07fb72c3372342811
7
- data.tar.gz: 187c475f9332cb69827e2664193faa7f001cb14a5709b3c28600286ac9e6c1fb3176e14e3a7ae3a658d232af6157a3dea83ad992cee75db966ff80213c3a0e52
6
+ metadata.gz: 4ffb5c69ec1327bacbb54b4afdd6718f8cc9b5afd450f739588d8e414c645eca9c1090c77efb464293f0e8e1b960cf4df75289785d3cd7b489404e31c24ecc8a
7
+ data.tar.gz: bf6a08216e0745d0727c356a5a239126bac8518f8fbb57f295b417e14ed0878c8f0c32774b83137b1feb58d8230140492cb469bb18dc9d5df5db5aa088410ffb
data/README.md CHANGED
@@ -10,7 +10,7 @@ Also providing [ruby/rbs](https://github.com/ruby/rbs) signature files.
10
10
 
11
11
  ![ULIDlogo](https://raw.githubusercontent.com/kachick/ruby-ulid/main/logo.png)
12
12
 
13
- ![Build Status](https://github.com/kachick/ruby-ulid/actions/workflows/test.yml/badge.svg?branch=main)
13
+ ![Build Status](https://github.com/kachick/ruby-ulid/actions/workflows/test_behaviors.yml/badge.svg?branch=main)
14
14
  [![Gem Version](https://badge.fury.io/rb/ruby-ulid.png)](http://badge.fury.io/rb/ruby-ulid)
15
15
 
16
16
  ## Universally Unique Lexicographically Sortable Identifier
@@ -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](https://www.crockford.com/base32.html) for better efficiency and readability (5 bits per character) # See also exists issues in [Note](#note)
31
+ - Uses [Crockford's base32](https://www.crockford.com/base32.html) for better efficiency and readability (5 bits per character)
32
32
  - Case insensitive
33
33
  - No special characters (URL safe)
34
34
  - Monotonic sort order (correctly detects and handles the same millisecond)
@@ -49,7 +49,7 @@ Should be installed!
49
49
  Add this line to your application/library's `Gemfile` is needed in basic use-case
50
50
 
51
51
  ```ruby
52
- gem 'ruby-ulid', '0.0.19'
52
+ gem 'ruby-ulid', '>= 0.1.4', '< 0.2.0'
53
53
  ```
54
54
 
55
55
  ### Generator and Parser
@@ -146,6 +146,8 @@ sample_ulids_by_the_time.take(5) #=>
146
146
  ulids.sort == ulids #=> true
147
147
  ```
148
148
 
149
+ Same generator does not generate duplicated ULIDs even in multi threads environment. It is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255)
150
+
149
151
  ### Filtering IDs with `Time`
150
152
 
151
153
  `ULID` can be element of the `Range`. If you generated the IDs in monotonic generator, ID based filtering is easy and reliable
@@ -168,7 +170,7 @@ exclude_end = ULID.range(time1...time2) #=> The end of `Range[ULID]` will be the
168
170
 
169
171
  # Below patterns are acceptable
170
172
  pinpointing = ULID.range(time1..time1) #=> This will match only for all IDs in `time1`
171
- 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)
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)
172
174
  until_the_end = ULID.range(ULID.min.to_time..time1) #=> This is same as above for Ruby 2.6
173
175
  until_the_ulid_limit = ULID.range(time1..) # This will match only for all IDs from `time1` to max value of the ULID limit
174
176
 
@@ -243,13 +245,15 @@ ULID.parse('01F4GNBXW1AM2KWW52PVT3ZY9X').patterns
243
245
 
244
246
  `ULID.min` and `ULID.max` return termination values for ULID spec.
245
247
 
248
+ It can take `Time` instance as an optional argument. Then returns min/max ID that has limit of randomness part in the time.
249
+
246
250
  ```ruby
247
251
  ULID.min #=> ULID(1970-01-01 00:00:00.000 UTC: 00000000000000000000000000)
248
252
  ULID.max #=> ULID(10889-08-02 05:31:50.655 UTC: 7ZZZZZZZZZZZZZZZZZZZZZZZZZ)
249
253
 
250
254
  time = Time.at(946684800, Rational('123456.789')).utc #=> 2000-01-01 00:00:00.123456789 UTC
251
- ULID.min(moment: time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
252
- ULID.max(moment: time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
255
+ ULID.min(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
256
+ ULID.max(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
253
257
  ```
254
258
 
255
259
  `ULID#next` and `ULID#succ` returns next(successor) ULID.
@@ -309,7 +313,7 @@ ulids.take(10)
309
313
  # ULID(2021-04-29 03:18:24.152 UTC: 01F4DT4Z4RA0QV8WFQGRAG63EH),
310
314
  # ULID(2021-05-02 13:27:16.394 UTC: 01F4PM605ABF5SDVMEHBH8JJ9R)]
311
315
  ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
312
- #=>
316
+ #=>
313
317
  # [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
314
318
  # ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
315
319
  # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
@@ -322,6 +326,34 @@ ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
322
326
  # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
323
327
  ```
324
328
 
329
+ ### ULID specification ambiguity around orthographical variants of the format
330
+
331
+ I'm afraid so, we should consider [Current ULID spec](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#universally-unique-lexicographically-sortable-identifier) has `orthographical variants of the format` possibilities.
332
+
333
+ >Uses Crockford's base32 for better efficiency and readability (5 bits per character)
334
+
335
+ The original `Crockford's base32` maps `I`, `L` to `1`, `O` to `0`.
336
+ And accepts freestyle inserting `Hyphens (-)`.
337
+ To consider this patterns or not is different in each implementations.
338
+
339
+ Current parser/validator/matcher aims to cover `subset of Crockford's base32`.
340
+ I have suggested it would be clarified in [ulid/spec#57](https://github.com/ulid/spec/pull/57).
341
+
342
+ >Case insensitive
343
+
344
+ I can understand it might be considered in actual use-case.
345
+ But it is a controversial point, discussing in [ulid/spec#3](https://github.com/ulid/spec/issues/3).
346
+
347
+ Be that as it may, this gem provides API for handling the nasty possibilities.
348
+
349
+ `ULID.normalize` and `ULID.normalized?`
350
+
351
+ ```ruby
352
+ ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV"
353
+ ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false
354
+ ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true
355
+ ```
356
+
325
357
  ### UUIDv4 converter for migration use-cases
326
358
 
327
359
  `ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
@@ -331,7 +363,7 @@ The imported timestamp is meaningless. So ULID's benefit will lost.
331
363
  # Currently experimental feature, so needed to load the extension.
332
364
  require 'ulid/uuid'
333
365
 
334
- # Basically reversible
366
+ # Basically reversible
335
367
  ulid = ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39') #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
336
368
  ulid.to_uuidv4 #=> "0983d0a2-ff15-4d83-8f37-7dd945b5aa39"
337
369
 
@@ -388,9 +420,9 @@ Major methods can be replaced as below.
388
420
  -ULID.time(string)
389
421
  +ULID.parse(string).to_time
390
422
  -ULID.min_ulid_at(time)
391
- +ULID.min(moment: time).to_s
423
+ +ULID.min(time).to_s
392
424
  -ULID.max_ulid_at(time)
393
- +ULID.max(moment: time).to_s
425
+ +ULID.max(time).to_s
394
426
  ```
395
427
 
396
428
  NOTE: It is still having precision issue similar as `ulid gem` in the both generator and parser. I sent PRs.
@@ -399,6 +431,12 @@ NOTE: It is still having precision issue similar as `ulid gem` in the both gener
399
431
  1. [Fix to handle timestamp precision in parser](https://github.com/abachman/ulid-ruby/pull/5)
400
432
  1. [Fix to handle timestamp precision in generator](https://github.com/abachman/ulid-ruby/pull/4)
401
433
 
434
+ ### Compare performance with them
435
+
436
+ See [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
437
+
438
+ The results are not something to be proud of.
439
+
402
440
  ## References
403
441
 
404
442
  - [Repository](https://github.com/kachick/ruby-ulid)
@@ -408,4 +446,3 @@ NOTE: It is still having precision issue similar as `ulid gem` in the both gener
408
446
  ## Note
409
447
 
410
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)
411
- - 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
@@ -15,6 +15,7 @@ class ULID
15
15
  class Error < StandardError; end
16
16
  class OverflowError < Error; end
17
17
  class ParserError < Error; end
18
+ class UnexpectedError < Error; end
18
19
 
19
20
  # Excluded I, L, O, U, -.
20
21
  # This is the encoding patterns.
@@ -51,7 +52,7 @@ class ULID
51
52
  # @param [Integer] entropy
52
53
  # @return [ULID]
53
54
  def self.generate(moment: current_milliseconds, entropy: reasonable_entropy)
54
- new milliseconds: milliseconds_from_moment(moment), entropy: entropy
55
+ from_milliseconds_and_entropy(milliseconds: milliseconds_from_moment(moment), entropy: entropy)
55
56
  end
56
57
 
57
58
  # Short hand of `ULID.generate(moment: time)`
@@ -59,18 +60,18 @@ class ULID
59
60
  # @return [ULID]
60
61
  def self.at(time)
61
62
  raise ArgumentError, 'ULID.at takes only `Time` instance' unless Time === time
62
- new milliseconds: milliseconds_from_time(time), entropy: reasonable_entropy
63
+ from_milliseconds_and_entropy(milliseconds: milliseconds_from_time(time), entropy: reasonable_entropy)
63
64
  end
64
65
 
65
- # @param [Integer, Time] moment
66
+ # @param [Time, Integer] moment
66
67
  # @return [ULID]
67
- def self.min(moment: 0)
68
+ def self.min(moment=0)
68
69
  0.equal?(moment) ? MIN : generate(moment: moment, entropy: 0)
69
70
  end
70
71
 
71
- # @param [Integer, Time] moment
72
+ # @param [Time, Integer] moment
72
73
  # @return [ULID]
73
- def self.max(moment: MAX_MILLISECONDS)
74
+ def self.max(moment=MAX_MILLISECONDS)
74
75
  MAX_MILLISECONDS.equal?(moment) ? MAX : generate(moment: moment, entropy: MAX_ENTROPY)
75
76
  end
76
77
 
@@ -112,11 +113,11 @@ class ULID
112
113
  raise ArgumentError, 'accepts no argument or integer only' unless Integer === number
113
114
 
114
115
  if number > MAX_INTEGER || number.negative?
115
- raise ArgumentError, "given number #{number} is larger than ULID limit #{MAX_INTEGER} or negative: #{number.inspect}"
116
+ raise ArgumentError, "given number `#{number}` is larger than ULID limit `#{MAX_INTEGER}` or negative"
116
117
  end
117
118
 
118
119
  if period && (number > possibilities)
119
- raise ArgumentError, "given number #{number} is larger than given possibilities #{possibilities}"
120
+ raise ArgumentError, "given number `#{number}` is larger than given possibilities `#{possibilities}`"
120
121
  end
121
122
 
122
123
  Array.new(number) { from_integer(int_generator.call) }
@@ -139,12 +140,12 @@ class ULID
139
140
  self
140
141
  end
141
142
 
142
- # @param [Integer, #to_int] integer
143
+ # @param [Integer] integer
143
144
  # @return [ULID]
144
145
  # @raise [OverflowError] if the given integer is larger than the ULID limit
145
146
  # @raise [ArgumentError] if the given integer is negative number
146
147
  def self.from_integer(integer)
147
- integer = integer.to_int
148
+ raise ArgumentError, 'ULID.from_integer takes only `Integer`' unless Integer === integer
148
149
  raise OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}" unless integer <= MAX_INTEGER
149
150
  raise ArgumentError, "integer should not be negative: given: #{integer}" if integer.negative?
150
151
 
@@ -160,29 +161,29 @@ class ULID
160
161
 
161
162
  # @param [Range<Time>, Range<nil>, Range[ULID]] period
162
163
  # @return [Range<ULID>]
163
- # @raise [ArgumentError] if the given period is not a `Range[Time]` or `Range[nil]`
164
+ # @raise [ArgumentError] if the given period is not a `Range[Time]`, `Range[nil]` or `Range[ULID]`
164
165
  def self.range(period)
165
- raise ArgumentError, 'ULID.range takes only `Range[Time]` or `Range[nil]`' unless Range === period
166
+ raise ArgumentError, 'ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`' unless Range === period
166
167
  begin_element, end_element, exclude_end = period.begin, period.end, period.exclude_end?
167
168
  return period if self === begin_element && self === end_element
168
169
 
169
170
  case begin_element
170
171
  when Time
171
- begin_ulid = min(moment: begin_element)
172
+ begin_ulid = min(begin_element)
172
173
  when nil
173
174
  begin_ulid = MIN
174
175
  when self
175
176
  begin_ulid = begin_element
176
177
  else
177
- raise ArgumentError, "ULID.range takes only `Range[Time]` or `Range[nil]`, given: #{period.inspect}"
178
+ raise ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}"
178
179
  end
179
180
 
180
181
  case end_element
181
182
  when Time
182
183
  if exclude_end
183
- end_ulid = min(moment: end_element)
184
+ end_ulid = min(end_element)
184
185
  else
185
- end_ulid = max(moment: end_element)
186
+ end_ulid = max(end_element)
186
187
  end
187
188
  when nil
188
189
  # The end should be max and include end, because nil end means to cover endless ULIDs until the limit
@@ -191,7 +192,7 @@ class ULID
191
192
  when self
192
193
  end_ulid = end_element
193
194
  else
194
- raise ArgumentError, "ULID.range takes only `Range[Time]` or `Range[nil]`, given: #{period.inspect}"
195
+ raise ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}"
195
196
  end
196
197
 
197
198
  begin_ulid.freeze
@@ -218,6 +219,7 @@ class ULID
218
219
  milliseconds_from_time(Time.now)
219
220
  end
220
221
 
222
+ # @api private
221
223
  # @param [Time] time
222
224
  # @return [Integer]
223
225
  private_class_method def self.milliseconds_from_time(time)
@@ -238,9 +240,8 @@ class ULID
238
240
  end
239
241
  end
240
242
 
241
- # @api private
242
243
  # @return [Integer]
243
- def self.reasonable_entropy
244
+ private_class_method def self.reasonable_entropy
244
245
  SecureRandom.random_number(MAX_ENTROPY)
245
246
  end
246
247
 
@@ -259,18 +260,82 @@ class ULID
259
260
  end
260
261
 
261
262
  # @param [String, #to_str] string
262
- # @return [Boolean]
263
- def self.valid?(string)
263
+ # @return [String]
264
+ # @raise [ParserError] if the given format is not correct for ULID specs, even if ignored `orthographical variants of the format`
265
+ def self.normalize(string)
264
266
  string = String.try_convert(string)
267
+ raise ArgumentError, 'ULID.normalize takes only strings' unless string
268
+
269
+ normalized_in_crockford = CrockfordBase32.normalize(string)
270
+ # Ensure the ULID correctness, because CrockfordBase32 does not always mean to satisfy ULID format
271
+ parse(normalized_in_crockford).to_s
272
+ end
273
+
274
+ # @return [Boolean]
275
+ def self.normalized?(object)
276
+ normalized = normalize(object)
277
+ rescue Exception
278
+ false
279
+ else
280
+ normalized == object
281
+ end
282
+
283
+ # @return [Boolean]
284
+ def self.valid?(object)
285
+ string = String.try_convert(object)
265
286
  string ? STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string) : false
266
287
  end
267
288
 
289
+ # @param [ULID, #to_ulid] object
290
+ # @return [ULID, nil]
291
+ # @raise [TypeError] if `object.to_ulid` did not return ULID instance
292
+ def self.try_convert(object)
293
+ begin
294
+ converted = object.to_ulid
295
+ rescue NoMethodError
296
+ nil
297
+ else
298
+ if ULID === converted
299
+ converted
300
+ else
301
+ object_class_name = safe_get_class_name(object)
302
+ converted_class_name = safe_get_class_name(converted)
303
+ raise TypeError, "can't convert #{object_class_name} to ULID (#{object_class_name}#to_ulid gives #{converted_class_name})"
304
+ end
305
+ end
306
+ end
307
+
308
+ # @param [BasicObject] object
309
+ # @return [String]
310
+ private_class_method def self.safe_get_class_name(object)
311
+ fallback = 'UnknownObject'
312
+
313
+ begin
314
+ name = String.try_convert(object.class.name)
315
+ rescue Exception
316
+ fallback
317
+ else
318
+ name || fallback
319
+ end
320
+ end
321
+
268
322
  # @api private
269
- # @param [MonotonicGenerator] generator
323
+ # @param [Integer] milliseconds
324
+ # @param [Integer] entropy
270
325
  # @return [ULID]
271
- def self.from_monotonic_generator(generator)
272
- raise ArgumentError, 'this method provided only for MonotonicGenerator' unless MonotonicGenerator === generator
273
- new milliseconds: generator.latest_milliseconds, entropy: generator.latest_entropy
326
+ # @raise [OverflowError] if the given value is larger than the ULID limit
327
+ # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
328
+ def self.from_milliseconds_and_entropy(milliseconds:, entropy:)
329
+ raise ArgumentError, 'milliseconds and entropy should be an `Integer`' unless Integer === milliseconds && Integer === entropy
330
+ raise OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}" unless milliseconds <= MAX_MILLISECONDS
331
+ raise OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}" unless entropy <= MAX_ENTROPY
332
+ raise ArgumentError, 'milliseconds and entropy should not be negative' if milliseconds.negative? || entropy.negative?
333
+
334
+ n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
335
+ n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
336
+ integer = (n32encoded_timestamp + n32encoded_randomness).to_i(32)
337
+
338
+ new milliseconds: milliseconds, entropy: entropy, integer: integer
274
339
  end
275
340
 
276
341
  attr_reader :milliseconds, :entropy
@@ -280,42 +345,27 @@ class ULID
280
345
  # @param [Integer] entropy
281
346
  # @param [Integer] integer
282
347
  # @return [void]
283
- # @raise [OverflowError] if the given value is larger than the ULID limit
284
- # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
285
- def initialize(milliseconds:, entropy:, integer: nil)
286
- if integer
287
- @integer = integer
288
- else
289
- milliseconds = milliseconds.to_int
290
- entropy = entropy.to_int
291
-
292
- raise OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}" unless milliseconds <= MAX_MILLISECONDS
293
- raise OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}" unless entropy <= MAX_ENTROPY
294
- raise ArgumentError, 'milliseconds and entropy should not be negative' if milliseconds.negative? || entropy.negative?
295
- end
296
-
348
+ def initialize(milliseconds:, entropy:, integer:)
349
+ # All arguments check should be done with each constructors, not here
350
+ @integer = integer
297
351
  @milliseconds = milliseconds
298
352
  @entropy = entropy
299
353
  end
300
354
 
301
355
  # @return [String]
302
356
  def to_s
303
- @string ||= CrockfordBase32.encode(to_i).freeze
357
+ @string ||= CrockfordBase32.encode(@integer).freeze
304
358
  end
305
359
 
306
360
  # @return [Integer]
307
361
  def to_i
308
- @integer ||= begin
309
- n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
310
- n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
311
- (n32encoded_timestamp + n32encoded_randomness).to_i(32)
312
- end
362
+ @integer
313
363
  end
314
364
  alias_method :hash, :to_i
315
365
 
316
366
  # @return [Integer, nil]
317
367
  def <=>(other)
318
- (ULID === other) ? (to_i <=> other.to_i) : nil
368
+ (ULID === other) ? (@integer <=> other.to_i) : nil
319
369
  end
320
370
 
321
371
  # @return [String]
@@ -325,7 +375,7 @@ class ULID
325
375
 
326
376
  # @return [Boolean]
327
377
  def eql?(other)
328
- equal?(other) || (ULID === other && to_i == other.to_i)
378
+ equal?(other) || (ULID === other && @integer == other.to_i)
329
379
  end
330
380
  alias_method :==, :eql?
331
381
 
@@ -333,7 +383,7 @@ class ULID
333
383
  def ===(other)
334
384
  case other
335
385
  when ULID
336
- to_i == other.to_i
386
+ @integer == other.to_i
337
387
  when String
338
388
  to_s == other.upcase
339
389
  else
@@ -354,7 +404,7 @@ class ULID
354
404
 
355
405
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
356
406
  def octets
357
- digits = to_i.digits(256)
407
+ digits = @integer.digits(256)
358
408
  (OCTETS_LENGTH - digits.size).times do
359
409
  digits.push 0
360
410
  end
@@ -393,7 +443,7 @@ class ULID
393
443
 
394
444
  # @return [ULID, nil] when called on ULID as `7ZZZZZZZZZZZZZZZZZZZZZZZZZ`, returns `nil` instead of ULID
395
445
  def succ
396
- succ_int = to_i.succ
446
+ succ_int = @integer.succ
397
447
  if succ_int >= MAX_INTEGER
398
448
  if succ_int == MAX_INTEGER
399
449
  MAX
@@ -408,7 +458,7 @@ class ULID
408
458
 
409
459
  # @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
410
460
  def pred
411
- pred_int = to_i.pred
461
+ pred_int = @integer.pred
412
462
  if pred_int <= 0
413
463
  if pred_int == 0
414
464
  MIN
@@ -427,6 +477,23 @@ class ULID
427
477
  super
428
478
  end
429
479
 
480
+ # @return [self]
481
+ def to_ulid
482
+ self
483
+ end
484
+
485
+ # @return [self]
486
+ def dup
487
+ self
488
+ end
489
+
490
+ # @return [self]
491
+ def clone(freeze: true)
492
+ self
493
+ end
494
+
495
+ undef_method :instance_variable_set
496
+
430
497
  private
431
498
 
432
499
  # @return [void]