ruby-ulid 0.0.19 → 0.1.4

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: 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]