ruby-ulid 0.0.10 → 0.0.15

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: ce2bb6c517e9fee4f758577e57d2c5eddfd16db9ee3c0d17ad41db8d9f1de383
4
- data.tar.gz: 1cfb027a652e2035445483bdff2511c2837441e2e664286d60682fec6769cd17
3
+ metadata.gz: 6a620e80f82e91c778329ccf7da8cca96bda4b12e047610b9543768c5ec85d22
4
+ data.tar.gz: ea28469d63348758f52e1460b5330ce177ad2281e63060800a0669054758f3ee
5
5
  SHA512:
6
- metadata.gz: ea00703256bc218a1e022dd732da22b37b9caeec30bdbdb4df945f9f185d408f0b746c36b36377dba2d4acaae5d2cf0b4db8b574ecc9da0e6e4b9b0af5c2404f
7
- data.tar.gz: 6cd088ae1d539d8cb1d2690eb4248be413e527a87db7eb67b3c1adfc167c285cf2b90bb5701e82208586996649d56d1ef6c7c30396d1fbf0fc68220487711a54
6
+ metadata.gz: acb07ae797c3a06a6626d34e15dc1056a30586e8bc151a3773770259d2f4047d0708593e702fbd67a7609758165f20a15f208fede068717a5555733535ed4bde
7
+ data.tar.gz: 57c9819d86f2a0f002cf3b9a7b76b3a36bc412fa874d50136ec296f1217fb905fa19feb52de2c9d5250547da384580cf882121f0e331054cd665bd113d1f10e3
data/README.md CHANGED
@@ -2,11 +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).
6
- Formal name is `Universally Unique Lexicographically Sortable Identifier`.
7
- It has useful specs for actual applications.
8
- This gem aims to provide the generator, monotonic generator, parser and handy manipulation methods for the ID.
9
- Also having rbs signature files.
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.
10
8
 
11
9
  ---
12
10
 
@@ -30,13 +28,15 @@ Instead, herein is proposed ULID:
30
28
  - 1.21e+24 unique ULIDs per millisecond
31
29
  - Lexicographically sortable!
32
30
  - Canonically encoded as a 26 character string, as opposed to the 36 character UUID
33
- - 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)
34
32
  - Case insensitive
35
33
  - No special characters (URL safe)
36
34
  - Monotonic sort order (correctly detects and handles the same millisecond)
37
35
 
38
36
  ## Install
39
37
 
38
+ Require Ruby 2.6 or later
39
+
40
40
  ```console
41
41
  $ gem install ruby-ulid
42
42
  #=> Installed
@@ -57,13 +57,14 @@ ulid.octets #=> [1, 121, 20, 95, 7, 202, 187, 60, 175, 51, 76, 60, 49, 73, 37, 7
57
57
  ulid.pattern #=> /(?<timestamp>01F4A5Y1YA)(?<randomness>QCYAYCTC7GRMJ9AA)/i
58
58
  ```
59
59
 
60
- You can parse from exists IDs
60
+ You can get the objects from exists encoded ULIDs
61
61
 
62
62
  ```ruby
63
63
  ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV') #=> ULID(2016-07-30 23:54:10.259 UTC: 01ARZ3NDEKTSV4RRFFQ69G5FAV)
64
+ ulid.to_time #=> 2016-07-30 23:54:10.259 UTC
64
65
  ```
65
66
 
66
- ULID is sortable when they are generated different timestamp in milliseconds precision
67
+ ULIDs are sortable when they are generated in different timestamp with milliseconds precision
67
68
 
68
69
  ```ruby
69
70
  ulids = 1000.times.map do
@@ -74,29 +75,71 @@ ulids.sort == ulids #=> true
74
75
  ulids.uniq(&:to_time).size #=> 1000
75
76
  ```
76
77
 
77
- Providing monotonic generator for same milliseconds use-cases. It is called as [Monotonicity](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#monotonicity) on the spec.
78
+ `ULID.generate` can take fixed `Time` instance
79
+
80
+ ```ruby
81
+ time = Time.at(946684800, in: 'UTC') #=> 2000-01-01 00:00:00 UTC
82
+ ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
83
+ ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
84
+
85
+ ulids = 1000.times.map do |n|
86
+ ULID.generate(moment: time + n)
87
+ end
88
+ ulids.sort == ulids #=> true
89
+ ```
90
+
91
+ The basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
78
92
 
79
93
  ```ruby
80
94
  ulids = 10000.times.map do
81
95
  ULID.generate
82
96
  end
83
- ulids.uniq(&:to_time).size #=> 35 (the number will be changed by every creation)
97
+ ulids.uniq(&:to_time).size #=> 35 (the size is not fixed, might be changed in environment)
84
98
  ulids.sort == ulids #=> false
99
+ ```
85
100
 
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
+ (Though it starts with new random value when changed the timestamp)
86
103
 
104
+ ```ruby
87
105
  monotonic_generator = ULID::MonotonicGenerator.new
88
- monotonic_ulids = 10000.times.map do
106
+ ulids = 10000.times.map do
89
107
  monotonic_generator.generate
90
108
  end
91
- monotonic_ulids.uniq(&:to_time).size #=> 34 (the number will be changed by every creation)
92
- monotonic_ulids.sort == monotonic_ulids #=> true
109
+ sample_ulids_by_the_time = ulids.uniq(&:to_time)
110
+ sample_ulids_by_the_time.size #=> 32 (the size is not fixed, might be changed in environment)
111
+
112
+ # In same milliseconds creation, it just increments the end of randomness part
113
+ ulids.take(5) #=>
114
+ # [ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4),
115
+ # ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK5),
116
+ # ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK6),
117
+ # ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK7),
118
+ # ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK8)]
119
+
120
+ # When the milliseconds is updated, it starts with new randomness
121
+ sample_ulids_by_the_time.take(5) #=>
122
+ # [ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4),
123
+ # ULID(2021-05-02 15:23:48.918 UTC: 01F4PTVCSPF2KXG4ABT7CK3204),
124
+ # ULID(2021-05-02 15:23:48.919 UTC: 01F4PTVCSQF1GERBPCQV6TCX2K),
125
+ # ULID(2021-05-02 15:23:48.920 UTC: 01F4PTVCSRBXN2H4P1EYWZ27AK),
126
+ # ULID(2021-05-02 15:23:48.921 UTC: 01F4PTVCSSK0ASBBZARV7013F8)]
127
+
128
+ ulids.sort == ulids #=> true
93
129
  ```
94
130
 
95
- Providing converter for UUIDv4. (Of course the timestamp will be useless one.)
131
+ When filtering ULIDs by `Time`, we should consider to handle the precision.
132
+ So this gem provides `ULID.range` to generate `Range[ULID]` from given `Range[Time]`
96
133
 
97
134
  ```ruby
98
- ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
99
- #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
135
+ # Both of below, The begin of `Range[ULID]` will be the minimum in the floored milliseconds of the time1
136
+ include_end = ULID.range(time1..time2) #=> The end of `Range[ULID]` will be the maximum in the floored milliseconds of the time2
137
+ exclude_end = ULID.range(time1...time2) #=> The end of `Range[ULID]` will be the minimum in the floored milliseconds of the time2
138
+
139
+ # So you can use the generated range objects as below
140
+ ulids.grep(include_end)
141
+ ulids.grep(exclude_end)
142
+ #=> I hope the results should be actually you want!
100
143
  ```
101
144
 
102
145
  For rough operations, `ULID.scan` might be useful.
@@ -150,50 +193,59 @@ ULID.min(moment: time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V000000000
150
193
  ULID.max(moment: time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
151
194
  ```
152
195
 
153
- ## Development
154
-
155
- At first, you should install development dependencies
196
+ `ULID#next` and `ULID#succ` returns next(successor) ULID
156
197
 
157
- ```console
158
- $ git clone git@github.com:kachick/ruby-ulid.git
159
- $ cd ./ruby-ulid
160
- $ bundle install
161
- # Executing first time might take longtime, because development mode dependent active_support via steep
198
+ ```ruby
199
+ ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZY').next.to_s #=> "01BX5ZZKBKZZZZZZZZZZZZZZZZ"
200
+ ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZZ').next.to_s #=> "01BX5ZZKBM0000000000000000"
201
+ ULID.parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').next #=> nil
162
202
  ```
163
203
 
164
- Play with the behaviors in REPL.
204
+ `ULID#pred` returns predecessor ULID
165
205
 
166
- ```console
167
- $ ./bin/console
168
- # Starting up IRB with loading developing ULID library
169
- irb(main):001:0>
206
+ ```ruby
207
+ ULID.parse('01BX5ZZKBK0000000000000001').pred.to_s #=> "01BX5ZZKBK0000000000000000"
208
+ ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZZZ"
209
+ ULID.parse('00000000000000000000000000').pred #=> nil
170
210
  ```
171
211
 
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
216
+
172
217
  ```ruby
173
- # On IRB, you can check behaviors even if it is undocumented
174
- ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
175
- ls ULID
176
- ```
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"
177
221
 
178
- If you try to add/change/fix features, please update tests and ensure they are not broken.
222
+ uuid_v4s = 10000.times.map { SecureRandom.uuid }
223
+ uuid_v4s.uniq.size == 10000 #=> Probably `true`
179
224
 
180
- ```console
181
- $ bundle exec rake test
182
- $ echo $?
183
- 0
184
- ```
225
+ ulids = uuid_v4s.map { |uuid_v4| ULID.from_uuidv4(uuid_v4) }
226
+ ulids.map(&:to_uuidv4) == uuid_v4s #=> **Probably** `true` except below examples.
185
227
 
186
- If you try to improve any performance issue, please add benchmarking and check the result of before and after.
228
+ # NOTE: Some boundary values are not reversible. See below.
187
229
 
188
- ```console
189
- $ bundle exec ruby benchmark/the_added_file.rb
190
- # Showing the results
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
191
240
  ```
192
241
 
193
- ## Documents
242
+ ## References
194
243
 
195
- - [API documents generated by YARD](https://kachick.github.io/ruby-ulid/)
244
+ - [Repository](https://github.com/kachick/ruby-ulid)
245
+ - [API documents](https://kachick.github.io/ruby-ulid/)
246
+ - [ulid/spec](https://github.com/ulid/spec)
196
247
 
197
- ## Author
248
+ ## Note
198
249
 
199
- Kenichi Kamiya - [@kachick](https://github.com/kachick)
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
@@ -45,20 +45,19 @@ class ULID
45
45
  # @param [Integer] entropy
46
46
  # @return [ULID]
47
47
  def self.generate(moment: current_milliseconds, entropy: reasonable_entropy)
48
- milliseconds = moment.kind_of?(Time) ? time_to_milliseconds(moment) : moment
49
- new milliseconds: milliseconds, entropy: entropy
48
+ new milliseconds: milliseconds_from_moment(moment), entropy: entropy
50
49
  end
51
50
 
52
51
  # @param [Integer, Time] moment
53
52
  # @return [ULID]
54
53
  def self.min(moment: 0)
55
- generate(moment: moment, entropy: 0)
54
+ 0.equal?(moment) ? MIN : generate(moment: moment, entropy: 0)
56
55
  end
57
56
 
58
57
  # @param [Integer, Time] moment
59
58
  # @return [ULID]
60
59
  def self.max(moment: MAX_MILLISECONDS)
61
- generate(moment: moment, entropy: MAX_ENTROPY)
60
+ MAX_MILLISECONDS.equal?(moment) ? MAX : generate(moment: moment, entropy: MAX_ENTROPY)
62
61
  end
63
62
 
64
63
  # @deprecated This method actually changes class state. Use {ULID::MonotonicGenerator} instead.
@@ -107,6 +106,7 @@ class ULID
107
106
  # @return [ULID]
108
107
  # @raise [OverflowError] if the given integer is larger than the ULID limit
109
108
  # @raise [ArgumentError] if the given integer is negative number
109
+ # @todo Need optimized for performance
110
110
  def self.from_integer(integer)
111
111
  integer = integer.to_int
112
112
  raise OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}" unless integer <= MAX_INTEGER
@@ -121,17 +121,70 @@ class ULID
121
121
  new milliseconds: milliseconds, entropy: entropy
122
122
  end
123
123
 
124
+ # @param [Range<Time>, Range<nil>] time_range
125
+ # @return [Range<ULID>]
126
+ # @raise [ArgumentError] if the given time_range is not a `Range[Time]` or `Range[nil]`
127
+ def self.range(time_range)
128
+ raise argument_error_for_range_building(time_range) unless time_range.kind_of?(Range)
129
+ begin_time, end_time, exclude_end = time_range.begin, time_range.end, time_range.exclude_end?
130
+
131
+ case begin_time
132
+ when Time
133
+ begin_ulid = min(moment: begin_time)
134
+ when nil
135
+ begin_ulid = min
136
+ else
137
+ raise argument_error_for_range_building(time_range)
138
+ end
139
+
140
+ case end_time
141
+ when Time
142
+ if exclude_end
143
+ end_ulid = min(moment: end_time)
144
+ else
145
+ end_ulid = max(moment: end_time)
146
+ end
147
+ when nil
148
+ # The end should be max and include end, because nil end means to cover endless ULIDs until the limit
149
+ end_ulid = max
150
+ exclude_end = false
151
+ else
152
+ raise argument_error_for_range_building(time_range)
153
+ end
154
+
155
+ begin_ulid.freeze
156
+ end_ulid.freeze
157
+
158
+ Range.new(begin_ulid, end_ulid, exclude_end)
159
+ end
160
+
161
+ # @param [Time] time
162
+ # @return [Time]
163
+ def self.floor(time)
164
+ if RUBY_VERSION >= '2.7'
165
+ time.floor(3)
166
+ else
167
+ Time.at(0, milliseconds_from_time(time), :millisecond)
168
+ end
169
+ end
170
+
124
171
  # @return [Integer]
125
172
  def self.current_milliseconds
126
- time_to_milliseconds(Time.now)
173
+ milliseconds_from_time(Time.now)
127
174
  end
128
175
 
129
176
  # @param [Time] time
130
177
  # @return [Integer]
131
- def self.time_to_milliseconds(time)
178
+ def self.milliseconds_from_time(time)
132
179
  (time.to_r * 1000).to_i
133
180
  end
134
181
 
182
+ # @param [Time, Integer] moment
183
+ # @return [Integer]
184
+ def self.milliseconds_from_moment(moment)
185
+ moment.kind_of?(Time) ? milliseconds_from_time(moment) : moment.to_int
186
+ end
187
+
135
188
  # @return [Integer]
136
189
  def self.reasonable_entropy
137
190
  SecureRandom.random_number(MAX_ENTROPY)
@@ -167,6 +220,7 @@ class ULID
167
220
  true
168
221
  end
169
222
 
223
+ # @api private
170
224
  # @param [Integer] integer
171
225
  # @param [Integer] length
172
226
  # @return [Array<Integer>]
@@ -178,6 +232,7 @@ class ULID
178
232
  digits.reverse!
179
233
  end
180
234
 
235
+ # @api private
181
236
  # @see The logics taken from https://bugs.ruby-lang.org/issues/14401, thanks!
182
237
  # @param [Array<Integer>] reversed_digits
183
238
  # @return [Integer]
@@ -190,8 +245,14 @@ class ULID
190
245
  num
191
246
  end
192
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
+
193
253
  attr_reader :milliseconds, :entropy
194
254
 
255
+ # @api private
195
256
  # @param [Integer] milliseconds
196
257
  # @param [Integer] entropy
197
258
  # @return [void]
@@ -209,10 +270,9 @@ class ULID
209
270
  end
210
271
 
211
272
  # @return [String]
212
- def to_str
273
+ def to_s
213
274
  @string ||= Integer::Base.string_for(to_i, ENCODING_CHARS).rjust(ENCODED_ID_LENGTH, '0').upcase.freeze
214
275
  end
215
- alias_method :to_s, :to_str
216
276
 
217
277
  # @return [Integer]
218
278
  def to_i
@@ -227,7 +287,7 @@ class ULID
227
287
 
228
288
  # @return [String]
229
289
  def inspect
230
- @inspect ||= "ULID(#{to_time.strftime(TIME_FORMAT_IN_INSPECT)}: #{to_str})".freeze
290
+ @inspect ||= "ULID(#{to_time.strftime(TIME_FORMAT_IN_INSPECT)}: #{to_s})".freeze
231
291
  end
232
292
 
233
293
  # @return [Boolean]
@@ -254,7 +314,13 @@ class ULID
254
314
 
255
315
  # @return [Time]
256
316
  def to_time
257
- @time ||= Time.at(0, @milliseconds, :millisecond).utc.freeze
317
+ @time ||= begin
318
+ if RUBY_VERSION >= '2.7'
319
+ Time.at(0, @milliseconds, :millisecond, in: 'UTC').freeze
320
+ else
321
+ Time.at(0, @milliseconds, :millisecond).utc.freeze
322
+ end
323
+ end
258
324
  end
259
325
 
260
326
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
@@ -292,21 +358,36 @@ class ULID
292
358
  @strict_pattern ||= /\A#{pattern.source}\z/i.freeze
293
359
  end
294
360
 
295
- # @raise [OverflowError] if the next entropy part is larger than the ULID limit
296
- # @return [ULID]
361
+ # @return [ULID, nil] when called on ULID as `7ZZZZZZZZZZZZZZZZZZZZZZZZZ`, returns `nil` instead of ULID
297
362
  def next
298
- @next ||= self.class.new(milliseconds: @milliseconds, entropy: @entropy + 1)
363
+ next_int = to_i.next
364
+ return nil if next_int > MAX_INTEGER
365
+ @next ||= self.class.from_integer(next_int)
299
366
  end
300
367
  alias_method :succ, :next
301
368
 
369
+ # @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
370
+ def pred
371
+ pre_int = to_i.pred
372
+ return nil if pre_int.negative?
373
+ @pred ||= self.class.from_integer(pre_int)
374
+ end
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
+
302
387
  # @return [self]
303
388
  def freeze
304
- # Evaluate all caching
305
- inspect
306
- octets
307
- succ
308
- to_i
309
- strict_pattern
389
+ # Need to cache before freezing, because frozen objects can't assign instance variables
390
+ cache_all_instance_variables
310
391
  super
311
392
  end
312
393
 
@@ -314,7 +395,18 @@ class ULID
314
395
 
315
396
  # @return [MatchData]
316
397
  def matchdata
317
- @matchdata ||= STRICT_PATTERN.match(to_str).freeze
398
+ @matchdata ||= STRICT_PATTERN.match(to_s).freeze
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
318
410
  end
319
411
  end
320
412
 
@@ -322,7 +414,9 @@ require_relative 'ulid/version'
322
414
  require_relative 'ulid/monotonic_generator'
323
415
 
324
416
  class ULID
417
+ MIN = parse('00000000000000000000000000').freeze
418
+ MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
325
419
  MONOTONIC_GENERATOR = MonotonicGenerator.new
326
420
 
327
- private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN
421
+ private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN, :MIN, :MAX
328
422
  end
@@ -4,35 +4,37 @@
4
4
 
5
5
  class ULID
6
6
  class MonotonicGenerator
7
+ # @api private
7
8
  attr_accessor :latest_milliseconds, :latest_entropy
8
9
 
9
10
  def initialize
10
11
  reset
11
12
  end
12
13
 
13
- # @raise [OverflowError] if the entropy part is larger than the ULID limit in same milliseconds
14
+ # @param [Time, Integer] moment
14
15
  # @return [ULID]
15
- def generate
16
- milliseconds = ULID.current_milliseconds
17
- reasonable_entropy = ULID.reasonable_entropy
16
+ # @raise [OverflowError] if the entropy part is larger than the ULID limit in same milliseconds
17
+ # @raise [ArgumentError] if the given moment(milliseconds) is negative number
18
+ def generate(moment: ULID.current_milliseconds)
19
+ milliseconds = ULID.milliseconds_from_moment(moment)
20
+ raise ArgumentError, "milliseconds should not be negative: given: #{milliseconds}" if milliseconds.negative?
18
21
 
19
- @latest_milliseconds ||= milliseconds
20
- @latest_entropy ||= reasonable_entropy
21
- if @latest_milliseconds != milliseconds
22
+ if @latest_milliseconds < milliseconds
22
23
  @latest_milliseconds = milliseconds
23
- @latest_entropy = reasonable_entropy
24
+ @latest_entropy = ULID.reasonable_entropy
24
25
  else
25
26
  @latest_entropy += 1
26
27
  end
27
28
 
28
- ULID.new milliseconds: milliseconds, entropy: @latest_entropy
29
+ ULID.new milliseconds: @latest_milliseconds, entropy: @latest_entropy
29
30
  end
30
31
 
31
- # @return [self]
32
+ # @api private
33
+ # @return [void]
32
34
  def reset
33
- @latest_milliseconds = nil
34
- @latest_entropy = nil
35
- self
35
+ @latest_milliseconds = 0
36
+ @latest_entropy = ULID.reasonable_entropy
37
+ nil
36
38
  end
37
39
 
38
40
  # @raise [TypeError] always raises exception and does not freeze self
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.10'
5
+ VERSION = '0.0.15'
6
6
  end
data/sig/ulid.rbs CHANGED
@@ -16,8 +16,13 @@ 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`
24
+ type moment = Time | Integer
25
+
21
26
  class Error < StandardError
22
27
  end
23
28
 
@@ -28,15 +33,14 @@ class ULID
28
33
  end
29
34
 
30
35
  class MonotonicGenerator
31
- attr_accessor latest_milliseconds: Integer?
32
- attr_accessor latest_entropy: Integer?
36
+ attr_accessor latest_milliseconds: Integer
37
+ attr_accessor latest_entropy: Integer
33
38
  def initialize: -> void
34
- def generate: -> ULID
39
+ def generate: (?moment: moment) -> ULID
35
40
  def reset: -> void
36
41
  def freeze: -> void
37
42
  end
38
43
 
39
- type moment = Time | Integer
40
44
  type octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
41
45
  type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
42
46
  type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
@@ -55,12 +59,16 @@ class ULID
55
59
  @next: ULID?
56
60
  @pattern: Regexp?
57
61
  @strict_pattern: Regexp?
62
+ @uuidv4: String?
58
63
  @matchdata: MatchData?
59
64
 
60
65
  def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
61
66
  def self.monotonic_generate: -> ULID
62
67
  def self.current_milliseconds: -> Integer
63
- def self.time_to_milliseconds: (Time time) -> Integer
68
+ def self.milliseconds_from_time: (Time time) -> Integer
69
+ def self.milliseconds_from_moment: (moment moment) -> Integer
70
+ def self.range: (Range[Time] | Range[nil] time_range) -> Range[ULID]
71
+ def self.floor: (Time time) -> Time
64
72
  def self.reasonable_entropy: -> Integer
65
73
  def self.parse: (String string) -> ULID
66
74
  def self.from_uuidv4: (String uuid) -> ULID
@@ -75,8 +83,7 @@ class ULID
75
83
  attr_reader milliseconds: Integer
76
84
  attr_reader entropy: Integer
77
85
  def initialize: (milliseconds: Integer, entropy: Integer) -> void
78
- def to_str: -> String
79
- alias to_s to_str
86
+ def to_s: -> String
80
87
  def to_i: -> Integer
81
88
  alias hash to_i
82
89
  def <=>: (ULID other) -> Integer
@@ -93,10 +100,14 @@ class ULID
93
100
  def octets: -> octets
94
101
  def timestamp_octets: -> timestamp_octets
95
102
  def randomness_octets: -> randomness_octets
96
- def next: -> ULID
103
+ def to_uuidv4: -> String
104
+ def next: -> ULID?
97
105
  alias succ next
106
+ def pred: -> ULID?
98
107
  def freeze: -> self
99
108
 
100
109
  private
110
+ def self.argument_error_for_range_building: (untyped argument) -> ArgumentError
101
111
  def matchdata: -> MatchData
112
+ def cache_all_instance_variables: -> void
102
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.10
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-01 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
@@ -30,6 +30,20 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: 0.2.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: rbs
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 1.2.0
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 1.2.0
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: benchmark-ips
35
49
  requirement: !ruby/object:Gem::Requirement
@@ -70,11 +84,10 @@ dependencies:
70
84
  - - "<"
71
85
  - !ruby/object:Gem::Version
72
86
  version: '2'
73
- description: |2
74
- ULID(Universally Unique Lexicographically Sortable Identifier) is defined on https://github.com/ulid/spec.
75
- It has useful specs for actual applications.
76
- This gem aims to provide the generator, monotonic generator, parser and handy manipulation methods for the ID.
77
- Also having rbs signature files.
87
+ description: " ULID(Universally Unique Lexicographically Sortable Identifier) has
88
+ useful specs for applications (e.g. `Database key`). \n This gem aims to provide
89
+ the generator, monotonic generator, parser and handy manipulation features around
90
+ the ULID.\n Also providing `rbs` signature files.\n"
78
91
  email:
79
92
  - kachick1+ruby@gmail.com
80
93
  executables: []
@@ -83,7 +96,6 @@ extra_rdoc_files: []
83
96
  files:
84
97
  - LICENSE
85
98
  - README.md
86
- - Steepfile
87
99
  - lib/ulid.rb
88
100
  - lib/ulid/monotonic_generator.rb
89
101
  - lib/ulid/version.rb
@@ -103,7 +115,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
103
115
  requirements:
104
116
  - - ">="
105
117
  - !ruby/object:Gem::Version
106
- version: '2.5'
118
+ version: 2.6.0
107
119
  required_rubygems_version: !ruby/object:Gem::Requirement
108
120
  requirements:
109
121
  - - ">="
data/Steepfile DELETED
@@ -1,7 +0,0 @@
1
- target :lib do
2
- signature 'sig'
3
-
4
- check 'lib'
5
-
6
- library 'securerandom'
7
- end