ruby-ulid 0.0.12 → 0.0.17
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 +4 -4
- data/README.md +197 -58
- data/lib/ulid.rb +169 -39
- data/lib/ulid/version.rb +1 -1
- data/sig/ulid.rbs +21 -4
- metadata +11 -18
- data/Steepfile +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29a9e3cd7ec91b93c24691976fef74ea5ab0cbc8f4fa237f449fa9322e42c6d2
|
4
|
+
data.tar.gz: 9a3c0e12e7d2fe8b6057662ebf6a392c749f7254ee41c4e77221a2e9f45ff6ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53a12c330c32f76f13e651cef96ce29a6a442cf3dc7742faf2409a0ee17c7dfdcee1031c17696cd51d7bd3cf5b22b2df34b8be5a203cd1d4fd5df079eb357b82
|
7
|
+
data.tar.gz: f6c8f8850272353bbd32cc15d437d0b021c13d9bc93c5c988255d6c50fe21ff41aef5b719261ca95b00c129024fe325e2d3c8683c70f9b55e89a09a261fff31f
|
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# ruby-ulid
|
2
2
|
|
3
|
-
|
3
|
+
## Overview
|
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,19 +28,31 @@ 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)
|
35
35
|
|
36
|
-
##
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
### Install
|
39
|
+
|
40
|
+
Require Ruby 2.6 or later
|
41
|
+
|
42
|
+
This command will install the latest version into your environment
|
37
43
|
|
38
44
|
```console
|
39
45
|
$ gem install ruby-ulid
|
40
|
-
|
46
|
+
Should be installed!
|
41
47
|
```
|
42
48
|
|
43
|
-
|
49
|
+
Add this line to your application/library's `Gemfile` is needed in basic use-case
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
gem 'ruby-ulid', '0.0.16'
|
53
|
+
```
|
54
|
+
|
55
|
+
### Generator and Parser
|
44
56
|
|
45
57
|
The generated `ULID` is an object not just a string.
|
46
58
|
It means easily get the timestamps and binary formats.
|
@@ -52,36 +64,17 @@ ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GR
|
|
52
64
|
ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
|
53
65
|
ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
|
54
66
|
ulid.octets #=> [1, 121, 20, 95, 7, 202, 187, 60, 175, 51, 76, 60, 49, 73, 37, 74]
|
55
|
-
ulid.pattern #=> /(?<timestamp>01F4A5Y1YA)(?<randomness>QCYAYCTC7GRMJ9AA)/i
|
56
|
-
```
|
57
|
-
|
58
|
-
Generator can take `Time` instance
|
59
|
-
|
60
|
-
```ruby
|
61
|
-
time = Time.at(946684800, in: 'UTC') #=> 2000-01-01 00:00:00 UTC
|
62
|
-
ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
|
63
|
-
ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
|
64
|
-
|
65
|
-
ulids = 1000.times.map do
|
66
|
-
ULID.generate(moment: time)
|
67
|
-
end
|
68
|
-
ulids.sort == ulids #=> false
|
69
|
-
|
70
|
-
ulids = 1000.times.map do |n|
|
71
|
-
ULID.generate(moment: time + n)
|
72
|
-
end
|
73
|
-
ulids.sort == ulids #=> true
|
74
67
|
```
|
75
68
|
|
76
|
-
You can
|
77
|
-
|
78
|
-
FYI: 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).
|
69
|
+
You can get the objects from exists encoded ULIDs
|
79
70
|
|
80
71
|
```ruby
|
81
72
|
ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV') #=> ULID(2016-07-30 23:54:10.259 UTC: 01ARZ3NDEKTSV4RRFFQ69G5FAV)
|
82
73
|
ulid.to_time #=> 2016-07-30 23:54:10.259 UTC
|
83
74
|
```
|
84
75
|
|
76
|
+
### Sortable with the timestamp
|
77
|
+
|
85
78
|
ULIDs are sortable when they are generated in different timestamp with milliseconds precision
|
86
79
|
|
87
80
|
```ruby
|
@@ -89,8 +82,21 @@ ulids = 1000.times.map do
|
|
89
82
|
sleep(0.001)
|
90
83
|
ULID.generate
|
91
84
|
end
|
92
|
-
ulids.sort == ulids #=> true
|
93
85
|
ulids.uniq(&:to_time).size #=> 1000
|
86
|
+
ulids.sort == ulids #=> true
|
87
|
+
```
|
88
|
+
|
89
|
+
`ULID.generate` can take fixed `Time` instance
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
|
93
|
+
ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
|
94
|
+
ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
|
95
|
+
|
96
|
+
ulids = 1000.times.map do |n|
|
97
|
+
ULID.generate(moment: time + n)
|
98
|
+
end
|
99
|
+
ulids.sort == ulids #=> true
|
94
100
|
```
|
95
101
|
|
96
102
|
The basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
|
@@ -103,32 +109,72 @@ ulids.uniq(&:to_time).size #=> 35 (the size is not fixed, might be changed in en
|
|
103
109
|
ulids.sort == ulids #=> false
|
104
110
|
```
|
105
111
|
|
106
|
-
|
112
|
+
### How to keep `Sortable` even if in same timestamp
|
113
|
+
|
114
|
+
If you want to prefer `sortable`, Use `MonotonicGenerator` instead. It is called as [Monotonicity](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#monotonicity) on the spec.
|
107
115
|
(Though it starts with new random value when changed the timestamp)
|
108
116
|
|
109
117
|
```ruby
|
110
118
|
monotonic_generator = ULID::MonotonicGenerator.new
|
111
|
-
|
119
|
+
ulids = 10000.times.map do
|
112
120
|
monotonic_generator.generate
|
113
121
|
end
|
114
|
-
sample_ulids_by_the_time =
|
115
|
-
sample_ulids_by_the_time.size #=>
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
122
|
+
sample_ulids_by_the_time = ulids.uniq(&:to_time)
|
123
|
+
sample_ulids_by_the_time.size #=> 32 (the size is not fixed, might be changed in environment)
|
124
|
+
|
125
|
+
# In same milliseconds creation, it just increments the end of randomness part
|
126
|
+
ulids.take(5) #=>
|
127
|
+
# [ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4),
|
128
|
+
# ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK5),
|
129
|
+
# ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK6),
|
130
|
+
# ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK7),
|
131
|
+
# ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK8)]
|
132
|
+
|
133
|
+
# When the milliseconds is updated, it starts with new randomness
|
134
|
+
sample_ulids_by_the_time.take(5) #=>
|
135
|
+
# [ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4),
|
136
|
+
# ULID(2021-05-02 15:23:48.918 UTC: 01F4PTVCSPF2KXG4ABT7CK3204),
|
137
|
+
# ULID(2021-05-02 15:23:48.919 UTC: 01F4PTVCSQF1GERBPCQV6TCX2K),
|
138
|
+
# ULID(2021-05-02 15:23:48.920 UTC: 01F4PTVCSRBXN2H4P1EYWZ27AK),
|
139
|
+
# ULID(2021-05-02 15:23:48.921 UTC: 01F4PTVCSSK0ASBBZARV7013F8)]
|
128
140
|
|
129
|
-
|
141
|
+
ulids.sort == ulids #=> true
|
142
|
+
```
|
143
|
+
|
144
|
+
### Filtering IDs with `Time`
|
145
|
+
|
146
|
+
`ULID` can be element of the `Range`. If you generated the IDs in monotonic generator, ID based filtering is easy and reliable
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
include_end = ulid1..ulid2
|
150
|
+
exclude_end = ulid1...ulid2
|
151
|
+
|
152
|
+
ulids.grep(one_of_the_above)
|
153
|
+
ulids.grep_v(one_of_the_above)
|
154
|
+
```
|
155
|
+
|
156
|
+
When want to filter ULIDs with `Time`, we should consider to handle the precision.
|
157
|
+
So this gem provides `ULID.range` to generate reasonable `Range[ULID]` from `Range[Time]`
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
# Both of below, The begin of `Range[ULID]` will be the minimum in the floored milliseconds of the time1
|
161
|
+
include_end = ULID.range(time1..time2) #=> The end of `Range[ULID]` will be the maximum in the floored milliseconds of the time2
|
162
|
+
exclude_end = ULID.range(time1...time2) #=> The end of `Range[ULID]` will be the minimum in the floored milliseconds of the time2
|
163
|
+
|
164
|
+
# Below patterns are acceptable
|
165
|
+
pinpointing = ULID.range(time1..time1) #=> This will match only for all IDs in `time1`
|
166
|
+
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)
|
167
|
+
until_the_end = ULID.range(ULID.min.to_time..time1) #=> This is same as above for Ruby 2.6
|
168
|
+
until_the_ulid_limit = ULID.range(time1..) # This will match only for all IDs from `time1` to max value of the ULID limit
|
169
|
+
|
170
|
+
# So you can use the generated range objects as below
|
171
|
+
ulids.grep(one_of_the_above)
|
172
|
+
ulids.grep_v(one_of_the_above)
|
173
|
+
#=> I hope the results should be actually you want!
|
130
174
|
```
|
131
175
|
|
176
|
+
### Scanner for string (e.g. `JSON`)
|
177
|
+
|
132
178
|
For rough operations, `ULID.scan` might be useful.
|
133
179
|
|
134
180
|
```ruby
|
@@ -161,14 +207,16 @@ EOD
|
|
161
207
|
|
162
208
|
ULID.scan(json).to_a
|
163
209
|
#=>
|
164
|
-
[ULID(2021-04-30 05:51:57.119 UTC: 01F4GNAV5ZR6FJQ5SFQC7WDSY3),
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
210
|
+
# [ULID(2021-04-30 05:51:57.119 UTC: 01F4GNAV5ZR6FJQ5SFQC7WDSY3),
|
211
|
+
# ULID(2021-04-30 05:52:32.641 UTC: 01F4GNBXW1AM2KWW52PVT3ZY9X),
|
212
|
+
# ULID(2021-04-30 05:52:56.707 UTC: 01F4GNCNC3CH0BCRZBPPDEKBKS),
|
213
|
+
# ULID(2021-04-30 05:52:32.641 UTC: 01F4GNBXW1AM2KWW52PVT3ZY9X),
|
214
|
+
# ULID(2021-04-30 05:53:04.852 UTC: 01F4GNCXAMXQ1SGBH5XCR6ZH0M),
|
215
|
+
# ULID(2021-04-30 05:53:12.478 UTC: 01F4GND4RYYSKNAADHQ9BNXAWJ)]
|
170
216
|
```
|
171
217
|
|
218
|
+
### Some methods to help manipulations
|
219
|
+
|
172
220
|
`ULID.min` and `ULID.max` return termination values for ULID spec.
|
173
221
|
|
174
222
|
```ruby
|
@@ -180,7 +228,10 @@ ULID.min(moment: time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V000000000
|
|
180
228
|
ULID.max(moment: time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
|
181
229
|
```
|
182
230
|
|
183
|
-
`ULID#next` and `ULID#succ` returns next(successor) ULID
|
231
|
+
`ULID#next` and `ULID#succ` returns next(successor) ULID.
|
232
|
+
Especially `ULID#succ` makes it possible `Range[ULID]#each`.
|
233
|
+
|
234
|
+
NOTE: But basically `Range[ULID]#each` should not be used, incrementing 128 bits IDs are not reasonable operation in most case
|
184
235
|
|
185
236
|
```ruby
|
186
237
|
ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZY').next.to_s #=> "01BX5ZZKBKZZZZZZZZZZZZZZZZ"
|
@@ -196,15 +247,103 @@ ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZ
|
|
196
247
|
ULID.parse('00000000000000000000000000').pred #=> nil
|
197
248
|
```
|
198
249
|
|
199
|
-
|
250
|
+
`ULID.sample` returns random ULIDs ignoring the generating time
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
ULID.sample #=> ULID(2545-07-26 06:51:20.085 UTC: 0GGKQ45GMNMZR6N8A8GFG0ZXST)
|
254
|
+
ULID.sample #=> ULID(5098-07-26 21:31:06.946 UTC: 2SSBNGGYA272J7BMDCG4Z6EEM5)
|
255
|
+
ULID.sample(0) #=> []
|
256
|
+
ULID.sample(1) #=> [ULID(2241-04-16 03:31:18.440 UTC: 07S52YWZ98AZ8T565MD9VRYMQH)]
|
257
|
+
ULID.sample(5)
|
258
|
+
#=>
|
259
|
+
#[ULID(5701-04-29 12:41:19.647 UTC: 3B2YH2DV0ZYDDATGTYSKMM1CMT),
|
260
|
+
# ULID(2816-08-01 01:21:46.612 UTC: 0R9GT6RZKMK3RG02Q2HAFVKEY2),
|
261
|
+
# ULID(10408-10-05 17:06:27.848 UTC: 7J6CPTEEC86Y24EQ4F1Y93YYN0),
|
262
|
+
# ULID(2741-09-02 16:24:18.803 UTC: 0P4Q4V34KKAJW46QW47WQB5463),
|
263
|
+
# ULID(2665-03-16 14:50:22.724 UTC: 0KYFW9DWM4CEGFNTAC6YFAVVJ6)]
|
264
|
+
```
|
265
|
+
|
266
|
+
### UUIDv4 converter for migration use-cases
|
267
|
+
|
268
|
+
`ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
|
269
|
+
The imported timestamp is meaningless. So ULID's benefit will lost
|
200
270
|
|
201
271
|
```ruby
|
202
|
-
|
203
|
-
#=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
|
272
|
+
# Basically reversible
|
273
|
+
ulid = ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39') #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
|
274
|
+
ulid.to_uuidv4 #=> "0983d0a2-ff15-4d83-8f37-7dd945b5aa39"
|
275
|
+
|
276
|
+
uuid_v4s = 10000.times.map { SecureRandom.uuid }
|
277
|
+
uuid_v4s.uniq.size == 10000 #=> Probably `true`
|
278
|
+
|
279
|
+
ulids = uuid_v4s.map { |uuid_v4| ULID.from_uuidv4(uuid_v4) }
|
280
|
+
ulids.map(&:to_uuidv4) == uuid_v4s #=> **Probably** `true` except below examples.
|
281
|
+
|
282
|
+
# NOTE: Some boundary values are not reversible. See below.
|
283
|
+
|
284
|
+
ULID.min.to_uuidv4 #=> "00000000-0000-4000-8000-000000000000"
|
285
|
+
ULID.max.to_uuidv4 #=> "ffffffff-ffff-4fff-bfff-ffffffffffff"
|
286
|
+
|
287
|
+
# These importing results are same as https://github.com/ahawker/ulid/tree/96bdb1daad7ce96f6db8c91ac0410b66d2e1c4c1 on CPython 3.9.4
|
288
|
+
reversed_min = ULID.from_uuidv4('00000000-0000-4000-8000-000000000000') #=> ULID(1970-01-01 00:00:00.000 UTC: 00000000008008000000000000)
|
289
|
+
reversed_max = ULID.from_uuidv4('ffffffff-ffff-4fff-bfff-ffffffffffff') #=> ULID(10889-08-02 05:31:50.655 UTC: 7ZZZZZZZZZ9ZZVZZZZZZZZZZZZ)
|
290
|
+
|
291
|
+
# But they are not reversible! Need to consider this issue in https://github.com/kachick/ruby-ulid/issues/76
|
292
|
+
ULID.min == reversed_min #=> false
|
293
|
+
ULID.max == reversed_max #=> false
|
294
|
+
```
|
295
|
+
|
296
|
+
## How to migrate from other gems
|
297
|
+
|
298
|
+
As far as I know, major prior arts are below
|
299
|
+
|
300
|
+
### [ulid gem](https://rubygems.org/gems/ulid) - [rafaelsales/ulid](https://github.com/rafaelsales/ulid)
|
301
|
+
|
302
|
+
It is just providing basic `String` generator only.
|
303
|
+
So you can replace the code as below
|
304
|
+
|
305
|
+
```diff
|
306
|
+
-ULID.generate
|
307
|
+
+ULID.generate.to_s
|
204
308
|
```
|
205
309
|
|
310
|
+
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.
|
311
|
+
|
312
|
+
1. [Sort order does not respect millisecond ordering](https://github.com/rafaelsales/ulid/issues/22)
|
313
|
+
1. [Fixed in this PR](https://github.com/rafaelsales/ulid/pull/23)
|
314
|
+
1. [Released in 1.3.0](https://github.com/rafaelsales/ulid/compare/1.2.0...v1.3.0)
|
315
|
+
|
316
|
+
### [ulid-ruby gem](https://rubygems.org/gems/ulid-ruby) - [abachman/ulid-ruby](https://github.com/abachman/ulid-ruby)
|
317
|
+
|
318
|
+
It is providing basic generator(except monotonic generator) and parser.
|
319
|
+
Major methods can be replaced as below.
|
320
|
+
|
321
|
+
```diff
|
322
|
+
-ULID.generate
|
323
|
+
+ULID.generate.to_s
|
324
|
+
-ULID.at(time)
|
325
|
+
+ULID.generate(moment: time).to_s
|
326
|
+
-ULID.time(string)
|
327
|
+
+ULID.parse(string).to_time
|
328
|
+
-ULID.min_ulid_at(time)
|
329
|
+
+ULID.min(moment: time).to_s
|
330
|
+
-ULID.max_ulid_at(time)
|
331
|
+
+ULID.max(moment: time).to_s
|
332
|
+
```
|
333
|
+
|
334
|
+
NOTE: It is still having precision issue similar as `ulid gem` in the both generator and parser. I sent PRs.
|
335
|
+
|
336
|
+
1. [Parsed time object has more than milliseconds](https://github.com/abachman/ulid-ruby/issues/3)
|
337
|
+
1. [Fix to handle timestamp precision in parser](https://github.com/abachman/ulid-ruby/pull/5)
|
338
|
+
1. [Fix to handle timestamp precision in generator](https://github.com/abachman/ulid-ruby/pull/4)
|
339
|
+
|
206
340
|
## References
|
207
341
|
|
342
|
+
- [Repository](https://github.com/kachick/ruby-ulid)
|
208
343
|
- [API documents](https://kachick.github.io/ruby-ulid/)
|
209
344
|
- [ulid/spec](https://github.com/ulid/spec)
|
210
|
-
|
345
|
+
|
346
|
+
## Note
|
347
|
+
|
348
|
+
- 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)
|
349
|
+
- 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
@@ -3,7 +3,6 @@
|
|
3
3
|
# Copyright (C) 2021 Kenichi Kamiya
|
4
4
|
|
5
5
|
require 'securerandom'
|
6
|
-
require 'integer/base'
|
7
6
|
|
8
7
|
# @see https://github.com/ulid/spec
|
9
8
|
# @!attribute [r] milliseconds
|
@@ -16,6 +15,7 @@ class ULID
|
|
16
15
|
class Error < StandardError; end
|
17
16
|
class OverflowError < Error; end
|
18
17
|
class ParserError < Error; end
|
18
|
+
class SetupError < ScriptError; end
|
19
19
|
|
20
20
|
encoding_string = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
|
21
21
|
# Crockford's Base32. Excluded I, L, O, U.
|
@@ -35,12 +35,15 @@ 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
|
42
42
|
TIME_FORMAT_IN_INSPECT = '%Y-%m-%d %H:%M:%S.%3N %Z'
|
43
43
|
|
44
|
+
UNDEFINED = BasicObject.new
|
45
|
+
Kernel.instance_method(:freeze).bind(UNDEFINED).call
|
46
|
+
|
44
47
|
# @param [Integer, Time] moment
|
45
48
|
# @param [Integer] entropy
|
46
49
|
# @return [ULID]
|
@@ -51,27 +54,38 @@ class ULID
|
|
51
54
|
# @param [Integer, Time] moment
|
52
55
|
# @return [ULID]
|
53
56
|
def self.min(moment: 0)
|
54
|
-
generate(moment: moment, entropy: 0)
|
57
|
+
0.equal?(moment) ? MIN : generate(moment: moment, entropy: 0)
|
55
58
|
end
|
56
59
|
|
57
60
|
# @param [Integer, Time] moment
|
58
61
|
# @return [ULID]
|
59
62
|
def self.max(moment: MAX_MILLISECONDS)
|
60
|
-
generate(moment: moment, entropy: MAX_ENTROPY)
|
61
|
-
end
|
62
|
-
|
63
|
-
# @
|
64
|
-
# @
|
65
|
-
# @
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
63
|
+
MAX_MILLISECONDS.equal?(moment) ? MAX : generate(moment: moment, entropy: MAX_ENTROPY)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param [Integer] number
|
67
|
+
# @return [ULID, Array<ULID>]
|
68
|
+
# @raise [ArgumentError] if the given number is lager than ULID spec limits or given negative number
|
69
|
+
# @note Major difference of `Array#sample` interface is below
|
70
|
+
# * Do not ensure the uniqueness
|
71
|
+
# * Do not take random generator for the arguments
|
72
|
+
# * Raising error instead of truncating elements for the given number
|
73
|
+
def self.sample(number=UNDEFINED)
|
74
|
+
if UNDEFINED.equal?(number)
|
75
|
+
from_integer(SecureRandom.random_number(MAX_INTEGER))
|
70
76
|
else
|
71
|
-
|
72
|
-
|
77
|
+
begin
|
78
|
+
int = number.to_int
|
79
|
+
rescue
|
80
|
+
# Can not use `number.to_s` and `number.inspect` for considering BasicObject here
|
81
|
+
raise TypeError, 'accepts no argument or integer only'
|
82
|
+
end
|
73
83
|
|
74
|
-
|
84
|
+
if int > MAX_INTEGER || int.negative?
|
85
|
+
raise ArgumentError, "given number is larger than ULID limit #{MAX_INTEGER} or negative: #{number.inspect}"
|
86
|
+
end
|
87
|
+
int.times.map { from_integer(SecureRandom.random_number(MAX_INTEGER)) }
|
88
|
+
end
|
75
89
|
end
|
76
90
|
|
77
91
|
# @param [String, #to_str] string
|
@@ -121,6 +135,43 @@ class ULID
|
|
121
135
|
new milliseconds: milliseconds, entropy: entropy
|
122
136
|
end
|
123
137
|
|
138
|
+
# @param [Range<Time>, Range<nil>] time_range
|
139
|
+
# @return [Range<ULID>]
|
140
|
+
# @raise [ArgumentError] if the given time_range is not a `Range[Time]` or `Range[nil]`
|
141
|
+
def self.range(time_range)
|
142
|
+
raise argument_error_for_range_building(time_range) unless time_range.kind_of?(Range)
|
143
|
+
begin_time, end_time, exclude_end = time_range.begin, time_range.end, time_range.exclude_end?
|
144
|
+
|
145
|
+
case begin_time
|
146
|
+
when Time
|
147
|
+
begin_ulid = min(moment: begin_time)
|
148
|
+
when nil
|
149
|
+
begin_ulid = MIN
|
150
|
+
else
|
151
|
+
raise argument_error_for_range_building(time_range)
|
152
|
+
end
|
153
|
+
|
154
|
+
case end_time
|
155
|
+
when Time
|
156
|
+
if exclude_end
|
157
|
+
end_ulid = min(moment: end_time)
|
158
|
+
else
|
159
|
+
end_ulid = max(moment: end_time)
|
160
|
+
end
|
161
|
+
when nil
|
162
|
+
# The end should be max and include end, because nil end means to cover endless ULIDs until the limit
|
163
|
+
end_ulid = MAX
|
164
|
+
exclude_end = false
|
165
|
+
else
|
166
|
+
raise argument_error_for_range_building(time_range)
|
167
|
+
end
|
168
|
+
|
169
|
+
begin_ulid.freeze
|
170
|
+
end_ulid.freeze
|
171
|
+
|
172
|
+
Range.new(begin_ulid, end_ulid, exclude_end)
|
173
|
+
end
|
174
|
+
|
124
175
|
# @param [Time] time
|
125
176
|
# @return [Time]
|
126
177
|
def self.floor(time)
|
@@ -131,49 +182,99 @@ class ULID
|
|
131
182
|
end
|
132
183
|
end
|
133
184
|
|
185
|
+
# @api private
|
134
186
|
# @return [Integer]
|
135
187
|
def self.current_milliseconds
|
136
188
|
milliseconds_from_time(Time.now)
|
137
189
|
end
|
138
190
|
|
191
|
+
# @api private
|
139
192
|
# @param [Time] time
|
140
193
|
# @return [Integer]
|
141
194
|
def self.milliseconds_from_time(time)
|
142
195
|
(time.to_r * 1000).to_i
|
143
196
|
end
|
144
197
|
|
198
|
+
# @api private
|
145
199
|
# @param [Time, Integer] moment
|
146
200
|
# @return [Integer]
|
147
201
|
def self.milliseconds_from_moment(moment)
|
148
202
|
moment.kind_of?(Time) ? milliseconds_from_time(moment) : moment.to_int
|
149
203
|
end
|
150
204
|
|
205
|
+
# @api private
|
151
206
|
# @return [Integer]
|
152
207
|
def self.reasonable_entropy
|
153
208
|
SecureRandom.random_number(MAX_ENTROPY)
|
154
209
|
end
|
155
210
|
|
211
|
+
n32_chars = [*'0'..'9', *'A'..'V'].map(&:freeze).freeze
|
212
|
+
raise SetupError, 'obvious bug exists in the mapping algorithm' unless n32_chars.size == 32
|
213
|
+
|
214
|
+
n32_char_by_number = {}
|
215
|
+
n32_chars.each_with_index do |char, index|
|
216
|
+
n32_char_by_number[index] = char
|
217
|
+
end
|
218
|
+
n32_char_by_number.freeze
|
219
|
+
|
220
|
+
# Currently supporting only for `subset for actual use-case`
|
221
|
+
# See below
|
222
|
+
# * https://github.com/ulid/spec/pull/57
|
223
|
+
# * https://github.com/kachick/ruby-ulid/issues/57
|
224
|
+
# * https://github.com/kachick/ruby-ulid/issues/78
|
225
|
+
crockford_base32_mappings = {
|
226
|
+
'J' => 18,
|
227
|
+
'K' => 19,
|
228
|
+
'M' => 20,
|
229
|
+
'N' => 21,
|
230
|
+
'P' => 22,
|
231
|
+
'Q' => 23,
|
232
|
+
'R' => 24,
|
233
|
+
'S' => 25,
|
234
|
+
'T' => 26,
|
235
|
+
'V' => 27,
|
236
|
+
'W' => 28,
|
237
|
+
'X' => 29,
|
238
|
+
'Y' => 30,
|
239
|
+
'Z' => 31
|
240
|
+
}.freeze
|
241
|
+
|
242
|
+
N32_CHAR_BY_CROCKFORD_BASE32_CHAR = ENCODING_CHARS.each_with_object({}) do |encoding_char, map|
|
243
|
+
if n = crockford_base32_mappings[encoding_char]
|
244
|
+
char_32 = n32_char_by_number.fetch(n)
|
245
|
+
map[encoding_char] = char_32
|
246
|
+
end
|
247
|
+
end.freeze
|
248
|
+
raise SetupError, 'obvious bug exists in the mapping algorithm' unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
|
249
|
+
CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
|
250
|
+
|
251
|
+
CROCKFORD_BASE32_CHAR_BY_N32_CHAR = N32_CHAR_BY_CROCKFORD_BASE32_CHAR.invert.freeze
|
252
|
+
N32_CHAR_PATTERN = /[#{CROCKFORD_BASE32_CHAR_BY_N32_CHAR.keys.join}]/.freeze
|
253
|
+
|
156
254
|
# @param [String, #to_str] string
|
157
255
|
# @return [ULID]
|
158
256
|
# @raise [ParserError] if the given format is not correct for ULID specs
|
159
|
-
# @raise [OverflowError] if the given value is larger than the ULID limit
|
160
257
|
def self.parse(string)
|
161
258
|
begin
|
162
259
|
string = string.to_str
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
entropy = Integer::Base.parse(randomness, ENCODING_CHARS)
|
260
|
+
raise "given argument does not match to `#{STRICT_PATTERN.inspect}`" unless STRICT_PATTERN.match?(string)
|
261
|
+
n32encoded = convert_crockford_base32_to_n32(string.upcase)
|
262
|
+
timestamp = n32encoded.slice(0, TIMESTAMP_PART_LENGTH)
|
263
|
+
randomness = n32encoded.slice(TIMESTAMP_PART_LENGTH, RANDOMNESS_PART_LENGTH)
|
264
|
+
milliseconds = timestamp.to_i(32)
|
265
|
+
entropy = randomness.to_i(32)
|
170
266
|
rescue => err
|
171
267
|
raise ParserError, "parsing failure as #{err.inspect} for given #{string.inspect}"
|
172
268
|
end
|
173
|
-
|
269
|
+
|
174
270
|
new milliseconds: milliseconds, entropy: entropy
|
175
271
|
end
|
176
272
|
|
273
|
+
# @api private
|
274
|
+
private_class_method def self.convert_crockford_base32_to_n32(string)
|
275
|
+
string.gsub(CROCKFORD_BASE32_CHAR_PATTERN, N32_CHAR_BY_CROCKFORD_BASE32_CHAR)
|
276
|
+
end
|
277
|
+
|
177
278
|
# @return [Boolean]
|
178
279
|
def self.valid?(string)
|
179
280
|
parse(string)
|
@@ -208,6 +309,11 @@ class ULID
|
|
208
309
|
num
|
209
310
|
end
|
210
311
|
|
312
|
+
# @return [ArgumentError]
|
313
|
+
private_class_method def self.argument_error_for_range_building(argument)
|
314
|
+
ArgumentError.new "ULID.range takes only `Range[Time]` or `Range[nil]`, given: #{argument.inspect}"
|
315
|
+
end
|
316
|
+
|
211
317
|
attr_reader :milliseconds, :entropy
|
212
318
|
|
213
319
|
# @api private
|
@@ -228,10 +334,9 @@ class ULID
|
|
228
334
|
end
|
229
335
|
|
230
336
|
# @return [String]
|
231
|
-
def
|
232
|
-
@string ||=
|
337
|
+
def to_s
|
338
|
+
@string ||= convert_n32_to_crockford_base32(to_i.to_s(32).rjust(ENCODED_ID_LENGTH, '0').upcase).freeze
|
233
339
|
end
|
234
|
-
alias_method :to_s, :to_str
|
235
340
|
|
236
341
|
# @return [Integer]
|
237
342
|
def to_i
|
@@ -246,7 +351,7 @@ class ULID
|
|
246
351
|
|
247
352
|
# @return [String]
|
248
353
|
def inspect
|
249
|
-
@inspect ||= "ULID(#{to_time.strftime(TIME_FORMAT_IN_INSPECT)}: #{
|
354
|
+
@inspect ||= "ULID(#{to_time.strftime(TIME_FORMAT_IN_INSPECT)}: #{to_s})".freeze
|
250
355
|
end
|
251
356
|
|
252
357
|
# @return [Boolean]
|
@@ -307,11 +412,13 @@ class ULID
|
|
307
412
|
@randomness ||= matchdata[:randomness].freeze
|
308
413
|
end
|
309
414
|
|
415
|
+
# @deprecated This method might be changed in https://github.com/kachick/ruby-ulid/issues/84
|
310
416
|
# @return [Regexp]
|
311
417
|
def pattern
|
312
418
|
@pattern ||= /(?<timestamp>#{timestamp})(?<randomness>#{randomness})/i.freeze
|
313
419
|
end
|
314
420
|
|
421
|
+
# @deprecated This method might be changed in https://github.com/kachick/ruby-ulid/issues/84
|
315
422
|
# @return [Regexp]
|
316
423
|
def strict_pattern
|
317
424
|
@strict_pattern ||= /\A#{pattern.source}\z/i.freeze
|
@@ -332,23 +439,45 @@ class ULID
|
|
332
439
|
@pred ||= self.class.from_integer(pre_int)
|
333
440
|
end
|
334
441
|
|
442
|
+
# @return [String]
|
443
|
+
def to_uuidv4
|
444
|
+
@uuidv4 ||= begin
|
445
|
+
# This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
|
446
|
+
array = octets.pack('C*').unpack('NnnnnN')
|
447
|
+
array[2] = (array[2] & 0x0fff) | 0x4000
|
448
|
+
array[3] = (array[3] & 0x3fff) | 0x8000
|
449
|
+
('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
335
453
|
# @return [self]
|
336
454
|
def freeze
|
337
|
-
#
|
338
|
-
|
339
|
-
octets
|
340
|
-
to_i
|
341
|
-
succ
|
342
|
-
pred
|
343
|
-
strict_pattern
|
455
|
+
# Need to cache before freezing, because frozen objects can't assign instance variables
|
456
|
+
cache_all_instance_variables
|
344
457
|
super
|
345
458
|
end
|
346
459
|
|
347
460
|
private
|
348
461
|
|
462
|
+
# @api private
|
463
|
+
def convert_n32_to_crockford_base32(string)
|
464
|
+
string.gsub(N32_CHAR_PATTERN, CROCKFORD_BASE32_CHAR_BY_N32_CHAR)
|
465
|
+
end
|
466
|
+
|
349
467
|
# @return [MatchData]
|
350
468
|
def matchdata
|
351
|
-
@matchdata ||= STRICT_PATTERN.match(
|
469
|
+
@matchdata ||= STRICT_PATTERN.match(to_s).freeze
|
470
|
+
end
|
471
|
+
|
472
|
+
# @return [void]
|
473
|
+
def cache_all_instance_variables
|
474
|
+
inspect
|
475
|
+
octets
|
476
|
+
to_i
|
477
|
+
succ
|
478
|
+
pred
|
479
|
+
strict_pattern
|
480
|
+
to_uuidv4
|
352
481
|
end
|
353
482
|
end
|
354
483
|
|
@@ -356,7 +485,8 @@ require_relative 'ulid/version'
|
|
356
485
|
require_relative 'ulid/monotonic_generator'
|
357
486
|
|
358
487
|
class ULID
|
359
|
-
|
488
|
+
MIN = parse('00000000000000000000000000').freeze
|
489
|
+
MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
|
360
490
|
|
361
|
-
private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN
|
491
|
+
private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN, :MIN, :MAX, :CROCKFORD_BASE32_CHAR_PATTERN, :N32_CHAR_BY_CROCKFORD_BASE32_CHAR, :CROCKFORD_BASE32_CHAR_BY_N32_CHAR, :N32_CHAR_PATTERN, :UNDEFINED
|
362
492
|
end
|
data/lib/ulid/version.rb
CHANGED
data/sig/ulid.rbs
CHANGED
@@ -15,9 +15,16 @@ class ULID
|
|
15
15
|
PATTERN: Regexp
|
16
16
|
STRICT_PATTERN: Regexp
|
17
17
|
UUIDV4_PATTERN: Regexp
|
18
|
-
|
18
|
+
N32_CHAR_BY_CROCKFORD_BASE32_CHAR: Hash[String, String]
|
19
|
+
CROCKFORD_BASE32_CHAR_PATTERN: Regexp
|
20
|
+
CROCKFORD_BASE32_CHAR_BY_N32_CHAR: Hash[String, String]
|
21
|
+
N32_CHAR_PATTERN: Regexp
|
22
|
+
MIN: ULID
|
23
|
+
MAX: ULID
|
24
|
+
UNDEFINED: BasicObject
|
19
25
|
include Comparable
|
20
26
|
|
27
|
+
# The `moment` is a `Time` or `Intger of the milliseconds`
|
21
28
|
type moment = Time | Integer
|
22
29
|
|
23
30
|
class Error < StandardError
|
@@ -29,6 +36,9 @@ class ULID
|
|
29
36
|
class ParserError < Error
|
30
37
|
end
|
31
38
|
|
39
|
+
class SetupError < ScriptError
|
40
|
+
end
|
41
|
+
|
32
42
|
class MonotonicGenerator
|
33
43
|
attr_accessor latest_milliseconds: Integer
|
34
44
|
attr_accessor latest_entropy: Integer
|
@@ -56,13 +66,14 @@ class ULID
|
|
56
66
|
@next: ULID?
|
57
67
|
@pattern: Regexp?
|
58
68
|
@strict_pattern: Regexp?
|
69
|
+
@uuidv4: String?
|
59
70
|
@matchdata: MatchData?
|
60
71
|
|
61
72
|
def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
|
62
|
-
def self.monotonic_generate: -> ULID
|
63
73
|
def self.current_milliseconds: -> Integer
|
64
74
|
def self.milliseconds_from_time: (Time time) -> Integer
|
65
75
|
def self.milliseconds_from_moment: (moment moment) -> Integer
|
76
|
+
def self.range: (Range[Time] | Range[nil] time_range) -> Range[ULID]
|
66
77
|
def self.floor: (Time time) -> Time
|
67
78
|
def self.reasonable_entropy: -> Integer
|
68
79
|
def self.parse: (String string) -> ULID
|
@@ -70,6 +81,8 @@ class ULID
|
|
70
81
|
def self.from_integer: (Integer integer) -> ULID
|
71
82
|
def self.min: (?moment: moment) -> ULID
|
72
83
|
def self.max: (?moment: moment) -> ULID
|
84
|
+
def self.sample: -> ULID
|
85
|
+
| (Integer number) -> Array[ULID]
|
73
86
|
def self.valid?: (untyped string) -> bool
|
74
87
|
def self.scan: (String string) -> Enumerator[ULID, singleton(ULID)]
|
75
88
|
| (String string) { (ULID ulid) -> void } -> singleton(ULID)
|
@@ -78,8 +91,7 @@ class ULID
|
|
78
91
|
attr_reader milliseconds: Integer
|
79
92
|
attr_reader entropy: Integer
|
80
93
|
def initialize: (milliseconds: Integer, entropy: Integer) -> void
|
81
|
-
def
|
82
|
-
alias to_s to_str
|
94
|
+
def to_s: -> String
|
83
95
|
def to_i: -> Integer
|
84
96
|
alias hash to_i
|
85
97
|
def <=>: (ULID other) -> Integer
|
@@ -96,11 +108,16 @@ class ULID
|
|
96
108
|
def octets: -> octets
|
97
109
|
def timestamp_octets: -> timestamp_octets
|
98
110
|
def randomness_octets: -> randomness_octets
|
111
|
+
def to_uuidv4: -> String
|
99
112
|
def next: -> ULID?
|
100
113
|
alias succ next
|
101
114
|
def pred: -> ULID?
|
102
115
|
def freeze: -> self
|
103
116
|
|
104
117
|
private
|
118
|
+
def self.convert_crockford_base32_to_n32: (String) -> String
|
119
|
+
def self.argument_error_for_range_building: (untyped argument) -> ArgumentError
|
120
|
+
def convert_n32_to_crockford_base32: (String) -> String
|
105
121
|
def matchdata: -> MatchData
|
122
|
+
def cache_all_instance_variables: -> void
|
106
123
|
end
|
metadata
CHANGED
@@ -1,35 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-ulid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.17
|
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-
|
11
|
+
date: 2021-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: rbs
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: 0.2.0
|
23
|
-
type: :runtime
|
19
|
+
version: 1.2.0
|
20
|
+
type: :development
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
24
|
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
30
|
-
- - "<"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: 0.2.0
|
26
|
+
version: 1.2.0
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: benchmark-ips
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,10 +64,10 @@ dependencies:
|
|
70
64
|
- - "<"
|
71
65
|
- !ruby/object:Gem::Version
|
72
66
|
version: '2'
|
73
|
-
description:
|
74
|
-
|
75
|
-
|
76
|
-
|
67
|
+
description: |2
|
68
|
+
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.
|
69
|
+
This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around the ULID.
|
70
|
+
Also providing `ruby/rbs` signature files.
|
77
71
|
email:
|
78
72
|
- kachick1+ruby@gmail.com
|
79
73
|
executables: []
|
@@ -82,7 +76,6 @@ extra_rdoc_files: []
|
|
82
76
|
files:
|
83
77
|
- LICENSE
|
84
78
|
- README.md
|
85
|
-
- Steepfile
|
86
79
|
- lib/ulid.rb
|
87
80
|
- lib/ulid/monotonic_generator.rb
|
88
81
|
- lib/ulid/version.rb
|
@@ -102,7 +95,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
102
95
|
requirements:
|
103
96
|
- - ">="
|
104
97
|
- !ruby/object:Gem::Version
|
105
|
-
version:
|
98
|
+
version: 2.6.0
|
106
99
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
100
|
requirements:
|
108
101
|
- - ">="
|