lite-uxid 1.5.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +1 -1
- data/README.md +34 -12
- data/lib/generators/lite/uxid/templates/install.rb +1 -0
- data/lib/lite/uxid/base.rb +58 -0
- data/lib/lite/uxid/configuration.rb +10 -4
- data/lib/lite/uxid/irreversible/base.rb +33 -0
- data/lib/lite/uxid/irreversible/nanoid.rb +44 -0
- data/lib/lite/uxid/irreversible/ulid.rb +47 -0
- data/lib/lite/uxid/irreversible/uuid.rb +20 -0
- data/lib/lite/uxid/record/hashid.rb +13 -12
- data/lib/lite/uxid/record/nanoid.rb +6 -8
- data/lib/lite/uxid/record/scatterid.rb +58 -0
- data/lib/lite/uxid/record/ulid.rb +6 -8
- data/lib/lite/uxid/record/uuid.rb +6 -8
- data/lib/lite/uxid/reversible/base.rb +30 -0
- data/lib/lite/uxid/reversible/hashid.rb +52 -0
- data/lib/lite/uxid/reversible/scatterid.rb +71 -0
- data/lib/lite/uxid/version.rb +1 -1
- data/lib/lite/uxid.rb +10 -6
- metadata +11 -8
- data/lib/lite/uxid/base/irreversible.rb +0 -78
- data/lib/lite/uxid/base/reversible.rb +0 -36
- data/lib/lite/uxid/hashid.rb +0 -50
- data/lib/lite/uxid/nanoid.rb +0 -42
- data/lib/lite/uxid/ulid.rb +0 -45
- data/lib/lite/uxid/uuid.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 767645737040b210cc058a0a781f89751f447e991aa03c527f5d12b039d1980a
|
4
|
+
data.tar.gz: 1c777db2675144f26b24ed2ec75d175c83f55d5ede9e83ab5d0d75ca2b76e692
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 313da972f69d52e8951d552c3269500767e7fb80255de25c44d1bbe0d6497aaa3b0c398736b2e451d393ad0642446e298ebbad36a225528f010a96a8684f221e
|
7
|
+
data.tar.gz: 61ce05436d5e2793164007f1b28edd28ea138c9efbf02f0a6ac6ef021708d3171b61c77e2811dfe9c65ef764b46d55713e6121eefd57d87a22cb10e80a9c0eef
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [2.0.0] - 2024-09-23
|
10
|
+
### Added
|
11
|
+
- Scatterid reversible lib
|
12
|
+
### Changed
|
13
|
+
- Reorganize generators into better namespaces
|
14
|
+
- Fixed issue where nanoid would return without the prefix
|
15
|
+
- Moved charsets to constants
|
16
|
+
|
9
17
|
## [1.5.2] - 2024-09-20
|
10
18
|
### Changed
|
11
19
|
- Fixed nanoid of differing length error
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -29,6 +29,7 @@ Or install it yourself as:
|
|
29
29
|
* [Usage](#usage)
|
30
30
|
* [Hashid](#hashid)
|
31
31
|
* [NanoID](#nanoid)
|
32
|
+
* [Scatterid](#scatterid)
|
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 =
|
50
|
+
config.hashid_charset = ALPHANUMERIC
|
46
51
|
config.hashid_salt = 1_369_136
|
47
52
|
config.hashid_size = 16
|
48
|
-
config.nanoid_charset =
|
53
|
+
config.nanoid_charset = WEB_SAFE
|
49
54
|
config.nanoid_size = 21
|
50
|
-
config.ulid_charset =
|
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
|
-
##
|
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
|
+
## ScatterID
|
92
|
+
|
93
|
+
[More information](https://github.com/namick/scatter_swap)
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
Lite::Uxid::Reversible::Scatterid.encode(10) #=> '2056964183'
|
97
|
+
Lite::Uxid::Reversible::Scatterid.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: scatterid
|
117
132
|
version: 'integer', # Available for: uuid
|
118
133
|
prefix: 'string' # Available for: hashid, nanoid
|
119
134
|
}
|
@@ -155,6 +170,13 @@ class User < ActiveRecord::Base
|
|
155
170
|
end
|
156
171
|
```
|
157
172
|
|
173
|
+
#### ScatterID
|
174
|
+
```ruby
|
175
|
+
class User < ActiveRecord::Base
|
176
|
+
include Lite::Uxid::Record::Scatterid
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
158
180
|
#### ULID
|
159
181
|
```ruby
|
160
182
|
class User < ActiveRecord::Base
|
@@ -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.scatterid_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
|
-
:
|
14
|
+
:scatterid_spin,
|
15
|
+
:ulid_charset, :ulid_size,
|
11
16
|
:uuid_version
|
12
17
|
|
13
18
|
def initialize
|
14
|
-
@hashid_charset =
|
19
|
+
@hashid_charset = ALPHANUMERIC
|
15
20
|
@hashid_salt = 1_369_136
|
16
21
|
@hashid_size = 16
|
17
|
-
@nanoid_charset =
|
22
|
+
@nanoid_charset = WEB_SAFE
|
18
23
|
@nanoid_size = 21
|
19
|
-
@
|
24
|
+
@scatterid_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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lite
|
4
|
+
module Uxid
|
5
|
+
module Record
|
6
|
+
module Scatterid
|
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::Scatterid.decode(uxid, prefix: new.uxid_prefix)
|
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::Scatterid.encode(id, prefix: uxid_prefix)
|
37
|
+
end
|
38
|
+
|
39
|
+
def uxid_to_id
|
40
|
+
return unless respond_to?(:uxid)
|
41
|
+
|
42
|
+
Lite::Uxid::Reversible::Scatterid.decode(uxid, prefix: uxid_prefix)
|
43
|
+
end
|
44
|
+
|
45
|
+
def uxid_prefix
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def callback_generate_uxid!
|
52
|
+
update_column(:uxid, id_to_uxid)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lite
|
4
|
+
module Uxid
|
5
|
+
module Reversible
|
6
|
+
class Scatterid < Base
|
7
|
+
|
8
|
+
def encode
|
9
|
+
swap
|
10
|
+
scatter
|
11
|
+
|
12
|
+
"#{coder_prefix}#{joined_array}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def decode
|
16
|
+
@id = id.delete_prefix(coder_prefix.to_s)
|
17
|
+
|
18
|
+
unscatter
|
19
|
+
unswap
|
20
|
+
joined_array.to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def zero_padded_id
|
26
|
+
@zero_padded_id ||= id.to_s.rjust(10, "0")
|
27
|
+
end
|
28
|
+
|
29
|
+
def coder_array
|
30
|
+
@coder_array ||= zero_padded_id.chars.collect(&:to_i)
|
31
|
+
end
|
32
|
+
|
33
|
+
def joined_array
|
34
|
+
coder_array.join
|
35
|
+
end
|
36
|
+
|
37
|
+
def swapper_map(index)
|
38
|
+
array = (0..9).to_a
|
39
|
+
Array.new(10) { |i| array.rotate!((index + i) ^ coder_spin).pop }
|
40
|
+
end
|
41
|
+
|
42
|
+
def swap
|
43
|
+
coder_array.map!.with_index { |digit, i| swapper_map(i)[digit] }
|
44
|
+
end
|
45
|
+
|
46
|
+
def unswap
|
47
|
+
coder_array.map!.with_index { |digit, i| swapper_map(i).rindex(digit) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def scatter
|
51
|
+
sum_of_digits = coder_array.sum.to_i
|
52
|
+
@coder_array = Array.new(10) { coder_array.rotate!(coder_spin ^ sum_of_digits).pop }
|
53
|
+
end
|
54
|
+
|
55
|
+
def unscatter
|
56
|
+
scattered_array = coder_array
|
57
|
+
sum_of_digits = scattered_array.sum.to_i
|
58
|
+
@coder_array = []
|
59
|
+
|
60
|
+
coder_array.tap do |unscatter|
|
61
|
+
10.times do
|
62
|
+
unscatter.push(scattered_array.pop)
|
63
|
+
unscatter.rotate!((sum_of_digits ^ coder_spin) * -1)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/lite/uxid/version.rb
CHANGED
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
|
8
|
-
require "lite/uxid/base
|
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/scatterid"
|
9
16
|
require "lite/uxid/record/hashid"
|
10
17
|
require "lite/uxid/record/nanoid"
|
18
|
+
require "lite/uxid/record/scatterid"
|
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:
|
4
|
+
version: 2.0.0
|
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-
|
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
|
221
|
-
- lib/lite/uxid/base/reversible.rb
|
220
|
+
- lib/lite/uxid/base.rb
|
222
221
|
- lib/lite/uxid/configuration.rb
|
223
|
-
- lib/lite/uxid/
|
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/scatterid.rb
|
227
229
|
- lib/lite/uxid/record/ulid.rb
|
228
230
|
- lib/lite/uxid/record/uuid.rb
|
229
|
-
- lib/lite/uxid/
|
230
|
-
- lib/lite/uxid/
|
231
|
+
- lib/lite/uxid/reversible/base.rb
|
232
|
+
- lib/lite/uxid/reversible/hashid.rb
|
233
|
+
- lib/lite/uxid/reversible/scatterid.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
|
data/lib/lite/uxid/hashid.rb
DELETED
@@ -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
|
data/lib/lite/uxid/nanoid.rb
DELETED
@@ -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
|
data/lib/lite/uxid/ulid.rb
DELETED
@@ -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
|
data/lib/lite/uxid/uuid.rb
DELETED