Simple slug generator
One of the often-used patterns in web development is generating a quasi-random string. These are great for creating ‘slugs’ as often seen in URL shorteners, & in this mobile-friendly age, passing around small URLs is obligatory due to the limitations of small screens & SMS.
In Erlang, we are going to write a small module called ‘slug.erl’ which can handle all of this. Our home-spun module will yield a quasi-random string of characters & with that, we can let our imagination run wild with what to do with the results.
Preppin’
Before we open an editor, let’s lay out a few of the goals for what our code should do:
- The API should be flexible in generating slugs of a given length
- For UX purposes, we don’t want any slug to contain the zero or capital oh characters — they look the same on some screens after all
- Our code should make a valiant effort to generate solid randomness
With that small specification in place, we should be good to take a stab at it.
Code
Our slug should incorporate a range of characters, and although there are a plethora of ways to accomplish this, there’s nothing wrong with being pedantic about it and just list them all out by hand.
With that concession made, here’s the first few lines of our new module, ‘slug.erl’ :
-module(slug).
-export([new/1]).
-define(CHARS, "ABCDEFGHIJKLMNPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz123456789").
If you take a close look, you’ll notice that our macro does not include our forbidden characters (by design, of course).
Moving along, we next need to implement new/1
. First off,
it will have to seed a random value; a set of integers which
gets stored via rand:seed/n
in the calling process’s ‘process
dictionary.’ We use the ‘crypto’ module to generate the randomness,
though this may be overkill for some.
To understand this, open an Erlang shell & perform the following:
1> process_info(self()). % Note the `dictionary' value
2> <<X:32, Y:32, Z:32>> = crypto:strong_rand_bytes(12).
3> rand:seed(exs1024s, {X, Y, Z}).
4> process_info(self()). % Note the change?
N.B. Older installs of OTP will want to leverage
the random
module, instead of the newer rand
one.
From there, there’s almost nothing left to code. Here’s ‘slug.erl’ in its entirety:
-module(slug).
-export([new/1]).
-define(CHARS, "ABCDEFGHIJKLMNPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz123456789").
new(N) ->
<<X:32, Y:32, Z:32>> = crypto:strong_rand_bytes(12),
rand:seed(exs1024s, {X, Y, Z}),
Cs = list_to_tuple(?CHARS),
CsLen = tuple_size(Cs),
[ pick(CsLen, Cs) || _ <- lists:seq(1, N) ].
pick(K, Ts) ->
C = rand:uniform(K),
element(C, Ts).
Working backwards, our pick/2
routine first
chooses a random value between 1
and the given
argument/number, K
. With that random index, it
looks up the apt value from tuple Ts
.
In the new/1
routine, after the familiar seeding
is accomplished, we then cast our string literal
from a list
(more on that here) to a tuple, so we can benefit from O(1)
access.
Finally, for given length N
, we iterate N
times to get a random string of characters from
the list we have specified in our macro.
Fin
With that, we can efficiently generate random slugs for constructing identifiers for URLs, create quasi-unique IDs for database records, or what have you. Not bad for a mere dozen lines of code!