lite-uxid 1.5.2 → 2.0.1

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: d000996f6e79fc0122e86a88c034391d9853146de2af1e44a1da19234ad538b1
4
- data.tar.gz: 932aa03aeb3f62c58cb96328d38d9bc97ca09cc15f5f9a617147464448a45856
3
+ metadata.gz: 9078e4e13dc6877ed30a6a0028ad4327ff95d3056a5303f789137272e2bb0f12
4
+ data.tar.gz: c25e8acbb22ad4e4381bf9b07018eaf12daaf8e1d0123cee24f5fa9c8af8e87a
5
5
  SHA512:
6
- metadata.gz: 1241d664904c1db3b9008bbd70bfb509070dc7adab95313ab5cac6a859fafeb6254f902ab0664086163bfa6d5998643d7f74f059525206d9354214f5595767ac
7
- data.tar.gz: 51df74a8df875bbe8e67179960953d6ab54f8f49c11ab5783e90e5c31f6b8e8022ecf1074439083341cbdf9c40dce070c102d29bb947dd148b362f1dabdfc6a6
6
+ metadata.gz: bd5d7ef2cd4c425fb798dea874a7f395e20e5514ca07fb807d673feb75187e8a236236bd7f833084983a820d71a05a7a27c80d72138a13f888fb5b088a4c869f
7
+ data.tar.gz: 24462c53958076c1435e1012f3608002d23fdb01b3d59d00ac9d7d78c81f8f1d91b3a80fcac78d9a6a9ce358d9db728af6dd6df3d6815037a1c7dfa25515ba3f
data/.rubocop.yml CHANGED
@@ -23,6 +23,8 @@ Layout/LineLength:
23
23
  Enabled: false
24
24
  Layout/SpaceAroundMethodCallOperator:
25
25
  Enabled: true
26
+ Lint/MissingSuper:
27
+ Enabled: false
26
28
  Lint/RaiseException:
27
29
  Enabled: true
28
30
  Lint/StructNewOverride:
data/CHANGELOG.md CHANGED
@@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.0.1] - 2024-09-23
10
+ ### Changed
11
+ - Renamed `Scatterid` to `Obfuscateid`
12
+ ### Removed
13
+ - Removed prefix option from obfuscateid
14
+
15
+ ## [2.0.0] - 2024-09-23
16
+ ### Added
17
+ - Scatterid reversible lib
18
+ ### Changed
19
+ - Reorganize generators into better namespaces
20
+ - Fixed issue where nanoid would return without the prefix
21
+ - Moved charsets to constants
22
+
9
23
  ## [1.5.2] - 2024-09-20
10
24
  ### Changed
11
25
  - Fixed nanoid of differing length error
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lite-uxid (1.5.2)
4
+ lite-uxid (2.0.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -27,8 +27,9 @@ Or install it yourself as:
27
27
 
28
28
  * [Configuration](#configuration)
29
29
  * [Usage](#usage)
30
- * [Hashid](#hashid)
30
+ * [HashID](#hashid)
31
31
  * [NanoID](#nanoid)
32
+ * [ObfuscateID](#obfuscateid)
32
33
  * [ULID](#ulid)
33
34
  * [UUID](#uuid)
34
35
  * [Options](#options)
@@ -41,13 +42,17 @@ Or install it yourself as:
41
42
  `config/initalizers/lite_uxid.rb`
42
43
 
43
44
  ```ruby
45
+ Lite::Uxid::ALPHANUMERIC = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
46
+ Lite::Uxid::COCKFORDS_32 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
47
+ Lite::Uxid::WEB_SAFE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
48
+
44
49
  Lite::Uxid.configure do |config|
45
- config.hashid_charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
50
+ config.hashid_charset = ALPHANUMERIC
46
51
  config.hashid_salt = 1_369_136
47
52
  config.hashid_size = 16
48
- config.nanoid_charset = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
53
+ config.nanoid_charset = WEB_SAFE
49
54
  config.nanoid_size = 21
50
- config.ulid_charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
55
+ config.ulid_charset = COCKFORDS_32
51
56
  config.ulid_size = 26
52
57
  config.uuid_version = 4
53
58
  end
@@ -57,22 +62,22 @@ end
57
62
 
58
63
  #### Instance
59
64
  ```ruby
60
- coder = Lite::Uxid::Hashid.new(10, size: 12)
65
+ coder = Lite::Uxid::Reversible::Hashid.new(10, size: 12)
61
66
  coder.encode #=> '67wGI0'
62
67
  ```
63
68
 
64
69
  #### Class
65
70
  ```ruby
66
- Lite::Uxid::Hashid.decode('67wGI0', size: 12) #=> 10
71
+ Lite::Uxid::Reversible::Hashid.decode('67wGI0', size: 12) #=> 10
67
72
  ```
68
73
 
69
- ## Hashid
74
+ ## HashID
70
75
 
71
76
  [More information](https://hashids.org)
72
77
 
73
78
  ```ruby
74
- Lite::Uxid::Hashid.encode(10) #=> '1zWr1m0'
75
- Lite::Uxid::Hashid.decode('1zWr1m0') #=> 10
79
+ Lite::Uxid::Reversible::Hashid.encode(10) #=> '1zWr1m0'
80
+ Lite::Uxid::Reversible::Hashid.decode('1zWr1m0') #=> 10
76
81
  ```
77
82
 
78
83
  ## NanoID
@@ -80,7 +85,16 @@ Lite::Uxid::Hashid.decode('1zWr1m0') #=> 10
80
85
  [More information](https://github.com/ai/nanoid)
81
86
 
82
87
  ```ruby
83
- Lite::Uxid::Nanoid.encode #=> 'sMuNUa3Cegn6r5GRQ4Ij2'
88
+ Lite::Uxid::Irreversible::Nanoid.encode #=> 'sMuNUa3Cegn6r5GRQ4Ij2'
89
+ ```
90
+
91
+ ## ObfuscateID
92
+
93
+ [More information](https://github.com/namick/scatter_swap)
94
+
95
+ ```ruby
96
+ Lite::Uxid::Reversible::Obfuscateid.encode(10) #=> '2056964183'
97
+ Lite::Uxid::Reversible::Obfuscateid.decode(2056964183) #=> 10
84
98
  ```
85
99
 
86
100
  ## ULID
@@ -88,7 +102,7 @@ Lite::Uxid::Nanoid.encode #=> 'sMuNUa3Cegn6r5GRQ4Ij2'
88
102
  [More information](https://github.com/ulid/spec)
89
103
 
90
104
  ```ruby
91
- Lite::Uxid::Ulid.encode #=> '01GJAY9KGR539EZF4QWYEJGSN7'
105
+ Lite::Uxid::Irreversible::Ulid.encode #=> '01GJAY9KGR539EZF4QWYEJGSN7'
92
106
  ```
93
107
 
94
108
  ## UUID
@@ -96,7 +110,7 @@ Lite::Uxid::Ulid.encode #=> '01GJAY9KGR539EZF4QWYEJGSN7'
96
110
  Implements `v4` and `v7` of the specification. [More information](https://en.wikipedia.org/wiki/Universally_unique_identifier)
97
111
 
98
112
  ```ruby
99
- Lite::Uxid::Uuid.encode #=> '4376a67e-1189-44b3-a599-7f7566bf105b'
113
+ Lite::Uxid::Irreversible::Uuid.encode #=> '4376a67e-1189-44b3-a599-7f7566bf105b'
100
114
  ```
101
115
 
102
116
  ## Options
@@ -104,7 +118,7 @@ Lite::Uxid::Uuid.encode #=> '4376a67e-1189-44b3-a599-7f7566bf105b'
104
118
  Local options can be passed to override global options.
105
119
 
106
120
  ```ruby
107
- Lite::Uxid::Ulid.encode(charset: 'abc123', size: 12) #=> 'a3b12c12c3ca'
121
+ Lite::Uxid::Irreversible::Ulid.encode(charset: 'abc123', size: 12) #=> 'a3b12c12c3ca'
108
122
  ```
109
123
 
110
124
  Passable options are:
@@ -114,6 +128,7 @@ Passable options are:
114
128
  charset: 'string', # Available for: hashid, nanoid, ulid
115
129
  salt: 'string', # Available for: hashid
116
130
  size: 'integer', # Available for: hashid, nanoid, ulid
131
+ spin: 'integer', # Available for: obfuscateid
117
132
  version: 'integer', # Available for: uuid
118
133
  prefix: 'string' # Available for: hashid, nanoid
119
134
  }
@@ -144,27 +159,11 @@ t.uuid :uxid, null: false, index: { unique: true }
144
159
  #### HashID
145
160
  ```ruby
146
161
  class User < ActiveRecord::Base
162
+ # Pick one:
147
163
  include Lite::Uxid::Record::Hashid
148
- end
149
- ```
150
-
151
- #### NanoID
152
- ```ruby
153
- class User < ActiveRecord::Base
154
164
  include Lite::Uxid::Record::Nanoid
155
- end
156
- ```
157
-
158
- #### ULID
159
- ```ruby
160
- class User < ActiveRecord::Base
165
+ include Lite::Uxid::Record::Obfuscateid
161
166
  include Lite::Uxid::Record::Ulid
162
- end
163
- ```
164
-
165
- #### UUID
166
- ```ruby
167
- class User < ActiveRecord::Base
168
167
  include Lite::Uxid::Record::Uuid
169
168
  end
170
169
  ```
@@ -176,16 +175,16 @@ class User < ActiveRecord::Base
176
175
  include Lite::Uxid::Record::Hashid
177
176
 
178
177
  def uxid_prefix
179
- "sub_"
178
+ "usr_"
180
179
  end
181
180
  end
182
181
  ```
183
182
 
184
183
  **Usage**
185
184
 
186
- Using one of the mixins above provides a handy method to find records by uxid.
185
+ Using the `hashid` and `nanoid` above provide handy methods to find records by uxid.
187
186
 
188
- #### HashID methods
187
+ #### Hashing methods
189
188
  ```ruby
190
189
  user = User.new
191
190
  user.id_to_uxid #=> Encodes the records id to uxid
@@ -200,15 +199,10 @@ User.find_by_uxid!('x123') #=> Raises an ActiveRecord::RecordNotFound error if n
200
199
 
201
200
  ## Benchmarks
202
201
 
203
- The classes ranked from fastest to slowest are `UUID`, `Hashid`, `Nanoid`, and `Ulid`.
202
+ The classes ranked from fastest to slowest are `UUID`, `HashID`, `NanoID`, `ULID`, and `ObfuscateID`.
204
203
 
205
204
  View how each compares by running the [benchmarks](https://github.com/drexed/lite-uxid/tree/master/benchmarks).
206
205
 
207
- #### Alternatives
208
-
209
- Learn more about alternative functions and more advance hashing setups:
210
- [hashids.org](https://hashids.org)
211
-
212
206
  ## Development
213
207
 
214
208
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -8,22 +8,30 @@ require "lite/uxid"
8
8
  Benchmark.ips do |x|
9
9
  x.report("Hashid") do
10
10
  id = rand(1..1_000_000)
11
- Lite::Uxid::Hashid.encode(id)
11
+ Lite::Uxid::Reversible::Hashid.encode(id)
12
12
  end
13
13
 
14
+ x.report("Obfuscateid") do
15
+ id = rand(1..1_000_000)
16
+ Lite::Uxid::Reversible::Obfuscateid.encode(id)
17
+ end
18
+
19
+ # The irreversible examples include `rand` simulate
20
+ # the extra work just like reversible examples.
21
+
14
22
  x.report("NanoID") do
15
- _id = rand(1..1_000_000) # To simulate the extra work from `rand`
16
- Lite::Uxid::Nanoid.encode
23
+ _id = rand(1..1_000_000)
24
+ Lite::Uxid::Irreversible::Nanoid.encode
17
25
  end
18
26
 
19
27
  x.report("ULID") do
20
- _id = rand(1..1_000_000) # To simulate the extra work from `rand`
21
- Lite::Uxid::Ulid.encode
28
+ _id = rand(1..1_000_000)
29
+ Lite::Uxid::Irreversible::Ulid.encode
22
30
  end
23
31
 
24
32
  x.report("UUID") do
25
- _id = rand(1..1_000_000) # To simulate the extra work from `rand`
26
- Lite::Uxid::Uuid.encode
33
+ _id = rand(1..1_000_000)
34
+ Lite::Uxid::Irreversible::Uuid.encode
27
35
  end
28
36
 
29
37
  x.compare!
@@ -6,6 +6,7 @@ Lite::Uxid.configure do |config|
6
6
  config.hashid_size = 16
7
7
  config.nanoid_charset = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
8
8
  config.nanoid_size = 21
9
+ config.obfuscateid_spin = 0
9
10
  config.ulid_charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
10
11
  config.ulid_size = 26
11
12
  config.uuid_version = 4
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Uxid
5
+ module Base
6
+
7
+ def encode
8
+ raise NotImplementedError, "override encode method in #{coder_class}"
9
+ end
10
+
11
+ def decode
12
+ raise NotImplementedError, "override decode method in #{coder_class}"
13
+ end
14
+
15
+ private
16
+
17
+ def coder_value_for(key)
18
+ sym_key = :"#{coder_class.downcase}_#{key}"
19
+ return unless Lite::Uxid.configuration.respond_to?(sym_key)
20
+
21
+ opts.delete(key) || Lite::Uxid.configuration.send(sym_key)
22
+ end
23
+
24
+ def coder_charset
25
+ @coder_charset ||= coder_value_for(:charset)
26
+ end
27
+
28
+ def coder_class
29
+ @coder_class ||= self.class.name.split("::").last
30
+ end
31
+
32
+ def coder_length
33
+ @coder_length ||= coder_charset.size
34
+ end
35
+
36
+ def coder_prefix
37
+ @coder_prefix ||= opts.delete(:prefix)
38
+ end
39
+
40
+ def coder_salt
41
+ @coder_salt ||= coder_value_for(:salt)
42
+ end
43
+
44
+ def coder_size
45
+ @coder_size ||= coder_value_for(:size)
46
+ end
47
+
48
+ def coder_spin
49
+ @coder_spin ||= coder_value_for(:spin)
50
+ end
51
+
52
+ def coder_version
53
+ @coder_version ||= coder_value_for(:version)
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -3,20 +3,26 @@
3
3
  module Lite
4
4
  module Uxid
5
5
 
6
+ ALPHANUMERIC = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
7
+ COCKFORDS_32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
8
+ WEB_SAFE = "#{ALPHANUMERIC}-_".freeze
9
+
6
10
  class Configuration
7
11
 
8
12
  attr_accessor :hashid_charset, :hashid_size, :hashid_salt,
9
13
  :nanoid_charset, :nanoid_size,
10
- :ulid_charset, :ulid_size,
14
+ :obfuscateid_spin,
15
+ :ulid_charset, :ulid_size,
11
16
  :uuid_version
12
17
 
13
18
  def initialize
14
- @hashid_charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
19
+ @hashid_charset = ALPHANUMERIC
15
20
  @hashid_salt = 1_369_136
16
21
  @hashid_size = 16
17
- @nanoid_charset = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
22
+ @nanoid_charset = WEB_SAFE
18
23
  @nanoid_size = 21
19
- @ulid_charset = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
24
+ @obfuscateid_spin = 0
25
+ @ulid_charset = COCKFORDS_32
20
26
  @ulid_size = 26
21
27
  @uuid_version = 4
22
28
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Uxid
5
+ module Irreversible
6
+ class Base
7
+
8
+ include Lite::Uxid::Base
9
+
10
+ attr_reader :opts
11
+
12
+ def initialize(opts = {})
13
+ @opts = opts
14
+ end
15
+
16
+ def self.encode(opts = {})
17
+ klass = new(opts)
18
+ klass.encode
19
+ end
20
+
21
+ def self.decode(opts = {})
22
+ klass = new(opts)
23
+ klass.decode
24
+ end
25
+
26
+ def decode
27
+ raise "#{coder_class} does not support decoding"
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Uxid
5
+ module Irreversible
6
+ class Nanoid < Base
7
+
8
+ def encode
9
+ encoded_id = +""
10
+
11
+ loop do
12
+ cached_bytes = bytes
13
+
14
+ (0...step).each do |idx|
15
+ byte = cached_bytes[idx] & mask
16
+ char = byte && coder_charset[byte]
17
+ next unless char
18
+
19
+ encoded_id << char
20
+ return "#{coder_prefix}#{encoded_id}" if encoded_id.size == coder_size
21
+ end
22
+ end
23
+
24
+ "#{coder_prefix}#{encoded_id}"
25
+ end
26
+
27
+ private
28
+
29
+ def bytes
30
+ SecureRandom.random_bytes(coder_size).bytes
31
+ end
32
+
33
+ def mask
34
+ @mask ||= (2 << (Math.log(coder_length - 1) / Math.log(2))) - 1
35
+ end
36
+
37
+ def step
38
+ @step = (1.6 * mask * coder_size / coder_length).ceil
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Uxid
5
+ module Irreversible
6
+ class Ulid < Base
7
+
8
+ MASK = 0x1f
9
+
10
+ def encode
11
+ encoded_id = "0" * coder_size
12
+ oct = octect
13
+ pos = coder_size - 1
14
+
15
+ while oct.positive?
16
+ encoded_id[pos] = coder_charset[oct & MASK]
17
+ oct >>= 5
18
+ pos -= 1
19
+ end
20
+
21
+ encoded_id
22
+ end
23
+
24
+ private
25
+
26
+ def bytes
27
+ "#{unixtime_48bit}#{SecureRandom.random_bytes(10)}"
28
+ end
29
+
30
+ def octect
31
+ (hi, lo) = bytes.unpack("Q>Q>")
32
+ (hi << 64) | lo
33
+ end
34
+
35
+ def unixtime_ms
36
+ time = Time.respond_to?(:current) ? Time.current : Time.now
37
+ time.to_i * 1_000
38
+ end
39
+
40
+ def unixtime_48bit
41
+ [unixtime_ms].pack("Q>")[2..-1]
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Uxid
5
+ module Irreversible
6
+ class Uuid < Base
7
+
8
+ def encode
9
+ case coder_version
10
+ when 7
11
+ SecureRandom.uuid_v7
12
+ else
13
+ SecureRandom.uuid
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,23 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support" unless defined?(ActiveSupport)
4
-
5
3
  module Lite
6
4
  module Uxid
7
5
  module Record
8
6
  module Hashid
9
7
 
10
- extend ActiveSupport::Concern
11
-
12
- included do
13
- after_commit :callback_generate_uxid!,
14
- if: proc { respond_to?(:uxid) && !uxid? },
15
- on: :create
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ base.class_eval do
11
+ after_commit :callback_generate_uxid!,
12
+ if: proc { respond_to?(:uxid) && !uxid? },
13
+ on: :create
14
+ end
16
15
  end
17
16
 
18
- class_methods do
17
+ module ClassMethods
18
+
19
19
  def find_by_uxid(uxid)
20
- decoded_id = Lite::Uxid::Hashid.decode(uxid, prefix: new.uxid_prefix)
20
+ decoded_id = Lite::Uxid::Reversible::Hashid.decode(uxid, prefix: new.uxid_prefix)
21
21
  find_by(id: decoded_id)
22
22
  end
23
23
 
@@ -27,18 +27,19 @@ module Lite
27
27
 
28
28
  raise ActiveRecord::RecordNotFound
29
29
  end
30
+
30
31
  end
31
32
 
32
33
  def id_to_uxid
33
34
  return unless respond_to?(:uxid)
34
35
 
35
- Lite::Uxid::Hashid.encode(id, prefix: uxid_prefix)
36
+ Lite::Uxid::Reversible::Hashid.encode(id, prefix: uxid_prefix)
36
37
  end
37
38
 
38
39
  def uxid_to_id
39
40
  return unless respond_to?(:uxid)
40
41
 
41
- Lite::Uxid::Hashid.decode(uxid, prefix: uxid_prefix)
42
+ Lite::Uxid::Reversible::Hashid.decode(uxid, prefix: uxid_prefix)
42
43
  end
43
44
 
44
45
  def uxid_prefix
@@ -1,17 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support" unless defined?(ActiveSupport)
4
-
5
3
  module Lite
6
4
  module Uxid
7
5
  module Record
8
6
  module Nanoid
9
7
 
10
- extend ActiveSupport::Concern
11
-
12
- included do
13
- before_create :callback_generate_uxid!,
14
- if: proc { respond_to?(:uxid) && !uxid? }
8
+ def self.included(base)
9
+ base.class_eval do
10
+ before_create :callback_generate_uxid!,
11
+ if: proc { respond_to?(:uxid) && !uxid? }
12
+ end
15
13
  end
16
14
 
17
15
  def uxid_prefix
@@ -24,7 +22,7 @@ module Lite
24
22
  random_nanoid = nil
25
23
 
26
24
  loop do
27
- random_nanoid = Lite::Uxid::Nanoid.encode(prefix: uxid_prefix)
25
+ random_nanoid = Lite::Uxid::Irreversible::Nanoid.encode(prefix: uxid_prefix)
28
26
  break unless self.class.exists?(uxid: random_nanoid)
29
27
  end
30
28
 
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Uxid
5
+ module Record
6
+ module Obfuscateid
7
+
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ base.class_eval do
11
+ after_commit :callback_generate_uxid!,
12
+ if: proc { respond_to?(:uxid) && !uxid? },
13
+ on: :create
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ def find_by_uxid(uxid)
20
+ decoded_id = Lite::Uxid::Reversible::Obfuscateid.decode(uxid)
21
+ find_by(id: decoded_id)
22
+ end
23
+
24
+ def find_by_uxid!(uxid)
25
+ record = find_by_uxid(uxid)
26
+ return record unless record.nil?
27
+
28
+ raise ActiveRecord::RecordNotFound
29
+ end
30
+
31
+ end
32
+
33
+ def id_to_uxid
34
+ return unless respond_to?(:uxid)
35
+
36
+ Lite::Uxid::Reversible::Obfuscateid.encode(id)
37
+ end
38
+
39
+ def uxid_to_id
40
+ return unless respond_to?(:uxid)
41
+
42
+ Lite::Uxid::Reversible::Obfuscateid.decode(uxid)
43
+ end
44
+
45
+ private
46
+
47
+ def callback_generate_uxid!
48
+ update_column(:uxid, id_to_uxid)
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,23 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support" unless defined?(ActiveSupport)
4
-
5
3
  module Lite
6
4
  module Uxid
7
5
  module Record
8
6
  module Ulid
9
7
 
10
- extend ActiveSupport::Concern
11
-
12
- included do
13
- before_create :callback_generate_uxid!,
14
- if: proc { respond_to?(:uxid) && !uxid? }
8
+ def self.included(base)
9
+ base.class_eval do
10
+ before_create :callback_generate_uxid!,
11
+ if: proc { respond_to?(:uxid) && !uxid? }
12
+ end
15
13
  end
16
14
 
17
15
  private
18
16
 
19
17
  def callback_generate_uxid!
20
- self.uxid = Lite::Uxid::Ulid.encode
18
+ self.uxid = Lite::Uxid::Irreversible::Ulid.encode
21
19
  end
22
20
 
23
21
  end
@@ -1,23 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support" unless defined?(ActiveSupport)
4
-
5
3
  module Lite
6
4
  module Uxid
7
5
  module Record
8
6
  module Uuid
9
7
 
10
- extend ActiveSupport::Concern
11
-
12
- included do
13
- before_create :callback_generate_uxid!,
14
- if: proc { respond_to?(:uxid) && !uxid? }
8
+ def self.included(base)
9
+ base.class_eval do
10
+ before_create :callback_generate_uxid!,
11
+ if: proc { respond_to?(:uxid) && !uxid? }
12
+ end
15
13
  end
16
14
 
17
15
  private
18
16
 
19
17
  def callback_generate_uxid!
20
- self.uxid = Lite::Uxid::Uuid.encode
18
+ self.uxid = Lite::Uxid::Irreversible::Uuid.encode
21
19
  end
22
20
 
23
21
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Uxid
5
+ module Reversible
6
+ class Base
7
+
8
+ include Lite::Uxid::Base
9
+
10
+ attr_reader :id, :opts
11
+
12
+ def initialize(id, opts = {})
13
+ @id = id
14
+ @opts = opts
15
+ end
16
+
17
+ def self.encode(id, opts = {})
18
+ klass = new(id, opts)
19
+ klass.encode
20
+ end
21
+
22
+ def self.decode(id, opts = {})
23
+ klass = new(id, opts)
24
+ klass.decode
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Uxid
5
+ module Reversible
6
+ class Hashid < Base
7
+
8
+ def encode
9
+ encoded_id = encode_chars((id + coder_salt) << coder_size)
10
+ "#{coder_prefix}#{encoded_id}"
11
+ end
12
+
13
+ def decode
14
+ encoded_id = id.delete_prefix(coder_prefix.to_s)
15
+ (decode_chars(encoded_id) >> coder_size) - coder_salt
16
+ end
17
+
18
+ private
19
+
20
+ def encode_chars(decoded_id)
21
+ return "0" if decoded_id.zero?
22
+ return nil if decoded_id.negative?
23
+
24
+ str = ""
25
+
26
+ while decoded_id.positive?
27
+ str = "#{coder_charset[decoded_id % coder_length]}#{str}"
28
+ decoded_id /= coder_length
29
+ end
30
+
31
+ str
32
+ end
33
+
34
+ def decode_chars(encoded_id)
35
+ pos = 0
36
+ num = 0
37
+ len = encoded_id.size
38
+ max = len - 1
39
+
40
+ while pos < len
41
+ pow = coder_length**(max - pos)
42
+ num += coder_charset.index(encoded_id[pos]) * pow
43
+ pos += 1
44
+ end
45
+
46
+ num
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lite
4
+ module Uxid
5
+ module Reversible
6
+ class Obfuscateid < Base
7
+
8
+ def encode
9
+ swap
10
+ scatter
11
+ result
12
+ end
13
+
14
+ def decode
15
+ unscatter
16
+ unswap
17
+ result
18
+ end
19
+
20
+ private
21
+
22
+ def zero_padded_id
23
+ @zero_padded_id ||= id.to_s.rjust(10, "0")
24
+ end
25
+
26
+ def coder_array
27
+ @coder_array ||= zero_padded_id.chars.collect(&:to_i)
28
+ end
29
+
30
+ def result
31
+ coder_array.join.to_i
32
+ end
33
+
34
+ def swapper_map(index)
35
+ array = (0..9).to_a
36
+ Array.new(10) { |i| array.rotate!((index + i) ^ coder_spin).pop }
37
+ end
38
+
39
+ def swap
40
+ coder_array.map!.with_index { |digit, i| swapper_map(i)[digit] }
41
+ end
42
+
43
+ def unswap
44
+ coder_array.map!.with_index { |digit, i| swapper_map(i).rindex(digit) }
45
+ end
46
+
47
+ def scatter
48
+ sum_of_digits = coder_array.sum.to_i
49
+ @coder_array = Array.new(10) { coder_array.rotate!(coder_spin ^ sum_of_digits).pop }
50
+ end
51
+
52
+ def unscatter
53
+ scattered_array = coder_array
54
+ sum_of_digits = scattered_array.sum.to_i
55
+ @coder_array = []
56
+
57
+ coder_array.tap do |unscatter|
58
+ 10.times do
59
+ unscatter.push(scattered_array.pop)
60
+ unscatter.rotate!((sum_of_digits ^ coder_spin) * -1)
61
+ end
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
@@ -3,7 +3,7 @@
3
3
  module Lite
4
4
  module Uxid
5
5
 
6
- VERSION = "1.5.2"
6
+ VERSION = "2.0.1"
7
7
 
8
8
  end
9
9
  end
data/lib/lite/uxid.rb CHANGED
@@ -1,16 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "securerandom" unless defined?(SecureRandom)
3
4
  require "generators/lite/uxid/install_generator" if defined?(Rails::Generators)
4
5
 
5
6
  require "lite/uxid/version"
6
7
  require "lite/uxid/configuration"
7
- require "lite/uxid/base/irreversible"
8
- require "lite/uxid/base/reversible"
8
+ require "lite/uxid/base"
9
+ require "lite/uxid/irreversible/base"
10
+ require "lite/uxid/irreversible/nanoid"
11
+ require "lite/uxid/irreversible/ulid"
12
+ require "lite/uxid/irreversible/uuid"
13
+ require "lite/uxid/reversible/base"
14
+ require "lite/uxid/reversible/hashid"
15
+ require "lite/uxid/reversible/obfuscateid"
9
16
  require "lite/uxid/record/hashid"
10
17
  require "lite/uxid/record/nanoid"
18
+ require "lite/uxid/record/obfuscateid"
11
19
  require "lite/uxid/record/ulid"
12
20
  require "lite/uxid/record/uuid"
13
- require "lite/uxid/hashid"
14
- require "lite/uxid/nanoid"
15
- require "lite/uxid/ulid"
16
- require "lite/uxid/uuid"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lite-uxid
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Gomez
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-20 00:00:00.000000000 Z
11
+ date: 2024-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -217,17 +217,20 @@ files:
217
217
  - lib/generators/lite/uxid/install_generator.rb
218
218
  - lib/generators/lite/uxid/templates/install.rb
219
219
  - lib/lite/uxid.rb
220
- - lib/lite/uxid/base/irreversible.rb
221
- - lib/lite/uxid/base/reversible.rb
220
+ - lib/lite/uxid/base.rb
222
221
  - lib/lite/uxid/configuration.rb
223
- - lib/lite/uxid/hashid.rb
224
- - lib/lite/uxid/nanoid.rb
222
+ - lib/lite/uxid/irreversible/base.rb
223
+ - lib/lite/uxid/irreversible/nanoid.rb
224
+ - lib/lite/uxid/irreversible/ulid.rb
225
+ - lib/lite/uxid/irreversible/uuid.rb
225
226
  - lib/lite/uxid/record/hashid.rb
226
227
  - lib/lite/uxid/record/nanoid.rb
228
+ - lib/lite/uxid/record/obfuscateid.rb
227
229
  - lib/lite/uxid/record/ulid.rb
228
230
  - lib/lite/uxid/record/uuid.rb
229
- - lib/lite/uxid/ulid.rb
230
- - lib/lite/uxid/uuid.rb
231
+ - lib/lite/uxid/reversible/base.rb
232
+ - lib/lite/uxid/reversible/hashid.rb
233
+ - lib/lite/uxid/reversible/obfuscateid.rb
231
234
  - lib/lite/uxid/version.rb
232
235
  - lite-uxid.gemspec
233
236
  homepage: http://drexed.github.io/lite-uxid
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom" unless defined?(SecureRandom)
4
-
5
- module Lite
6
- module Uxid
7
- module Base
8
- class Irreversible
9
-
10
- attr_reader :opts
11
-
12
- def initialize(opts = {})
13
- @opts = opts
14
- end
15
-
16
- class << self
17
-
18
- def encode(opts = {})
19
- klass = new(opts)
20
- klass.encode
21
- end
22
-
23
- def decode(opts = {})
24
- klass = new(opts)
25
- klass.decode
26
- end
27
-
28
- end
29
-
30
- def encode
31
- raise NotImplementedError, "override method in #{coder_class}"
32
- end
33
-
34
- def decode
35
- raise NotImplementedError, "coder does not support decoding"
36
- end
37
-
38
- private
39
-
40
- def coder_value_for(key)
41
- sym_key = :"#{coder_class.downcase}_#{key}"
42
- return unless Lite::Uxid.configuration.respond_to?(sym_key)
43
-
44
- opts.delete(key) || Lite::Uxid.configuration.send(sym_key)
45
- end
46
-
47
- def coder_charset
48
- @coder_charset ||= coder_value_for(:charset)
49
- end
50
-
51
- def coder_class
52
- @coder_class ||= self.class.name.split("::").last
53
- end
54
-
55
- def coder_length
56
- @coder_length ||= coder_charset.size
57
- end
58
-
59
- def coder_prefix
60
- @coder_prefix ||= opts.delete(:prefix)
61
- end
62
-
63
- def coder_salt
64
- @coder_salt ||= coder_value_for(:salt)
65
- end
66
-
67
- def coder_size
68
- @coder_size ||= coder_value_for(:size)
69
- end
70
-
71
- def coder_version
72
- @coder_version ||= coder_value_for(:version)
73
- end
74
-
75
- end
76
- end
77
- end
78
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lite
4
- module Uxid
5
- module Base
6
- class Reversible < Irreversible
7
-
8
- attr_reader :id
9
-
10
- def initialize(id, opts = {})
11
- @id = id
12
- super(opts)
13
- end
14
-
15
- class << self
16
-
17
- def encode(id, opts = {})
18
- klass = new(id, opts)
19
- klass.encode
20
- end
21
-
22
- def decode(id, opts = {})
23
- klass = new(id, opts)
24
- klass.decode
25
- end
26
-
27
- end
28
-
29
- def decode
30
- raise NotImplementedError, "override method in #{coder_class}"
31
- end
32
-
33
- end
34
- end
35
- end
36
- end
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lite
4
- module Uxid
5
- class Hashid < Base::Reversible
6
-
7
- def encode
8
- uxid = encode_chars((id + coder_salt) << coder_size)
9
- "#{coder_prefix}#{uxid}"
10
- end
11
-
12
- def decode
13
- uxid = id.delete_prefix(coder_prefix.to_s)
14
- (decode_chars(uxid) >> coder_size) - coder_salt
15
- end
16
-
17
- private
18
-
19
- def encode_chars(decoded_id)
20
- return "0" if decoded_id.zero?
21
- return nil if decoded_id.negative?
22
-
23
- str = ""
24
-
25
- while decoded_id.positive?
26
- str = "#{coder_charset[decoded_id % coder_length]}#{str}"
27
- decoded_id /= coder_length
28
- end
29
-
30
- str
31
- end
32
-
33
- def decode_chars(encoded_id)
34
- pos = 0
35
- num = 0
36
- len = encoded_id.size
37
- max = len - 1
38
-
39
- while pos < len
40
- pow = coder_length**(max - pos)
41
- num += coder_charset.index(encoded_id[pos]) * pow
42
- pos += 1
43
- end
44
-
45
- num
46
- end
47
-
48
- end
49
- end
50
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lite
4
- module Uxid
5
- class Nanoid < Base::Irreversible
6
-
7
- def encode
8
- uxid = +""
9
-
10
- loop do
11
- cached_bytes = bytes
12
-
13
- (0...step).each do |idx|
14
- byte = cached_bytes[idx] & mask
15
- char = byte && coder_charset[byte]
16
- next unless char
17
-
18
- uxid << char
19
- return uxid if uxid.size == coder_size
20
- end
21
- end
22
-
23
- "#{coder_prefix}#{uxid}"
24
- end
25
-
26
- private
27
-
28
- def bytes
29
- SecureRandom.random_bytes(coder_size).bytes
30
- end
31
-
32
- def mask
33
- @mask ||= (2 << (Math.log(coder_length - 1) / Math.log(2))) - 1
34
- end
35
-
36
- def step
37
- @step = (1.6 * mask * coder_size / coder_length).ceil
38
- end
39
-
40
- end
41
- end
42
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lite
4
- module Uxid
5
- class Ulid < Base::Irreversible
6
-
7
- MASK = 0x1f
8
-
9
- def encode
10
- oct = octect
11
- ele = "0" * coder_size
12
- pos = coder_size - 1
13
-
14
- while oct.positive?
15
- ele[pos] = coder_charset[oct & MASK]
16
- oct >>= 5
17
- pos -= 1
18
- end
19
-
20
- ele
21
- end
22
-
23
- private
24
-
25
- def bytes
26
- "#{unixtime_48bit}#{SecureRandom.random_bytes(10)}"
27
- end
28
-
29
- def octect
30
- (hi, lo) = bytes.unpack("Q>Q>")
31
- (hi << 64) | lo
32
- end
33
-
34
- def unixtime_ms
35
- time = Time.respond_to?(:current) ? Time.current : Time.now
36
- time.to_i * 1_000
37
- end
38
-
39
- def unixtime_48bit
40
- [unixtime_ms].pack("Q>")[2..-1]
41
- end
42
-
43
- end
44
- end
45
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lite
4
- module Uxid
5
- class Uuid < Base::Irreversible
6
-
7
- def encode
8
- case coder_version
9
- when 7
10
- SecureRandom.uuid_v7
11
- else
12
- SecureRandom.uuid
13
- end
14
- end
15
-
16
- end
17
- end
18
- end