Skip to content

Commit f1cf43f

Browse files
committed
Add support for Sequel
1 parent f83e7a4 commit f1cf43f

File tree

7 files changed

+445
-0
lines changed

7 files changed

+445
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
99
### Added
1010

1111
- 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+
- A plugin for Sequel to support KSUID fields. You can include the plugin via `plugin :ksuid` within a `Sequel::Model` class.
1213

1314
### Changed
1415

Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,8 @@ group :ci do
3232
end
3333

3434
group :test do
35+
gem 'jdbc-sqlite3', platforms: %i[jruby]
3536
gem 'rspec', '~> 3.6'
37+
gem 'sequel'
38+
gem 'sqlite3', platforms: %i[mri mingw x64_mingw]
3639
end

config.reek

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
ManualDispatch:
33
exclude:
44
- "KSUID::Configuration#assert_generator_is_callable"
5+
- "Sequel::Plugins::Ksuid::InstanceMethods#set_ksuid"
56

67
UncommunicativeModuleName:
78
exclude:

lib/ksuid.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require_relative 'ksuid/configuration'
44
require_relative 'ksuid/type'
55
require_relative 'ksuid/version'
6+
require_relative 'sequel/plugins/ksuid' if defined?(Sequel)
67

78
# The K-Sortable Unique IDentifier (KSUID)
89
#

lib/sequel/plugins/ksuid.rb

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# frozen_string_literal: true
2+
3+
module Sequel # :nodoc:
4+
module Plugins # :nodoc:
5+
# Adds KSUID support to the Sequel ORM
6+
#
7+
# @api public
8+
#
9+
# @example Creates a model with a standard, string-based KSUID
10+
# connection_string = 'sqlite:/'
11+
# connection_string = 'jdbc:sqlite::memory:' if RUBY_ENGINE == 'jruby'
12+
# DB = Sequel.connect(connection_string)
13+
#
14+
# DB.create_table!(:events) do
15+
# Integer :id
16+
# String :ksuid
17+
# end
18+
#
19+
# class Event < Sequel::Model(:events)
20+
# plugin :ksuid
21+
# end
22+
#
23+
# @example Creates a model with a customized KSUID field
24+
# connection_string = 'sqlite:/'
25+
# connection_string = 'jdbc:sqlite::memory:' if RUBY_ENGINE == 'jruby'
26+
# DB = Sequel.connect(connection_string)
27+
#
28+
# DB.create_table!(:events) do
29+
# Integer :id
30+
# String :correlation_id
31+
# end
32+
#
33+
# class Event < Sequel::Model(:events)
34+
# plugin :ksuid, field: :correlation_id
35+
# end
36+
#
37+
# @example Creates a model that always overwrites the KSUID on save
38+
# connection_string = 'sqlite:/'
39+
# connection_string = 'jdbc:sqlite::memory:' if RUBY_ENGINE == 'jruby'
40+
# DB = Sequel.connect(connection_string)
41+
#
42+
# DB.create_table!(:events) do
43+
# Integer :id
44+
# String :ksuid
45+
# end
46+
#
47+
# class Event < Sequel::Model(:events)
48+
# plugin :ksuid, force: true
49+
# end
50+
#
51+
# @example Creates a model with a binary-encoded KSUID
52+
# connection_string = 'sqlite:/'
53+
# connection_string = 'jdbc:sqlite::memory:' if RUBY_ENGINE == 'jruby'
54+
# DB = Sequel.connect(connection_string)
55+
#
56+
# DB.create_table!(:events) do
57+
# Integer :id
58+
# blob :ksuid
59+
# end
60+
#
61+
# class Event < Sequel::Model(:events)
62+
# plugin :ksuid, binary: true
63+
# end
64+
module Ksuid
65+
# Configures the plugin by setting available options
66+
#
67+
# @api private
68+
#
69+
# @param model [Sequel::Model] the model to configure
70+
# @param options [Hash] the hash of available options
71+
# @option options [Boolean] :binary encode the KSUID as a binary string
72+
# @option options [Boolean] :field the field to use as a KSUID
73+
# @option options [Boolean] :force overwrite the field on save
74+
# @option options [Boolean] :wrap wraps the KSUID into a KSUID type
75+
# @return [void]
76+
def self.configure(model, options = OPTS)
77+
model.instance_exec do
78+
@ksuid_binary = options.fetch(:binary, false)
79+
@ksuid_field = options.fetch(:field, :ksuid)
80+
@ksuid_overwrite = options.fetch(:force, false)
81+
if (@ksuid_wrap = options.fetch(:wrap, false))
82+
define_ksuid_accessor
83+
end
84+
end
85+
end
86+
87+
# Class methods that are extended onto an enabling model class
88+
#
89+
# @api private
90+
module ClassMethods
91+
# The field that is enabled with KSUID handling
92+
#
93+
# @api private
94+
#
95+
# @return [Symbol]
96+
attr_reader :ksuid_field
97+
98+
# Checks whether the KSUID should be binary encoded
99+
#
100+
# @api private
101+
#
102+
# @return [Boolean]
103+
def ksuid_binary?
104+
@ksuid_binary
105+
end
106+
107+
# Defines an accessor for the KSUID that converts it into a KSUID
108+
#
109+
# @api private
110+
#
111+
# @return [void]
112+
def define_ksuid_accessor
113+
define_ksuid_getter
114+
define_ksuid_setter
115+
end
116+
117+
# Defines a getter for the KSUID that converts it into a KSUID
118+
#
119+
# @api private
120+
#
121+
# @return [void]
122+
def define_ksuid_getter
123+
define_method(@ksuid_field) do
124+
KSUID.call(super())
125+
end
126+
end
127+
128+
# Defines a setter for the KSUID that converts the value properly
129+
#
130+
# @api private
131+
#
132+
# @return [void]
133+
def define_ksuid_setter
134+
define_method("#{@ksuid_field}=") do |ksuid|
135+
ksuid = KSUID.call(ksuid)
136+
137+
if self.class.ksuid_binary?
138+
super(ksuid.to_bytes)
139+
else
140+
super(ksuid.to_s)
141+
end
142+
end
143+
end
144+
145+
# Checks whether the KSUID should be overwritten upon save
146+
#
147+
# @api private
148+
#
149+
# @return [Boolean]
150+
def ksuid_overwrite?
151+
@ksuid_overwrite
152+
end
153+
154+
# Checks whether the model should wrap its KSUID field in a type
155+
#
156+
# @api private
157+
#
158+
# @return [Boolean]
159+
def ksuid_wrap?
160+
@ksuid_wrap
161+
end
162+
163+
Plugins.inherited_instance_variables(
164+
self,
165+
:@ksuid_binary => nil,
166+
:@ksuid_field => nil,
167+
:@ksuid_overwrite => nil,
168+
:@ksuid_wrap => nil
169+
)
170+
end
171+
172+
# Instance methods that are included in an enabling model class
173+
#
174+
# @api private
175+
module InstanceMethods
176+
# Generates a KSUID for the field before validation
177+
#
178+
# @api private
179+
#
180+
# @return [void]
181+
def before_validation
182+
set_ksuid if new?
183+
super
184+
end
185+
186+
private
187+
188+
# A hook method for generating a new KSUID
189+
#
190+
# @api private
191+
#
192+
# @return [String] a binary or base 62-encoded string
193+
def create_ksuid
194+
ksuid = KSUID.new
195+
196+
if self.class.ksuid_binary?
197+
ksuid.to_bytes
198+
else
199+
ksuid.to_s
200+
end
201+
end
202+
203+
# Initializes the KSUID field when it is not set, or overwrites it if enabled
204+
#
205+
# Note: The disabled Rubocop rule is to allow the method to follow
206+
# Sequel conventions.
207+
#
208+
# @api private
209+
#
210+
# @param ksuid [String] the normal string or byte string of the KSUID
211+
# @return [void]
212+
# rubocop:disable Naming/AccessorMethodName
213+
def set_ksuid(ksuid = create_ksuid)
214+
field = model.ksuid_field
215+
setter = :"#{field}="
216+
217+
return unless respond_to?(field) &&
218+
respond_to?(setter) &&
219+
(model.ksuid_overwrite? || !get_column_value(field))
220+
221+
set_column_value(setter, ksuid)
222+
end
223+
end
224+
end
225+
end
226+
end

spec/doctest_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
# frozen_string_literal: true
22

3+
require 'sequel'
4+
require 'sqlite3' unless RUBY_ENGINE == 'jruby'
35
require 'ksuid'

0 commit comments

Comments
 (0)