ruby-ulid 0.0.15 → 0.0.16

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