ksuid 0.1.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +29 -3
- data/CONTRIBUTING.md +4 -5
- data/README.md +124 -6
- data/ksuid.gemspec +3 -3
- data/lib/ksuid/activerecord/binary_type.rb +58 -0
- data/lib/ksuid/activerecord/table_definition.rb +56 -0
- data/lib/ksuid/activerecord/type.rb +57 -0
- data/lib/ksuid/activerecord.rb +75 -0
- data/lib/ksuid/base62.rb +19 -1
- data/lib/ksuid/configuration.rb +94 -0
- data/lib/ksuid/railtie.rb +15 -0
- data/lib/ksuid/type.rb +45 -6
- data/lib/ksuid/utils.rb +33 -8
- data/lib/ksuid/version.rb +1 -1
- data/lib/ksuid.rb +71 -2
- metadata +16 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 65b5bd73fb4864bb0df8cf57c1214622325afa077e031baffb2a1e73b2820f2c
|
4
|
+
data.tar.gz: 55245cb9fba9e57c9bf115f189fb954f2e1b08604e89ba8b5c9aeea73967e5ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af5cc8ed5f9d81616fd61e6f03690c841489e9f2c2d28d892137c1eb410ec88e61540cd3dd10d5ac0d0439f9b9ecd9afdc00cceae61276cca521d5436c159e29
|
7
|
+
data.tar.gz: 13479dc7d24cf9422c6bc3ca705c6c777abf121f5ade7a9f9c8124c7b9f8034844d8707b2f8265c18178b9b16b50be28a3c1894888d97fe7f42aa1e981d11a97
|
data/CHANGELOG.md
CHANGED
@@ -4,12 +4,38 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
-
## [0.
|
7
|
+
## [0.4.0](https://github.com/michaelherold/ksuid/compare/v0.3.0...v0.4.0) - 2022-07-29
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- `KSUID::Type` acts as a proper value object now, which means that you may use it as a Hash key or use it in ActiveRecord's `.includes`. `KSUID::Type#eql?`, `KSUID::Type#hash`, and `KSUID::Type#==` now work as expected. Note that `KSUID::Type#==` is more lax than `KSUID::Type#eql?` because it can also match any object that converts to a string matching its value. This means that you can use it to match against `String` KSUIDs.
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
|
15
|
+
- `ActiveRecord::QueryMethods#include` works as expected now due to the fix on the value object semantics of `KSUID::Type`.
|
16
|
+
- Binary KSUID primary and foreign keys work as expected on JRuby.
|
17
|
+
|
18
|
+
## [0.3.0](https://github.com/michaelherold/ksuid/compare/v0.2.0...v0.3.0) - 2021-10-07
|
19
|
+
|
20
|
+
### Added
|
21
|
+
|
22
|
+
- A utility function for converting from a hexidecimal-encoded string to a byte string. This is necessary to handle the default encoding of binary fields within PostgreSQL.
|
23
|
+
|
24
|
+
## [0.2.0](https://github.com/michaelherold/ksuid/compare/v0.1.0...v0.2.0) - 2020-11-11
|
25
|
+
|
26
|
+
### Added
|
27
|
+
|
28
|
+
- The ability to configure the random generator for the gem via `KSUID.configure`. This allows you to set up random generation to the specifications you need, whether that is for speed or for security.
|
29
|
+
- Support for ActiveRecord. You can now use `KSUID::ActiveRecord[:my_field]` to define a KSUID field using the Rails 5 Attributes API. There is also two new column types for migrations: `ksuid` and `ksuid_binary`. The first stores your KSUID as a string in the database, the latter as binary data.
|
30
|
+
|
31
|
+
### Changed
|
32
|
+
|
33
|
+
- The `KSUID::Type#inspect` method now makes it much easier to see what you're looking at in the console when you're debugging.
|
34
|
+
|
35
|
+
## [0.1.0](https://github.com/michaelherold/ksuid/tree/v0.1.0) - 2017-11-05
|
8
36
|
|
9
37
|
### Added
|
10
38
|
|
11
39
|
- Basic `KSUID.new` interface.
|
12
40
|
- Parsing of bytes through `KSUID.from_bytes`.
|
13
41
|
- Parsing of strings through `KSUID.from_base62`.
|
14
|
-
|
15
|
-
[0.1.0]: https://github.com/michaelherold/interactor-contracts/tree/v0.1.0
|
data/CONTRIBUTING.md
CHANGED
@@ -28,14 +28,13 @@ Ideally, a bug report should include a pull request with failing specs.
|
|
28
28
|
1. [Fork the repository].
|
29
29
|
2. [Create a topic branch].
|
30
30
|
3. Add specs for your unimplemented feature or bug fix.
|
31
|
-
4. Run `
|
31
|
+
4. Run `appraisal rake spec`. If your specs pass, return to step 3.
|
32
32
|
5. Implement your feature or bug fix.
|
33
|
-
6. Run `
|
33
|
+
6. Run `appraisal rake`. If your specs or any of the linters fail, return to step 5.
|
34
34
|
7. Open `coverage/index.html`. If your changes are not completely covered by your tests, return to step 3.
|
35
35
|
8. Add documentation for your feature or bug fix.
|
36
|
-
9.
|
37
|
-
10.
|
38
|
-
11. [Submit a pull request].
|
36
|
+
9. Commit and push your changes.
|
37
|
+
10. [Submit a pull request].
|
39
38
|
|
40
39
|
[Create a topic branch]: https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/
|
41
40
|
[Fork the repository]: http://learn.github.com/p/branching.html
|
data/README.md
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
# KSUID for Ruby
|
2
2
|
|
3
|
-
[![Build Status](https://
|
3
|
+
[![Build Status](https://github.com/michaelherold/ksuid-ruby/workflows/Continuous%20integration/badge.svg)][actions]
|
4
4
|
[![Test Coverage](https://api.codeclimate.com/v1/badges/94b2a2d4082bff21c10f/test_coverage)][test-coverage]
|
5
5
|
[![Maintainability](https://api.codeclimate.com/v1/badges/94b2a2d4082bff21c10f/maintainability)][maintainability]
|
6
6
|
[![Inline docs](http://inch-ci.org/github/michaelherold/ksuid-ruby.svg?branch=master)][inch]
|
7
7
|
|
8
|
+
[actions]: https://github.com/michaelherold/ksuid-ruby/actions
|
8
9
|
[inch]: http://inch-ci.org/github/michaelherold/ksuid-ruby
|
9
10
|
[maintainability]: https://codeclimate.com/github/michaelherold/ksuid-ruby/maintainability
|
10
11
|
[test-coverage]: https://codeclimate.com/github/michaelherold/ksuid-ruby/test_coverage
|
11
|
-
[travis]: https://travis-ci.org/michaelherold/ksuid-ruby
|
12
12
|
|
13
13
|
ksuid is a Ruby library that can generate and parse [KSUIDs](https://github.com/segmentio/ksuid). The original readme for the Go version of KSUID does a great job of explaining what they are and how they should be used, so it is excerpted here.
|
14
14
|
|
@@ -92,17 +92,135 @@ If you need to generate a KSUID for a specific timestamp, use:
|
|
92
92
|
ksuid = KSUID.new(time: time) # where time is a Time-like object
|
93
93
|
```
|
94
94
|
|
95
|
+
If you need to use a faster or more secure way of generating the random payloads (or if you want the payload to be non-random data), you can configure the gem for those use cases:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
KSUID.configure do |config|
|
99
|
+
config.random_generator = -> { Random.new.bytes(16) }
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
### ActiveRecord
|
104
|
+
|
105
|
+
Whether you are using ActiveRecord inside an existing project or in a new project, usage is simple. Additionally, you can use it with or without Rails.
|
106
|
+
|
107
|
+
#### Adding to an existing model
|
108
|
+
|
109
|
+
Within a Rails project, it is very easy to get started using KSUIDs within your models. You can use the `ksuid` column type in a Rails migration to add a column to an existing model:
|
110
|
+
|
111
|
+
rails generate migration add_ksuid_to_events ksuid:ksuid
|
112
|
+
|
113
|
+
This will generate a migration like the following:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
class AddKsuidToEvents < ActiveRecord::Migration[5.2]
|
117
|
+
def change
|
118
|
+
add_column :events, :unique_id, :ksuid
|
119
|
+
end
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
Then, to add proper handling to the field, you will want to mix a module into the model:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
class Event < ApplicationRecord
|
127
|
+
include KSUID::ActiveRecord[:unique_id]
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
#### Creating a new model
|
132
|
+
|
133
|
+
To create a new model with a `ksuid` field that is stored as a KSUID, use the `ksuid` column type. Using the Rails generators, this looks like:
|
134
|
+
|
135
|
+
rails generate model Event my_field_name:ksuid
|
136
|
+
|
137
|
+
If you would like to add a KSUID to an existing model, you can do so with the following:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
class AddKsuidToEvents < ActiveRecord::Migration[5.2]
|
141
|
+
change_table :events do |table|
|
142
|
+
table.ksuid :my_field_name
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
Once you have generated the table that you will use for your model, you will need to include a module into the model class, as follows:
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
class Event < ApplicationRecord
|
151
|
+
include KSUID::ActiveRecord[:my_field_name]
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
##### With a KSUID primary key
|
156
|
+
|
157
|
+
You can also use a KSUID as the primary key on a table, much like you can use a UUID in vanilla Rails. To do so requires a little more finagling than you can manage through the generators. When hand-writing the migration, it will look like this:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
class CreateEvents < ActiveRecord::Migration[5.2]
|
161
|
+
create_table :events, id: false do |table|
|
162
|
+
table.ksuid :id, primary_key: true
|
163
|
+
end
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
You will need to mix in the module into your model as well:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
class Event < ApplicationRecord
|
171
|
+
include KSUID::ActiveRecord[:id]
|
172
|
+
end
|
173
|
+
```
|
174
|
+
|
175
|
+
#### Outside of Rails
|
176
|
+
|
177
|
+
Outside of Rails, you cannot rely on the Railtie to load the appropriate files for you automatically. Toward the start of your application's boot process, you will want to require the following:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
require 'ksuid/activerecord'
|
181
|
+
|
182
|
+
# If you will be using the ksuid column type in a migration
|
183
|
+
require 'ksuid/activerecord/table_definition'
|
184
|
+
```
|
185
|
+
|
186
|
+
Once you have required the file(s) that you need, everything else will work as it does above.
|
187
|
+
|
188
|
+
#### Binary vs. String KSUIDs
|
189
|
+
|
190
|
+
These examples all store your identifier as a string-based KSUID. If you would like to use binary KSUIDs instead, use the `ksuid_binary` column type. Unless you need to be super-efficient with your database, we recommend using string-based KSUIDs because it makes looking at the data while in the database a little easier to understand.
|
191
|
+
|
192
|
+
When you include the KSUID module into your model, you will want to pass the `:binary` option as well:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
class Event < ApplicationRecord
|
196
|
+
include KSUID::ActiveRecord[:my_field_name, binary: true]
|
197
|
+
end
|
198
|
+
```
|
199
|
+
|
200
|
+
#### Use the KSUID as your `created_at` timestamp
|
201
|
+
|
202
|
+
Since KSUIDs include a timestamp as well, you can infer the `#created_at` timestamp from the KSUID. The module builder enables that option automatically with the `:created_at` option, like so:
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
class Event < ApplicationRecord
|
206
|
+
include KSUID::ActiveRecord[:my_field_name, created_at: true]
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
This allows you to be efficient in your database design if that is a constraint you need to satisfy.
|
211
|
+
|
95
212
|
## Contributing
|
96
213
|
|
97
214
|
So you’re interested in contributing to KSUID? Check out our [contributing guidelines](CONTRIBUTING.md) for more information on how to do that.
|
98
215
|
|
99
216
|
## Supported Ruby Versions
|
100
217
|
|
101
|
-
This library aims to support and is [tested against][
|
218
|
+
This library aims to support and is [tested against][actions] the following Ruby versions:
|
102
219
|
|
103
|
-
* Ruby 2.
|
104
|
-
* Ruby 2.
|
105
|
-
*
|
220
|
+
* Ruby 2.5
|
221
|
+
* Ruby 2.6
|
222
|
+
* Ruby 2.7
|
223
|
+
* JRuby 9.2
|
106
224
|
|
107
225
|
If something doesn't work on one of these versions, it's a bug.
|
108
226
|
|
data/ksuid.gemspec
CHANGED
@@ -6,11 +6,11 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = 'ksuid'
|
7
7
|
spec.version = KSUID::VERSION
|
8
8
|
spec.authors = ['Michael Herold']
|
9
|
-
spec.email = ['
|
9
|
+
spec.email = ['opensource@michaeljherold.com']
|
10
10
|
|
11
11
|
spec.summary = 'Ruby implementation of the K-Sortable Unique IDentifier'
|
12
12
|
spec.description = spec.summary
|
13
|
-
spec.homepage = 'https://github.com/michaelherold/ksuid'
|
13
|
+
spec.homepage = 'https://github.com/michaelherold/ksuid-ruby'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = %w[CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md]
|
@@ -18,5 +18,5 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.files += Dir['lib/**/*.rb']
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_development_dependency 'bundler', '
|
21
|
+
spec.add_development_dependency 'bundler', '>= 1.15'
|
22
22
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KSUID
|
4
|
+
module ActiveRecord
|
5
|
+
# A binary-serialized KSUID for storage within an ActiveRecord database
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
# @example Set an attribute as a KSUID using the verbose syntax
|
10
|
+
# class Event < ActiveRecord::Base
|
11
|
+
# attribute :ksuid, KSUID::ActiveRecord::BinaryType.new, default: -> { KSUID.new }
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# @example Set an attribute as a KSUID using the pre-registered type
|
15
|
+
# class Event < ActiveRecord::Base
|
16
|
+
# attribute :ksuid, :ksuid_binary, default: -> { KSUID.new }
|
17
|
+
# end
|
18
|
+
class BinaryType < ::ActiveRecord::Type::Binary
|
19
|
+
# Casts a value from user input into a KSUID
|
20
|
+
#
|
21
|
+
# Type casting happens via the attribute setter and can take input from
|
22
|
+
# many places, including:
|
23
|
+
#
|
24
|
+
# 1. The Rails form builder
|
25
|
+
# 2. Directly from the attribute setter
|
26
|
+
# 3. From the model initializer
|
27
|
+
#
|
28
|
+
# @param value [String, Array<Integer>, KSUID::Type] the value to cast into a KSUID
|
29
|
+
# @return [KSUID::Type] the type-casted value
|
30
|
+
def cast(value)
|
31
|
+
KSUID.call(value)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Converts a value from database input to a KSUID
|
35
|
+
#
|
36
|
+
# @param value [String, nil] the database-serialized KSUID to convert
|
37
|
+
# @return [KSUID::Type] the deserialized KSUID
|
38
|
+
def deserialize(value)
|
39
|
+
return unless value
|
40
|
+
|
41
|
+
value = value.to_s if value.is_a?(::ActiveRecord::Type::Binary::Data)
|
42
|
+
KSUID.call(value)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Casts the value from a KSUID into a database-understandable format
|
46
|
+
#
|
47
|
+
# @param value [KSUID::Type, nil] the KSUID in Ruby format
|
48
|
+
# @return [String, nil] the base 62-encoded KSUID for storage in the database
|
49
|
+
def serialize(value)
|
50
|
+
return unless value
|
51
|
+
|
52
|
+
super(KSUID.call(value).to_bytes)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
ActiveRecord::Type.register(:ksuid_binary, KSUID::ActiveRecord::BinaryType)
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KSUID
|
4
|
+
module ActiveRecord
|
5
|
+
# Extends ActiveRecord's table definition language for KSUIDs
|
6
|
+
module TableDefinition
|
7
|
+
# Defines a field as a string-based KSUID
|
8
|
+
#
|
9
|
+
# @example Define a KSUID field as a non-primary key
|
10
|
+
# ActiveRecord::Schema.define do
|
11
|
+
# create_table :events, force: true do |table|
|
12
|
+
# table.ksuid :ksuid, index: true, unique: true
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# @example Define a KSUID field as a primary key
|
17
|
+
# ActiveRecord::Schema.define do
|
18
|
+
# create_table :events, force: true, id: false do |table|
|
19
|
+
# table.ksuid :id, primary_key: true
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @param args [Array<Symbol>] the list of fields to define as KSUIDs
|
24
|
+
# @param options [Hash] see {ActiveRecord::ConnectionAdapters::TableDefinition}
|
25
|
+
# @return [void]
|
26
|
+
def ksuid(*args, **options)
|
27
|
+
args.each { |name| column(name, :string, **options.merge(limit: 27)) }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Defines a field as a binary-based KSUID
|
31
|
+
#
|
32
|
+
# @example Define a KSUID field as a non-primary key
|
33
|
+
# ActiveRecord::Schema.define do
|
34
|
+
# create_table :events, force: true do |table|
|
35
|
+
# table.ksuid_binary :ksuid, index: true, unique: true
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# @example Define a KSUID field as a primary key
|
40
|
+
# ActiveRecord::Schema.define do
|
41
|
+
# create_table :events, force: true, id: false do |table|
|
42
|
+
# table.ksuid_binary :id, primary_key: true
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# @param args [Array<Symbol>] the list of fields to define as KSUIDs
|
47
|
+
# @param options [Hash] see {ActiveRecord::ConnectionAdapters::TableDefinition}
|
48
|
+
# @return [void]
|
49
|
+
def ksuid_binary(*args, **options)
|
50
|
+
args.each { |name| column(name, :binary, **options.merge(limit: 20)) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.include(KSUID::ActiveRecord::TableDefinition)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KSUID
|
4
|
+
module ActiveRecord
|
5
|
+
# A string-serialized KSUID for storage within an ActiveRecord database
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
# @example Set an attribute as a KSUID using the verbose syntax
|
10
|
+
# class Event < ActiveRecord::Base
|
11
|
+
# attribute :ksuid, KSUID::ActiveRecord::Type.new, default: -> { KSUID.new }
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# @example Set an attribute as a KSUID using the pre-registered type
|
15
|
+
# class Event < ActiveRecord::Base
|
16
|
+
# attribute :ksuid, :ksuid, default: -> { KSUID.new }
|
17
|
+
# end
|
18
|
+
class Type < ::ActiveRecord::Type::String
|
19
|
+
# Casts a value from user input into a KSUID
|
20
|
+
#
|
21
|
+
# Type casting happens via the attribute setter and can take input from
|
22
|
+
# many places, including:
|
23
|
+
#
|
24
|
+
# 1. The Rails form builder
|
25
|
+
# 2. Directly from the attribute setter
|
26
|
+
# 3. From the model initializer
|
27
|
+
#
|
28
|
+
# @param value [String, Array<Integer>, KSUID::Type] the value to cast into a KSUID
|
29
|
+
# @return [KSUID::Type] the type-casted value
|
30
|
+
def cast(value)
|
31
|
+
KSUID.call(value)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Converts a value from database input to a KSUID
|
35
|
+
#
|
36
|
+
# @param value [String, nil] the database-serialized KSUID to convert
|
37
|
+
# @return [KSUID::Type] the deserialized KSUID
|
38
|
+
def deserialize(value)
|
39
|
+
return unless value
|
40
|
+
|
41
|
+
KSUID.from_base62(value)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Casts the value from a KSUID into a database-understandable format
|
45
|
+
#
|
46
|
+
# @param value [KSUID::Type, nil] the KSUID in Ruby format
|
47
|
+
# @return [String, nil] the base 62-encoded KSUID for storage in the database
|
48
|
+
def serialize(value)
|
49
|
+
return unless value
|
50
|
+
|
51
|
+
KSUID.call(value).to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
ActiveRecord::Type.register(:ksuid, KSUID::ActiveRecord::Type)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ksuid/activerecord/binary_type'
|
4
|
+
require 'ksuid/activerecord/type'
|
5
|
+
|
6
|
+
module KSUID
|
7
|
+
# Enables an Active Record model to have a KSUID attribute
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
module ActiveRecord
|
11
|
+
# Builds a module to include into the model
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
#
|
15
|
+
# @example Add a `#ksuid` attribute to a model
|
16
|
+
# class Event < ActiveRecord::Base
|
17
|
+
# include KSUID::ActiveRecord[:ksuid]
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# @example Add a `#remote_id` attribute to a model and overrides `#created_at` to use the KSUID
|
21
|
+
# class Event < ActiveRecord::Base
|
22
|
+
# include KSUID::ActiveRecord[:remote_id, created_at: true]
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# @param field [String, Symbol] the name of the field to use as a KSUID
|
26
|
+
# @param created_at [Boolean] whether to override the `#created_at` method
|
27
|
+
# @param binary [Boolean] whether to store the KSUID as a binary or a string
|
28
|
+
# @return [Module] the module to include into the model
|
29
|
+
def self.[](field, created_at: false, binary: false)
|
30
|
+
Module
|
31
|
+
.new
|
32
|
+
.tap do |mod|
|
33
|
+
define_attribute(field, mod, binary)
|
34
|
+
define_created_at(field, mod) if created_at
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Defines the attribute method that will be written in the module
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
#
|
42
|
+
# @param field [String, Symbol] the name of the field to set as an attribute
|
43
|
+
# @param mod [Module] the module to extend
|
44
|
+
# @return [void]
|
45
|
+
def self.define_attribute(field, mod, binary)
|
46
|
+
type = 'ksuid'
|
47
|
+
type = 'ksuid_binary' if binary
|
48
|
+
|
49
|
+
mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
50
|
+
def self.included(base)
|
51
|
+
base.__send__(:attribute, :#{field}, :#{type}, default: -> { KSUID.new })
|
52
|
+
end
|
53
|
+
RUBY
|
54
|
+
end
|
55
|
+
private_class_method :define_attribute
|
56
|
+
|
57
|
+
# Defines the `#created_at` method that will be written in the module
|
58
|
+
#
|
59
|
+
# @api private
|
60
|
+
#
|
61
|
+
# @param field [String, Symbol] the name of the KSUID attribute field
|
62
|
+
# @param mod [Module] the module to extend
|
63
|
+
# @return [void]
|
64
|
+
def self.define_created_at(field, mod)
|
65
|
+
mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
66
|
+
def created_at
|
67
|
+
return unless #{field}
|
68
|
+
|
69
|
+
#{field}.to_time
|
70
|
+
end
|
71
|
+
RUBY
|
72
|
+
end
|
73
|
+
private_class_method :define_created_at
|
74
|
+
end
|
75
|
+
end
|
data/lib/ksuid/base62.rb
CHANGED
@@ -20,6 +20,24 @@ module KSUID
|
|
20
20
|
# @api private
|
21
21
|
BASE = CHARSET.size
|
22
22
|
|
23
|
+
# A matcher that checks whether a String has a character outside the charset
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
MATCHER = /[^#{CHARSET}]/.freeze
|
27
|
+
|
28
|
+
# Checks whether a string is a base 62-compatible string
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
#
|
32
|
+
# @example Checks a KSUID for base 62 compatibility
|
33
|
+
# KSUID::Base62.compatible?("15Ew2nYeRDscBipuJicYjl970D1") #=> true
|
34
|
+
#
|
35
|
+
# @param string [String] the string to check for compatibility
|
36
|
+
# @return [Boolean]
|
37
|
+
def self.compatible?(string)
|
38
|
+
string.each_char.all? { |char| !MATCHER.match?(char) }
|
39
|
+
end
|
40
|
+
|
23
41
|
# Decodes a base 62-encoded string into an integer
|
24
42
|
#
|
25
43
|
# @api public
|
@@ -71,7 +89,7 @@ module KSUID
|
|
71
89
|
# 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
|
72
90
|
# )
|
73
91
|
#
|
74
|
-
# @param bytes [String
|
92
|
+
# @param bytes [String, Array<Integer>] the bytes to encode
|
75
93
|
# @return [String] the encoded bytes as a base 62 string
|
76
94
|
def self.encode_bytes(bytes)
|
77
95
|
encode(Utils.int_from_bytes(bytes))
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module KSUID
|
6
|
+
# Encapsulates the configuration for the KSUID gem as a whole.
|
7
|
+
#
|
8
|
+
# You can override the generation of the random payload data by setting the
|
9
|
+
# {#random_generator} value to a valid random payload generator. This should
|
10
|
+
# be done via the module-level {KSUID.configure} method.
|
11
|
+
#
|
12
|
+
# The gem-level configuration lives at the module-level {KSUID.config}.
|
13
|
+
#
|
14
|
+
# @api semipublic
|
15
|
+
class Configuration
|
16
|
+
# Raised when the gem is misconfigured.
|
17
|
+
ConfigurationError = Class.new(StandardError)
|
18
|
+
|
19
|
+
# The default generator for generating random payloads
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
#
|
23
|
+
# @return [Proc]
|
24
|
+
def self.default_generator
|
25
|
+
-> { SecureRandom.random_bytes(BYTES[:payload]) }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Instantiates a new KSUID configuration
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
#
|
32
|
+
# @return [KSUID::Configuration] the new configuration
|
33
|
+
def initialize
|
34
|
+
self.random_generator = self.class.default_generator
|
35
|
+
end
|
36
|
+
|
37
|
+
# The method for generating random payloads in the gem
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
#
|
41
|
+
# @return [#call] a callable that returns 16 bytes
|
42
|
+
attr_reader :random_generator
|
43
|
+
|
44
|
+
# Override the method for generating random payloads in the gem
|
45
|
+
#
|
46
|
+
# @api semipublic
|
47
|
+
#
|
48
|
+
# @example Override the random generator with a null data generator
|
49
|
+
# KSUID.configure do |config|
|
50
|
+
# config.random_generator = -> { "\x00" * KSUID::BYTES[:payload] }
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# @example Override the random generator with the faster, but less secure, Random
|
54
|
+
# KSUID.configure do |config|
|
55
|
+
# config.random_generator = -> { Random.new.bytes(KSUID::BYTES[:payload]) }
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# @param generator [#call] a callable that returns 16 bytes
|
59
|
+
# @return [#call] a callable that returns 16 bytes
|
60
|
+
def random_generator=(generator)
|
61
|
+
assert_generator_is_callable(generator)
|
62
|
+
assert_payload_size(generator)
|
63
|
+
|
64
|
+
@random_generator = generator
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Raises an error if the assigned generator is not callable
|
70
|
+
#
|
71
|
+
# @api private
|
72
|
+
#
|
73
|
+
# @raise [ConfigurationError] if the generator is not callable
|
74
|
+
# @return [nil]
|
75
|
+
def assert_generator_is_callable(generator)
|
76
|
+
return if generator.respond_to?(:call)
|
77
|
+
|
78
|
+
raise ConfigurationError, "Random generator #{generator} is not callable"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Raises an error if the assigned generator generates the wrong size
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
#
|
85
|
+
# @raise [ConfigurationError] if the generator generates the wrong size payload
|
86
|
+
# @return [nil]
|
87
|
+
def assert_payload_size(generator)
|
88
|
+
return if (length = generator.call.length) == (expected_length = BYTES[:payload])
|
89
|
+
|
90
|
+
raise ConfigurationError, 'Random generator generates the wrong number of bytes ' \
|
91
|
+
"(#{length} generated, #{expected_length} expected)"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KSUID
|
4
|
+
# Enables the usage of KSUID types within ActiveRecord when Rails is loaded
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class Railtie < ::Rails::Railtie
|
8
|
+
initializer 'ksuid' do
|
9
|
+
ActiveSupport.on_load :active_record do
|
10
|
+
require 'ksuid/activerecord'
|
11
|
+
require 'ksuid/activerecord/table_definition'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/ksuid/type.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'securerandom'
|
4
3
|
require_relative 'base62'
|
5
4
|
require_relative 'utils'
|
6
5
|
|
@@ -31,11 +30,11 @@ module KSUID
|
|
31
30
|
# @example Generate a new KSUID for a given timestamp
|
32
31
|
# KSUID::Type.new(time: Time.parse('2017-11-05 15:00:04 UTC'))
|
33
32
|
#
|
34
|
-
# @param payload [String
|
33
|
+
# @param payload [String, Array<Integer>, nil] the payload for the KSUID
|
35
34
|
# @param time [Time] the timestamp to use for the KSUID
|
36
35
|
# @return [KSUID::Type] the generated KSUID
|
37
36
|
def initialize(payload: nil, time: Time.now)
|
38
|
-
payload ||=
|
37
|
+
payload ||= KSUID.config.random_generator.call
|
39
38
|
byte_encoding = Utils.int_to_bytes(time.to_i - EPOCH_TIME)
|
40
39
|
|
41
40
|
@uid = byte_encoding.bytes + payload.bytes
|
@@ -64,6 +63,48 @@ module KSUID
|
|
64
63
|
other.to_s == to_s
|
65
64
|
end
|
66
65
|
|
66
|
+
# Checks whether this KSUID hashes to the same hash key as another
|
67
|
+
#
|
68
|
+
# @api semipublic
|
69
|
+
#
|
70
|
+
# @example Checks whether two KSUIDs hash to the same key
|
71
|
+
# KSUID.new.eql? KSUID.new
|
72
|
+
#
|
73
|
+
# @param other [KSUID::Type] the other KSUID to check against
|
74
|
+
# @return [Boolean]
|
75
|
+
def eql?(other)
|
76
|
+
hash == other.hash
|
77
|
+
end
|
78
|
+
|
79
|
+
# Generates the key to use when using a KSUID as a hash key
|
80
|
+
#
|
81
|
+
# @api semipublic
|
82
|
+
#
|
83
|
+
# @example Using a KSUID as a Hash key
|
84
|
+
# ksuid1 = KSUID.new
|
85
|
+
# ksuid2 = KSUID.from_base62(ksuid1.to_s)
|
86
|
+
# values_by_ksuid = {}
|
87
|
+
#
|
88
|
+
# values_by_ksuid[ksuid1] = "example"
|
89
|
+
# values_by_ksuid[ksuid2] #=> "example"
|
90
|
+
#
|
91
|
+
# @return [Integer]
|
92
|
+
def hash
|
93
|
+
@uid.hash
|
94
|
+
end
|
95
|
+
|
96
|
+
# Prints the KSUID for debugging within a console
|
97
|
+
#
|
98
|
+
# @api public
|
99
|
+
#
|
100
|
+
# @example Show the maximum KSUID
|
101
|
+
# KSUID.max.inspect #=> "<KSUID(aWgEPTl1tmebfsQzFP4bxwgy80V)>"
|
102
|
+
#
|
103
|
+
# @return [String]
|
104
|
+
def inspect
|
105
|
+
"<KSUID(#{self})>"
|
106
|
+
end
|
107
|
+
|
67
108
|
# The payload for the KSUID, as a hex-encoded string
|
68
109
|
#
|
69
110
|
# This is generally useful for comparing against the Go tool
|
@@ -121,9 +162,7 @@ module KSUID
|
|
121
162
|
#
|
122
163
|
# @return [Integer] the Unix timestamp for the event (without the epoch shift)
|
123
164
|
def to_i
|
124
|
-
|
125
|
-
|
126
|
-
unix_time
|
165
|
+
Utils.int_from_bytes(uid.first(BYTES[:timestamp]))
|
127
166
|
end
|
128
167
|
|
129
168
|
# The KSUID as a base 62-encoded string
|
data/lib/ksuid/utils.rb
CHANGED
@@ -5,7 +5,19 @@ module KSUID
|
|
5
5
|
#
|
6
6
|
# @api private
|
7
7
|
module Utils
|
8
|
-
#
|
8
|
+
# A regular expression for splitting bytes out of a "binary" string
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
# @return [Regexp] the splitter
|
12
|
+
BYTES = /.{8}/.freeze
|
13
|
+
|
14
|
+
# A regular expression for splitting a String into pairs of characters
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
# @return [Regexp] the splitter
|
18
|
+
PAIRS = /.{2}/.freeze
|
19
|
+
|
20
|
+
# Converts a byte array into a byte string
|
9
21
|
#
|
10
22
|
# @param bytes [String] a byte string
|
11
23
|
# @return [Array<Integer>] an array of bytes from the byte string
|
@@ -13,22 +25,36 @@ module KSUID
|
|
13
25
|
bytes.pack('C*')
|
14
26
|
end
|
15
27
|
|
28
|
+
# Converts a hex string into a byte string
|
29
|
+
#
|
30
|
+
# @param hex [String] a hex-encoded KSUID
|
31
|
+
# @param bits [Integer] the expected number of bits for the result
|
32
|
+
# @return [String] the byte string
|
33
|
+
def self.byte_string_from_hex(hex, bits = 32)
|
34
|
+
byte_array =
|
35
|
+
hex
|
36
|
+
.rjust(bits, '0')
|
37
|
+
.scan(PAIRS)
|
38
|
+
.map { |bytes| bytes.to_i(16) }
|
39
|
+
|
40
|
+
byte_string_from_array(byte_array)
|
41
|
+
end
|
42
|
+
|
16
43
|
# Converts a byte string or byte array into a hex-encoded string
|
17
44
|
#
|
18
|
-
# @param bytes [String
|
45
|
+
# @param bytes [String, Array<Integer>] the byte string or array
|
19
46
|
# @return [String] the byte string as a hex-encoded string
|
20
47
|
def self.bytes_to_hex_string(bytes)
|
21
48
|
bytes = bytes.bytes if bytes.is_a?(String)
|
22
49
|
|
23
50
|
byte_string_from_array(bytes)
|
24
|
-
.
|
25
|
-
.first
|
51
|
+
.unpack1('H*')
|
26
52
|
.upcase
|
27
53
|
end
|
28
54
|
|
29
55
|
# Converts a byte string or byte array into an integer
|
30
56
|
#
|
31
|
-
# @param bytes [String
|
57
|
+
# @param bytes [String, Array<Integer>] the byte string or array
|
32
58
|
# @return [Integer] the resulting integer
|
33
59
|
def self.int_from_bytes(bytes)
|
34
60
|
bytes = bytes.bytes if bytes.is_a?(String)
|
@@ -48,9 +74,8 @@ module KSUID
|
|
48
74
|
int
|
49
75
|
.to_s(2)
|
50
76
|
.rjust(bits, '0')
|
51
|
-
.
|
52
|
-
.
|
53
|
-
.map { |digits| digits.join.to_i(2) }
|
77
|
+
.scan(BYTES)
|
78
|
+
.map { |digits| digits.to_i(2) }
|
54
79
|
.pack("C#{bits / 8}")
|
55
80
|
end
|
56
81
|
end
|
data/lib/ksuid/version.rb
CHANGED
data/lib/ksuid.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'ksuid/configuration'
|
3
4
|
require_relative 'ksuid/type'
|
4
5
|
require_relative 'ksuid/version'
|
5
6
|
|
@@ -74,6 +75,57 @@ module KSUID
|
|
74
75
|
# @return [String]
|
75
76
|
MAX_STRING_ENCODED = 'aWgEPTl1tmebfsQzFP4bxwgy80V'
|
76
77
|
|
78
|
+
# Converts a KSUID-compatible value into an actual KSUID
|
79
|
+
#
|
80
|
+
# @api public
|
81
|
+
#
|
82
|
+
# @example Converts a base 62 KSUID string into a KSUID
|
83
|
+
# KSUID.call('15Ew2nYeRDscBipuJicYjl970D1')
|
84
|
+
#
|
85
|
+
# @param ksuid [String, Array<Integer>, KSUID::Type] the KSUID-compatible value
|
86
|
+
# @return [KSUID::Type] the converted KSUID
|
87
|
+
# @raise [ArgumentError] if the value is not KSUID-compatible
|
88
|
+
def self.call(ksuid)
|
89
|
+
return unless ksuid
|
90
|
+
|
91
|
+
case ksuid
|
92
|
+
when KSUID::Type then ksuid
|
93
|
+
when Array then KSUID.from_bytes(ksuid)
|
94
|
+
when String then cast_string(ksuid)
|
95
|
+
else
|
96
|
+
raise ArgumentError, "Cannot convert #{ksuid.inspect} to KSUID"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# The configuration for creating new KSUIDs
|
101
|
+
#
|
102
|
+
# @api private
|
103
|
+
#
|
104
|
+
# @return [KSUID::Configuration] the gem's configuration
|
105
|
+
def self.config
|
106
|
+
@config ||= KSUID::Configuration.new
|
107
|
+
end
|
108
|
+
|
109
|
+
# Configures the KSUID gem by passing a block
|
110
|
+
#
|
111
|
+
# @api public
|
112
|
+
#
|
113
|
+
# @example Override the random generator with a null data generator
|
114
|
+
# KSUID.configure do |config|
|
115
|
+
# config.random_generator = -> { "\x00" * KSUID::BYTES[:payload] }
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# @example Override the random generator with the faster, but less secure, Random
|
119
|
+
# KSUID.configure do |config|
|
120
|
+
# config.random_generator = -> { Random.new.bytes(KSUID::BYTES[:payload]) }
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# @return [KSUID::Configuration] the gem's configuration
|
124
|
+
def self.configure
|
125
|
+
yield config if block_given?
|
126
|
+
config
|
127
|
+
end
|
128
|
+
|
77
129
|
# Converts a base 62-encoded string into a KSUID
|
78
130
|
#
|
79
131
|
# @api public
|
@@ -98,7 +150,7 @@ module KSUID
|
|
98
150
|
# @example Parse a KSUID byte string into an object
|
99
151
|
# KSUID.from_bytes("\x06\x83\xF7\x89\x04\x9C\xC2\x15\xC0\x99\xD4+xM\xBE\x994\e\xD7\x9C")
|
100
152
|
#
|
101
|
-
# @param bytes [String
|
153
|
+
# @param bytes [String, Array<Integer>] the byte string or array to convert into an object
|
102
154
|
# @return [KSUID::Type] the KSUID generated from the bytes
|
103
155
|
def self.from_bytes(bytes)
|
104
156
|
bytes = bytes.bytes if bytes.is_a?(String)
|
@@ -131,10 +183,27 @@ module KSUID
|
|
131
183
|
# @example Generate a new KSUID for a given timestamp
|
132
184
|
# KSUID.new(time: Time.parse('2017-11-05 15:00:04 UTC'))
|
133
185
|
#
|
134
|
-
# @param payload [String
|
186
|
+
# @param payload [String, Array<Integer>, nil] the payload for the KSUID
|
135
187
|
# @param time [Time] the timestamp to use for the KSUID
|
136
188
|
# @return [KSUID::Type] the generated KSUID
|
137
189
|
def self.new(payload: nil, time: Time.now)
|
138
190
|
Type.new(payload: payload, time: time)
|
139
191
|
end
|
192
|
+
|
193
|
+
# Casts a string into a KSUID
|
194
|
+
#
|
195
|
+
# @api private
|
196
|
+
#
|
197
|
+
# @param ksuid [String] the string to convert into a KSUID
|
198
|
+
# @return [KSUID::Type] the converted KSUID
|
199
|
+
def self.cast_string(ksuid)
|
200
|
+
if Base62.compatible?(ksuid)
|
201
|
+
KSUID.from_base62(ksuid)
|
202
|
+
else
|
203
|
+
KSUID.from_bytes(ksuid)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
private_class_method :cast_string
|
140
207
|
end
|
208
|
+
|
209
|
+
require 'ksuid/railtie' if defined?(Rails)
|
metadata
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ksuid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Herold
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.15'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.15'
|
27
27
|
description: Ruby implementation of the K-Sortable Unique IDentifier
|
28
28
|
email:
|
29
|
-
-
|
29
|
+
- opensource@michaeljherold.com
|
30
30
|
executables: []
|
31
31
|
extensions: []
|
32
32
|
extra_rdoc_files: []
|
@@ -37,15 +37,21 @@ files:
|
|
37
37
|
- README.md
|
38
38
|
- ksuid.gemspec
|
39
39
|
- lib/ksuid.rb
|
40
|
+
- lib/ksuid/activerecord.rb
|
41
|
+
- lib/ksuid/activerecord/binary_type.rb
|
42
|
+
- lib/ksuid/activerecord/table_definition.rb
|
43
|
+
- lib/ksuid/activerecord/type.rb
|
40
44
|
- lib/ksuid/base62.rb
|
45
|
+
- lib/ksuid/configuration.rb
|
46
|
+
- lib/ksuid/railtie.rb
|
41
47
|
- lib/ksuid/type.rb
|
42
48
|
- lib/ksuid/utils.rb
|
43
49
|
- lib/ksuid/version.rb
|
44
|
-
homepage: https://github.com/michaelherold/ksuid
|
50
|
+
homepage: https://github.com/michaelherold/ksuid-ruby
|
45
51
|
licenses:
|
46
52
|
- MIT
|
47
53
|
metadata: {}
|
48
|
-
post_install_message:
|
54
|
+
post_install_message:
|
49
55
|
rdoc_options: []
|
50
56
|
require_paths:
|
51
57
|
- lib
|
@@ -60,10 +66,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
66
|
- !ruby/object:Gem::Version
|
61
67
|
version: '0'
|
62
68
|
requirements: []
|
63
|
-
|
64
|
-
|
65
|
-
signing_key:
|
69
|
+
rubygems_version: 3.1.6
|
70
|
+
signing_key:
|
66
71
|
specification_version: 4
|
67
72
|
summary: Ruby implementation of the K-Sortable Unique IDentifier
|
68
73
|
test_files: []
|
69
|
-
has_rdoc:
|