ruby-ulid 0.0.9 → 0.0.10

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: cc7674472c14c213d6fe01c6994520b33bc82ab164b538948a4f1b532d0d88f2
4
- data.tar.gz: 85987c59ca6f119d40caaf69cc830a8e1b5cc2f9ea3250c315be2f9d8b7faa09
3
+ metadata.gz: ce2bb6c517e9fee4f758577e57d2c5eddfd16db9ee3c0d17ad41db8d9f1de383
4
+ data.tar.gz: 1cfb027a652e2035445483bdff2511c2837441e2e664286d60682fec6769cd17
5
5
  SHA512:
6
- metadata.gz: f56db0a74a7a6559189039a9f7f13a1f514b4fc956f7993a52ca16a7a978b4b93aa352b9f74b72da73c7b0485ebe1f80052d2e3c7b04a1baba231d06ba594f42
7
- data.tar.gz: 597260a0eb43c0fdc187aeb961521fbce22c68fc75b35d445e73d60564b72208aaf1e83a8428daa99423e001572f187872f4077445d6bced8b35d197d7e700ff
6
+ metadata.gz: ea00703256bc218a1e022dd732da22b37b9caeec30bdbdb4df945f9f185d408f0b746c36b36377dba2d4acaae5d2cf0b4db8b574ecc9da0e6e4b9b0af5c2404f
7
+ data.tar.gz: 6cd088ae1d539d8cb1d2690eb4248be413e527a87db7eb67b3c1adfc167c285cf2b90bb5701e82208586996649d56d1ef6c7c30396d1fbf0fc68220487711a54
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Kenichi Kamiya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # ruby-ulid
2
+
3
+ A handy `ULID` library
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.
10
+
11
+ ---
12
+
13
+ ![ULIDlogo](https://raw.githubusercontent.com/kachick/ruby-ulid/main/logo.png)
14
+
15
+ ![Build Status](https://github.com/kachick/ruby-ulid/actions/workflows/test.yml/badge.svg?branch=main)
16
+ [![Gem Version](https://badge.fury.io/rb/ruby-ulid.png)](http://badge.fury.io/rb/ruby-ulid)
17
+
18
+ ## Universally Unique Lexicographically Sortable Identifier
19
+
20
+ UUID can be suboptimal for many uses-cases because:
21
+
22
+ - It isn't the most character efficient way of encoding 128 bits of randomness
23
+ - UUID v1/v2 is impractical in many environments, as it requires access to a unique, stable MAC address
24
+ - UUID v3/v5 requires a unique seed and produces randomly distributed IDs, which can cause fragmentation in many data structures
25
+ - UUID v4 provides no other information than randomness which can cause fragmentation in many data structures
26
+
27
+ Instead, herein is proposed ULID:
28
+
29
+ - 128-bit compatibility with UUID
30
+ - 1.21e+24 unique ULIDs per millisecond
31
+ - Lexicographically sortable!
32
+ - 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)
34
+ - Case insensitive
35
+ - No special characters (URL safe)
36
+ - Monotonic sort order (correctly detects and handles the same millisecond)
37
+
38
+ ## Install
39
+
40
+ ```console
41
+ $ gem install ruby-ulid
42
+ #=> Installed
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ The generated `ULID` is an object not just a string.
48
+ It means easily get the timestamps and binary formats.
49
+
50
+ ```ruby
51
+ require 'ulid'
52
+
53
+ ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
54
+ ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
55
+ ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
56
+ 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
+ ```
59
+
60
+ You can parse from exists IDs
61
+
62
+ ```ruby
63
+ ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV') #=> ULID(2016-07-30 23:54:10.259 UTC: 01ARZ3NDEKTSV4RRFFQ69G5FAV)
64
+ ```
65
+
66
+ ULID is sortable when they are generated different timestamp in milliseconds precision
67
+
68
+ ```ruby
69
+ ulids = 1000.times.map do
70
+ sleep(0.001)
71
+ ULID.generate
72
+ end
73
+ ulids.sort == ulids #=> true
74
+ ulids.uniq(&:to_time).size #=> 1000
75
+ ```
76
+
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
+
79
+ ```ruby
80
+ ulids = 10000.times.map do
81
+ ULID.generate
82
+ end
83
+ ulids.uniq(&:to_time).size #=> 35 (the number will be changed by every creation)
84
+ ulids.sort == ulids #=> false
85
+
86
+
87
+ monotonic_generator = ULID::MonotonicGenerator.new
88
+ monotonic_ulids = 10000.times.map do
89
+ monotonic_generator.generate
90
+ end
91
+ monotonic_ulids.uniq(&:to_time).size #=> 34 (the number will be changed by every creation)
92
+ monotonic_ulids.sort == monotonic_ulids #=> true
93
+ ```
94
+
95
+ Providing converter for UUIDv4. (Of course the timestamp will be useless one.)
96
+
97
+ ```ruby
98
+ ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
99
+ #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
100
+ ```
101
+
102
+ For rough operations, `ULID.scan` might be useful.
103
+
104
+ ```ruby
105
+ json =<<'EOD'
106
+ {
107
+ "id": "01F4GNAV5ZR6FJQ5SFQC7WDSY3",
108
+ "author": {
109
+ "id": "01F4GNBXW1AM2KWW52PVT3ZY9X",
110
+ "name": "kachick"
111
+ },
112
+ "title": "My awesome blog post",
113
+ "comments": [
114
+ {
115
+ "id": "01F4GNCNC3CH0BCRZBPPDEKBKS",
116
+ "commenter": {
117
+ "id": "01F4GNBXW1AM2KWW52PVT3ZY9X",
118
+ "name": "kachick"
119
+ }
120
+ },
121
+ {
122
+ "id": "01F4GNCXAMXQ1SGBH5XCR6ZH0M",
123
+ "commenter": {
124
+ "id": "01F4GND4RYYSKNAADHQ9BNXAWJ",
125
+ "name": "pankona"
126
+ }
127
+ }
128
+ ]
129
+ }
130
+ EOD
131
+
132
+ ULID.scan(json).to_a
133
+ #=>
134
+ [ULID(2021-04-30 05:51:57.119 UTC: 01F4GNAV5ZR6FJQ5SFQC7WDSY3),
135
+ ULID(2021-04-30 05:52:32.641 UTC: 01F4GNBXW1AM2KWW52PVT3ZY9X),
136
+ ULID(2021-04-30 05:52:56.707 UTC: 01F4GNCNC3CH0BCRZBPPDEKBKS),
137
+ ULID(2021-04-30 05:52:32.641 UTC: 01F4GNBXW1AM2KWW52PVT3ZY9X),
138
+ ULID(2021-04-30 05:53:04.852 UTC: 01F4GNCXAMXQ1SGBH5XCR6ZH0M),
139
+ ULID(2021-04-30 05:53:12.478 UTC: 01F4GND4RYYSKNAADHQ9BNXAWJ)]
140
+ ```
141
+
142
+ `ULID.min` and `ULID.max` return termination values for ULID spec.
143
+
144
+ ```ruby
145
+ ULID.min #=> ULID(1970-01-01 00:00:00.000 UTC: 00000000000000000000000000)
146
+ ULID.max #=> ULID(10889-08-02 05:31:50.655 UTC: 7ZZZZZZZZZZZZZZZZZZZZZZZZZ)
147
+
148
+ time = Time.at(946684800, Rational('123456.789')).utc #=> 2000-01-01 00:00:00.123456789 UTC
149
+ ULID.min(moment: time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
150
+ ULID.max(moment: time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
151
+ ```
152
+
153
+ ## Development
154
+
155
+ At first, you should install development dependencies
156
+
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
162
+ ```
163
+
164
+ Play with the behaviors in REPL.
165
+
166
+ ```console
167
+ $ ./bin/console
168
+ # Starting up IRB with loading developing ULID library
169
+ irb(main):001:0>
170
+ ```
171
+
172
+ ```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
+ ```
177
+
178
+ If you try to add/change/fix features, please update tests and ensure they are not broken.
179
+
180
+ ```console
181
+ $ bundle exec rake test
182
+ $ echo $?
183
+ 0
184
+ ```
185
+
186
+ If you try to improve any performance issue, please add benchmarking and check the result of before and after.
187
+
188
+ ```console
189
+ $ bundle exec ruby benchmark/the_added_file.rb
190
+ # Showing the results
191
+ ```
192
+
193
+ ## Documents
194
+
195
+ - [API documents generated by YARD](https://kachick.github.io/ruby-ulid/)
196
+
197
+ ## Author
198
+
199
+ Kenichi Kamiya - [@kachick](https://github.com/kachick)
data/Steepfile ADDED
@@ -0,0 +1,7 @@
1
+ target :lib do
2
+ signature 'sig'
3
+
4
+ check 'lib'
5
+
6
+ library 'securerandom'
7
+ end
data/lib/ulid.rb CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  require 'securerandom'
6
6
  require 'integer/base'
7
- require_relative 'ulid/version'
8
7
 
9
8
  # @see https://github.com/ulid/spec
10
9
  # @!attribute [r] milliseconds
@@ -23,16 +22,16 @@ class ULID
23
22
  # @see https://www.crockford.com/base32.html
24
23
  ENCODING_CHARS = encoding_string.chars.map(&:freeze).freeze
25
24
 
26
- TIME_PART_LENGTH = 10
25
+ TIMESTAMP_PART_LENGTH = 10
27
26
  RANDOMNESS_PART_LENGTH = 16
28
- ENCODED_ID_LENGTH = TIME_PART_LENGTH + RANDOMNESS_PART_LENGTH
27
+ ENCODED_ID_LENGTH = TIMESTAMP_PART_LENGTH + RANDOMNESS_PART_LENGTH
29
28
  TIMESTAMP_OCTETS_LENGTH = 6
30
29
  RANDOMNESS_OCTETS_LENGTH = 10
31
30
  OCTETS_LENGTH = TIMESTAMP_OCTETS_LENGTH + RANDOMNESS_OCTETS_LENGTH
32
31
  MAX_MILLISECONDS = 281474976710655
33
32
  MAX_ENTROPY = 1208925819614629174706175
34
33
  MAX_INTEGER = 340282366920938463463374607431768211455
35
- PATTERN = /(?<timestamp>[0-7][#{encoding_string}]{#{TIME_PART_LENGTH - 1}})(?<randomness>[#{encoding_string}]{#{RANDOMNESS_PART_LENGTH}})/i.freeze
34
+ PATTERN = /(?<timestamp>[0-7][#{encoding_string}]{#{TIMESTAMP_PART_LENGTH - 1}})(?<randomness>[#{encoding_string}]{#{RANDOMNESS_PART_LENGTH}})/i.freeze
36
35
  STRICT_PATTERN = /\A#{PATTERN.source}\z/i.freeze
37
36
 
38
37
  # Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
@@ -42,49 +41,6 @@ class ULID
42
41
  # @see https://bugs.ruby-lang.org/issues/15958
43
42
  TIME_FORMAT_IN_INSPECT = '%Y-%m-%d %H:%M:%S.%3N %Z'
44
43
 
45
- class MonotonicGenerator
46
- attr_accessor :latest_milliseconds, :latest_entropy
47
-
48
- def initialize
49
- reset
50
- end
51
-
52
- # @raise [OverflowError] if the entropy part is larger than the ULID limit in same milliseconds
53
- # @return [ULID]
54
- def generate
55
- milliseconds = ULID.current_milliseconds
56
- reasonable_entropy = ULID.reasonable_entropy
57
-
58
- @latest_milliseconds ||= milliseconds
59
- @latest_entropy ||= reasonable_entropy
60
- if @latest_milliseconds != milliseconds
61
- @latest_milliseconds = milliseconds
62
- @latest_entropy = reasonable_entropy
63
- else
64
- @latest_entropy += 1
65
- end
66
-
67
- ULID.new milliseconds: milliseconds, entropy: @latest_entropy
68
- end
69
-
70
- # @return [self]
71
- def reset
72
- @latest_milliseconds = nil
73
- @latest_entropy = nil
74
- self
75
- end
76
-
77
- # @raise [TypeError] always raises exception and does not freeze self
78
- # @return [void]
79
- def freeze
80
- raise TypeError, "cannot freeze #{self.class}"
81
- end
82
- end
83
-
84
- MONOTONIC_GENERATOR = MonotonicGenerator.new
85
-
86
- private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN
87
-
88
44
  # @param [Integer, Time] moment
89
45
  # @param [Integer] entropy
90
46
  # @return [ULID]
@@ -93,6 +49,18 @@ class ULID
93
49
  new milliseconds: milliseconds, entropy: entropy
94
50
  end
95
51
 
52
+ # @param [Integer, Time] moment
53
+ # @return [ULID]
54
+ def self.min(moment: 0)
55
+ generate(moment: moment, entropy: 0)
56
+ end
57
+
58
+ # @param [Integer, Time] moment
59
+ # @return [ULID]
60
+ def self.max(moment: MAX_MILLISECONDS)
61
+ generate(moment: moment, entropy: MAX_ENTROPY)
62
+ end
63
+
96
64
  # @deprecated This method actually changes class state. Use {ULID::MonotonicGenerator} instead.
97
65
  # @raise [OverflowError] if the entropy part is larger than the ULID limit in same milliseconds
98
66
  # @return [ULID]
@@ -179,8 +147,8 @@ class ULID
179
147
  unless string.size == ENCODED_ID_LENGTH
180
148
  raise "parsable string must be #{ENCODED_ID_LENGTH} characters, but actually given #{string.size} characters"
181
149
  end
182
- timestamp = string.slice(0, TIME_PART_LENGTH)
183
- randomness = string.slice(TIME_PART_LENGTH, RANDOMNESS_PART_LENGTH)
150
+ timestamp = string.slice(0, TIMESTAMP_PART_LENGTH)
151
+ randomness = string.slice(TIMESTAMP_PART_LENGTH, RANDOMNESS_PART_LENGTH)
184
152
  milliseconds = Integer::Base.parse(timestamp, ENCODING_CHARS)
185
153
  entropy = Integer::Base.parse(randomness, ENCODING_CHARS)
186
154
  rescue => err
@@ -349,3 +317,12 @@ class ULID
349
317
  @matchdata ||= STRICT_PATTERN.match(to_str).freeze
350
318
  end
351
319
  end
320
+
321
+ require_relative 'ulid/version'
322
+ require_relative 'ulid/monotonic_generator'
323
+
324
+ class ULID
325
+ MONOTONIC_GENERATOR = MonotonicGenerator.new
326
+
327
+ private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN
328
+ end
@@ -0,0 +1,44 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+ # Copyright (C) 2021 Kenichi Kamiya
4
+
5
+ class ULID
6
+ class MonotonicGenerator
7
+ attr_accessor :latest_milliseconds, :latest_entropy
8
+
9
+ def initialize
10
+ reset
11
+ end
12
+
13
+ # @raise [OverflowError] if the entropy part is larger than the ULID limit in same milliseconds
14
+ # @return [ULID]
15
+ def generate
16
+ milliseconds = ULID.current_milliseconds
17
+ reasonable_entropy = ULID.reasonable_entropy
18
+
19
+ @latest_milliseconds ||= milliseconds
20
+ @latest_entropy ||= reasonable_entropy
21
+ if @latest_milliseconds != milliseconds
22
+ @latest_milliseconds = milliseconds
23
+ @latest_entropy = reasonable_entropy
24
+ else
25
+ @latest_entropy += 1
26
+ end
27
+
28
+ ULID.new milliseconds: milliseconds, entropy: @latest_entropy
29
+ end
30
+
31
+ # @return [self]
32
+ def reset
33
+ @latest_milliseconds = nil
34
+ @latest_entropy = nil
35
+ self
36
+ end
37
+
38
+ # @raise [TypeError] always raises exception and does not freeze self
39
+ # @return [void]
40
+ def freeze
41
+ raise TypeError, "cannot freeze #{self.class}"
42
+ end
43
+ end
44
+ 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.9'
5
+ VERSION = '0.0.10'
6
6
  end
data/sig/ulid.rbs CHANGED
@@ -2,7 +2,7 @@
2
2
  class ULID
3
3
  VERSION: String
4
4
  ENCODING_CHARS: Array[String]
5
- TIME_PART_LENGTH: 10
5
+ TIMESTAMP_PART_LENGTH: 10
6
6
  RANDOMNESS_PART_LENGTH: 16
7
7
  ENCODED_ID_LENGTH: 26
8
8
  TIMESTAMP_OCTETS_LENGTH: 6
@@ -36,6 +36,7 @@ class ULID
36
36
  def freeze: -> void
37
37
  end
38
38
 
39
+ type moment = Time | Integer
39
40
  type octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
40
41
  type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
41
42
  type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
@@ -56,7 +57,7 @@ class ULID
56
57
  @strict_pattern: Regexp?
57
58
  @matchdata: MatchData?
58
59
 
59
- def self.generate: (?moment: Time | Integer, ?entropy: Integer) -> ULID
60
+ def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
60
61
  def self.monotonic_generate: -> ULID
61
62
  def self.current_milliseconds: -> Integer
62
63
  def self.time_to_milliseconds: (Time time) -> Integer
@@ -64,6 +65,8 @@ class ULID
64
65
  def self.parse: (String string) -> ULID
65
66
  def self.from_uuidv4: (String uuid) -> ULID
66
67
  def self.from_integer: (Integer integer) -> ULID
68
+ def self.min: (?moment: moment) -> ULID
69
+ def self.max: (?moment: moment) -> ULID
67
70
  def self.valid?: (untyped string) -> bool
68
71
  def self.scan: (String string) -> Enumerator[ULID, singleton(ULID)]
69
72
  | (String string) { (ULID ulid) -> void } -> singleton(ULID)
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.9
4
+ version: 0.0.10
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-04-30 00:00:00.000000000 Z
11
+ date: 2021-05-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: integer-base
@@ -81,7 +81,11 @@ executables: []
81
81
  extensions: []
82
82
  extra_rdoc_files: []
83
83
  files:
84
+ - LICENSE
85
+ - README.md
86
+ - Steepfile
84
87
  - lib/ulid.rb
88
+ - lib/ulid/monotonic_generator.rb
85
89
  - lib/ulid/version.rb
86
90
  - sig/ulid.rbs
87
91
  homepage: https://github.com/kachick/ruby-ulid