Skip to content

Commit

Permalink
Merge pull request #4 from matic-insurance/feature/configuration_inte…
Browse files Browse the repository at this point in the history
…rface

Singleton Interface
  • Loading branch information
volodymyr-mykhailyk authored Oct 29, 2018
2 parents 6e9832f + 06c34f2 commit 6efb436
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 99 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ deploy:
provider: rubygems
api_key: "$RUBY_GEMS_API_KEY"
on:
branch: master
tags: true
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ gem 'id_generator'
```ruby
# Somewhere during project start
context_id = 165 # value from 0 to 255
ID_GENERATOR = IdGenerator.new(context_id)
IdGenerator.configuration.context_id = context_id
# Or using block
IdGenerator.configure {|config| config.context_id = context_id}

#Inside of the actual code
ID_GENERATOR.generate
IdGenerator.generate
```

## Contributing
Expand Down
52 changes: 14 additions & 38 deletions lib/id_generator.rb
Original file line number Diff line number Diff line change
@@ -1,46 +1,22 @@
require 'securerandom'

class IdGenerator
VERSION = '0.1.2'.freeze

COUNTER_PART_SIZE = 8
CONTEXT_PART_SIZE = 2

RANDOM_PART_BYTES = 11
COUNTER_START = Time.new(2000).to_i

class Error < StandardError
end

def initialize(context_id)
raise(IdGenerator::Error, 'Invalid project id') unless context_id_valid?(context_id)

@context_id = value_to_hex(context_id, CONTEXT_PART_SIZE)
end

def generate
"#{time}-#{@context_id}-#{random_number}"
end

private

def context_id_valid?(context_id)
return false unless context_id.is_a?(Integer)
return false unless context_id.between?(0, 255)

true
require 'id_generator/version'
require 'id_generator/configuration'
require 'id_generator/errors'
require 'id_generator/generators/timestamped_random'

module IdGenerator
def self.configure
yield(configuration)
end

def time
timestamp = Time.now.to_i - COUNTER_START
value_to_hex(timestamp, COUNTER_PART_SIZE)
def self.generate
id_generator.generate
end

def random_number
SecureRandom.hex(RANDOM_PART_BYTES)
def self.configuration
@configuration ||= Configuration.new
end

def value_to_hex(value, size)
format("%0#{size}x", value)
def self.id_generator
@id_generator ||= IdGenerator::Generators::TimestampedRandom.new(configuration)
end
end
24 changes: 24 additions & 0 deletions lib/id_generator/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module IdGenerator
class Configuration
attr_reader :context_id

def initialize(context_id: 0)
self.context_id = context_id
end

def context_id=(context_id)
raise(IdGenerator::Errors::InvalidContextId, 'Invalid context id') unless context_id_valid?(context_id)

@context_id = context_id
end

protected

def context_id_valid?(context_id)
return false unless context_id.is_a?(Integer)
return false unless context_id.between?(0, 255)

true
end
end
end
7 changes: 7 additions & 0 deletions lib/id_generator/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module IdGenerator
module Errors
class InvalidContextId < StandardError
# Just empty line to trick codacy
end
end
end
42 changes: 42 additions & 0 deletions lib/id_generator/generators/timestamped_random.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
require 'securerandom'

module IdGenerator
module Generators
class TimestampedRandom
COUNTER_PART_SIZE = 8
CONTEXT_PART_SIZE = 2

RANDOM_PART_BYTES = 11
COUNTER_START = Time.new(2000).to_i

attr_reader :config

def initialize(config)
@config = config
end

def generate
"#{time}-#{context_id}-#{random_number}"
end

private

def time
timestamp = Time.now.to_i - COUNTER_START
value_to_hex(timestamp, COUNTER_PART_SIZE)
end

def context_id
value_to_hex(config.context_id, CONTEXT_PART_SIZE)
end

def random_number
SecureRandom.hex(RANDOM_PART_BYTES)
end

def value_to_hex(value, size)
format("%0#{size}x", value)
end
end
end
end
3 changes: 3 additions & 0 deletions lib/id_generator/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module IdGenerator
VERSION = '0.1.2'.freeze
end
44 changes: 44 additions & 0 deletions spec/lib/id_generator/configuration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require 'spec_helper'

RSpec.describe IdGenerator::Configuration do
describe '#context_id' do
let(:configuration) { described_class.new(context_id: context_id) }
let(:context_id) { rand(255) }

it 'returns initialized value' do
expect(configuration.context_id).to eq(context_id)
end

describe 'validation' do
let(:error) { IdGenerator::Errors::InvalidContextId }

it 'allows empty' do
expect { described_class.new }.not_to raise_error
end

it 'allows 0' do
expect { described_class.new(context_id: 0) }.not_to raise_error
end

it 'allows 255' do
expect { described_class.new(context_id: 255) }.not_to raise_error
end

it 'rejects negative values' do
expect { described_class.new(context_id: -1) }.to raise_error(error, 'Invalid context id')
end

it 'rejects large values' do
expect { described_class.new(context_id: rand(256..1000)) }.to raise_error(error, 'Invalid context id')
end

it 'rejects empty values' do
expect { described_class.new(context_id: nil) }.to raise_error(error, 'Invalid context id')
end

it 'rejects invalid values' do
expect { described_class.new(context_id: 'id') }.to raise_error(error, 'Invalid context id')
end
end
end
end
55 changes: 55 additions & 0 deletions spec/lib/id_generator/generators/timestamped_random_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
RSpec.describe IdGenerator::Generators::TimestampedRandom do
let(:generator) { described_class.new(configuration) }
let(:context_id) { rand(255) }
let(:configuration) { IdGenerator::Configuration.new(context_id: context_id) }

describe 'id format' do
let(:parts) { uniq_id.split('-') }

it 'has correct size' do
expect(uniq_id.size).to eq(34)
end

it 'has correct format' do
expect(uniq_id).to match(/^[a-z0-9\-]+$/)
end

it 'has 2 separators' do
expect(parts.size).to eq(3)
end

it 'has correct timestamp size' do
expect(parts[0].size).to eq(8)
end

it 'has correct project id size' do
expect(parts[1].size).to eq(2)
end

it 'has correct random part size' do
expect(parts[2].size).to eq(22)
end

it 'includes timestamp' do
timestamp = Time.now.to_i - Time.new(2000).to_i
id_timestamp = uniq_id.split('-').first.to_i(16)
expect(id_timestamp).to be_between(timestamp, timestamp + 2)
end

it 'include project id' do
expect(uniq_id).to include(format('-%02x-', context_id))
end
end

describe '#generate' do
it 'generates random id every time' do
expect(uniq_id).not_to eq(uniq_id)
end
end

protected

def uniq_id
generator.generate
end
end
79 changes: 21 additions & 58 deletions spec/lib/id_generator_spec.rb
Original file line number Diff line number Diff line change
@@ -1,80 +1,43 @@
RSpec.describe IdGenerator do
let(:generator) { described_class.new(context_id) }
let(:context_id) { rand(255) }
let(:context_error) { IdGenerator::Errors::InvalidContextId }

describe 'id format' do
let(:parts) { uniq_id.split('-') }
after { described_class.configuration.context_id = 0 }

it 'has correct size' do
expect(uniq_id.size).to eq(34)
end

it 'has correct format' do
expect(uniq_id).to match(/^[a-z0-9\-]+$/)
end

it 'has 2 separators' do
expect(parts.size).to eq(3)
end

it 'has correct timestamp size' do
expect(parts[0].size).to eq(8)
end

it 'has correct project id size' do
expect(parts[1].size).to eq(2)
end

it 'has correct random part size' do
expect(parts[2].size).to eq(22)
end

it 'includes timestamp' do
timestamp = Time.now.to_i - Time.new(2000).to_i
id_timestamp = uniq_id.split('-').first.to_i(16)
expect(id_timestamp).to be_between(timestamp, timestamp + 2)
describe '#generate' do
it 'generates random id every time' do
expect(uniq_id).not_to eq(uniq_id)
end

it 'include project id' do
expect(uniq_id).to include(format('-%02x-', context_id))
it 'using default context id' do
expect(uniq_id).to include('-00-')
end
end

describe 'context_id' do
it 'allows 0' do
expect { described_class.new(0) }.not_to raise_error
end

it 'allows 255' do
expect { described_class.new(255) }.not_to raise_error
describe '#configure' do
it 'setting context id' do
described_class.configure { |c| c.context_id = 15 }
expect(uniq_id).to include('-0f-')
end

it 'rejects negative values' do
expect { described_class.new(-1) }.to raise_error(IdGenerator::Error, 'Invalid project id')
end

it 'rejects large values' do
expect { described_class.new(rand(256..1000)) }.to raise_error(IdGenerator::Error, 'Invalid project id')
end

it 'rejects empty values' do
expect { described_class.new(nil) }.to raise_error(IdGenerator::Error, 'Invalid project id')
it 'validating context id' do
expect { described_class.configure { |c| c.context_id = -1 } }.to raise_error(context_error)
end
end

it 'rejects invalid values' do
expect { described_class.new('id') }.to raise_error(IdGenerator::Error, 'Invalid project id')
describe '#configuration' do
it 'setting context id' do
described_class.configuration.context_id = 15
expect(uniq_id).to include('-0f-')
end
end

describe '#generate' do
it 'generates random id every time' do
expect(uniq_id).not_to eq(uniq_id)
it 'validating context id' do
expect { described_class.configuration.context_id = -1 }.to raise_error(context_error)
end
end

protected

def uniq_id
generator.generate
described_class.generate
end
end

0 comments on commit 6efb436

Please sign in to comment.