ksuid 0.1.0 → 0.2.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
- SHA1:
3
- metadata.gz: d7e3d2e87bfbd69ce702f4dcc0d7067394f39fe7
4
- data.tar.gz: 8159913d3848e49c9e41143db7e1fc4fb115deee
2
+ SHA256:
3
+ metadata.gz: 39789ed89a8ef5f2a949dc5cf716ef87f98904902dd8262e123b325408e6e69c
4
+ data.tar.gz: e0d3f66c83acab156cc260d063c0a69921b880a83dce3ca32edd0635ed230579
5
5
  SHA512:
6
- metadata.gz: a298ed92c64c0398c14853de8d246fc0e78b29aca724dde716fe8c67640e37621db09f62f4a76da86de0576d64a2be7d8265f1cdb692056244a0838efe9a1da9
7
- data.tar.gz: 5282c1bfe4bc4a09761510fd4a330c7e8429626e67c245e62af3d41b7b391be2470c598fa91aaf6a5ebe7df51a0aec8a0e3a7f46ac01afc5afbbf733e51e1dd8
6
+ metadata.gz: c4a500c7863252cbe4a55a898ee941e800757676ae3a78c5f191a61173267768ea5b8352609e3689295eb233e14551fea87333fe3f0158208154aed17d9035b4
7
+ data.tar.gz: de9846561e6d4a2282bbbdf16ea524f6160198916e4cee8292968cb58d378cd66b2ea4699a0698a3f95db69070d3307118bb000726cb6ce05192eac20ed73a90
@@ -4,12 +4,21 @@ 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.1.0] - 2017-11-05
7
+ ## [0.2.0](https://github.com/michaelherold/ksuid/compare/v0.1.0...v0.2.0) - 2020-11-11
8
+
9
+ ### Added
10
+
11
+ - 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.
12
+ - 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.
13
+
14
+ ### Changed
15
+
16
+ - The `KSUID::Type#inspect` method now makes it much easier to see what you're looking at in the console when you're debugging.
17
+
18
+ ## [0.1.0](https://github.com/michaelherold/ksuid/tree/v0.1.0) - 2017-11-05
8
19
 
9
20
  ### Added
10
21
 
11
22
  - Basic `KSUID.new` interface.
12
23
  - Parsing of bytes through `KSUID.from_bytes`.
13
24
  - Parsing of strings through `KSUID.from_base62`.
14
-
15
- [0.1.0]: https://github.com/michaelherold/interactor-contracts/tree/v0.1.0
@@ -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 `bundle exec rake spec`. If your specs pass, return to step 3.
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 `bundle exec rake`. If your specs or any of the linters fail, return to step 5.
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. Run `bundle exec inch`. If your changes are below a B in documentation, go back to step 8.
37
- 10. Commit and push your changes.
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
@@ -92,6 +92,123 @@ 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.
@@ -100,9 +217,9 @@ So you’re interested in contributing to KSUID? Check out our [contributing gui
100
217
 
101
218
  This library aims to support and is [tested against][travis] the following Ruby versions:
102
219
 
103
- * Ruby 2.3
104
- * Ruby 2.4
105
- * JRuby 9.1
220
+ * Ruby 2.5
221
+ * Ruby 2.6
222
+ * JRuby 9.2
106
223
 
107
224
  If something doesn't work on one of these versions, it's a bug.
108
225
 
@@ -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', '~> 1.15'
21
+ spec.add_development_dependency 'bundler', '>= 1.15'
22
22
  end
@@ -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|Array<Integer>] the byte string or array to convert into an object
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|Array<Integer>|nil] the payload for the KSUID
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)
@@ -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
@@ -0,0 +1,66 @@
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
+
55
+ # The identifier to use within ActiveRecord's type registry
56
+ #
57
+ # @api private
58
+ # @return [Symbol]
59
+ def type
60
+ :ksuid_binary
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ 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,65 @@
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
+
54
+ # The identifier to use within ActiveRecord's type registry
55
+ #
56
+ # @api private
57
+ # @return [Symbol]
58
+ def type
59
+ :ksuid
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ ActiveRecord::Type.register(:ksuid, KSUID::ActiveRecord::Type)
@@ -20,6 +20,19 @@ module KSUID
20
20
  # @api private
21
21
  BASE = CHARSET.size
22
22
 
23
+ # Checks whether a string is a base 62-compatible string
24
+ #
25
+ # @api public
26
+ #
27
+ # @example Checks a KSUID for base 62 compatibility
28
+ # KSUID::Base62.compatible?("15Ew2nYeRDscBipuJicYjl970D1") #=> true
29
+ #
30
+ # @param string [String] the string to check for compatibility
31
+ # @return [Boolean]
32
+ def self.compatible?(string)
33
+ string.each_char.all? { |char| CHARSET.include?(char) }
34
+ end
35
+
23
36
  # Decodes a base 62-encoded string into an integer
24
37
  #
25
38
  # @api public
@@ -71,7 +84,7 @@ module KSUID
71
84
  # 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
72
85
  # )
73
86
  #
74
- # @param bytes [String|Array<Integer>] the bytes to encode
87
+ # @param bytes [String, Array<Integer>] the bytes to encode
75
88
  # @return [String] the encoded bytes as a base 62 string
76
89
  def self.encode_bytes(bytes)
77
90
  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
@@ -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|Array<Integer>|nil] the payload for the KSUID
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 ||= SecureRandom.random_bytes(BYTES[: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,18 @@ module KSUID
64
63
  other.to_s == to_s
65
64
  end
66
65
 
66
+ # Prints the KSUID for debugging within a console
67
+ #
68
+ # @api public
69
+ #
70
+ # @example Show the maximum KSUID
71
+ # KSUID.max.inspect #=> "<KSUID(aWgEPTl1tmebfsQzFP4bxwgy80V)>"
72
+ #
73
+ # @return [String]
74
+ def inspect
75
+ "<KSUID(#{self})>"
76
+ end
77
+
67
78
  # The payload for the KSUID, as a hex-encoded string
68
79
  #
69
80
  # This is generally useful for comparing against the Go tool
@@ -121,9 +132,7 @@ module KSUID
121
132
  #
122
133
  # @return [Integer] the Unix timestamp for the event (without the epoch shift)
123
134
  def to_i
124
- unix_time = Utils.int_from_bytes(uid.first(BYTES[:timestamp]))
125
-
126
- unix_time
135
+ Utils.int_from_bytes(uid.first(BYTES[:timestamp]))
127
136
  end
128
137
 
129
138
  # The KSUID as a base 62-encoded string
@@ -15,20 +15,19 @@ module KSUID
15
15
 
16
16
  # Converts a byte string or byte array into a hex-encoded string
17
17
  #
18
- # @param bytes [String|Array<Integer>] the byte string or array
18
+ # @param bytes [String, Array<Integer>] the byte string or array
19
19
  # @return [String] the byte string as a hex-encoded string
20
20
  def self.bytes_to_hex_string(bytes)
21
21
  bytes = bytes.bytes if bytes.is_a?(String)
22
22
 
23
23
  byte_string_from_array(bytes)
24
- .unpack('H*')
25
- .first
24
+ .unpack1('H*')
26
25
  .upcase
27
26
  end
28
27
 
29
28
  # Converts a byte string or byte array into an integer
30
29
  #
31
- # @param bytes [String|Array<Integer>] the byte string or array
30
+ # @param bytes [String, Array<Integer>] the byte string or array
32
31
  # @return [Integer] the resulting integer
33
32
  def self.int_from_bytes(bytes)
34
33
  bytes = bytes.bytes if bytes.is_a?(String)
@@ -4,5 +4,5 @@ module KSUID
4
4
  # The version of the KSUID gem
5
5
  #
6
6
  # @return [String]
7
- VERSION = '0.1.0'
7
+ VERSION = '0.2.0'
8
8
  end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ksuid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.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: 2017-11-05 00:00:00.000000000 Z
11
+ date: 2020-11-12 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
@@ -37,7 +37,13 @@ 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
@@ -45,7 +51,7 @@ homepage: https://github.com/michaelherold/ksuid
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
- rubyforge_project:
64
- rubygems_version: 2.6.13
65
- signing_key:
69
+ rubygems_version: 3.0.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: