acts_as_identifier 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50941b62619d0fa3829df18f02e97e46d02e7b35f052db3c64d9c7b821699826
4
- data.tar.gz: 728fbeb1a33e0dd5f5f734f5a1e18821320df7118e79717b5bc543c9fceacec4
3
+ metadata.gz: 5eb6246b99ed8a6b043f1b86f7280676f22107154a2b885b9414058e44d8e11d
4
+ data.tar.gz: e7ebde2dc53be641c11f26fffefc450568fd2775fe5382e352b16945c7965741
5
5
  SHA512:
6
- metadata.gz: d323726b48026b4bb396a8837d47f1c45b6f3fded3972c3c7a51e7ca3bccbfdcfa11f33ba895dd081b21a2c3b79327f9348ad6987a0bff2695c32b868c6c1fd2
7
- data.tar.gz: 6fba6c6ade83590f4c91909298da3133d01665014c187d12324d129f8d03a992cfaab01cab7693d869c124ec233e8e485de17ca7a3175e15b2c7aed6be9abbfe
6
+ metadata.gz: 4e88efefec94d938295a34dadb93b29ec350ac7680b997d36499a2f6b2e81c3244dec7142800576752b8b25056e3df1c83c5c237e80546822c1f044136f408e7
7
+ data.tar.gz: 5a61dcbb7a9f9d114768a82e28cfe68c3a57834cfc93f154f007774b84554bcf68d4df9cbc3cafafa2a068ef0412d69ced5ab0178eb09c0739508a00056a44f6
data/README.md CHANGED
@@ -6,29 +6,30 @@ Automatically generate unique secure random string for one or more columns of Ac
6
6
 
7
7
  ## Usage
8
8
 
9
- > `ActsAsIdentifier` only generate identifier `before create`
9
+ > `ActsAsIdentifier` only generate identifier `after_create_commit`
10
10
 
11
11
  ```ruby
12
12
  class Account < ActiveRecord::Base
13
13
  #
14
14
  # Note: without Rails, should include ActsAsIdentifier
15
15
  #
16
-
17
- # with default options:
18
- # column: :identifier
19
- # length: 6
20
- # case sensitive: true
21
- # max_loop: 100
22
- # scope: []
16
+ # def acts_as_identifier(attr = :identifier,
17
+ # length: 6,
18
+ # prefix: '',
19
+ # id_column: :id,
20
+ # chars: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.chars,
21
+ # mappings: '3NjncZg82M5fe1PuSABJG9kiRQOqlVa0ybKXYDmtTxCp6Lh7rsIFUWd4vowzHE'.chars)
22
+ #
23
23
  acts_as_identifier
24
-
25
- # extra column
26
- acts_as_identifier :slug, length: 8, case_sensitive: false, max_loop: 1000, scope: [:tenant_id]
24
+ # or customize options:
25
+ acts_as_identifier :slug, length: 6, prefix: 's-'
27
26
  end
28
- # => [Account(id: integer, name: string, tenant_id: string, identifier: string, slug: string)]
27
+ # => [Account(id: integer, name: string, tenant_id: string, slug: string)]
29
28
 
30
29
  Account.create
31
- # => #<Account:0x00007fcdb90830c0 id: 1, name: nil, tenant_id: nil, identifier: "PWbYHd", slug: "5fabb1e7">
30
+ # => #<Account:0x00007fcdb90830c0 id: 1, name: nil, tenant_id: nil, slug: "s-HuF2Od">
31
+ Account.create
32
+ # => #<Account:0x00007fcdb90830c0 id: 2, name: nil, tenant_id: nil, slug: "s-g3SIB8">
32
33
 
33
34
  ```
34
35
 
@@ -41,5 +42,10 @@ bundle add acts_as_identifier
41
42
  ## Contributing
42
43
  Contribution directions go here.
43
44
 
44
- ## License
45
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
45
+ ## Testing
46
+
47
+ ```shell
48
+ bundle exec rspec
49
+ # or test specific range
50
+ EXTRA_TEST=100000,1000000 bundle exec rspec
51
+ ```
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'securerandom'
4
4
  require 'acts_as_identifier/version'
5
+ require 'acts_as_identifier/xbase_integer'
6
+ require 'acts_as_identifier/encoder'
5
7
 
6
8
  module ActsAsIdentifier
7
9
  LoopTooManyTimesError = Class.new(StandardError)
@@ -12,28 +14,39 @@ module ActsAsIdentifier
12
14
 
13
15
  module ClassMethods
14
16
  #
15
- # == Automatically generate unique secure random string
17
+ # == Automatically generate unique string based on id
16
18
  #
17
19
  # @param attr [String, Symbol] column name, default: :identifier
18
20
  # @param length [Integer] length of identifier, default: 6
19
- # @param case_sensitive [Boolean] Case-sensitive? default: true
20
- # @param max_loop [Integer] max loop count to generate unique identifier, in case of running endless loop, default: 100
21
- # @param scope [Array<Symbol,String>] scope of identifier, default: []
22
21
  # @params prefix [String, Symbol] add prefix to value, default: ''
23
- def acts_as_identifier(attr = nil, length: 6, case_sensitive: true, max_loop: 100, scope: [], prefix: '')
24
- attr ||= :identifier
25
- scope = Array(scope)
26
- method = case_sensitive ? :alphanumeric : :hex
27
- length /= 2 if method == :hex
28
-
29
- before_create do
30
- query = self.class.unscoped.where(scope.inject({}) { |memo, i| memo.merge(i => send(i)) })
31
- n = 0
32
- loop do
33
- n += 1
34
- value = send("#{attr}=", "#{prefix}#{SecureRandom.send(method, length)}")
35
- break unless query.where(attr => value).exists?
36
- raise LoopTooManyTimesError if n > max_loop
22
+ # @params id_column [String, Symbol] column name of id, default: :id
23
+ # @params chars [Array<String>] chars
24
+ # @params mappings [Array<String>] mappings must have the same characters as chars
25
+ def acts_as_identifier(attr = :identifier,
26
+ length: 6,
27
+ prefix: '',
28
+ id_column: :id,
29
+ chars: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.chars,
30
+ mappings: '3NjncZg82M5fe1PuSABJG9kiRQOqlVa0ybKXYDmtTxCp6Lh7rsIFUWd4vowzHE'.chars)
31
+ raise 'chars must be an array' unless chars.is_a?(Array)
32
+ raise 'mappings must be an array' unless mappings.is_a?(Array)
33
+ unless (chars - mappings).empty? && (mappings - chars).empty?
34
+ raise 'chars and mappings must have the same characters'
35
+ end
36
+
37
+ encoder = Encoder.new(chars: chars, mappings: mappings, length: length)
38
+
39
+ define_singleton_method "decode_#{attr}" do |str|
40
+ encoder.decode(str[prefix.length..-1])
41
+ end
42
+
43
+ define_singleton_method "encode_#{attr}" do |num|
44
+ "#{prefix}#{encoder.encode(num)}"
45
+ end
46
+
47
+ before_commit do |record|
48
+ if record.previous_changes.key?(id_column.to_s) && !record.destroyed?
49
+ record.update_column attr, self.class.send("encode_#{attr}", record.send(id_column))
37
50
  end
38
51
  end
39
52
  end
@@ -0,0 +1,36 @@
1
+ module ActsAsIdentifier
2
+ class Encoder
3
+ attr_reader :max, :base, :chars, :mappings, :length
4
+
5
+ def initialize(chars:, mappings:, length:)
6
+ @chars = chars.dup
7
+ @mappings = mappings.dup
8
+ @length = length
9
+ @xbase_integer = XbaseInteger.new(chars)
10
+ @base = @xbase_integer.to_i("#{chars[1]}#{chars[0] * (length - 1)}")
11
+ @max = @xbase_integer.to_i(chars[-1] * length) - @base
12
+ end
13
+
14
+ def encode(num)
15
+ str = @xbase_integer.to_x(num + base)
16
+ (str.length - 1).downto(1).each do |i|
17
+ idx = @mappings.index(str[i])
18
+ idx2 = (@mappings.index(str[i - 1]) + idx) % @mappings.size
19
+ str[i] = @chars.at(idx)
20
+ str[i - 1] = @chars.at(idx2)
21
+ end
22
+ str
23
+ end
24
+
25
+ def decode(str)
26
+ str = str.dup
27
+ 0.upto(str.length - 2).each do |i|
28
+ idx = @chars.index(str[i + 1])
29
+ idx2 = (@chars.index(str[i]) - idx + @chars.size) % @chars.size
30
+ str[i] = @mappings.at(idx2)
31
+ str[i + 1] = @mappings.at(idx)
32
+ end
33
+ @xbase_integer.to_i(str) - base
34
+ end
35
+ end
36
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActsAsIdentifier
4
- VERSION = '0.2.0'
4
+ VERSION = '1.0.0'
5
5
  end
@@ -0,0 +1,23 @@
1
+ module ActsAsIdentifier
2
+ class XbaseInteger
3
+ attr_reader :chars
4
+ def initialize(chars)
5
+ @chars = chars.dup
6
+ end
7
+
8
+ def to_i(str)
9
+ sum = 0
10
+ str.chars.reverse.each_with_index do |char, i|
11
+ sum += chars.index(char) * chars.size**i
12
+ end
13
+ sum
14
+ end
15
+
16
+ def to_x(num)
17
+ str = chars.at(num % chars.size)
18
+ num /= chars.size
19
+ str = "#{to_x(num)}#{str}" if num > 0
20
+ str
21
+ end
22
+ end
23
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_identifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - xiaohui
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-12 00:00:00.000000000 Z
11
+ date: 2020-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -48,8 +48,10 @@ files:
48
48
  - MIT-LICENSE
49
49
  - README.md
50
50
  - lib/acts_as_identifier.rb
51
+ - lib/acts_as_identifier/encoder.rb
51
52
  - lib/acts_as_identifier/railtie.rb
52
53
  - lib/acts_as_identifier/version.rb
54
+ - lib/acts_as_identifier/xbase_integer.rb
53
55
  homepage: https://github.com/xiaohui-zhangxh/acts_as_identifier
54
56
  licenses:
55
57
  - MIT