Skip to content

Commit

Permalink
Add support for Sequel
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelherold committed May 29, 2018
1 parent f83e7a4 commit aea0390
Show file tree
Hide file tree
Showing 7 changed files with 445 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
### Added

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

### Changed

Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,8 @@ group :ci do
end

group :test do
gem 'jdbc-sqlite3', platforms: %i[jruby]
gem 'rspec', '~> 3.6'
gem 'sequel'
gem 'sqlite3', platforms: %i[mri mingw x64_mingw]
end
1 change: 1 addition & 0 deletions config.reek
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
ManualDispatch:
exclude:
- "KSUID::Configuration#assert_generator_is_callable"
- "Sequel::Plugins::Ksuid::InstanceMethods#set_ksuid"

UncommunicativeModuleName:
exclude:
Expand Down
1 change: 1 addition & 0 deletions lib/ksuid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require_relative 'ksuid/configuration'
require_relative 'ksuid/type'
require_relative 'ksuid/version'
require_relative 'sequel/plugins/ksuid' if defined?(Sequel)

# The K-Sortable Unique IDentifier (KSUID)
#
Expand Down
226 changes: 226 additions & 0 deletions lib/sequel/plugins/ksuid.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# frozen_string_literal: true

module Sequel # :nodoc:
module Plugins # :nodoc:
# Adds KSUID support to the Sequel ORM
#
# @api public
#
# @example Creates a model with a standard, string-based KSUID
# connection_string = 'sqlite:/'
# connection_string = 'jdbc:sqlite::memory:' if RUBY_ENGINE == 'jruby'
# DB = Sequel.connect(connection_string)
#
# DB.create_table!(:events) do
# Integer :id
# String :ksuid
# end
#
# class Event < Sequel::Model(:events)
# plugin :ksuid
# end
#
# @example Creates a model with a customized KSUID field
# connection_string = 'sqlite:/'
# connection_string = 'jdbc:sqlite::memory:' if RUBY_ENGINE == 'jruby'
# DB = Sequel.connect(connection_string)
#
# DB.create_table!(:events) do
# Integer :id
# String :correlation_id
# end
#
# class Event < Sequel::Model(:events)
# plugin :ksuid, field: :correlation_id
# end
#
# @example Creates a model that always overwrites the KSUID on save
# connection_string = 'sqlite:/'
# connection_string = 'jdbc:sqlite::memory:' if RUBY_ENGINE == 'jruby'
# DB = Sequel.connect(connection_string)
#
# DB.create_table!(:events) do
# Integer :id
# String :ksuid
# end
#
# class Event < Sequel::Model(:events)
# plugin :ksuid, force: true
# end
#
# @example Creates a model with a binary-encoded KSUID
# connection_string = 'sqlite:/'
# connection_string = 'jdbc:sqlite::memory:' if RUBY_ENGINE == 'jruby'
# DB = Sequel.connect(connection_string)
#
# DB.create_table!(:events) do
# Integer :id
# blob :ksuid
# end
#
# class Event < Sequel::Model(:events)
# plugin :ksuid, binary: true
# end
module Ksuid
# Configures the plugin by setting available options
#
# @api private
#
# @param model [Sequel::Model] the model to configure
# @param options [Hash] the hash of available options
# @option options [Boolean] :binary encode the KSUID as a binary string
# @option options [Boolean] :field the field to use as a KSUID
# @option options [Boolean] :force overwrite the field on save
# @option options [Boolean] :wrap wraps the KSUID into a KSUID type
# @return [void]
def self.configure(model, options = OPTS)
model.instance_exec do
@ksuid_binary = options.fetch(:binary, false)
@ksuid_field = options.fetch(:field, :ksuid)
@ksuid_overwrite = options.fetch(:force, false)
@ksuid_wrap = options.fetch(:wrap, false)

define_ksuid_accessor if @ksuid_wrap
end
end

# Class methods that are extended onto an enabling model class
#
# @api private
module ClassMethods
# The field that is enabled with KSUID handling
#
# @api private
#
# @return [Symbol]
attr_reader :ksuid_field

# Checks whether the KSUID should be binary encoded
#
# @api private
#
# @return [Boolean]
def ksuid_binary?
@ksuid_binary
end

# Defines an accessor for the KSUID that converts it into a KSUID
#
# @api private
#
# @return [void]
def define_ksuid_accessor
define_ksuid_getter
define_ksuid_setter
end

# Defines a getter for the KSUID that converts it into a KSUID
#
# @api private
#
# @return [void]
def define_ksuid_getter
define_method(@ksuid_field) do
KSUID.call(super())
end
end

# Defines a setter for the KSUID that converts the value properly
#
# @api private
#
# @return [void]
def define_ksuid_setter
define_method("#{@ksuid_field}=") do |ksuid|
ksuid = KSUID.call(ksuid)

if self.class.ksuid_binary?
super(ksuid.to_bytes)
else
super(ksuid.to_s)
end
end
end

# Checks whether the KSUID should be overwritten upon save
#
# @api private
#
# @return [Boolean]
def ksuid_overwrite?
@ksuid_overwrite
end

# Checks whether the model should wrap its KSUID field in a type
#
# @api private
#
# @return [Boolean]
def ksuid_wrap?
@ksuid_wrap
end

Plugins.inherited_instance_variables(
self,
:@ksuid_binary => nil,
:@ksuid_field => nil,
:@ksuid_overwrite => nil,
:@ksuid_wrap => nil
)
end

# Instance methods that are included in an enabling model class
#
# @api private
module InstanceMethods
# Generates a KSUID for the field before validation
#
# @api private
#
# @return [void]
def before_validation
set_ksuid if new?
super
end

private

# A hook method for generating a new KSUID
#
# @api private
#
# @return [String] a binary or base 62-encoded string
def create_ksuid
ksuid = KSUID.new

if self.class.ksuid_binary?
ksuid.to_bytes
else
ksuid.to_s
end
end

# Initializes the KSUID field when it is not set, or overwrites it if enabled
#
# Note: The disabled Rubocop rule is to allow the method to follow
# Sequel conventions.
#
# @api private
#
# @param ksuid [String] the normal string or byte string of the KSUID
# @return [void]
# rubocop:disable Naming/AccessorMethodName
def set_ksuid(ksuid = create_ksuid)
field = model.ksuid_field
setter = :"#{field}="

return unless respond_to?(field) &&
respond_to?(setter) &&
(model.ksuid_overwrite? || !get_column_value(field))

set_column_value(setter, ksuid)
end
end
end
end
end
2 changes: 2 additions & 0 deletions spec/doctest_helper.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'sequel'
require 'sqlite3' unless RUBY_ENGINE == 'jruby'
require 'ksuid'
Loading

0 comments on commit aea0390

Please sign in to comment.