ksuid 0.2.0 → 0.5.0

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: 39789ed89a8ef5f2a949dc5cf716ef87f98904902dd8262e123b325408e6e69c
4
- data.tar.gz: e0d3f66c83acab156cc260d063c0a69921b880a83dce3ca32edd0635ed230579
3
+ metadata.gz: a25a1c0246464a41b5b419ad0fffbd8c5b08b7dfb882ccb31a29a0a6fbbc0fe1
4
+ data.tar.gz: 70a978445c93cc062cb704a5156fd919c623677fc1666723ba1f828067978fcd
5
5
  SHA512:
6
- metadata.gz: c4a500c7863252cbe4a55a898ee941e800757676ae3a78c5f191a61173267768ea5b8352609e3689295eb233e14551fea87333fe3f0158208154aed17d9035b4
7
- data.tar.gz: de9846561e6d4a2282bbbdf16ea524f6160198916e4cee8292968cb58d378cd66b2ea4699a0698a3f95db69070d3307118bb000726cb6ce05192eac20ed73a90
6
+ metadata.gz: 7ed9fde240ebee2cdffcb81877548b780286844d2ccfa6a4200f4a0c36161e1d53f073b761e22d7b3fb0d857a31b2fae50fdb30a6ca3571b1cc933ea09281363
7
+ data.tar.gz: 946ad6b51f84fca3b1a0230c4892fc3afd862baabe232efac3b0f9d196d16e2c16702e645faf12bcb9e2614c15a504f007c9cc44383c5ac6e87b8feeb5554d7f
data/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@ 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.5.0](https://github.com/michaelherold/ksuid/compare/v0.4.0...v0.5.0) - 2022-08-18
8
+
9
+ ### Added
10
+
11
+ - If you'd rather deal in KSUID strings instead of `KSUID::Type`s, you can now generate them simply with `KSUID.string`. It takes the same arguments, `payload` and `time` as `KSUID.new`, but returns a string instead of a `KSUID::Type`.
12
+ - `KSUID.prefixed` and the `KSUID::Prefixed` class now can generate prefixed KSUIDs to make them visually identifiable for their source. You cannot prefix a binary-encoded KSUID, only base 62-encoded ones.
13
+ - `ActiveRecord::KSUID` now accepts a `prefix:` argument for handling prefixed KSUIDs. In addition, the `ksuid` column type also accepts a `prefix:` argument to calculate the intended size of the column with the prefix.
14
+
15
+ ### Deprecated
16
+
17
+ - `KSUID::ActiveRecord` is now `ActiveRecord::KSUID`. The original constant will continue to work until v1.0.0, but will emit a warning upon boot of your application. To silence the deprecation, change all uses of `KSUID::ActiveRecord` to the new constant, `ActiveRecord::KSUID`. See the [upgrading notice][./UPGRADING.md] for more information.
18
+
19
+ ### Miscellaneous
20
+
21
+ - The compatibility check for the Base62 implementation in the gem is about 10x faster now. The original optimization did not optimize as much due to an error with the benchmark. This change has a tested benchmark that shows a great improvement. Note that this is a micro-optimization and we see no real performance gain in the parsing of KSUID strings.
22
+
23
+ ## [0.4.0](https://github.com/michaelherold/ksuid/compare/v0.3.0...v0.4.0) - 2022-07-29
24
+
25
+ ### Added
26
+
27
+ - `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.
28
+
29
+ ### Fixed
30
+
31
+ - `ActiveRecord::QueryMethods#include` works as expected now due to the fix on the value object semantics of `KSUID::Type`.
32
+ - Binary KSUID primary and foreign keys work as expected on JRuby.
33
+
34
+ ## [0.3.0](https://github.com/michaelherold/ksuid/compare/v0.2.0...v0.3.0) - 2021-10-07
35
+
36
+ ### Added
37
+
38
+ - 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.
39
+
7
40
  ## [0.2.0](https://github.com/michaelherold/ksuid/compare/v0.1.0...v0.2.0) - 2020-11-11
8
41
 
9
42
  ### Added
data/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # KSUID for Ruby
2
2
 
3
- [![Build Status](https://travis-ci.org/michaelherold/ksuid-ruby.svg)][travis]
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
 
@@ -100,10 +100,34 @@ KSUID.configure do |config|
100
100
  end
101
101
  ```
102
102
 
103
+ ### Prefixed KSUIDs
104
+
105
+ If you use KSUIDs in multiple contexts, you can prefix them to make them easily identifiable.
106
+
107
+ ```ruby
108
+ ksuid = KSUID.prefixed('evt_')
109
+ ```
110
+
111
+ Just like a normal KSUID, you can use a specific timestamp:
112
+
113
+ ``` ruby
114
+ ksuid = KSUID.prefixed('evt_', time: time) # where time is a Time-like object
115
+ ```
116
+
117
+ You can also parse a prefixed KSUID from a string that you received:
118
+
119
+ ```ruby
120
+ ksuid = KSUID::Prefixed.from_base62(base62_string, prefix: 'evt_')
121
+ ```
122
+
123
+ Prefixed KSUIDs order themselves with non-prefixed KSUIDs as if their prefix did not exist. With other prefixed KSUIDs, they order first by their prefix, then their timestamp.
124
+
103
125
  ### ActiveRecord
104
126
 
105
127
  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
128
 
129
+ _Note: In v1.0.0 of KSUID for Ruby, we will extract this behavior into a separate gem, `activerecord-ksuid`. It will be API-compatible with the implementation in v0.5.0+ of KSUID for Ruby. There will be upgrade instructions for v1.0.0 once we release it._
130
+
107
131
  #### Adding to an existing model
108
132
 
109
133
  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:
@@ -124,7 +148,7 @@ Then, to add proper handling to the field, you will want to mix a module into th
124
148
 
125
149
  ```ruby
126
150
  class Event < ApplicationRecord
127
- include KSUID::ActiveRecord[:unique_id]
151
+ include ActiveRecord::KSUID[:unique_id]
128
152
  end
129
153
  ```
130
154
 
@@ -148,7 +172,7 @@ Once you have generated the table that you will use for your model, you will nee
148
172
 
149
173
  ```ruby
150
174
  class Event < ApplicationRecord
151
- include KSUID::ActiveRecord[:my_field_name]
175
+ include ActiveRecord::KSUID[:my_field_name]
152
176
  end
153
177
  ```
154
178
 
@@ -168,7 +192,7 @@ You will need to mix in the module into your model as well:
168
192
 
169
193
  ```ruby
170
194
  class Event < ApplicationRecord
171
- include KSUID::ActiveRecord[:id]
195
+ include ActiveRecord::KSUID[:id]
172
196
  end
173
197
  ```
174
198
 
@@ -177,10 +201,10 @@ end
177
201
  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
202
 
179
203
  ```ruby
180
- require 'ksuid/activerecord'
204
+ require 'active_record/ksuid'
181
205
 
182
206
  # If you will be using the ksuid column type in a migration
183
- require 'ksuid/activerecord/table_definition'
207
+ require 'active_record/ksuid/table_definition'
184
208
  ```
185
209
 
186
210
  Once you have required the file(s) that you need, everything else will work as it does above.
@@ -193,17 +217,39 @@ When you include the KSUID module into your model, you will want to pass the `:b
193
217
 
194
218
  ```ruby
195
219
  class Event < ApplicationRecord
196
- include KSUID::ActiveRecord[:my_field_name, binary: true]
220
+ include ActiveRecord::KSUID[:my_field_name, binary: true]
197
221
  end
198
222
  ```
199
223
 
224
+ #### Using a prefix on your KSUID field
225
+
226
+ For prefixed KSUIDs in ActiveRecord, you must pass the intended prefix during table definition so that the field is of appropriate size.
227
+
228
+ ```ruby
229
+ class CreateEvents < ActiveRecord::Migration[5.2]
230
+ create_table :events do |table|
231
+ table.ksuid :ksuid, prefix: 'evt_'
232
+ end
233
+ end
234
+ ```
235
+
236
+ You also must pass it in the module builder that you include in your model:
237
+
238
+ ```ruby
239
+ class Event < ApplicationRecord
240
+ include ActiveRecord::KSUID[:ksuid, prefix: 'evt_']
241
+ end
242
+ ```
243
+
244
+ You cannot use a prefix with a binary-encoded KSUID.
245
+
200
246
  #### Use the KSUID as your `created_at` timestamp
201
247
 
202
248
  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
249
 
204
250
  ```ruby
205
251
  class Event < ApplicationRecord
206
- include KSUID::ActiveRecord[:my_field_name, created_at: true]
252
+ include ActiveRecord::KSUID[:my_field_name, created_at: true]
207
253
  end
208
254
  ```
209
255
 
@@ -215,11 +261,12 @@ So you’re interested in contributing to KSUID? Check out our [contributing gui
215
261
 
216
262
  ## Supported Ruby Versions
217
263
 
218
- This library aims to support and is [tested against][travis] the following Ruby versions:
264
+ This library aims to support and is [tested against][actions] the following Ruby versions:
219
265
 
220
- * Ruby 2.5
221
- * Ruby 2.6
222
- * JRuby 9.2
266
+ * Ruby 2.7
267
+ * Ruby 3.0
268
+ * Ruby 3.1
269
+ * JRuby 9.3
223
270
 
224
271
  If something doesn't work on one of these versions, it's a bug.
225
272
 
data/UPGRADING.md ADDED
@@ -0,0 +1,11 @@
1
+ # Upgrading instructions for KSUID for Ruby
2
+
3
+ ## v0.5.0
4
+
5
+ ### Deprecated `KSUID::ActiveRecord` in favor of `ActiveRecord::KSUID`
6
+
7
+ This version deprecates the original constant for the ActiveRecord integration, `KSUID::ActiveRecord`. This change is in preparation for extracting the ActiveRecord integration into its own gem. Continuing to use the original constant will show deprecation warnings upon boot of your application.
8
+
9
+ Migrating for this version should be quick: simply do a global replace of `KSUID::ActiveRecord` for `ActiveRecord::KSUID`. No other changes should be necessary.
10
+
11
+ In the future release of v1.0.0, you will need to also include `activerecord-ksuid` your Gemfile. This gem is as-yet unreleased, with a release intended concurrently with v1.0.0 of KSUID for Ruby.
data/ksuid.gemspec CHANGED
@@ -6,17 +6,19 @@ 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 = ['michael@michaeljherold.com']
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
- spec.files = %w[CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md]
16
+ spec.files = %w[CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md UPGRADING.md]
17
17
  spec.files += %w[ksuid.gemspec]
18
18
  spec.files += Dir['lib/**/*.rb']
19
19
  spec.require_paths = ['lib']
20
20
 
21
+ spec.metadata['rubygems_mfa_required'] = 'true'
22
+
21
23
  spec.add_development_dependency 'bundler', '>= 1.15'
22
24
  end
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module KSUID
4
- module ActiveRecord
3
+ module ActiveRecord
4
+ module KSUID
5
5
  # A binary-serialized KSUID for storage within an ActiveRecord database
6
6
  #
7
7
  # @api private
8
8
  #
9
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 }
10
+ # class EventWithBareBinaryType < ActiveRecord::Base
11
+ # attribute :ksuid, ActiveRecord::KSUID::BinaryType.new, default: -> { KSUID.new }
12
12
  # end
13
13
  #
14
14
  # @example Set an attribute as a KSUID using the pre-registered type
15
- # class Event < ActiveRecord::Base
15
+ # class EventWithRegisteredBinaryType < ActiveRecord::Base
16
16
  # attribute :ksuid, :ksuid_binary, default: -> { KSUID.new }
17
17
  # end
18
18
  class BinaryType < ::ActiveRecord::Type::Binary
@@ -28,7 +28,7 @@ module KSUID
28
28
  # @param value [String, Array<Integer>, KSUID::Type] the value to cast into a KSUID
29
29
  # @return [KSUID::Type] the type-casted value
30
30
  def cast(value)
31
- KSUID.call(value)
31
+ ::KSUID.call(value)
32
32
  end
33
33
 
34
34
  # Converts a value from database input to a KSUID
@@ -39,7 +39,7 @@ module KSUID
39
39
  return unless value
40
40
 
41
41
  value = value.to_s if value.is_a?(::ActiveRecord::Type::Binary::Data)
42
- KSUID.call(value)
42
+ ::KSUID.call(value)
43
43
  end
44
44
 
45
45
  # Casts the value from a KSUID into a database-understandable format
@@ -49,18 +49,10 @@ module KSUID
49
49
  def serialize(value)
50
50
  return unless value
51
51
 
52
- super(KSUID.call(value).to_bytes)
53
- end
54
-
55
- # The identifier to use within ActiveRecord's type registry
56
- #
57
- # @api private
58
- # @return [Symbol]
59
- def type
60
- :ksuid_binary
52
+ super(::KSUID.call(value).to_bytes)
61
53
  end
62
54
  end
63
55
  end
64
56
  end
65
57
 
66
- ActiveRecord::Type.register(:ksuid_binary, KSUID::ActiveRecord::BinaryType)
58
+ ActiveRecord::Type.register(:ksuid_binary, ActiveRecord::KSUID::BinaryType)
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module KSUID
5
+ # A string-serialized, prefixed KSUID for storage within an ActiveRecord database
6
+ #
7
+ # @api private
8
+ # @since 0.5.0
9
+ #
10
+ # @example Set an attribute as a prefixed KSUID using the verbose syntax
11
+ # class EventWithBarePrefixedType < ActiveRecord::Base
12
+ # attribute(
13
+ # :ksuid,
14
+ # ActiveRecord::KSUID::PrefixedType.new(prefix: 'evt_'),
15
+ # default: -> { KSUID.prefixed('evt_') }
16
+ # )
17
+ # end
18
+ #
19
+ # @example Set an attribute as a prefixed KSUID using the pre-registered type
20
+ # class EventWithRegisteredPrefixedType < ActiveRecord::Base
21
+ # attribute :ksuid, :ksuid_prefixed, prefix: 'evt_', default: -> { KSUID.prefixed('evt_') }
22
+ # end
23
+ class PrefixedType < ::ActiveRecord::Type::String
24
+ # Instantiates an ActiveRecord::Type for handling prefixed KSUIDs
25
+ #
26
+ # @param prefix [String] the prefix to add to the KSUID
27
+ def initialize(prefix: '')
28
+ @prefix = prefix
29
+ super()
30
+ end
31
+
32
+ # Casts a value from user input into a {KSUID::Prefixed}
33
+ #
34
+ # Type casting happens via the attribute setter and can take input from
35
+ # many places, including:
36
+ #
37
+ # 1. The Rails form builder
38
+ # 2. Directly from the attribute setter
39
+ # 3. From the model initializer
40
+ #
41
+ # @param value [String, Array<Integer>, KSUID::Prefixed] the value to cast into a KSUID
42
+ # @return [KSUID::Prefixed] the type-casted value
43
+ def cast(value)
44
+ ::KSUID::Prefixed.call(value, prefix: @prefix)
45
+ end
46
+
47
+ # Converts a value from database input to a {KSUID::Prefixed}
48
+ #
49
+ # @param value [String, nil] the database-serialized, prefixed KSUID to convert
50
+ # @return [KSUID::Prefixed] the deserialized, prefixed KSUID
51
+ def deserialize(value)
52
+ return unless value
53
+
54
+ ::KSUID::Prefixed.from_base62(value, prefix: @prefix)
55
+ end
56
+
57
+ # Casts the value from a KSUID into a database-understandable format
58
+ #
59
+ # @param value [KSUID::Prefixed, nil] the prefixed KSUID in Ruby format
60
+ # @return [String, nil] the base 62-encoded, prefixed KSUID for storage in the database
61
+ def serialize(value)
62
+ return unless value
63
+
64
+ ::KSUID::Prefixed.call(value, prefix: @prefix).to_s
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ ActiveRecord::Type.register(:ksuid_prefixed, ActiveRecord::KSUID::PrefixedType)
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module KSUID
5
+ # Enables the usage of KSUID types within ActiveRecord when Rails is loaded
6
+ #
7
+ # @api private
8
+ class Railtie < ::Rails::Railtie
9
+ initializer 'ksuid' do
10
+ ActiveSupport.on_load :active_record do
11
+ require 'active_record/ksuid'
12
+ require 'ksuid/activerecord'
13
+ end
14
+ end
15
+
16
+ initializer 'ksuid.table_definition' do
17
+ ActiveSupport.on_load :active_record do
18
+ require 'active_record/ksuid/table_definition'
19
+
20
+ ActiveRecord::ConnectionAdapters::TableDefinition.include(
21
+ ActiveRecord::KSUID::TableDefinition
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module KSUID
4
- module ActiveRecord
3
+ module ActiveRecord
4
+ module KSUID
5
5
  # Extends ActiveRecord's table definition language for KSUIDs
6
6
  module TableDefinition
7
7
  # Defines a field as a string-based KSUID
@@ -22,9 +22,12 @@ module KSUID
22
22
  #
23
23
  # @param args [Array<Symbol>] the list of fields to define as KSUIDs
24
24
  # @param options [Hash] see {ActiveRecord::ConnectionAdapters::TableDefinition}
25
+ # @option options [String] :prefix the prefix expected in front of the KSUID
25
26
  # @return [void]
26
27
  def ksuid(*args, **options)
27
- args.each { |name| column(name, :string, **options.merge(limit: 27)) }
28
+ prefix_length = options.delete(:prefix)&.length || 0
29
+
30
+ args.each { |name| column(name, :string, **options.merge(limit: 27 + prefix_length)) }
28
31
  end
29
32
 
30
33
  # Defines a field as a binary-based KSUID
@@ -52,5 +55,3 @@ module KSUID
52
55
  end
53
56
  end
54
57
  end
55
-
56
- ActiveRecord::ConnectionAdapters::TableDefinition.include(KSUID::ActiveRecord::TableDefinition)
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module KSUID
4
- module ActiveRecord
3
+ module ActiveRecord
4
+ module KSUID
5
5
  # A string-serialized KSUID for storage within an ActiveRecord database
6
6
  #
7
7
  # @api private
8
8
  #
9
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 }
10
+ # class EventWithBareType < ActiveRecord::Base
11
+ # attribute :ksuid, ActiveRecord::KSUID::Type.new, default: -> { KSUID.new }
12
12
  # end
13
13
  #
14
14
  # @example Set an attribute as a KSUID using the pre-registered type
15
- # class Event < ActiveRecord::Base
15
+ # class EventWithRegisteredType < ActiveRecord::Base
16
16
  # attribute :ksuid, :ksuid, default: -> { KSUID.new }
17
17
  # end
18
18
  class Type < ::ActiveRecord::Type::String
@@ -28,7 +28,7 @@ module KSUID
28
28
  # @param value [String, Array<Integer>, KSUID::Type] the value to cast into a KSUID
29
29
  # @return [KSUID::Type] the type-casted value
30
30
  def cast(value)
31
- KSUID.call(value)
31
+ ::KSUID.call(value)
32
32
  end
33
33
 
34
34
  # Converts a value from database input to a KSUID
@@ -38,7 +38,7 @@ module KSUID
38
38
  def deserialize(value)
39
39
  return unless value
40
40
 
41
- KSUID.from_base62(value)
41
+ ::KSUID.from_base62(value)
42
42
  end
43
43
 
44
44
  # Casts the value from a KSUID into a database-understandable format
@@ -48,18 +48,10 @@ module KSUID
48
48
  def serialize(value)
49
49
  return unless value
50
50
 
51
- KSUID.call(value).to_s
52
- end
53
-
54
- # The identifier to use within ActiveRecord's type registry
55
- #
56
- # @api private
57
- # @return [Symbol]
58
- def type
59
- :ksuid
51
+ ::KSUID.call(value).to_s
60
52
  end
61
53
  end
62
54
  end
63
55
  end
64
56
 
65
- ActiveRecord::Type.register(:ksuid, KSUID::ActiveRecord::Type)
57
+ ActiveRecord::Type.register(:ksuid, ActiveRecord::KSUID::Type)
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record/ksuid/binary_type'
4
+ require 'active_record/ksuid/prefixed_type'
5
+ require 'active_record/ksuid/type'
6
+
7
+ # The Ruby on Rails object-relational mapper
8
+ #
9
+ # @see https://guides.rubyonrails.org/ Ruby on Rails documentation
10
+ module ActiveRecord
11
+ # Enables an Active Record model to have a KSUID attribute
12
+ #
13
+ # @api public
14
+ # @since 0.5.0
15
+ module KSUID
16
+ # Builds a module to include into the model
17
+ #
18
+ # @api public
19
+ #
20
+ # @example Add a `#ksuid` attribute to a model
21
+ # class Event < ActiveRecord::Base
22
+ # include ActiveRecord::KSUID[:ksuid]
23
+ # end
24
+ #
25
+ # @example Add a `#remote_id` attribute to a model and overrides `#created_at` to use the KSUID
26
+ # class Event < ActiveRecord::Base
27
+ # include ActiveRecord::KSUID[:remote_id, created_at: true]
28
+ # end
29
+ #
30
+ # @example Add a prefixed `#ksuid` attribute to a model
31
+ # class Event < ActiveRecord::Base
32
+ # include ActiveRecord::KSUID[:ksuid, prefix: 'evt_']
33
+ # end
34
+ #
35
+ # @param field [String, Symbol] the name of the field to use as a KSUID
36
+ # @param created_at [Boolean] whether to override the `#created_at` method
37
+ # @param binary [Boolean] whether to store the KSUID as a binary or a string
38
+ # @param prefix [String, nil] a prefix to prepend to the KSUID attribute
39
+ # @return [Module] the module to include into the model
40
+ def self.[](field, created_at: false, binary: false, prefix: nil)
41
+ raise ArgumentError, 'cannot include a prefix on a binary KSUID' if binary && prefix
42
+
43
+ Module.new.tap do |mod|
44
+ if prefix
45
+ define_prefixed_attribute(field, mod, prefix)
46
+ else
47
+ define_attribute(field, mod, binary)
48
+ end
49
+ define_created_at(field, mod) if created_at
50
+ end
51
+ end
52
+
53
+ # Defines the attribute method that will be written in the module
54
+ #
55
+ # @api private
56
+ #
57
+ # @param field [String, Symbol] the name of the field to set as an attribute
58
+ # @param mod [Module] the module to extend
59
+ # @param binary [Boolean] whether to store the KSUID as a binary or a string
60
+ # @return [void]
61
+ def self.define_attribute(field, mod, binary)
62
+ type = 'ksuid'
63
+ type = 'ksuid_binary' if binary
64
+
65
+ mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
66
+ def self.included(base) # def self.included(base)
67
+ base.__send__( # base.__send__(
68
+ :attribute, # :attribute,
69
+ :#{field}, # :id,
70
+ :#{type}, # :ksuid,
71
+ default: -> { ::KSUID.new } # default: -> { ::KSUID.new }
72
+ ) # )
73
+ end # end
74
+ RUBY
75
+ end
76
+ private_class_method :define_attribute
77
+
78
+ # Defines the attribute method that will be written in the module for a field
79
+ #
80
+ # @api private
81
+ #
82
+ # @param field [String, Symbol] the name of the field to set as an attribute
83
+ # @param mod [Module] the module to extend
84
+ # @param prefix [String] the prefix to add to the KSUID
85
+ # @return [void]
86
+ def self.define_prefixed_attribute(field, mod, prefix)
87
+ mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
88
+ def self.included(base) # def self.included(base)
89
+ base.__send__( # base.__send__(
90
+ :attribute, # :attribute,
91
+ :#{field}, # :id,
92
+ :ksuid_prefixed, # :ksuid_prefixed,
93
+ prefix: #{prefix.inspect}, # prefix: 'evt_'
94
+ default: -> { ::KSUID.prefixed(#{prefix.inspect}) } # default: -> { ::KSUID.prefixed('evt_') }
95
+ ) # )
96
+ end # end
97
+ RUBY
98
+ end
99
+ private_class_method :define_prefixed_attribute
100
+
101
+ # Defines the `#created_at` method that will be written in the module
102
+ #
103
+ # @api private
104
+ #
105
+ # @param field [String, Symbol] the name of the KSUID attribute field
106
+ # @param mod [Module] the module to extend
107
+ # @return [void]
108
+ def self.define_created_at(field, mod)
109
+ mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
110
+ def created_at # def created_at
111
+ return unless #{field} # return unless ksuid
112
+
113
+ #{field}.to_time # ksuid.to_time
114
+ end # end
115
+ RUBY
116
+ end
117
+ private_class_method :define_created_at
118
+ end
119
+ end
@@ -1,16 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ksuid/activerecord/binary_type'
4
- require 'ksuid/activerecord/type'
3
+ require 'active_record/ksuid'
5
4
 
6
5
  module KSUID
7
6
  # Enables an Active Record model to have a KSUID attribute
8
7
  #
9
8
  # @api public
9
+ # @deprecated Use {ActiveRecord::KSUID} instead.
10
10
  module ActiveRecord
11
11
  # Builds a module to include into the model
12
12
  #
13
13
  # @api public
14
+ # @deprecated Use {::ActiveRecord::KSUID.[]} instead.
14
15
  #
15
16
  # @example Add a `#ksuid` attribute to a model
16
17
  # class Event < ActiveRecord::Base
@@ -22,54 +23,15 @@ module KSUID
22
23
  # include KSUID::ActiveRecord[:remote_id, created_at: true]
23
24
  # end
24
25
  #
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}
26
+ # @param (see ::ActiveRecord::KSUID.[])
27
+ # @return (see ::ActiveRecord::KSUID.[])
28
+ def self.[](field, created_at: false, binary: false, prefix: nil)
29
+ ActiveSupport::Deprecation.instance.warn(
30
+ 'KSUID::ActiveRecord is deprecated! Use ActiveRecord::KSUID instead.',
31
+ caller_locations
32
+ )
68
33
 
69
- #{field}.to_time
70
- end
71
- RUBY
34
+ ::ActiveRecord::KSUID[field, created_at: created_at, binary: binary, prefix: prefix]
72
35
  end
73
- private_class_method :define_created_at
74
36
  end
75
37
  end
data/lib/ksuid/base62.rb CHANGED
@@ -20,6 +20,11 @@ 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
+
23
28
  # Checks whether a string is a base 62-compatible string
24
29
  #
25
30
  # @api public
@@ -30,7 +35,7 @@ module KSUID
30
35
  # @param string [String] the string to check for compatibility
31
36
  # @return [Boolean]
32
37
  def self.compatible?(string)
33
- string.each_char.all? { |char| CHARSET.include?(char) }
38
+ !MATCHER.match?(string)
34
39
  end
35
40
 
36
41
  # Decodes a base 62-encoded string into an integer
@@ -46,12 +51,12 @@ module KSUID
46
51
  def self.decode(ksuid)
47
52
  result = 0
48
53
 
49
- ksuid.split('').each_with_index do |char, position|
54
+ ksuid.chars.each_with_index do |char, position|
50
55
  unless (digit = CHARSET.index(char))
51
56
  raise(ArgumentError, "#{ksuid} is not a base 62 number")
52
57
  end
53
58
 
54
- result += digit * BASE**(ksuid.length - (position + 1))
59
+ result += digit * (BASE**(ksuid.length - (position + 1)))
55
60
  end
56
61
 
57
62
  result
@@ -71,7 +76,7 @@ module KSUID
71
76
  chars = encode_without_padding(number)
72
77
 
73
78
  chars << padding if chars.empty?
74
- chars.reverse.join('').rjust(STRING_LENGTH, padding)
79
+ chars.reverse.join.rjust(STRING_LENGTH, padding)
75
80
  end
76
81
 
77
82
  # Encodes a byte string or byte array into base 62
@@ -87,8 +87,11 @@ module KSUID
87
87
  def assert_payload_size(generator)
88
88
  return if (length = generator.call.length) == (expected_length = BYTES[:payload])
89
89
 
90
- raise ConfigurationError, 'Random generator generates the wrong number of bytes ' \
90
+ raise(
91
+ ConfigurationError,
92
+ 'Random generator generates the wrong number of bytes ' \
91
93
  "(#{length} generated, #{expected_length} expected)"
94
+ )
92
95
  end
93
96
  end
94
97
  end
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KSUID
4
+ # Encapsulates the data type for a prefixed KSUID
5
+ #
6
+ # When you have different types of KSUIDs in your application, it can be
7
+ # helpful to add an identifier to the front of them to give you an idea for
8
+ # what kind of object the KSUID belongs to.
9
+ #
10
+ # For example, you might use KSUIDs to identify both Events and Customers. For
11
+ # an Event, you could prefix the KSUID with the string `evt_`. Likewise, for
12
+ # Customers, you could prefix them with the string `cus_`.
13
+ #
14
+ # {KSUID::Prefixed} gives you affordances for doing just this.
15
+ #
16
+ # ## Ordering
17
+ #
18
+ # {KSUID::Prefixed}s are partially orderable with {KSUID::Type} by their
19
+ # timestamps. When ordering them with other {KSUID::Prefixed} instances, they
20
+ # order first by prefix, then by timestamp. This means that in a mixed
21
+ # collection, all Customer KSUIDs (prefix: `cus_`) would be grouped before all
22
+ # Event KSUIDs (prefix `evt_`).
23
+ #
24
+ # ## Interface
25
+ #
26
+ # You typically will not instantiate this class directly, but instead use the
27
+ # {KSUID.prefixed} builder method to save some typing.
28
+ #
29
+ # The most commonly used helper methods for the {KSUID} module also exist on
30
+ # {KSUID::Prefixed} for converting between different forms of output.
31
+ #
32
+ # ## Differences from {KSUID::Type}
33
+ #
34
+ # One other thing to note is that {KSUID::Prefixed} is not intended to handle
35
+ # binary data because the prefix does not make sense in either the byte string
36
+ # or packed array formats.
37
+ #
38
+ # @since 0.5.0
39
+ class Prefixed < Type
40
+ include Comparable
41
+
42
+ # Converts a KSUID-compatible value into a {KSUID::Prefixed}
43
+ #
44
+ # @api public
45
+ #
46
+ # @example Converts a base 62 KSUID string into a {KSUID::Prefixed}
47
+ # KSUID::Prefixed.call('15Ew2nYeRDscBipuJicYjl970D1', prefix: 'evt_')
48
+ #
49
+ # @param ksuid [String, KSUID::Prefixed, KSUID::Type] the prefixed KSUID-compatible value
50
+ # @return [KSUID::Prefixed] the converted, prefixed KSUID
51
+ # @raise [ArgumentError] if the value is not prefixed KSUID-compatible
52
+ def self.call(ksuid, prefix:)
53
+ return unless ksuid && prefix
54
+
55
+ case ksuid
56
+ when KSUID::Prefixed then from_base62(ksuid.to_ksuid.to_s, prefix: prefix)
57
+ when KSUID::Type then from_base62(ksuid.to_s, prefix: prefix)
58
+ when String then cast_string(ksuid, prefix: prefix)
59
+ else
60
+ raise ArgumentError, "Cannot convert #{ksuid.inspect} to KSUID::Prefixed"
61
+ end
62
+ end
63
+
64
+ # Converts a base 62-encoded string into a {KSUID::Prefixed}
65
+ #
66
+ # @api public
67
+ #
68
+ # @example Parse a KSUID string into a prefixed object
69
+ # KSUID::Prefixed.from_base62('0vdbMgWkU6slGpLVCqEFwkkZvuW', prefix: 'evt_')
70
+ #
71
+ # @param string [String] the base 62-encoded KSUID to convert into an object
72
+ # @param prefix [String] the prefix to add to the KSUID
73
+ # @return [KSUID::Prefixed] the prefixed KSUID generated from the string
74
+ def self.from_base62(string, prefix:)
75
+ string = string.sub(/\A#{prefix}/, '')
76
+ int = Base62.decode(string)
77
+ bytes = Utils.int_to_bytes(int, 160)
78
+
79
+ from_bytes(bytes, prefix: prefix)
80
+ end
81
+
82
+ # Casts a string into a {KSUID::Prefixed}
83
+ #
84
+ # @api private
85
+ #
86
+ # @param ksuid [String] the string to convert into a {KSUID::Prefixed}
87
+ # @param prefix [String] the prefix to prepend to the KSUID
88
+ # @return [KSUID::Prefixed] the converted, prefixed KSUID
89
+ def self.cast_string(ksuid, prefix:)
90
+ ksuid = ksuid[-KSUID::BYTES[:base62]..-1] if ksuid.length >= KSUID::BYTES[:base62]
91
+
92
+ unless Base62.compatible?(ksuid)
93
+ raise ArgumentError, 'Prefixed KSUIDs cannot be binary strings'
94
+ end
95
+
96
+ from_base62(ksuid, prefix: prefix)
97
+ end
98
+ private_class_method :cast_string
99
+
100
+ # Converts a byte string or byte array into a KSUID
101
+ #
102
+ # @api private
103
+ #
104
+ # @param bytes [String] the byte string to convert into an object
105
+ # @return [KSUID::Prefixed] the prefixed KSUID generated from the bytes
106
+ def self.from_bytes(bytes, prefix:)
107
+ bytes = bytes.bytes
108
+ timestamp = Utils.int_from_bytes(bytes.first(KSUID::BYTES[:timestamp]))
109
+ payload = Utils.byte_string_from_array(bytes.last(KSUID::BYTES[:payload]))
110
+
111
+ new(prefix, payload: payload, time: Time.at(timestamp + EPOCH_TIME))
112
+ end
113
+ private_class_method :from_bytes
114
+
115
+ # Instantiates a new {KSUID::Prefixed}
116
+ #
117
+ # @api semipublic
118
+ #
119
+ # @example Generate a new {KSUID::Prefixed} for the current second
120
+ # KSUID::Prefixed.new('evt_')
121
+ #
122
+ # @example Generate a new {KSUID::Prefixed} for a given timestamp
123
+ # KSUID::Prefixed.new('cus_', time: Time.parse('2017-11-05 15:00:04 UTC'))
124
+ #
125
+ # @param prefix [String] the prefix to add to the KSUID
126
+ # @param payload [String, Array<Integer>, nil] the payload for the KSUID
127
+ # @param time [Time] the timestamp to use for the KSUID
128
+ # @return [KSUID::Prefix] the generated, prefixed KSUID
129
+ def initialize(prefix, payload: nil, time: Time.now)
130
+ raise ArgumentError, 'requires a prefix' unless prefix
131
+
132
+ super(payload: payload, time: time)
133
+
134
+ @prefix = prefix
135
+ end
136
+
137
+ # The prefix in front of the KSUID
138
+ #
139
+ # @api semipublic
140
+ #
141
+ # @example Getting the prefix to create a similar {KSUID::Prefixed}
142
+ # ksuid1 = KSUID.prefixed('cus_')
143
+ # ksuid2 = KSUID.prefixed(ksuid1.prefix)
144
+ #
145
+ # @return [String] the prefix of the {KSUID::Prefixed}
146
+ attr_reader :prefix
147
+
148
+ # Implements the Comparable interface for sorting {KSUID::Prefixed}s
149
+ #
150
+ # @api private
151
+ #
152
+ # @param other [KSUID::Type] the other object to compare against
153
+ # @return [Integer, nil] nil for uncomparable, -1 for less than other,
154
+ # 0 for equal to, 1 for greater than other
155
+ def <=>(other)
156
+ return unless other.is_a?(Type)
157
+ return super if other.instance_of?(Type)
158
+
159
+ if (result = prefix <=> other.prefix).nonzero?
160
+ result
161
+ else
162
+ super
163
+ end
164
+ end
165
+
166
+ # Checks whether this {KSUID::Prefixed} is equal to another
167
+ #
168
+ # @api semipublic
169
+ #
170
+ # @example Checks whether two KSUIDs are equal
171
+ # KSUID.prefixed('evt_') == KSUID.prefixed('evt_')
172
+ #
173
+ # @param other [KSUID::Prefixed] the other {KSUID::Prefixed} to check against
174
+ # @return [Boolean]
175
+ def ==(other)
176
+ other.is_a?(Prefixed) &&
177
+ prefix == other.prefix &&
178
+ super
179
+ end
180
+
181
+ # Generates the key to use when using a {KSUID::Prefixed} as a hash key
182
+ #
183
+ # @api semipublic
184
+ #
185
+ # @example Using a KSUID as a Hash key
186
+ # ksuid1 = KSUID.prefixed('evt_')
187
+ # ksuid2 = ksuid1.dup
188
+ # values_by_ksuid = {}
189
+ #
190
+ # values_by_ksuid[ksuid1] = "example"
191
+ # values_by_ksuid[ksuid2] #=> "example"
192
+ #
193
+ # @return [Integer]
194
+ def hash
195
+ [prefix, @uid].hash
196
+ end
197
+
198
+ # The {KSUID::Prefixed} as a prefixed, hex-encoded string
199
+ #
200
+ # This is generally useful for comparing against the Go tool.
201
+ #
202
+ # @api public
203
+ #
204
+ # @example
205
+ # ksuid = KSUID::Prefixed.from_base62('0vdbMgWkU6slGpLVCqEFwkkZvuW', prefix: 'evt_')
206
+ #
207
+ # ksuid.raw #=> "evt_0683F789049CC215C099D42B784DBE99341BD79C"
208
+ #
209
+ # @return [String] a prefixed, hex-encoded string
210
+ def raw
211
+ super.prepend(prefix)
212
+ end
213
+
214
+ # Converts the {KSUID::Prefixed} into a {KSUID::Type} by dropping the prefix
215
+ #
216
+ # @api public
217
+ #
218
+ # @example Convert an Event KSUID into a plain KSUID
219
+ # ksuid = KSUID.prefixed('evt_')
220
+ #
221
+ # ksuid.to_ksuid
222
+ #
223
+ # @return [KSUID::Type] the non-prefixed KSUID
224
+ def to_ksuid
225
+ KSUID.from_base62(to_s.sub(/\A#{prefix}/, ''))
226
+ end
227
+
228
+ # The {KSUID::Prefixed} as a base 62-encoded string
229
+ #
230
+ # @api public
231
+ #
232
+ # @example
233
+ # ksuid = KSUID::Prefixed.from_base62('0vdbMgWkU6slGpLVCqEFwkkZvuW', prefix: 'evt_')
234
+ #
235
+ # ksuid.to_s #=> "evt_0vdbMgWkU6slGpLVCqEFwkkZvuW"
236
+ #
237
+ # @return [String] the prefixed, base 62-encoded string for the {KSUID::Prefixed}
238
+ def to_s
239
+ super.prepend(prefix)
240
+ end
241
+ end
242
+ end
data/lib/ksuid/type.rb CHANGED
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base62'
4
- require_relative 'utils'
5
-
6
3
  module KSUID
7
4
  # Encapsulates the data type for a KSUID
8
5
  #
@@ -63,6 +60,36 @@ module KSUID
63
60
  other.to_s == to_s
64
61
  end
65
62
 
63
+ # Checks whether this KSUID hashes to the same hash key as another
64
+ #
65
+ # @api semipublic
66
+ #
67
+ # @example Checks whether two KSUIDs hash to the same key
68
+ # KSUID.new.eql? KSUID.new
69
+ #
70
+ # @param other [KSUID::Type] the other KSUID to check against
71
+ # @return [Boolean]
72
+ def eql?(other)
73
+ hash == other.hash
74
+ end
75
+
76
+ # Generates the key to use when using a KSUID as a hash key
77
+ #
78
+ # @api semipublic
79
+ #
80
+ # @example Using a KSUID as a Hash key
81
+ # ksuid1 = KSUID.new
82
+ # ksuid2 = KSUID.from_base62(ksuid1.to_s)
83
+ # values_by_ksuid = {}
84
+ #
85
+ # values_by_ksuid[ksuid1] = "example"
86
+ # values_by_ksuid[ksuid2] #=> "example"
87
+ #
88
+ # @return [Integer]
89
+ def hash
90
+ @uid.hash
91
+ end
92
+
66
93
  # Prints the KSUID for debugging within a console
67
94
  #
68
95
  # @api public
data/lib/ksuid/utils.rb CHANGED
@@ -5,7 +5,19 @@ module KSUID
5
5
  #
6
6
  # @api private
7
7
  module Utils
8
- # Converts a byte string into a byte array
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,6 +25,21 @@ 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
45
  # @param bytes [String, Array<Integer>] the byte string or array
@@ -34,7 +61,7 @@ module KSUID
34
61
 
35
62
  bytes
36
63
  .map { |byte| byte.to_s(2).rjust(8, '0') }
37
- .join('')
64
+ .join
38
65
  .to_i(2)
39
66
  end
40
67
 
@@ -47,9 +74,8 @@ module KSUID
47
74
  int
48
75
  .to_s(2)
49
76
  .rjust(bits, '0')
50
- .split('')
51
- .each_slice(8)
52
- .map { |digits| digits.join.to_i(2) }
77
+ .scan(BYTES)
78
+ .map { |digits| digits.to_i(2) }
53
79
  .pack("C#{bits / 8}")
54
80
  end
55
81
  end
data/lib/ksuid/version.rb CHANGED
@@ -4,5 +4,5 @@ module KSUID
4
4
  # The version of the KSUID gem
5
5
  #
6
6
  # @return [String]
7
- VERSION = '0.2.0'
7
+ VERSION = '0.5.0'
8
8
  end
data/lib/ksuid.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'ksuid/configuration'
4
- require_relative 'ksuid/type'
5
4
  require_relative 'ksuid/version'
6
5
 
7
6
  # The K-Sortable Unique IDentifier (KSUID)
@@ -41,6 +40,9 @@ require_relative 'ksuid/version'
41
40
  # @example Generate a new KSUID
42
41
  # KSUID.new
43
42
  #
43
+ # @example Generate a KSUID prefixed by `evt_`
44
+ # KSUID.prefixed('evt_')
45
+ #
44
46
  # @example Parse a KSUID string that you have received
45
47
  # KSUID.from_base62('aWgEPTl1tmebfsQzFP4bxwgy80V')
46
48
  #
@@ -63,7 +65,7 @@ module KSUID
63
65
  # The number of bytes that are used to represent each part of a KSUID
64
66
  #
65
67
  # @return [Hash{Symbol => Integer}] the map of data type to number of bytes
66
- BYTES = { payload: 16, timestamp: 4, total: 20 }.freeze
68
+ BYTES = { base62: 27, payload: 16, timestamp: 4, total: 20 }.freeze
67
69
 
68
70
  # The number of characters in a base 62-encoded KSUID
69
71
  #
@@ -75,6 +77,11 @@ module KSUID
75
77
  # @return [String]
76
78
  MAX_STRING_ENCODED = 'aWgEPTl1tmebfsQzFP4bxwgy80V'
77
79
 
80
+ autoload :Base62, 'ksuid/base62'
81
+ autoload :Prefixed, 'ksuid/prefixed'
82
+ autoload :Type, 'ksuid/type'
83
+ autoload :Utils, 'ksuid/utils'
84
+
78
85
  # Converts a KSUID-compatible value into an actual KSUID
79
86
  #
80
87
  # @api public
@@ -89,6 +96,7 @@ module KSUID
89
96
  return unless ksuid
90
97
 
91
98
  case ksuid
99
+ when KSUID::Prefixed then ksuid.to_ksuid
92
100
  when KSUID::Type then ksuid
93
101
  when Array then KSUID.from_bytes(ksuid)
94
102
  when String then cast_string(ksuid)
@@ -190,6 +198,43 @@ module KSUID
190
198
  Type.new(payload: payload, time: time)
191
199
  end
192
200
 
201
+ # Instantiates a new {KSUID::Prefixed}
202
+ #
203
+ # @api public
204
+ # @since 0.5.0
205
+ #
206
+ # @example Generate a new prefixed KSUID for the current second
207
+ # KSUID.prefixed('evt_')
208
+ #
209
+ # @example Generate a new prefixed KSUID for a given timestamp
210
+ # KSUID.prefixed('cus_', time: Time.parse('2022-08-16 10:36:00 UTC'))
211
+ #
212
+ # @param prefix [String] the prefix to apply to the KSUID
213
+ # @param payload [String, Array<Integer>, nil] the payload for the KSUID
214
+ # @param time [Time] the timestamp to use for the KSUID
215
+ # @return [KSUID::Prefixed] the generated, prefixed KSUID
216
+ def self.prefixed(prefix, payload: nil, time: Time.now)
217
+ Prefixed.new(prefix, payload: payload, time: time)
218
+ end
219
+
220
+ # Generates a KSUID string
221
+ #
222
+ # @api public
223
+ # @since 0.5.0
224
+ #
225
+ # @example Generate a new KSUID string for the current second
226
+ # KSUID.string
227
+ #
228
+ # @example Generate a new KSUID string for a given timestamp
229
+ # KSUID.string(time: Time.parse('2017-11-05 15:00:04 UTC'))
230
+ #
231
+ # @param payload [String, Array<Integer>, nil] the payload for the KSUID string
232
+ # @param time [Time] the timestamp to use for the KSUID string
233
+ # @return [String] the generated string
234
+ def self.string(payload: nil, time: Time.now)
235
+ Type.new(payload: payload, time: time).to_s
236
+ end
237
+
193
238
  # Casts a string into a KSUID
194
239
  #
195
240
  # @api private
@@ -206,4 +251,4 @@ module KSUID
206
251
  private_class_method :cast_string
207
252
  end
208
253
 
209
- require 'ksuid/railtie' if defined?(Rails)
254
+ require 'active_record/ksuid/railtie' if defined?(Rails)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ksuid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Herold
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-12 00:00:00.000000000 Z
11
+ date: 2022-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -26,7 +26,7 @@ dependencies:
26
26
  version: '1.15'
27
27
  description: Ruby implementation of the K-Sortable Unique IDentifier
28
28
  email:
29
- - michael@michaeljherold.com
29
+ - opensource@michaeljherold.com
30
30
  executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files: []
@@ -35,22 +35,27 @@ files:
35
35
  - CONTRIBUTING.md
36
36
  - LICENSE.md
37
37
  - README.md
38
+ - UPGRADING.md
38
39
  - ksuid.gemspec
40
+ - lib/active_record/ksuid.rb
41
+ - lib/active_record/ksuid/binary_type.rb
42
+ - lib/active_record/ksuid/prefixed_type.rb
43
+ - lib/active_record/ksuid/railtie.rb
44
+ - lib/active_record/ksuid/table_definition.rb
45
+ - lib/active_record/ksuid/type.rb
39
46
  - lib/ksuid.rb
40
47
  - lib/ksuid/activerecord.rb
41
- - lib/ksuid/activerecord/binary_type.rb
42
- - lib/ksuid/activerecord/table_definition.rb
43
- - lib/ksuid/activerecord/type.rb
44
48
  - lib/ksuid/base62.rb
45
49
  - lib/ksuid/configuration.rb
46
- - lib/ksuid/railtie.rb
50
+ - lib/ksuid/prefixed.rb
47
51
  - lib/ksuid/type.rb
48
52
  - lib/ksuid/utils.rb
49
53
  - lib/ksuid/version.rb
50
- homepage: https://github.com/michaelherold/ksuid
54
+ homepage: https://github.com/michaelherold/ksuid-ruby
51
55
  licenses:
52
56
  - MIT
53
- metadata: {}
57
+ metadata:
58
+ rubygems_mfa_required: 'true'
54
59
  post_install_message:
55
60
  rdoc_options: []
56
61
  require_paths:
@@ -66,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
71
  - !ruby/object:Gem::Version
67
72
  version: '0'
68
73
  requirements: []
69
- rubygems_version: 3.0.6
74
+ rubygems_version: 3.1.6
70
75
  signing_key:
71
76
  specification_version: 4
72
77
  summary: Ruby implementation of the K-Sortable Unique IDentifier
data/lib/ksuid/railtie.rb DELETED
@@ -1,15 +0,0 @@
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