Skip to content

Commit 0f16c47

Browse files
committed
Add documentation
1 parent d3a713e commit 0f16c47

File tree

1 file changed

+229
-43
lines changed

1 file changed

+229
-43
lines changed

README.md

Lines changed: 229 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,77 @@
11
# EncodedId
22

3-
Encode your numerical IDs (eg record primary keys) into obfuscated strings that can be used in URLs.
3+
Encode numerical or hex IDs into obfuscated strings that can be used in URLs.
44

5-
`::EncodedId::ReversibleId.new(salt: my_salt).encode(123)` => `"p5w9-z27j"`
5+
```ruby
6+
coder = ::EncodedId::ReversibleId.new(salt: my_salt)
7+
coder.encode(123)
8+
# => "p5w9-z27j"
9+
coder.encode_hex("10f8c")
10+
# => "w72a-y0az"
11+
```
612

7-
The obfuscated strings are reversible, so you can decode them back into the original numerical IDs. Also supports
8-
encoding multiple IDs at once.
13+
The obfuscated strings are reversible (they decode them back into the original IDs).
914

10-
```
11-
reversibles = ::EncodedId::ReversibleId.new(salt: my_salt)
12-
reversibles.encode([78, 45]) # "7aq6-0zqw"
13-
reversibles.decode("7aq6-0zqw") # [78, 45]
14-
```
15+
Also supports encoding multiple IDs at once.
1516

16-
Length of the ID, the alphabet used, and the number of characters per group can be configured.
17+
```ruby
18+
my_salt = "salt!"
19+
coder = ::EncodedId::ReversibleId.new(salt: my_salt)
1720

18-
The custom alphabet (at least 16 characters needed) and character group sizes is to make the IDs easier to read or share.
19-
Easily confused characters (eg `i` and `j`, `0` and `O`, `1` and `I` etc) are mapped to counterpart characters, to help
20-
common mistakes when sharing (eg customer over phone to customer service agent).
21+
# One of more values can be encoded
22+
coder.encode([78, 45])
23+
# => "z2j7-0dmw"
2124

22-
Also supports UUIDs if needed
25+
# The encoded string can then be reversed back into the original IDs
26+
coder.decode("z2j7-0dmw")
27+
# => [78, 45]
2328

24-
```
25-
::EncodedId::ReversibleId.new(salt: my_salt).encode_hex("9a566b8b-8618-42ab-8db7-a5a0276401fd")
26-
=> "rppv-tg8a-cx8q-gu9e-zq15-jxes-4gpr-06xk-wfk8-aw"
29+
# The decoder can be resilient to easily confused characters
30+
coder.decode("z2j7-Odmw") # (note the capital 'o' instead of zero)
31+
# => [78, 45]
2732
```
2833

2934
## Features
3035

31-
Build with https://hashids.org
36+
* encoded IDs are reversible (uses with https://hashids.org)
37+
* supports slugged IDs (eg `beef-tenderloins-prime--p5w9-z27j`)
38+
* supports multiple IDs encoded in one encoded string (eg `7aq6-0zqw` decodes to `[78, 45]`)
39+
* supports encoding of hex strings (eg UUIDs), including multiple IDs encoded in one string **(experimental)**
40+
* supports custom alphabets for the encoded string (at least 16 characters needed)
41+
- by default uses a variation of the Crockford reduced character set (https://www.crockford.com/base32.html)
42+
- easily confused characters (eg `i` and `j`, `0` and `O`, `1` and `I` etc) are mapped to counterpart characters, to help
43+
avoid common readability mistakes when reading/sharing
44+
- build in profanity limitation
45+
* encoded string can be split into groups of letters to improve human-readability
46+
- eg `nft9hr834htu` as `nft9-hr83-4htu`
3247

33-
* Hashids are reversible, no need to persist the generated Id
34-
* supports slugged IDs (eg 'beef-tenderloins-prime--p5w9-z27j')
35-
* supports multiple IDs encoded in one `EncodedId` (eg '7aq6-0zqw' decodes to `[78, 45]`)
36-
* supports encoding of hex strings (eg UUIDs), including mutliple IDs encoded in one `EncodedId`
37-
* uses a reduced character set (Crockford alphabet) & ids split into groups of letters, ie 'human-readability'
38-
* profanity limitation
48+
### Rails support `encoded_id-rails`
3949

40-
To use with **Rails** check out the `encoded_id-rails` gem.
50+
To use with **Rails** check out the [`encoded_id-rails`](https://github.com/stevegeek/encoded_id-rails) gem.
4151

42-
## Note on security of encoded IDs (hashids)
52+
```ruby
53+
class User < ApplicationRecord
54+
include EncodedId::WithEncodedId
55+
end
56+
57+
User.find_by_encoded_id("p5w9-z27j")
58+
# => #<User id: 78>
59+
```
60+
61+
### Note on security of encoded IDs (hashids)
4362

4463
**Encoded IDs are not secure**. It maybe possible to reverse them via brute-force. They are meant to be used in URLs as
4564
an obfuscation. The algorithm is not an encryption.
4665

4766
Please read more on https://hashids.org/
4867

49-
50-
## Compared to alternate Gems
68+
## Compare to alternate Gems
5169

5270
- https://github.com/excid3/prefixed_ids
5371
- https://github.com/namick/obfuscate_id
5472
- https://github.com/norman/friendly_id
5573
- https://github.com/SPBTV/with_uid
5674

57-
## See also
58-
59-
- https://hashids.org
60-
- https://www.crockford.com/wrmg/base32.html
61-
62-
6375
## Installation
6476

6577
Install the gem and add to the application's Gemfile by executing:
@@ -70,20 +82,189 @@ If bundler is not being used to manage dependencies, install the gem by executin
7082

7183
$ gem install encoded_id
7284

73-
## Usage
85+
## `EncodedId::ReversibleId.new`
86+
87+
To create an instance of the encoder/decoder use `.new` with the `salt` option:
88+
89+
```ruby
90+
coder = EncodedId::ReversibleId.new(
91+
# The salt is required
92+
salt: ...,
93+
# And then the following options are optional
94+
length: 8,
95+
split_at: 4,
96+
split_with: "-",
97+
alphabet: EncodedId::Alphabet.modified_crockford,
98+
hex_digit_encoding_group_size: 4 # Experimental
99+
)
100+
```
101+
102+
Note the `salt` value is required and should be a string of some length (greater than 3 characters). This is used to generate the encoded string.
103+
104+
It will need to be the same value when decoding the string back into the original ID. If the salt is changed, the encoded
105+
strings will be different and possibly decode to different IDs.
106+
107+
### Options
74108

75-
TODO: Write usage instructions here
109+
The encoded ID is configurable. The following can be changed:
76110

77-
### Rails
111+
- the length, eg 8 characters for `p5w9-z27j`
112+
- the alphabet used in it (min 16 characters)
113+
- and the number of characters to split the output into and the separator
78114

79-
To use with rails try the `encoded_id-rails` gem.
115+
### `length`
116+
117+
`length`: the minimum length of the encoded string. The default is 8 characters.
118+
119+
The actual length of the encoded string can be longer if the inputs cannot be represented in the minimum length.
120+
121+
### `alphabet`
122+
123+
`alphabet`: the alphabet used in the encoded string. By default it uses a variation of the Crockford reduced character set (https://www.crockford.com/base32.html).
124+
125+
`alphabet` must be an instance of `EncodedId::Alphabet`.
126+
127+
The default alphabet is `EncodedId::Alphabet.modified_crockford`.
128+
129+
To create a new alphabet, use `EncodedId::Alphabet.new`:
80130

81131
```ruby
82-
class User < ApplicationRecord
83-
include EncodedId::WithEncodedId
84-
end
132+
alphabet = EncodedId::Alphabet.new("0123456789abcdef")
133+
```
134+
135+
`EncodedId::Alphabet.new(characters, equivalences)`
136+
137+
**characters**
138+
139+
`characters`: the characters of the alphabet. Can be a string or array of strings.
140+
141+
Note that the `characters` of the alphabet must be at least 16 _unique_ characters long.
85142

86-
User.find_by_encoded_id("p5w9-z27j") # => #<User id: 78>
143+
144+
```ruby
145+
alphabet = EncodedId::Alphabet.new("ςερτυθιοπλκξηγφδσαζχψωβνμ")
146+
coder = ::EncodedId::ReversibleId.new(salt: my_salt, alphabet: alphabet)
147+
coder.encode(123)
148+
# => "πφλχ-ψησω"
149+
```
150+
151+
Note that larger alphabets can result in shorter encoded strings (but remember that `length` specifies the minimum length
152+
of the encoded string).
153+
154+
**equivalences**
155+
156+
You can optionally pass an appropriate character `equivalences` mapping. This is used to map easily confused characters
157+
to their counterpart.
158+
159+
`equivalences`: a hash of characters keys, with their equivalent alphabet character mapped to in the values.
160+
161+
Note that the characters to be mapped:
162+
- must not be in the alphabet,
163+
- must map to a character that is in the alphabet.
164+
165+
`nil` is the default value which means no equivalences are used.
166+
167+
```ruby
168+
alphabet = EncodedId::Alphabet.new("!@#$%^&*()+-={}", {"_" => "-"})
169+
coder = ::EncodedId::ReversibleId.new(salt: my_salt, alphabet: alphabet)
170+
coder.encode(123)
171+
# => "}*^(-^}*="
172+
```
173+
174+
### `split_at` and `split_with`
175+
176+
For readability, the encoded string can be split into groups of characters.
177+
178+
`split_at`: specifies the number of characters to split the encoded string into. Defaults to 4.
179+
180+
`split_with`: specifies the separator to use between the groups. Default is `-`.
181+
182+
### `hex_digit_encoding_group_size`
183+
184+
**Experimental**
185+
186+
`hex_digit_encoding_group_size`: specifies the number of hex digits to encode in a group. Defaults to 4. Can be
187+
between 1 and 32.
188+
189+
Can be used to control the size of the encoded string when encoding hex strings. Larger values will result in shorter
190+
encoded strings for long inputs, and shorter values will result in shorter encoded strings for smaller inputs.
191+
192+
But note that bigger values will also result in larger markers that separate the groups so could end up increasing
193+
the encoded string length undesirably.
194+
195+
See below section `Using with hex strings` for more details.
196+
197+
## `EncodedId::ReversibleId#encode`
198+
199+
`#encode(id)`: where `id` is an integer or array of integers to encode.
200+
201+
```ruby
202+
coder.encode(123)
203+
# => "p5w9-z27j"
204+
205+
# One of more values can be encoded
206+
coder.encode([78, 45])
207+
# => "z2j7-0dmw"
208+
```
209+
210+
## `EncodedId::ReversibleId#decode`
211+
212+
`#decode(encoded_id)`: where `encoded_id` is a string to decode.
213+
214+
```ruby
215+
# The encoded string can then be reversed back into the original IDs
216+
coder.decode("z2j7-0dmw")
217+
# => [78, 45]
218+
```
219+
220+
## Using with hex strings
221+
222+
**Experimental** (subject to incompatible changes in future versions)
223+
224+
```ruby
225+
# Hex input strings are also supported
226+
coder.encode_hex("10f8c")
227+
# => "w72a-y0az"
228+
```
229+
230+
When encoding hex strings, the input is split into groups of hex digits, and each group is encoded separately as its
231+
integer equivalent. In other words the input is converted into an array of integers and encoded as normal with the
232+
`encode` method.
233+
234+
eg with `hex_digit_encoding_group_size=1` and inpu `f1`, is split into `f` and `1`, and then encoded as `15` and `1`
235+
respectively, ie `encode` is called with `[15, 1]`.
236+
237+
To encode multiple hex inputs the encoded string contains markers to indicate the start of a new hex input. This
238+
marker is equal to an integer value which is 1 larger than the maximum value the hex digit encoding group size can
239+
represent (ie it is `2^(hex_digit_encoding_group_size * 4)`).
240+
241+
So for a hex digit encoding group size of 4 (ie group max value is `0xFFFF`), the marker is `65536`
242+
243+
For example with `hex_digit_encoding_group_size=1` for the inputs `f1` and `e2` encoded together, the
244+
actual encoded integer array is `[15, 1, 16, 14, 2]`.
245+
246+
### `EncodedId::ReversibleId#encode_hex`
247+
248+
`encode_hex(hex_string)` , where `hex_string` is a string of hex digits or an array of hex strings.
249+
250+
```ruby
251+
# UUIDs will result in long output strings...
252+
coder.encode_hex("9a566b8b-8618-42ab-8db7-a5a0276401fd")
253+
# => "5jjy-c8d9-hxp2-qsve-rgh9-rxnt-7nb5-tve7-bf84-vr"
254+
#
255+
# but there is an option to help reduce this...
256+
coder = ::EncodedId::ReversibleId.new(salt: my_salt, hex_digit_encoding_group_size: 32)
257+
coder.encode_hex("9a566b8b-8618-42ab-8db7-a5a0276401fd")
258+
# => "vr7m-qra8-m5y6-dkgj-5rqr-q44e-gp4a-52"
259+
```
260+
261+
### `EncodedId::ReversibleId#decode_hex`
262+
263+
`decode_hex(encoded_id)` , where the output is an array of hex strings.
264+
265+
```ruby
266+
coder.decode_hex("5jjy-c8d9-hxp2-qsve-rgh9-rxnt-7nb5-tve7-bf84-vr")
267+
# => ["9a566b8b-8618-42ab-8db7-a5a0276401fd"]
87268
```
88269

89270
## Development
@@ -95,9 +276,14 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
95276
number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git
96277
commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
97278

279+
## See also
280+
281+
- https://hashids.org
282+
- https://www.crockford.com/base32.html
283+
98284
## Contributing
99285

100-
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/encoded_id.
286+
Bug reports and pull requests are welcome on GitHub at https://github.com/stevegeek/encoded_id.
101287

102288
## License
103289

0 commit comments

Comments
 (0)