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
8 changes: 7 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
Documentation:
Style/Documentation:
Enabled: false

Style/StringLiteralsInInterpolation:
Enabled: false

Style/StringLiterals:
Enabled: false

Style/FileName:
Expand Down
43 changes: 5 additions & 38 deletions lib/coupon_code.rb
Original file line number Diff line number Diff line change
@@ -1,46 +1,13 @@
require 'coupon_code/version'
require 'securerandom'
require 'digest/sha1'
require 'coupon_code/version'
require 'coupon_code/generator'
require 'coupon_code/configuration'
require 'coupon_code/validator'

module CouponCode
SYMBOL = '0123456789ABCDEFGHJKLMNPQRTUVWXY'
PARTS = 3
LENGTH = 4

def self.generate(options = { parts: PARTS })
num_parts = options.delete(:parts)
parts = []
(1..num_parts).each do |i|
part = ''
(1...LENGTH).each { part << random_symbol }
part << checkdigit_alg_1(part, i)
parts << part
end
parts.join('-')
end

def self.validate(orig, num_parts = PARTS)
code = orig.upcase
code.gsub!(/[^0-9A-Z]+/, '')
parts = code.scan(/[0-9A-Z]{#{LENGTH}}/)
return if parts.length != num_parts
parts.each_with_index do |part, i|
data = part[0...(LENGTH - 1)]
check = part[-1]
return if check != checkdigit_alg_1(data, i + 1)
end
parts.join('-')
end

def self.checkdigit_alg_1(orig, check)
orig.split('').each_with_index do |c, _|
k = SYMBOL.index(c)
check = check * 19 + k
end
SYMBOL[check % 31]
end

def self.random_symbol
SYMBOL[rand(SYMBOL.length)]
end
SEPARATOR = '-'
end
28 changes: 28 additions & 0 deletions lib/coupon_code/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module CouponCode
class << self
attr_accessor :configuration
end

def self.config
@config ||= Configuration.new
end

def self.configure
yield(config)
end

def self.reset
@config = Configuration.new
end

class Configuration

attr_accessor :separator, :part_length, :num_parts

def initialize
@separator = '-'
@part_length = 4
@num_parts = 3
end
end
end
18 changes: 18 additions & 0 deletions lib/coupon_code/generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module CouponCode
def self.generate(options = { parts: PARTS })
num_parts = options.delete(:parts)
parts = []
(1..num_parts).each do |i|
part = ''
(1...LENGTH).each { part << random_symbol }
part << checkdigit_alg_1(part, i)
parts << part
end
parts.join('-')
end

def self.random_symbol
SYMBOL[rand(SYMBOL.length)]
end
private_class_method :random_symbol
end
23 changes: 23 additions & 0 deletions lib/coupon_code/validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module CouponCode
def self.validate(orig, num_parts = PARTS)
code = orig.upcase
code.gsub!(/[^0-9A-Z]+/, '')
parts = code.scan(/[0-9A-Z]{#{LENGTH}}/)
return if parts.length != num_parts
parts.each_with_index do |part, i|
data = part[0...(LENGTH - 1)]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary spacing detected.

check = part[-1]
return if check != checkdigit_alg_1(data, i + 1)
end
parts.join('-')
end

def self.checkdigit_alg_1(orig, check)
orig.split('').each_with_index do |c, _|
k = SYMBOL.index(c)
check = check * 19 + k
end
SYMBOL[check % 31]
end
private_class_method :checkdigit_alg_1
end
9 changes: 9 additions & 0 deletions test/configuration_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require 'test_helper'

describe 'Configuration' do
it 'separator' do
config = CouponCode::Configuration.new
config.separator = '*'
config.separator.must_equal('*')
end
end
34 changes: 34 additions & 0 deletions test/generator_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require 'test_helper'

describe CouponCode do
def generate(*args)
CouponCode.generate(*args)
end

it 'should be loaded.' do
CouponCode.must_respond_to(:generate)
end

it 'should generate a code' do
generate.wont_be_empty
end

it 'should only contain uppercase letters, digits, and dashes.' do
generate.must_match(/^[0-9A-Z-]+$/)
end

it 'should look like XXXX-XXXX-XXXX.' do
generate.must_match(/^\w{4}-\w{4}-\w{4}$/)
end

it 'should generate different codes.' do
code1 = generate
code2 = generate
code1.wont_equal(code2)
end

it 'should generate an arbitrary number of parts.' do
generate(parts: 2).must_match(/^\w{4}-\w{4}$/)
generate(parts: 5).must_match(/^\w{4}-\w{4}-\w{4}-\w{4}-\w{4}$/)
end
end
63 changes: 63 additions & 0 deletions test/validator_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
require 'test_helper'

describe Validator do
it 'should be loaded.' do
CouponCode.must_respond_to(:validate)
end

it 'should fail to validate invalid code.' do
CouponCode.validate('').must_equal(nil)
end

it 'should accept a valid code.' do
CouponCode.validate('1K7Q-CTFM-LMTC').wont_be_nil
end

it 'should reject a short code.' do
CouponCode.validate('1K7Q-CTFM').must_be_nil
end

it 'should accept a short code with correct parts.' do
CouponCode.validate('1K7Q-CTFM', 2).wont_be_nil
end

it 'should reject a short code with wrong parts.' do
CouponCode.validate('CTFM-1K7Q', 2).must_be_nil
end

it 'should fix and validate a lowercase code.' do
code = '1k7q-ctfm-lmtc'
CouponCode.validate(code.downcase).must_equal(code.upcase)
end

it 'should validate alternative separators.' do
code = '1k7q/ctfm/lmtc'
CouponCode.validate(code).must_equal('1K7Q-CTFM-LMTC')

code = '1k7q ctfm lmtc'
CouponCode.validate(code).must_equal('1K7Q-CTFM-LMTC')

code = '1k7qctfmlmtc'
CouponCode.validate(code).must_equal('1K7Q-CTFM-LMTC')
end

it 'should valid code-pretest.' do
CouponCode.validate('1K7Q', 1).wont_be_nil
CouponCode.validate('1K7C', 1).must_be_nil

CouponCode.validate('1K7Q-CTFM', 2).wont_be_nil
CouponCode.validate('1K7Q-CTFW', 2).must_be_nil

CouponCode.validate('1K7Q-CTFM-LMTC', 3).wont_be_nil
CouponCode.validate('1K7Q-CTFM-LMT1', 3).must_be_nil

CouponCode.validate('7YQH-1FU7-E1HX-0BG9', 4).wont_be_nil
CouponCode.validate('7YQH-1FU7-E1HX-0BGP', 4).must_be_nil

CouponCode.validate('YENH-UPJK-PTE0-20U6-QYME', 5).wont_be_nil
CouponCode.validate('YENH-UPJK-PTE0-20U6-QYMT', 5).must_be_nil

CouponCode.validate('YENH-UPJK-PTE0-20U6-QYME-RBK1', 6).wont_be_nil
CouponCode.validate('YENH-UPJK-PTE0-20U6-QYME-RBK2', 6).must_be_nil
end
end