sqids 0.1.2 → 0.2.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: 8c79695a1c507e1785e6c454fed0e0f4bca689598608d5b680b019493dfe9406
4
- data.tar.gz: 9c64e11d7b0a8c551caf8b24d73fd01cb0aaf8ade971a4b6d535f120e1be8d9a
3
+ metadata.gz: 8738a24714961747ba8fb004ffba899f34889de1e65ba3932a59a50b85558aef
4
+ data.tar.gz: 6b26a781bd07bc2b805af36d29bd0cc838351569a3a44988e171b6083e75a3e4
5
5
  SHA512:
6
- metadata.gz: 9992329b56a99da95e7ee85e1b928407d7cbb40f5312cbc18a0f17ed95b0ba51b175088dc84db69d894bfa0814d587f5cdbe327c7a6a1d56c89eb004c68a83ae
7
- data.tar.gz: 00d797e9d692a9c4ad82f80971649d2e4553d317c78b72e258ae497fa94521246205e56873971685b42d9b4ee7d8507d71bdf4c05fd80eab9735f58176adf230
6
+ metadata.gz: 4dbacb914b13fc4a8125f3b313359db82afe1251d5f9ef7d5eaa724fb8b474521e102ec00ef938d621259c52541bf9f6b63647ef1d24243e3bd72a1ab07f6926
7
+ data.tar.gz: 33660c785f18c7a2692e3e184c275c104c73dd72ac5aa6bca940f1da7dda1a5b3f66c30f55c4010d488bec694bafd0329979bcce3f3e0db20bfb87bdf01a6f4b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # CHANGELOG
2
2
 
3
+ **v0.2.1:**
4
+ - Improvement: speeding up Sqids.new: [[PR #7](https://github.com/sqids/sqids-ruby/pull/7)] (thanks to [@lawrencegripper](https://github.com/lawrencegripper))
5
+
6
+ **v0.2.0:** **⚠️ BREAKING CHANGE**
7
+ - **Breaking change**: IDs change. Algorithm has been fine-tuned for better performance [[Issue #11](https://github.com/sqids/sqids-spec/issues/11)]
8
+ - `alphabet` cannot contain multibyte characters
9
+ - `min_length` upper limit has increased from alphabet length to `255`
10
+ - Max blocklist re-encoding attempts has been capped at the length of the alphabet - 1
11
+ - Minimum alphabet length has changed from 5 to 3
12
+ - `min_value()` and `max_value()` functions have been removed
13
+
3
14
  **v0.1.2:**
4
15
  - Bug fix: spec update (PR #7): blocklist filtering in uppercase-only alphabet [[PR #7](https://github.com/sqids/sqids-spec/pull/7)]
5
16
  - Lower uniques test from 1_000_000 to 10_000
data/README.md CHANGED
@@ -55,37 +55,42 @@ Simple encode & decode:
55
55
 
56
56
  ```ruby
57
57
  sqids = Sqids.new
58
- id = sqids.encode([1, 2, 3]) # '8QRLaD'
58
+ id = sqids.encode([1, 2, 3]) # '86Rf07'
59
59
  numbers = sqids.decode(id) # [1, 2, 3]
60
60
  ```
61
61
 
62
62
  > **Note**
63
63
  > 🚧 Because of the algorithm's design, **multiple IDs can decode back into the same sequence of numbers**. If it's important to your design that IDs are canonical, you have to manually re-encode decoded numbers and check that the generated ID matches.
64
64
 
65
- Randomize IDs by providing a custom alphabet:
65
+ Enforce a *minimum* length for IDs:
66
66
 
67
67
  ```ruby
68
- sqids = Sqids.new(alphabet: 'FxnXM1kBN6cuhsAvjW3Co7l2RePyY8DwaU04Tzt9fHQrqSVKdpimLGIJOgb5ZE')
69
- id = sqids.encode([1, 2, 3]) # 'B5aMa3'
68
+ sqids = Sqids.new(min_length: 10)
69
+ id = sqids.encode([1, 2, 3]) # '86Rf07xd4z'
70
70
  numbers = sqids.decode(id) # [1, 2, 3]
71
71
  ```
72
72
 
73
- Enforce a *minimum* length for IDs:
73
+ Randomize IDs by providing a custom alphabet:
74
74
 
75
75
  ```ruby
76
- sqids = Sqids.new(min_length: 10)
77
- id = sqids.encode([1, 2, 3]) # '75JT1cd0dL'
76
+ sqids = Sqids.new(alphabet: 'FxnXM1kBN6cuhsAvjW3Co7l2RePyY8DwaU04Tzt9fHQrqSVKdpimLGIJOgb5ZE')
77
+ id = sqids.encode([1, 2, 3]) # 'B4aajs'
78
78
  numbers = sqids.decode(id) # [1, 2, 3]
79
79
  ```
80
80
 
81
81
  Prevent specific words from appearing anywhere in the auto-generated IDs:
82
82
 
83
83
  ```ruby
84
- sqids = Sqids.new(blocklist: Set.new(%w[word1 word2]))
85
- id = sqids.encode([1, 2, 3]) # '8QRLaD'
84
+ sqids = Sqids.new(blocklist: Set.new(%w[86Rf07]))
85
+ id = sqids.encode([1, 2, 3]) # 'se8ojk'
86
86
  numbers = sqids.decode(id) # [1, 2, 3]
87
87
  ```
88
88
 
89
+ > [!WARNING]
90
+ > If you provide a large custom blocklist and/or custom alphabet, calls to `Sqid.new` can take
91
+ > ~1ms. You should create a singleton instance of `Sqid` at service start and reusing that rather than
92
+ > repeatedly calling `Squid.new`
93
+
89
94
  ## 📝 License
90
95
 
91
96
  [MIT](LICENSE)
data/lib/sqids.rb CHANGED
@@ -15,21 +15,33 @@ class Sqids
15
15
  min_length = options[:min_length] || DEFAULT_MIN_LENGTH
16
16
  blocklist = options[:blocklist] || DEFAULT_BLOCKLIST
17
17
 
18
- raise ArgumentError, 'Alphabet length must be at least 5' if alphabet.length < 5
18
+ raise ArgumentError, 'Alphabet cannot contain multibyte characters' if contains_multibyte_chars(alphabet)
19
+ raise ArgumentError, 'Alphabet length must be at least 3' if alphabet.length < 3
19
20
 
20
21
  if alphabet.chars.uniq.size != alphabet.length
21
22
  raise ArgumentError,
22
23
  'Alphabet must contain unique characters'
23
24
  end
24
25
 
25
- unless min_length.is_a?(Integer) && min_length >= Sqids.min_value && min_length <= alphabet.length
26
+ min_length_limit = 255
27
+ unless min_length.is_a?(Integer) && min_length >= 0 && min_length <= min_length_limit
26
28
  raise TypeError,
27
- "Minimum length has to be between #{Sqids.min_value} and #{alphabet.length}"
29
+ "Minimum length has to be between 0 and #{min_length_limit}"
28
30
  end
29
31
 
30
- filtered_blocklist = blocklist.select do |word|
31
- word.length >= 3 && (word.downcase.chars - alphabet.downcase.chars).empty?
32
- end.to_set(&:downcase)
32
+ filtered_blocklist = if blocklist == DEFAULT_BLOCKLIST && alphabet == DEFAULT_ALPHABET
33
+ # If the blocklist is the default one, we don't need to filter it
34
+ # we already know it's valid (lowercase and words longer than 3 chars)
35
+ blocklist
36
+ else
37
+ # Downcase the alphabet once, rather than in the loop, to save significant time
38
+ # with large blocklists
39
+ downcased_alphabet = alphabet.downcase.chars
40
+ # Filter the blocklist
41
+ blocklist.select do |word|
42
+ word.length >= 3 && (word.downcase.chars - downcased_alphabet).empty?
43
+ end.to_set(&:downcase)
44
+ end
33
45
 
34
46
  @alphabet = shuffle(alphabet)
35
47
  @min_length = min_length
@@ -39,13 +51,13 @@ class Sqids
39
51
  def encode(numbers)
40
52
  return '' if numbers.empty?
41
53
 
42
- in_range_numbers = numbers.select { |n| n >= Sqids.min_value && n <= Sqids.max_value }
54
+ in_range_numbers = numbers.select { |n| n >= 0 && n <= Sqids.max_value }
43
55
  unless in_range_numbers.length == numbers.length
44
56
  raise ArgumentError,
45
- "Encoding supports numbers between #{Sqids.min_value} and #{Sqids.max_value}"
57
+ "Encoding supports numbers between 0 and #{Sqids.max_value}"
46
58
  end
47
59
 
48
- encode_numbers(in_range_numbers, partitioned: false)
60
+ encode_numbers(in_range_numbers)
49
61
  end
50
62
 
51
63
  def decode(id)
@@ -61,26 +73,18 @@ class Sqids
61
73
  prefix = id[0]
62
74
  offset = @alphabet.index(prefix)
63
75
  alphabet = @alphabet.slice(offset, @alphabet.length) + @alphabet.slice(0, offset)
64
- partition = alphabet[1]
65
- alphabet = alphabet.slice(2, alphabet.length)
76
+ alphabet = alphabet.reverse
66
77
 
67
78
  id = id[1, id.length]
68
79
 
69
- partition_index = id.index(partition)
70
- if !partition_index.nil? && partition_index.positive? && partition_index < id.length - 1
71
- id = id[partition_index + 1, id.length]
72
- alphabet = shuffle(alphabet)
73
- end
74
-
75
80
  while id.length.positive?
76
- separator = alphabet[-1]
77
- chunks = id.split(separator, 2)
81
+ separator = alphabet[0]
78
82
 
83
+ chunks = id.split(separator, 2)
79
84
  if chunks.any?
80
- alphabet_without_separator = alphabet.slice(0, alphabet.length - 1)
81
- return [] unless chunks[0].chars.all? { |c| alphabet_without_separator.include?(c) }
85
+ return ret if chunks[0] == ''
82
86
 
83
- ret.push(to_number(chunks[0], alphabet_without_separator))
87
+ ret.push(to_number(chunks[0], alphabet.slice(1, alphabet.length - 1)))
84
88
  alphabet = shuffle(alphabet) if chunks.length > 1
85
89
  end
86
90
 
@@ -90,14 +94,6 @@ class Sqids
90
94
  ret
91
95
  end
92
96
 
93
- def self.min_value
94
- 0
95
- end
96
-
97
- def self.max_value
98
- defined?(Integer::MAX) ? Integer::MAX : ((2**((0.size * 8) - 2)) - 1)
99
- end
100
-
101
97
  private
102
98
 
103
99
  def shuffle(alphabet)
@@ -115,59 +111,42 @@ class Sqids
115
111
  chars.join
116
112
  end
117
113
 
118
- def encode_numbers(numbers, partitioned: false)
114
+ def encode_numbers(numbers, increment: 0)
115
+ raise ArgumentError, 'Reached max attempts to re-generate the ID' if increment > @alphabet.length
116
+
119
117
  offset = numbers.length
120
118
  numbers.each_with_index do |v, i|
121
119
  offset += @alphabet[v % @alphabet.length].ord + i
122
120
  end
123
121
  offset = offset % @alphabet.length
122
+ offset = (offset + increment) % @alphabet.length
124
123
 
125
124
  alphabet = @alphabet.slice(offset, @alphabet.length) + @alphabet.slice(0, offset)
126
125
  prefix = alphabet[0]
127
- partition = alphabet[1]
128
- alphabet = alphabet.slice(2, alphabet.length)
126
+ alphabet = alphabet.reverse
129
127
  ret = [prefix]
130
128
 
131
129
  numbers.each_with_index do |num, i|
132
- alphabet_without_separator = alphabet.slice(0, alphabet.length - 1)
133
- ret.push(to_id(num, alphabet_without_separator))
130
+ ret.push(to_id(num, alphabet.slice(1, alphabet.length - 1)))
134
131
 
135
132
  next unless i < numbers.length - 1
136
133
 
137
- separator = alphabet[-1]
138
- if partitioned && i.zero?
139
- ret.push(partition)
140
- else
141
- ret.push(separator)
142
- end
143
-
134
+ ret.push(alphabet[0])
144
135
  alphabet = shuffle(alphabet)
145
136
  end
146
137
 
147
138
  id = ret.join
148
139
 
149
140
  if @min_length > id.length
150
- unless partitioned
151
- numbers = [0] + numbers
152
- id = encode_numbers(numbers, partitioned: true)
153
- end
141
+ id += alphabet[0]
154
142
 
155
- if @min_length > id.length
156
- id = id[0] + alphabet[0,
157
- @min_length - id.length] + id[1,
158
- id.length - 1]
143
+ while (@min_length - id.length).positive?
144
+ alphabet = shuffle(alphabet)
145
+ id += alphabet.slice(0, [@min_length - id.length, alphabet.length].min)
159
146
  end
160
147
  end
161
148
 
162
- if blocked_id?(id)
163
- if partitioned
164
- numbers[0] += 1
165
- else
166
- numbers = [0] + numbers
167
- end
168
-
169
- id = encode_numbers(numbers, partitioned: true)
170
- end
149
+ id = encode_numbers(numbers, increment: increment + 1) if blocked_id?(id)
171
150
 
172
151
  id
173
152
  end
@@ -206,4 +185,16 @@ class Sqids
206
185
  end
207
186
  end
208
187
  end
188
+
189
+ def contains_multibyte_chars(input_str)
190
+ input_str.each_char do |char|
191
+ return true if char.bytesize > 1
192
+ end
193
+
194
+ false
195
+ end
196
+
197
+ def self.max_value
198
+ defined?(Integer::MAX) ? Integer::MAX : ((2**((0.size * 8) - 2)) - 1)
199
+ end
209
200
  end
@@ -8,14 +8,14 @@ describe Sqids do
8
8
  sqids = Sqids.new(alphabet: '0123456789abcdef')
9
9
 
10
10
  numbers = [1, 2, 3]
11
- id = '4d9fd2'
11
+ id = '489158'
12
12
 
13
13
  expect(sqids.encode(numbers)).to eq(id)
14
14
  expect(sqids.decode(id)).to eq(numbers)
15
15
  end
16
16
 
17
17
  it 'decodes after encoding with a short alphabet' do
18
- sqids = Sqids.new(alphabet: 'abcde')
18
+ sqids = Sqids.new(alphabet: 'abc')
19
19
 
20
20
  numbers = [1, 2, 3]
21
21
  encoded = sqids.encode(numbers)
@@ -33,6 +33,12 @@ describe Sqids do
33
33
  expect(sqids.decode(encoded)).to eq(numbers)
34
34
  end
35
35
 
36
+ it 'fails when alphabet has multibyte characters' do
37
+ expect do
38
+ Sqids.new(alphabet: 'ë1092')
39
+ end.to raise_error(ArgumentError)
40
+ end
41
+
36
42
  it 'fails when alphabet characters are repeated' do
37
43
  expect do
38
44
  Sqids.new(alphabet: 'aabcdefg')
@@ -41,7 +47,7 @@ describe Sqids do
41
47
 
42
48
  it 'fails when alphabet is too short' do
43
49
  expect do
44
- Sqids.new(alphabet: 'abcd')
50
+ Sqids.new(alphabet: 'ab')
45
51
  end.to raise_error(ArgumentError)
46
52
  end
47
53
  end
@@ -8,59 +8,74 @@ describe Sqids do
8
8
  it 'uses default blocklist if no custom blocklist is provided' do
9
9
  sqids = Sqids.new
10
10
 
11
- expect(sqids.decode('sexy')).to eq([200_044])
12
- expect(sqids.encode([200_044])).to eq('d171vI')
11
+ expect(sqids.decode('aho1e')).to eq([4_572_721])
12
+ expect(sqids.encode([4_572_721])).to eq('JExTR')
13
13
  end
14
14
 
15
15
  it 'does not use any blocklist if an empty blocklist is provided' do
16
16
  sqids = Sqids.new(blocklist: Set.new([]))
17
17
 
18
- expect(sqids.decode('sexy')).to eq([200_044])
19
- expect(sqids.encode([200_044])).to eq('sexy')
18
+ expect(sqids.decode('aho1e')).to eq([4_572_721])
19
+ expect(sqids.encode([4_572_721])).to eq('aho1e')
20
20
  end
21
21
 
22
22
  it 'uses provided blocklist if non-empty blocklist is provided' do
23
- sqids = Sqids.new(blocklist: Set.new(['AvTg']))
23
+ sqids = Sqids.new(blocklist: Set.new(['ArUO']))
24
24
 
25
- expect(sqids.decode('sexy')).to eq([200_044])
26
- expect(sqids.encode([200_044])).to eq('sexy')
25
+ expect(sqids.decode('aho1e')).to eq([4_572_721])
26
+ expect(sqids.encode([4_572_721])).to eq('aho1e')
27
27
 
28
- expect(sqids.decode('AvTg')).to eq([100_000])
29
- expect(sqids.encode([100_000])).to eq('7T1X8k')
30
- expect(sqids.decode('7T1X8k')).to eq([100_000])
28
+ expect(sqids.decode('ArUO')).to eq([100_000])
29
+ expect(sqids.encode([100_000])).to eq('QyG4')
30
+ expect(sqids.decode('QyG4')).to eq([100_000])
31
31
  end
32
32
 
33
33
  it 'uses blocklist to prevent certain encodings' do
34
- sqids = Sqids.new(blocklist: Set.new(%w[8QRLaD 7T1cd0dL UeIe imhw LfUQ]))
34
+ sqids = Sqids.new(blocklist: Set.new(%w[JSwXFaosAN OCjV9JK64o rBHf 79SM 7tE6]))
35
35
 
36
- expect(sqids.encode([1, 2, 3])).to eq('TM0x1Mxz')
37
- expect(sqids.decode('TM0x1Mxz')).to eq([1, 2, 3])
36
+ expect(sqids.encode([1_000_000, 2_000_000])).to eq('1aYeB7bRUt')
37
+ expect(sqids.decode('1aYeB7bRUt')).to eq([1_000_000, 2_000_000])
38
38
  end
39
39
 
40
40
  it 'can decode blocklist words' do
41
- sqids = Sqids.new(blocklist: Set.new(%w[8QRLaD 7T1cd0dL RA8UeIe7 WM3Limhw LfUQh4HN]))
41
+ sqids = Sqids.new(blocklist: Set.new(%w[86Rf07 se8ojk ARsz1p Q8AI49 5sQRZO]))
42
42
 
43
- expect(sqids.decode('8QRLaD')).to eq([1, 2, 3])
44
- expect(sqids.decode('7T1cd0dL')).to eq([1, 2, 3])
45
- expect(sqids.decode('RA8UeIe7')).to eq([1, 2, 3])
46
- expect(sqids.decode('WM3Limhw')).to eq([1, 2, 3])
47
- expect(sqids.decode('LfUQh4HN')).to eq([1, 2, 3])
43
+ expect(sqids.decode('86Rf07')).to eq([1, 2, 3])
44
+ expect(sqids.decode('se8ojk')).to eq([1, 2, 3])
45
+ expect(sqids.decode('ARsz1p')).to eq([1, 2, 3])
46
+ expect(sqids.decode('Q8AI49')).to eq([1, 2, 3])
47
+ expect(sqids.decode('5sQRZO')).to eq([1, 2, 3])
48
48
  end
49
49
 
50
50
  it 'matches against a short blocklist word' do
51
- sqids = Sqids.new(blocklist: Set.new(['pPQ']))
51
+ sqids = Sqids.new(blocklist: Set.new(['pnd']))
52
52
 
53
53
  expect(sqids.decode(sqids.encode([1_000]))).to eq([1_000])
54
54
  end
55
55
 
56
56
  it 'blocklist filtering in constructor' do
57
57
  # lowercase blocklist in only-uppercase alphabet
58
- sqids = Sqids.new(alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', blocklist: Set.new(['sqnmpn']))
58
+ sqids = Sqids.new(alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', blocklist: Set.new(['sxnzkl']))
59
59
 
60
60
  id = sqids.encode([1, 2, 3])
61
61
  numbers = sqids.decode(id)
62
62
 
63
- expect(id).to eq('ULPBZGBM') # without blocklist, would've been "SQNMPN"
63
+ expect(id).to eq('IBSHOZ') # without blocklist, would've been "SXNZKL"
64
64
  expect(numbers).to eq([1, 2, 3])
65
65
  end
66
+
67
+ it 'max encoding attempts' do
68
+ alphabet = 'abc'
69
+ min_length = 3
70
+ blocklist = Set.new(%w[cab abc bca])
71
+
72
+ sqids = Sqids.new(alphabet: alphabet, min_length: min_length, blocklist: blocklist)
73
+
74
+ expect(min_length).to eq(alphabet.length)
75
+ expect(min_length).to eq(blocklist.size)
76
+
77
+ expect do
78
+ sqids.encode([0])
79
+ end.to raise_error(ArgumentError)
80
+ end
66
81
  end
@@ -8,7 +8,7 @@ describe 'Sqids' do
8
8
  sqids = Sqids.new
9
9
 
10
10
  numbers = [1, 2, 3]
11
- id = '8QRLaD'
11
+ id = '86Rf07'
12
12
 
13
13
  expect(sqids.encode(numbers)).to eq(id)
14
14
  expect(sqids.decode(id)).to eq(numbers)
@@ -25,16 +25,16 @@ describe 'Sqids' do
25
25
  sqids = Sqids.new
26
26
 
27
27
  ids = {
28
- 'bV' => [0],
29
- 'U9' => [1],
30
- 'g8' => [2],
31
- 'Ez' => [3],
32
- 'V8' => [4],
33
- 'ul' => [5],
34
- 'O3' => [6],
35
- 'AF' => [7],
36
- 'ph' => [8],
37
- 'n8' => [9]
28
+ 'bM' => [0],
29
+ 'Uk' => [1],
30
+ 'gb' => [2],
31
+ 'Ef' => [3],
32
+ 'Vq' => [4],
33
+ 'uw' => [5],
34
+ 'OI' => [6],
35
+ 'AX' => [7],
36
+ 'p6' => [8],
37
+ 'nJ' => [9]
38
38
  }
39
39
 
40
40
  ids.each do |id, numbers|
@@ -47,16 +47,16 @@ describe 'Sqids' do
47
47
  sqids = Sqids.new
48
48
 
49
49
  ids = {
50
- 'SrIu' => [0, 0],
51
- 'nZqE' => [0, 1],
52
- 'tJyf' => [0, 2],
53
- 'e86S' => [0, 3],
54
- 'rtC7' => [0, 4],
55
- 'sQ8R' => [0, 5],
56
- 'uz2n' => [0, 6],
57
- '7Td9' => [0, 7],
58
- '3nWE' => [0, 8],
59
- 'mIxM' => [0, 9]
50
+ 'SvIz' => [0, 0],
51
+ 'n3qa' => [0, 1],
52
+ 'tryF' => [0, 2],
53
+ 'eg6q' => [0, 3],
54
+ 'rSCF' => [0, 4],
55
+ 'sR8x' => [0, 5],
56
+ 'uY2M' => [0, 6],
57
+ '74dI' => [0, 7],
58
+ '30WX' => [0, 8],
59
+ 'moxr' => [0, 9]
60
60
  }
61
61
 
62
62
  ids.each do |id, numbers|
@@ -69,16 +69,16 @@ describe 'Sqids' do
69
69
  sqids = Sqids.new
70
70
 
71
71
  ids = {
72
- 'SrIu' => [0, 0],
73
- 'nbqh' => [1, 0],
74
- 't4yj' => [2, 0],
75
- 'eQ6L' => [3, 0],
76
- 'r4Cc' => [4, 0],
77
- 'sL82' => [5, 0],
78
- 'uo2f' => [6, 0],
79
- '7Zdq' => [7, 0],
80
- '36Wf' => [8, 0],
81
- 'm4xT' => [9, 0]
72
+ 'SvIz' => [0, 0],
73
+ 'nWqP' => [1, 0],
74
+ 'tSyw' => [2, 0],
75
+ 'eX68' => [3, 0],
76
+ 'rxCY' => [4, 0],
77
+ 'sV8a' => [5, 0],
78
+ 'uf2K' => [6, 0],
79
+ '7Cdk' => [7, 0],
80
+ '3aWP' => [8, 0],
81
+ 'm2xn' => [9, 0]
82
82
  }
83
83
 
84
84
  ids.each do |id, numbers|
@@ -110,14 +110,9 @@ describe 'Sqids' do
110
110
  expect(sqids.decode('*')).to eq([])
111
111
  end
112
112
 
113
- it 'decoding an invalid ID with a repeating reserved character' do
114
- sqids = Sqids.new
115
- expect(sqids.decode('fff')).to eq([])
116
- end
117
-
118
113
  it 'encode out-of-range numbers' do
119
114
  sqids = Sqids.new
120
- expect { sqids.encode([Sqids.min_value - 1]) }.to raise_error(ArgumentError)
115
+ expect { sqids.encode([-1]) }.to raise_error(ArgumentError)
121
116
  expect { sqids.encode([Sqids.max_value + 1]) }.to raise_error(ArgumentError)
122
117
  end
123
118
  end
@@ -10,26 +10,52 @@ describe Sqids do
10
10
  sqids = Sqids.new(min_length: default_alphabet.length)
11
11
 
12
12
  numbers = [1, 2, 3]
13
- id = '75JILToVsGerOADWmHlY38xvbaNZKQ9wdFS0B6kcMEtnRpgizhjU42qT1cd0dL'
13
+ id = '86Rf07xd4zBmiJXQG6otHEbew02c3PWsUOLZxADhCpKj7aVFv9I8RquYrNlSTM'
14
14
 
15
15
  expect(sqids.encode(numbers)).to eq(id)
16
16
  expect(sqids.decode(id)).to eq(numbers)
17
17
  end
18
18
 
19
+ it 'encodes and decodes incremental' do
20
+ numbers = [1, 2, 3]
21
+ map = {
22
+ 6 => '86Rf07',
23
+ 7 => '86Rf07x',
24
+ 8 => '86Rf07xd',
25
+ 9 => '86Rf07xd4',
26
+ 10 => '86Rf07xd4z',
27
+ 11 => '86Rf07xd4zB',
28
+ 12 => '86Rf07xd4zBm',
29
+ 13 => '86Rf07xd4zBmi'
30
+ }
31
+ map[default_alphabet.length + 0] = '86Rf07xd4zBmiJXQG6otHEbew02c3PWsUOLZxADhCpKj7aVFv9I8RquYrNlSTM'
32
+ map[default_alphabet.length + 1] = '86Rf07xd4zBmiJXQG6otHEbew02c3PWsUOLZxADhCpKj7aVFv9I8RquYrNlSTMy'
33
+ map[default_alphabet.length + 2] = '86Rf07xd4zBmiJXQG6otHEbew02c3PWsUOLZxADhCpKj7aVFv9I8RquYrNlSTMyf'
34
+ map[default_alphabet.length + 3] = '86Rf07xd4zBmiJXQG6otHEbew02c3PWsUOLZxADhCpKj7aVFv9I8RquYrNlSTMyf1'
35
+
36
+ map.each do |min_length, id|
37
+ sqids = Sqids.new(min_length: min_length)
38
+
39
+ expect(sqids.encode(numbers)).to eq(id)
40
+ expect(sqids.encode(numbers).length).to eq(min_length)
41
+ expect(sqids.decode(id)).to eq(numbers)
42
+ end
43
+ end
44
+
19
45
  it 'encodes and decodes incremental numbers' do
20
46
  sqids = Sqids.new(min_length: default_alphabet.length)
21
47
 
22
48
  ids = {
23
- 'jf26PLNeO5WbJDUV7FmMtlGXps3CoqkHnZ8cYd19yIiTAQuvKSExzhrRghBlwf' => [0, 0],
24
- 'vQLUq7zWXC6k9cNOtgJ2ZK8rbxuipBFAS10yTdYeRa3ojHwGnmMV4PDhESI2jL' => [0, 1],
25
- 'YhcpVK3COXbifmnZoLuxWgBQwtjsSaDGAdr0ReTHM16yI9vU8JNzlFq5Eu2oPp' => [0, 2],
26
- 'OTkn9daFgDZX6LbmfxI83RSKetJu0APihlsrYoz5pvQw7GyWHEUcN2jBqd4kJ9' => [0, 3],
27
- 'h2cV5eLNYj1x4ToZpfM90UlgHBOKikQFvnW36AC8zrmuJ7XdRytIGPawqYEbBe' => [0, 4],
28
- '7Mf0HeUNkpsZOTvmcj836P9EWKaACBubInFJtwXR2DSzgYGhQV5i4lLxoT1qdU' => [0, 5],
29
- 'APVSD1ZIY4WGBK75xktMfTev8qsCJw6oyH2j3OnLcXRlhziUmpbuNEar05QCsI' => [0, 6],
30
- 'P0LUhnlT76rsWSofOeyRGQZv1cC5qu3dtaJYNEXwk8Vpx92bKiHIz4MgmiDOF7' => [0, 7],
31
- 'xAhypZMXYIGCL4uW0te6lsFHaPc3SiD1TBgw5O7bvodzjqUn89JQRfk2Nvm4JI' => [0, 8],
32
- '94dRPIZ6irlXWvTbKywFuAhBoECQOVMjDJp53s2xeqaSzHY8nc17tmkLGwfGNl' => [0, 9]
49
+ 'SvIzsqYMyQwI3GWgJAe17URxX8V924Co0DaTZLtFjHriEn5bPhcSkfmvOslpBu' => [0, 0],
50
+ 'n3qafPOLKdfHpuNw3M61r95svbeJGk7aAEgYn4WlSjXURmF8IDqZBy0CT2VxQc' => [0, 1],
51
+ 'tryFJbWcFMiYPg8sASm51uIV93GXTnvRzyfLleh06CpodJD42B7OraKtkQNxUZ' => [0, 2],
52
+ 'eg6ql0A3XmvPoCzMlB6DraNGcWSIy5VR8iYup2Qk4tjZFKe1hbwfgHdUTsnLqE' => [0, 3],
53
+ 'rSCFlp0rB2inEljaRdxKt7FkIbODSf8wYgTsZM1HL9JzN35cyoqueUvVWCm4hX' => [0, 4],
54
+ 'sR8xjC8WQkOwo74PnglH1YFdTI0eaf56RGVSitzbjuZ3shNUXBrqLxEJyAmKv2' => [0, 5],
55
+ 'uY2MYFqCLpgx5XQcjdtZK286AwWV7IBGEfuS9yTmbJvkzoUPeYRHr4iDs3naN0' => [0, 6],
56
+ '74dID7X28VLQhBlnGmjZrec5wTA1fqpWtK4YkaoEIM9SRNiC3gUJH0OFvsPDdy' => [0, 7],
57
+ '30WXpesPhgKiEI5RHTY7xbB1GnytJvXOl2p0AcUjdF6waZDo9Qk8VLzMuWrqCS' => [0, 8],
58
+ 'moxr3HqLAK0GsTND6jowfZz3SUx7cQ8aC54Pl1RbIvFXmEJuBMYVeW9yrdOtin' => [0, 9]
33
59
  }
34
60
 
35
61
  ids.each do |id, numbers|
@@ -41,7 +67,7 @@ describe Sqids do
41
67
  it 'encodes with different min lengths' do
42
68
  [0, 1, 5, 10, default_alphabet.length].each do |min_length|
43
69
  [
44
- [Sqids.min_value],
70
+ [0],
45
71
  [0, 0, 0, 0, 0],
46
72
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
47
73
  [100, 200, 300],
@@ -60,6 +86,6 @@ describe Sqids do
60
86
 
61
87
  it 'raises error for out-of-range invalid min lengths' do
62
88
  expect { Sqids.new(min_length: -1) }.to raise_error(TypeError)
63
- expect { Sqids.new(min_length: default_alphabet.length + 1) }.to raise_error(TypeError)
89
+ expect { Sqids.new(min_length: 256) }.to raise_error(TypeError)
64
90
  end
65
91
  end
data/sqids.gemspec CHANGED
@@ -7,7 +7,7 @@ require 'sqids'
7
7
 
8
8
  Gem::Specification.new do |gem|
9
9
  gem.name = 'sqids'
10
- gem.version = '0.1.2'
10
+ gem.version = '0.2.1'
11
11
  gem.authors = ['Sqids Maintainers']
12
12
  gem.summary = 'Generate YouTube-like ids from numbers.'
13
13
  gem.homepage = 'https://sqids.org/ruby'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqids
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sqids Maintainers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-31 00:00:00.000000000 Z
11
+ date: 2024-07-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -27,7 +27,6 @@ files:
27
27
  - spec/blocklist_spec.rb
28
28
  - spec/encoding_spec.rb
29
29
  - spec/minlength_spec.rb
30
- - spec/uniques_spec.rb
31
30
  - sqids.gemspec
32
31
  homepage: https://sqids.org/ruby
33
32
  licenses:
data/spec/uniques_spec.rb DELETED
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rspec'
4
- require_relative '../lib/sqids'
5
-
6
- upper = 10_000
7
-
8
- describe Sqids do
9
- let(:sqids) { Sqids.new(min_length: Sqids::DEFAULT_ALPHABET.length) }
10
-
11
- it 'uniques, with padding' do
12
- set = Set.new
13
-
14
- (0...upper).each do |i|
15
- numbers = [i]
16
- id = sqids.encode(numbers)
17
- set.add(id)
18
- expect(sqids.decode(id)).to eq(numbers)
19
- end
20
-
21
- expect(set.size).to eq(upper)
22
- end
23
-
24
- it 'uniques, low ranges' do
25
- set = Set.new
26
-
27
- (0...upper).each do |i|
28
- numbers = [i]
29
- id = sqids.encode(numbers)
30
- set.add(id)
31
- expect(sqids.decode(id)).to eq(numbers)
32
- end
33
-
34
- expect(set.size).to eq(upper)
35
- end
36
-
37
- it 'uniques, high ranges' do
38
- set = Set.new
39
-
40
- (100_000_000...(100_000_000 + upper)).each do |i|
41
- numbers = [i]
42
- id = sqids.encode(numbers)
43
- set.add(id)
44
- expect(sqids.decode(id)).to eq(numbers)
45
- end
46
-
47
- expect(set.size).to eq(upper)
48
- end
49
-
50
- it 'uniques, multi' do
51
- set = Set.new
52
-
53
- (0...upper).each do |i|
54
- numbers = [i, i, i, i, i]
55
- id = sqids.encode(numbers)
56
- set.add(id)
57
- expect(sqids.decode(id)).to eq(numbers)
58
- end
59
-
60
- expect(set.size).to eq(upper)
61
- end
62
- end