lite-uxid 1.5.2 → 2.0.1

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: 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