Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions lib/public_uid.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
# frozen_string_literal: true

require 'active_record'
require "public_uid/version"
require "public_uid/set_public_uid"
require "public_uid/model"
require "public_uid/generators/number_random"
require "public_uid/generators/range_string"
require "public_uid/generators/number_secure_random"
require "public_uid/generators/hex_string_secure_random"
require 'public_uid/version'
require 'public_uid/set_public_uid'
require 'public_uid/model'
require 'public_uid/generators/number_random'
require 'public_uid/generators/range_string'
require 'public_uid/generators/number_secure_random'
require 'public_uid/generators/hex_string_secure_random'
require 'public_uid/generators/readable_string'
require 'public_uid/model_concern'
require 'public_uid/tasks' if defined?(Rails)

module PublicUid
RecordNotFound = Class.new(StandardError)
end

ActiveRecord::Base.send(:include, PublicUid::Model)
ActiveRecord::Base.include PublicUid::Model
12 changes: 7 additions & 5 deletions lib/public_uid/generators/hex_string_secure_random.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
# frozen_string_literal: true

require 'securerandom'

module PublicUid
module Generators
class HexStringSecureRandom
def initialize(length=8)
def initialize(length = 8)
@length = length
end

def generate
def generate(...)
if @length.odd?
result = SecureRandom.hex( (@length+1)/2 ) #because in "SecureRandom.hex(@length)" @length means length in bytes = 2 hexadecimal characters
return result[0...-1]
result = SecureRandom.hex((@length + 1) / 2) # because in "SecureRandom.hex(@length)" @length means length in bytes = 2 hexadecimal characters
result[0...-1]
else
SecureRandom.hex(@length/2)
SecureRandom.hex(@length / 2)
end
end
end
Expand Down
4 changes: 3 additions & 1 deletion lib/public_uid/generators/number_random.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# frozen_string_literal: true

module PublicUid
module Generators
class NumberRandom
def initialize(scale = 1_000_000..9_999_999)
@scale = scale
end

def generate(randomizer = Random.new())
def generate(record: nil, randomizer: Random.new)
randomizer.rand(@scale)
end
end
Expand Down
8 changes: 5 additions & 3 deletions lib/public_uid/generators/number_secure_random.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'securerandom'

module PublicUid
Expand All @@ -7,9 +9,9 @@ def initialize(scale = 1_000_000..9_999_999)
@scale = scale
end

def generate()
generated_number = SecureRandom.random_number( (@scale.max - @scale.min) ) #because SecureRandom.random_number can have only one argument = max value.
return (generated_number + @scale.min)
def generate(...)
generated_number = SecureRandom.random_number((@scale.max - @scale.min)) # because SecureRandom.random_number can have only one argument = max value.
(generated_number + @scale.min)
end
end
end
Expand Down
8 changes: 5 additions & 3 deletions lib/public_uid/generators/range_string.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# frozen_string_literal: true

module PublicUid
module Generators
class RangeString
def initialize(length=8, scale='a'..'z')
def initialize(length = 8, scale = 'a'..'z')
@scale = scale
@length = length
end

def generate
@scale.to_a.shuffle[0,@length].join
def generate(...)
@scale.to_a.shuffle[0, @length].join
end
end
end
Expand Down
35 changes: 35 additions & 0 deletions lib/public_uid/generators/readable_string.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require 'nanoid'

module PublicUid
module Generators
class ReadableString
NUMBERS = [*'0'..'9'].freeze
UPPERCASE = [*'A'..'Z'].freeze
LOOKALIKES = %w[0 1 i I l O o].freeze

CHARS = (NUMBERS + UPPERCASE).freeze
DEFAULT_ALPHABET = (CHARS - LOOKALIKES).join.freeze

attr_reader :prefix, :length, :alphabet, :block

def initialize(prefix: '', length: 10, alphabet: DEFAULT_ALPHABET, &block)
@prefix = prefix
@length = length
@alphabet = alphabet
@block = block
end

def generate(record:)
[prefix, block&.call(record), random_string].compact.join.upcase
end

private

def random_string
Nanoid.generate(size: length, alphabet:)
end
end
end
end
12 changes: 8 additions & 4 deletions lib/public_uid/set_public_uid.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module PublicUid
class SetPublicUid
NewUidNotSetYet = Class.new(StandardError)
Expand All @@ -15,15 +17,17 @@ def initialize(options)
end

def generate(generator)
begin
@new_uid= generator.generate
end while similar_uid_exist?
loop do
@new_uid = generator.generate(record: @record)
break unless similar_uid_exist?
end
end

def set
new_uid || raise(NewUidNotSetYet)
@record.send("#{@column}=", new_uid )
@record.send("#{@column}=", new_uid)
end

private

def similar_uid_exist?
Expand Down
42 changes: 23 additions & 19 deletions public_uid.gemspec
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
# frozen_string_literal: true

require 'English'

lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'public_uid/version'

Gem::Specification.new do |spec|
spec.name = "public_uid"
spec.name = 'public_uid'
spec.version = PublicUid::VERSION
spec.authors = ["Tomas Valent"]
spec.email = ["equivalent@eq8.eu"]
spec.description = %q{Automatic generates public unique identifier for model}
spec.summary = %q{Automatic generates public UID column}
spec.homepage = "https://github.com/equivalent/public_uid"
spec.license = "MIT"
spec.authors = ['Tomas Valent']
spec.email = ['equivalent@eq8.eu']
spec.description = 'Automatic generates public unique identifier for model'
spec.summary = 'Automatic generates public UID column'
spec.homepage = 'https://github.com/equivalent/public_uid'
spec.license = 'MIT'

spec.files = `git ls-files`.split($/)
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]
spec.require_paths = ['lib']

spec.add_dependency "activerecord", '> 4.2' # ensures compatibility for ruby 2.0.0+ to head
spec.add_development_dependency "bundler"
spec.add_development_dependency "pry"
spec.add_development_dependency "rake"
spec.add_development_dependency "minitest", "~> 5"
spec.add_development_dependency "rr", "~> 1.1.2"
spec.add_development_dependency "sqlite3", "~> 1.4.1"
spec.add_development_dependency "activesupport", '> 4.2'
spec.add_dependency 'activerecord', '> 4.2' # ensures compatibility for ruby 2.0.0+ to head
spec.add_dependency 'nanoid', '~> 2.0'
spec.add_development_dependency 'activesupport', '> 4.2'
spec.add_development_dependency 'bundler'
spec.add_development_dependency 'minitest', '~> 5'
spec.add_development_dependency 'pry'
spec.add_development_dependency 'rake'
spec.add_development_dependency 'rr', '~> 1.1.2'
spec.add_development_dependency 'sqlite3', '~> 1.4.1'
end
44 changes: 44 additions & 0 deletions test/lib/generators/readable_string_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

require 'test_helper'

describe 'ReadableString' do
describe '#generate' do
let(:record) { nil }
subject { instance.generate(record:) }

context 'by default' do
let(:instance) { PublicUid::Generators::ReadableString.new }
it 'generates 10 chars string' do
expect(subject.length).must_equal 10
expect(subject).must_be_kind_of String
end

it 'generates upcased chars' do
expect(subject).must_match(/^[0-9A-Z]*$/)
end
end

context 'with args' do
let(:record) { 'WORLD' }

let(:instance) do
PublicUid::Generators::ReadableString.new(prefix: 'TEST-', length: 3) do |record|
"HELLO-#{record.to_s.upcase}--"
end
end

it 'generates a string with the prefix' do
expect(subject).must_match(/^TEST-/)
end

it 'generates a with the result of calling the block with the record' do
expect(subject).must_match(/HELLO-WORLD/)
end

it 'generates a random suffix of length 3' do
expect(subject).must_match(/^TEST-HELLO-WORLD--[0-9A-Z]{3}$/)
end
end
end
end
25 changes: 13 additions & 12 deletions test/lib/set_public_uid_test.rb
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
# frozen_string_literal: true

require 'test_helper'

class DummyGenerator
def initialize
@counter = 0
end

def generate
def generate(...)
if @counter > 1
'second try'
else
'first try'
end.tap { @counter = @counter + 1 }
end.tap { @counter += 1 }
end
end

TestConf.orm_modules.each do |orm_module|
describe orm_module.description do
describe 'PublicUid::SetPublicUid' do

let(:options) { {record: record, column: :public_uid} }
let(:options) { { record:, column: :public_uid } }
let(:instance) { PublicUid::SetPublicUid.new options }
let(:record) { record_class.new }
let(:record_class) { "#{orm_module}::ModelWithGeneratorDefaults".constantize }

describe 'initialization' do
context 'when column not specified' do
let(:options) { { record: record } }
let(:options) { { record: } }
it do
assert_raises PublicUid::SetPublicUid::NoPublicUidColumnSpecified do
instance
Expand All @@ -34,7 +35,7 @@ def generate
end

context 'when record not specified' do
let(:options) { {column: :foo} }
let(:options) { { column: :foo } }
it do
assert_raises PublicUid::SetPublicUid::NoRecordSpecified do
instance
Expand All @@ -43,19 +44,19 @@ def generate
end
end

describe "#generate" do
describe '#generate' do
subject { instance.new_uid }

it "should ask generator to generate random string" do
it 'should ask generator to generate random string' do
instance.generate(DummyGenerator.new)
expect(subject).must_equal 'first try'
end

context 'when record match random' do
before{ record_class.create public_uid: 'first try' }
before { record_class.create public_uid: 'first try' }
after { record_class.destroy_all }

it "should generate string once again" do
it 'should generate string once again' do
instance.generate(DummyGenerator.new)
expect(subject).must_equal 'second try'
end
Expand Down Expand Up @@ -92,9 +93,9 @@ def generate
# in previous version application deal with this issue by converting
# everything given by generator to string which is wrong. If the output
# of generator is not supported by DB don't use it.
it 'must pass exact type of generator to model' do
it 'must pass exact type of generator to model' do
count_mock = stub(record_class).count { 10 }
stub(record_class).where( { public_uid: 567 } ) { count_mock }
stub(record_class).where({ public_uid: 567 }) { count_mock }

expect(trigger).must_equal true
end
Expand Down