acts_as_identifier 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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