ruby-ulid 0.0.15 → 0.0.16

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: 6a620e80f82e91c778329ccf7da8cca96bda4b12e047610b9543768c5ec85d22
4
- data.tar.gz: ea28469d63348758f52e1460b5330ce177ad2281e63060800a0669054758f3ee
3
+ metadata.gz: 15a83604732cdc37f8015ee9382884e950852f8c9c82aba107ea4b3c065c5a4d
4
+ data.tar.gz: 06e7b69a5838786f4d23da19f5e1f00fb3173014e7438459165b66703637851a
5
5
  SHA512:
6
- metadata.gz: acb07ae797c3a06a6626d34e15dc1056a30586e8bc151a3773770259d2f4047d0708593e702fbd67a7609758165f20a15f208fede068717a5555733535ed4bde
7
- data.tar.gz: 57c9819d86f2a0f002cf3b9a7b76b3a36bc412fa874d50136ec296f1217fb905fa19feb52de2c9d5250547da384580cf882121f0e331054cd665bd113d1f10e3
6
+ metadata.gz: 6ca46525e80b1d832a703b8cb867466327de333853ee236654f722dc6abc30f46fd149ca9633c42ececcea08db2a01a6d82ab60f8eeb61ebb947f0441436dd96
7
+ data.tar.gz: ccd08e78dfb047f82af02eeaf14ccd12fcb05fe882edeb2bf48c2e7ec5b9fd698feccf0b9f9e187aaff97c2e793f671a3f9d32c11e3741656c3df94f944bc808
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ruby-ulid
2
2
 
3
- A handy `ULID` library
3
+ ## Overview
4
4
 
5
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.
@@ -33,16 +33,26 @@ Instead, herein is proposed ULID:
33
33
  - No special characters (URL safe)
34
34
  - Monotonic sort order (correctly detects and handles the same millisecond)
35
35
 
36
- ## Install
36
+ ## Usage
37
+
38
+ ### Install
37
39
 
38
40
  Require Ruby 2.6 or later
39
41
 
42
+ This command will install the latest version into your environment
43
+
40
44
  ```console
41
45
  $ gem install ruby-ulid
42
- #=> Installed
46
+ Should be installed!
43
47
  ```
44
48
 
45
- ## Usage
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
46
56
 
47
57
  The generated `ULID` is an object not just a string.
48
58
  It means easily get the timestamps and binary formats.
@@ -54,7 +64,6 @@ ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GR
54
64
  ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
55
65
  ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
56
66
  ulid.octets #=> [1, 121, 20, 95, 7, 202, 187, 60, 175, 51, 76, 60, 49, 73, 37, 74]
57
- ulid.pattern #=> /(?<timestamp>01F4A5Y1YA)(?<randomness>QCYAYCTC7GRMJ9AA)/i
58
67
  ```
59
68
 
60
69
  You can get the objects from exists encoded ULIDs
@@ -64,6 +73,8 @@ ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV') #=> ULID(2016-07-30 23:54:10.259
64
73
  ulid.to_time #=> 2016-07-30 23:54:10.259 UTC
65
74
  ```
66
75
 
76
+ ### Sortable with the timestamp
77
+
67
78
  ULIDs are sortable when they are generated in different timestamp with milliseconds precision
68
79
 
69
80
  ```ruby
@@ -71,14 +82,14 @@ ulids = 1000.times.map do
71
82
  sleep(0.001)
72
83
  ULID.generate
73
84
  end
74
- ulids.sort == ulids #=> true
75
85
  ulids.uniq(&:to_time).size #=> 1000
86
+ ulids.sort == ulids #=> true
76
87
  ```
77
88
 
78
89
  `ULID.generate` can take fixed `Time` instance
79
90
 
80
91
  ```ruby
81
- time = Time.at(946684800, in: 'UTC') #=> 2000-01-01 00:00:00 UTC
92
+ time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
82
93
  ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
83
94
  ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
84
95
 
@@ -98,7 +109,9 @@ ulids.uniq(&:to_time).size #=> 35 (the size is not fixed, might be changed in en
98
109
  ulids.sort == ulids #=> false
99
110
  ```
100
111
 
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.
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.
102
115
  (Though it starts with new random value when changed the timestamp)
103
116
 
104
117
  ```ruby
@@ -128,20 +141,40 @@ sample_ulids_by_the_time.take(5) #=>
128
141
  ulids.sort == ulids #=> true
129
142
  ```
130
143
 
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]`
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]`
133
158
 
134
159
  ```ruby
135
160
  # Both of below, The begin of `Range[ULID]` will be the minimum in the floored milliseconds of the time1
136
161
  include_end = ULID.range(time1..time2) #=> The end of `Range[ULID]` will be the maximum in the floored milliseconds of the time2
137
162
  exclude_end = ULID.range(time1...time2) #=> The end of `Range[ULID]` will be the minimum in the floored milliseconds of the time2
138
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
+
139
170
  # So you can use the generated range objects as below
140
- ulids.grep(include_end)
141
- ulids.grep(exclude_end)
171
+ ulids.grep(one_of_the_above)
172
+ ulids.grep_v(one_of_the_above)
142
173
  #=> I hope the results should be actually you want!
143
174
  ```
144
175
 
176
+ ### Scanner for string (e.g. `JSON`)
177
+
145
178
  For rough operations, `ULID.scan` might be useful.
146
179
 
147
180
  ```ruby
@@ -182,6 +215,8 @@ ULID.scan(json).to_a
182
215
  ULID(2021-04-30 05:53:12.478 UTC: 01F4GND4RYYSKNAADHQ9BNXAWJ)]
183
216
  ```
184
217
 
218
+ ### Some methods to help manipulations
219
+
185
220
  `ULID.min` and `ULID.max` return termination values for ULID spec.
186
221
 
187
222
  ```ruby
@@ -193,7 +228,10 @@ ULID.min(moment: time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V000000000
193
228
  ULID.max(moment: time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
194
229
  ```
195
230
 
196
- `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
197
235
 
198
236
  ```ruby
199
237
  ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZY').next.to_s #=> "01BX5ZZKBKZZZZZZZZZZZZZZZZ"
@@ -239,6 +277,50 @@ ULID.min == reversed_min #=> false
239
277
  ULID.max == reversed_max #=> false
240
278
  ```
241
279
 
280
+ ## How to migrate from other gems
281
+
282
+ As far as I know, major prior arts are below
283
+
284
+ ### [ulid gem](https://rubygems.org/gems/ulid) - [rafaelsales/ulid](https://github.com/rafaelsales/ulid)
285
+
286
+ It is just providing basic `String` generator only.
287
+ So you can replace the code as below
288
+
289
+ ```diff
290
+ -ULID.generate
291
+ +ULID.generate.to_s
292
+ ```
293
+
294
+ 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.
295
+
296
+ 1. [Sort order does not respect millisecond ordering](https://github.com/rafaelsales/ulid/issues/22)
297
+ 1. [Fixed in this PR](https://github.com/rafaelsales/ulid/pull/23)
298
+ 1. [Released in 1.3.0](https://github.com/rafaelsales/ulid/compare/1.2.0...v1.3.0)
299
+
300
+ ### [ulid-ruby gem](https://rubygems.org/gems/ulid-ruby) - [abachman/ulid-ruby](https://github.com/abachman/ulid-ruby)
301
+
302
+ It is providing basic generator(except monotonic generator) and parser.
303
+ Major methods can be replaced as below.
304
+
305
+ ```diff
306
+ -ULID.generate
307
+ +ULID.generate.to_s
308
+ -ULID.at(time)
309
+ +ULID.generate(moment: time).to_s
310
+ -ULID.time(string)
311
+ +ULID.parse(string).to_time
312
+ -ULID.min_ulid_at(time)
313
+ +ULID.min(moment: time).to_s
314
+ -ULID.max_ulid_at(time)
315
+ +ULID.max(moment: time).to_s
316
+ ```
317
+
318
+ NOTE: It is still having precision issue similar as `ulid gem` in the both generator and parser. I sent PRs.
319
+
320
+ 1. [Parsed time object has more than milliseconds](https://github.com/abachman/ulid-ruby/issues/3)
321
+ 1. [Fix to handle timestamp precision in parser](https://github.com/abachman/ulid-ruby/pull/5)
322
+ 1. [Fix to handle timestamp precision in generator](https://github.com/abachman/ulid-ruby/pull/4)
323
+
242
324
  ## References
243
325
 
244
326
  - [Repository](https://github.com/kachick/ruby-ulid)
data/lib/ulid.rb CHANGED
@@ -16,6 +16,7 @@ class ULID
16
16
  class Error < StandardError; end
17
17
  class OverflowError < Error; end
18
18
  class ParserError < Error; end
19
+ class SetupError < ScriptError; end
19
20
 
20
21
  encoding_string = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
21
22
  # Crockford's Base32. Excluded I, L, O, U.
@@ -190,11 +191,13 @@ class ULID
190
191
  SecureRandom.random_number(MAX_ENTROPY)
191
192
  end
192
193
 
194
+ # @api private
195
+ # @deprecated Just exists to compare performance with old implementation. ref: https://github.com/kachick/ruby-ulid/issues/7
193
196
  # @param [String, #to_str] string
194
197
  # @return [ULID]
195
198
  # @raise [ParserError] if the given format is not correct for ULID specs
196
199
  # @raise [OverflowError] if the given value is larger than the ULID limit
197
- def self.parse(string)
200
+ def self.parse_with_integer_base(string)
198
201
  begin
199
202
  string = string.to_str
200
203
  unless string.size == ENCODED_ID_LENGTH
@@ -211,6 +214,67 @@ class ULID
211
214
  new milliseconds: milliseconds, entropy: entropy
212
215
  end
213
216
 
217
+ n32_chars = [*'0'..'9', *'A'..'V'].map(&:freeze).freeze
218
+ raise SetupError, 'obvious bug exists in the mapping algorithm' unless n32_chars.size == 32
219
+
220
+ n32_char_by_number = {}
221
+ n32_chars.each_with_index do |char, index|
222
+ n32_char_by_number[index] = char
223
+ end
224
+ n32_char_by_number.freeze
225
+
226
+ # Currently supporting only for `subset for actual use-case`
227
+ # See below
228
+ # * https://github.com/ulid/spec/pull/57
229
+ # * https://github.com/kachick/ruby-ulid/issues/57
230
+ # * https://github.com/kachick/ruby-ulid/issues/78
231
+ crockford_base32_mappings = {
232
+ 'J' => 18,
233
+ 'K' => 19,
234
+ 'M' => 20,
235
+ 'N' => 21,
236
+ 'P' => 22,
237
+ 'Q' => 23,
238
+ 'R' => 24,
239
+ 'S' => 25,
240
+ 'T' => 26,
241
+ 'V' => 27,
242
+ 'W' => 28,
243
+ 'X' => 29,
244
+ 'Y' => 30,
245
+ 'Z' => 31
246
+ }.freeze
247
+
248
+ REPLACING_MAP = ENCODING_CHARS.each_with_object({}) do |encoding_char, map|
249
+ if n = crockford_base32_mappings[encoding_char]
250
+ char_32 = n32_char_by_number.fetch(n)
251
+ map[encoding_char] = char_32
252
+ end
253
+ end.freeze
254
+ raise SetupError, 'obvious bug exists in the mapping algorithm' unless REPLACING_MAP.keys == crockford_base32_mappings.keys
255
+ REPLACING_PATTERN = /[#{REPLACING_MAP.keys.join}]/.freeze
256
+
257
+ def self.parse(string)
258
+ begin
259
+ string = string.to_str
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)
266
+ rescue => err
267
+ raise ParserError, "parsing failure as #{err.inspect} for given #{string.inspect}"
268
+ end
269
+
270
+ new milliseconds: milliseconds, entropy: entropy
271
+ end
272
+
273
+ # @api private
274
+ private_class_method def self.convert_crockford_base32_to_n32(string)
275
+ string.gsub(REPLACING_PATTERN, REPLACING_MAP)
276
+ end
277
+
214
278
  # @return [Boolean]
215
279
  def self.valid?(string)
216
280
  parse(string)
@@ -348,11 +412,13 @@ class ULID
348
412
  @randomness ||= matchdata[:randomness].freeze
349
413
  end
350
414
 
415
+ # @deprecated This method might be changed in https://github.com/kachick/ruby-ulid/issues/84
351
416
  # @return [Regexp]
352
417
  def pattern
353
418
  @pattern ||= /(?<timestamp>#{timestamp})(?<randomness>#{randomness})/i.freeze
354
419
  end
355
420
 
421
+ # @deprecated This method might be changed in https://github.com/kachick/ruby-ulid/issues/84
356
422
  # @return [Regexp]
357
423
  def strict_pattern
358
424
  @strict_pattern ||= /\A#{pattern.source}\z/i.freeze
@@ -418,5 +484,5 @@ class ULID
418
484
  MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
419
485
  MONOTONIC_GENERATOR = MonotonicGenerator.new
420
486
 
421
- private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN, :MIN, :MAX
487
+ private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN, :MIN, :MAX, :REPLACING_PATTERN, :REPLACING_MAP
422
488
  end
data/lib/ulid/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class ULID
5
- VERSION = '0.0.15'
5
+ VERSION = '0.0.16'
6
6
  end
data/sig/ulid.rbs CHANGED
@@ -15,6 +15,8 @@ class ULID
15
15
  PATTERN: Regexp
16
16
  STRICT_PATTERN: Regexp
17
17
  UUIDV4_PATTERN: Regexp
18
+ REPLACING_MAP: Hash[String, String]
19
+ REPLACING_PATTERN: Regexp
18
20
  MONOTONIC_GENERATOR: MonotonicGenerator
19
21
  MIN: ULID
20
22
  MAX: ULID
@@ -32,6 +34,9 @@ class ULID
32
34
  class ParserError < Error
33
35
  end
34
36
 
37
+ class SetupError < ScriptError
38
+ end
39
+
35
40
  class MonotonicGenerator
36
41
  attr_accessor latest_milliseconds: Integer
37
42
  attr_accessor latest_entropy: Integer
@@ -80,6 +85,7 @@ class ULID
80
85
  | (String string) { (ULID ulid) -> void } -> singleton(ULID)
81
86
  def self.octets_from_integer: (Integer integer, length: Integer) -> Array[Integer]
82
87
  def self.inverse_of_digits: (Array[Integer] reversed_digits) -> Integer
88
+ def self.convert_crockford_base32_to_n32: (String) -> String
83
89
  attr_reader milliseconds: Integer
84
90
  attr_reader entropy: Integer
85
91
  def initialize: (milliseconds: Integer, entropy: Integer) -> void
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.15
4
+ version: 0.0.16
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-04 00:00:00.000000000 Z
11
+ date: 2021-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: integer-base
@@ -84,10 +84,10 @@ dependencies:
84
84
  - - "<"
85
85
  - !ruby/object:Gem::Version
86
86
  version: '2'
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"
87
+ description: |2
88
+ 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.
89
+ This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around the ULID.
90
+ Also providing `ruby/rbs` signature files.
91
91
  email:
92
92
  - kachick1+ruby@gmail.com
93
93
  executables: []