diff --git a/lib/public_uid.rb b/lib/public_uid.rb index 91b9416..446f85b 100644 --- a/lib/public_uid.rb +++ b/lib/public_uid.rb @@ -1,11 +1,14 @@ +# 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) @@ -13,4 +16,4 @@ module PublicUid RecordNotFound = Class.new(StandardError) end -ActiveRecord::Base.send(:include, PublicUid::Model) +ActiveRecord::Base.include PublicUid::Model diff --git a/lib/public_uid/generators/hex_string_secure_random.rb b/lib/public_uid/generators/hex_string_secure_random.rb index 6cdeacd..fae0ca4 100644 --- a/lib/public_uid/generators/hex_string_secure_random.rb +++ b/lib/public_uid/generators/hex_string_secure_random.rb @@ -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 diff --git a/lib/public_uid/generators/number_random.rb b/lib/public_uid/generators/number_random.rb index a8e8fec..558e99a 100644 --- a/lib/public_uid/generators/number_random.rb +++ b/lib/public_uid/generators/number_random.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module PublicUid module Generators class NumberRandom @@ -5,7 +7,7 @@ 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 diff --git a/lib/public_uid/generators/number_secure_random.rb b/lib/public_uid/generators/number_secure_random.rb index 6ecca99..efb8d29 100644 --- a/lib/public_uid/generators/number_secure_random.rb +++ b/lib/public_uid/generators/number_secure_random.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'securerandom' module PublicUid @@ -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 diff --git a/lib/public_uid/generators/range_string.rb b/lib/public_uid/generators/range_string.rb index 8c00584..5121b3b 100644 --- a/lib/public_uid/generators/range_string.rb +++ b/lib/public_uid/generators/range_string.rb @@ -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 diff --git a/lib/public_uid/generators/readable_string.rb b/lib/public_uid/generators/readable_string.rb new file mode 100644 index 0000000..72feafd --- /dev/null +++ b/lib/public_uid/generators/readable_string.rb @@ -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 diff --git a/lib/public_uid/set_public_uid.rb b/lib/public_uid/set_public_uid.rb index 08e1bc0..32d68c5 100644 --- a/lib/public_uid/set_public_uid.rb +++ b/lib/public_uid/set_public_uid.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module PublicUid class SetPublicUid NewUidNotSetYet = Class.new(StandardError) @@ -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? diff --git a/public_uid.gemspec b/public_uid.gemspec index b144d0b..9d230bb 100644 --- a/public_uid.gemspec +++ b/public_uid.gemspec @@ -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 diff --git a/test/lib/generators/readable_string_test.rb b/test/lib/generators/readable_string_test.rb new file mode 100644 index 0000000..e31f66b --- /dev/null +++ b/test/lib/generators/readable_string_test.rb @@ -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 diff --git a/test/lib/set_public_uid_test.rb b/test/lib/set_public_uid_test.rb index cd97fe5..495548e 100644 --- a/test/lib/set_public_uid_test.rb +++ b/test/lib/set_public_uid_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class DummyGenerator @@ -5,27 +7,26 @@ 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 @@ -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 @@ -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 @@ -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