Skip to content

Commit

Permalink
Merge pull request #4 from sealink/refactoring
Browse files Browse the repository at this point in the history
Refactoring
  • Loading branch information
Michael Noack committed Feb 12, 2016
2 parents 741b5e6 + 701ffd2 commit 94538eb
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 82 deletions.
8 changes: 7 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
AllCops:
TargetRubyVersion: 2.3
TargetRubyVersion: 2.1

Metrics/LineLength:
Max: 100
Expand All @@ -12,3 +12,9 @@ Style/BlockDelimiters:

Style/TrivialAccessors:
AllowPredicates: true

Style/SignalException:
EnforcedStyle: semantic

Style/SingleLineBlockParams:
Enabled: false
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
source 'https://rubygems.org'

gemspec
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'

Expand Down
29 changes: 13 additions & 16 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# HumanizedId

[![Gem Version](https://badge.fury.io/rb/humanized_id.svg)](https://badge.fury.io/rb/humanized_id)
[![Build Status](https://travis-ci.org/sealink/humanized_id.svg?branch=master)](https://travis-ci.org/sealink/humanized_id)
[![Coverage Status](https://coveralls.io/repos/github/sealink/humanized_id/badge.svg?branch=master)](https://coveralls.io/github/sealink/humanized_id?branch=master)
[![Dependency Status](https://gemnasium.com/sealink/humanized_id.svg)](https://gemnasium.com/sealink/humanized_id)
[![Code Climate](https://codeclimate.com/github/sealink/humanized_id/badges/gpa.svg)](https://codeclimate.com/github/sealink/humanized_id)

HumanizedId is a gem designed to help you either:
- Convert an existing numerical id into a 'human friendly' alphanumerical id
- Generate a random 'human friendly' id that is of a specified or default length
Expand Down Expand Up @@ -27,10 +33,7 @@ Or install it yourself as:

### Humanize an existing id

The simplest way to call humanize is to pass in an existing numerical id,
and let HumanizedId return a human friendly version of that id. This id will be shorter
in length (due to base conversion) and will be an alphanumerical string based on
a 'human safe' character-set
The simplest way to call humanize is to pass in an existing numerical id, and let HumanizedId return a human friendly version of that id. This id will be shorter in length (due to base conversion) and will be an alphanumerical string based on a 'human safe' character-set.

```ruby
humanized_id = HumanizedId.humanize id: 1234567
Expand All @@ -39,20 +42,16 @@ humanized_id = HumanizedId.humanize id: 1234567

#### Ensuring minimum length of output id

An optional min_length flag can be passed in order to guarantee the minimum length
of the returned value. This will be done by 'padding' the return id with the
safe-charset default value '2'
An optional min_length flag can be passed in order to guarantee the minimum length of the returned value. This will be done by 'padding' the return id with the safe-charset default value '2'.

```ruby
humanized_id = HumanizedId.humanize id: 1234567, min_length: 20
# Returns '222222222222226RDFD'
```

Note that the original length is not preserved during the base conversion, so you
will need to explicitely pass in a min_length if you'd like a return id of the same length
Note that the original length is not preserved during the base conversion, so you will need to explicitly pass in a min_length if you'd like a output id of the same length.

Also note that if you specify a min_length shorter than the actual output id length,
the output id will not be modified (as expected)
Also note that if you specify a min_length shorter than the actual output id length, the output id will not be modified (as expected).

#### Adding a prefix

Expand All @@ -63,12 +62,11 @@ humanized_id = HumanizedId.humanize id: 1234567, min_length: 20, prefix: 'TEST'
# Will return 'TEST222222222222226RDFD'
```

The prefix is added to the humanized id after all other processing (including min_length padding).
Therefore the total length of the above example wil be 20 + 'TEST'.length = 24
The prefix is added to the humanized id after all other processing (including min_length padding). Therefore the total length of the above example wil be 20 + 4 (length of 'TEST').

### Generating a random humanized id

Call 'generate_random' with optional length and prefix
Call 'generate_random' with optional length and prefix.

```ruby
random_humanized_id = HumanizedId.generate_random
Expand All @@ -80,8 +78,7 @@ Will generate a random human friendly id of default length (see HumanizedId::DEF
random_humanized_id = HumanizedId.generate_random length: 20, prefix: 'TEST'
```

This will generate a random humanized id of length 20 and then add the prefix to the id
thus resulting in a total length of 24
This will generate a random humanized id of length 20 and then add the prefix to the id thus resulting in a total length of 24.

## Development

Expand Down
1 change: 1 addition & 0 deletions bin/console
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'bundler/setup'
require 'humanized_id'
Expand Down
4 changes: 2 additions & 2 deletions humanized_id.gemspec
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# coding: utf-8
# frozen_string_literal: true

lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
Expand All @@ -24,11 +25,10 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']

spec.required_ruby_version = '>= 2.0'
spec.required_ruby_version = '>= 2.1'

spec.add_development_dependency 'bundler'
spec.add_development_dependency 'rake'
spec.add_development_dependency 'dotenv'
spec.add_development_dependency 'rspec'
spec.add_development_dependency 'simplecov'
spec.add_development_dependency 'simplecov-rcov'
Expand Down
7 changes: 3 additions & 4 deletions lib/humanized_id.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ module HumanizedId
DEFAULT_GENERATION_LENGTH = 24
SIMILAR_NUMBERS_LETTERS = %w(0 O 1 I L 5 S 8 B).freeze
VOWELS = %w(A E I O U).freeze
CHARACTERSET =
(('0'..'9').to_a + ('A'..'Z').to_a - SIMILAR_NUMBERS_LETTERS - VOWELS).freeze
CHARSET = (('0'..'9').to_a + ('A'..'Z').to_a - SIMILAR_NUMBERS_LETTERS - VOWELS).freeze

class << self
def humanize(id:, min_length: nil, prefix: '')
HumanizedId::Humanizer.new(id: id, min_length: min_length, prefix: prefix).generate_humanized_id
Humanizer.new(id: id, min_length: min_length, prefix: prefix).generate_humanized_id
end

def generate_random(prefix: '', length: DEFAULT_GENERATION_LENGTH)
HumanizedId::RandGenerator.new(prefix: prefix, length: length).generate_random_humanized_id
RandGenerator.new(prefix: prefix, length: length).generate_random_humanized_id
end
end
Error = Class.new(StandardError)
Expand Down
36 changes: 15 additions & 21 deletions lib/humanized_id/humanizer.rb
Original file line number Diff line number Diff line change
@@ -1,43 +1,37 @@
# frozen_string_literal: true
module HumanizedId
class Humanizer
SOURCE_CHARSET = %w(0123456789abcdefghijklmnopqrstuvwxyz).freeze
SOURCE_CHARSET = (('0'..'9').to_a + ('a'..'z').to_a).freeze

def initialize(id:, min_length: nil, prefix: '')
@id = id
@min_length = min_length
@prefix = prefix.nil? ? '' : prefix
@target_charset = HumanizedId::CHARACTERSET.join('')
@source_charset = SOURCE_CHARSET.join('')
@prefix = prefix.to_s
@target_charset = CHARSET.join
@source_charset = SOURCE_CHARSET.join
end

def generate_humanized_id
new_id = convert_to_target_base id: @id
new_id = convert_to_target_charset id: new_id
new_id = resize id: new_id if @min_length
new_id = resize convert_to_target_charset convert_to_target_base(@id)
"#{@prefix}#{new_id}"
end

private

def convert_to_target_base(id: @id, target_base: @target_charset.length)
id.to_s(target_base)
rescue ArgumentError
raise Error, 'id is not an integer'
def convert_to_target_base(id)
fail Error, 'id is not an integer' unless id.is_a? Integer
id.to_s(@target_charset.length)
end

def convert_to_target_charset(
id: @id, target_charset: @target_charset, source_charset: @source_charset)
source_charset_subset = source_charset.slice(0, target_charset.length)
id.tr(source_charset_subset, target_charset)
def convert_to_target_charset(id)
source_charset_subset = @source_charset.slice(0, @target_charset.length)
id.tr(source_charset_subset, @target_charset)
end

def resize(id: @id, min_length: @min_length, target_charset: @target_charset)
if min_length > id.length
padding = target_charset[0] * (min_length - id.length)
id = "#{padding}#{id}"
end
id
def resize(id)
return id if @min_length.nil? || @min_length <= id.length
padding = @target_charset[0] * (@min_length - id.length)
"#{padding}#{id}"
end
end
end
17 changes: 9 additions & 8 deletions lib/humanized_id/rand_generator.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# frozen_string_literal: true
module HumanizedId
class RandGenerator
def initialize(prefix: '', length:)
@prefix = prefix.nil? ? '' : prefix
@length = length
@target_charset = HumanizedId::CHARACTERSET
@target_charset = CHARSET
end

def generate_random_humanized_id
Expand All @@ -12,13 +13,13 @@ def generate_random_humanized_id

private

def generate_random(length: @length, target_charset: @target_charset)
SecureRandom.random_bytes(length).unpack('C*').map{ |byte|
idx = byte % 64
idx = SecureRandom.random_number(target_charset.size) if
idx >= target_charset.size
target_charset[idx]
}.join
def generate_random
SecureRandom.random_bytes(@length).unpack('C*').map { |byte| map_to_char(byte) }.join
end

def map_to_char(byte)
index = byte % @target_charset.size
@target_charset[index]
end
end
end
1 change: 1 addition & 0 deletions spec/humanized_id/humanizer_spec.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
require 'spec_helper'

describe HumanizedId::Humanizer do
Expand Down
9 changes: 9 additions & 0 deletions spec/humanized_id/rand_generator_spec.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
require 'spec_helper'

describe HumanizedId::RandGenerator do
Expand All @@ -6,17 +7,25 @@
let(:rand_humanized_id) { rand_generator.generate_random_humanized_id }

context 'when generating random humanized id' do
shared_examples 'a humanized id that only contains characters from the target charset' do
specify { expect(rand_humanized_id).to match(/#{params[:prefix]}#{HumanizedId::CHARSET}{3}/) }
end

context 'with only a length specified' do
it 'should generate random humanized id at required length' do
expect(rand_humanized_id.length).to eq params[:length]
end

it_behaves_like 'a humanized id that only contains characters from the target charset'
end

context 'with prefix added' do
let(:params) { super().merge(prefix: 'abc') }
it 'should generate random humanized id at required length plus a prefix' do
expect(rand_humanized_id[0..(params[:prefix].length - 1)]).to eq params[:prefix]
end

it_behaves_like 'a humanized id that only contains characters from the target charset'
end
end
end
91 changes: 61 additions & 30 deletions spec/humanized_id_spec.rb
Original file line number Diff line number Diff line change
@@ -1,52 +1,83 @@
# frozen_string_literal: true
require 'spec_helper'

describe HumanizedId do
let(:expected_params) { params }

context 'when calling humanize' do
let(:params) { { id: 1_234_567 } }
let(:expected_id) { '6RDFD' }
let(:humanized_id) { HumanizedId.humanize params }
let(:humanize) { HumanizedId.humanize params }

context 'with minimum required values' do
it 'should return expected id' do
expect(humanized_id).to eq expected_id
let(:humanizer_instance) { double }
let(:humanizer) { double }

before do
allow(humanizer).to receive(:new).and_return(humanizer_instance)
allow(humanizer_instance).to receive(:generate_humanized_id)
stub_const('HumanizedId::Humanizer', humanizer)
end

shared_examples 'a delegated call to humanizer' do
before do
humanize
end

specify do
expect(humanizer).to have_received(:new).with(expected_params)
end
specify do
expect(humanizer_instance).to have_received(:generate_humanized_id).with(no_args)
end
end

context 'with minimum required values' do
let(:expected_params) { super().merge(min_length: nil, prefix: '') }
it_behaves_like 'a delegated call to humanizer'
end

context 'with all params passed in' do
let(:params) {
{
id: 1_234_567,
min_length: 20,
prefix: 'test'
}
}
it 'should humanize id with appropriate min_length and prefix' do
expect(humanized_id).to eq(params[:prefix] + ('2' * 15) + expected_id)
end
let(:params) { super().merge(min_length: 20, prefix: 'test') }
it_behaves_like 'a delegated call to humanizer'
end
end

context 'when calling generate_random' do
let(:params) { {} }
let(:random_id) { HumanizedId.generate_random params }
let(:generate_random) { HumanizedId.generate_random params }

context 'with minimum required values' do
it 'should produce random id of default length' do
expect(random_id.length).to eq HumanizedId::DEFAULT_GENERATION_LENGTH
let(:rand_generator_instance) { double }
let(:rand_generator) { double }

before do
allow(rand_generator).to receive(:new).and_return(rand_generator_instance)
allow(rand_generator_instance).to receive(:generate_random_humanized_id)
stub_const('HumanizedId::RandGenerator', rand_generator)
end

shared_examples 'a delegated call to rand generator' do
before do
generate_random
end

specify do
expect(rand_generator).to have_received(:new).with(expected_params)
end
specify do
expect(rand_generator_instance)
.to have_received(:generate_random_humanized_id).with(no_args)
end
end

context 'with all values passed in' do
let(:params) {
{
prefix: 'test',
length: 3
}
context 'with minimum required values' do
let(:expected_params) {
super().merge(prefix: '', length: HumanizedId::DEFAULT_GENERATION_LENGTH)
}
it 'should produce random id with requested length and prefix' do
expect(random_id.length).to eq(params[:prefix].length + params[:length])
expect(random_id[0..(params[:prefix].length - 1)]).to eq params[:prefix]
expect(random_id).to match(/#{params[:prefix]}#{HumanizedId::CHARACTERSET}{3}/)
end
it_behaves_like 'a delegated call to rand generator'
end

context 'with all values passed in' do
let(:params) { super().merge(prefix: 'test', length: 3) }
it_behaves_like 'a delegated call to rand generator'
end
end
end
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
require 'support/coverage_loader'
require 'humanized_id'
1 change: 1 addition & 0 deletions spec/support/coverage_loader.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
MINIMUM_COVERAGE = 80

unless ENV['COVERAGE'] == 'off'
Expand Down

0 comments on commit 94538eb

Please sign in to comment.