Url Grey v 0.1.2 release notes# 10 Jun 2017 by Sean
New in release 0.1.2
This release introduces the ability to use randomly generated slugs for Short URLs.
To accomplish this, the following changes were made:
- Inclusion of
rails-settings-cachedgem to track the currently available random slug number, for base conversion
- Inclusion of the
radixgem for base conversion into base 37
- Class methods for
ShortUrlwere added to base convert into a custom base 37 alphabet
- A new
before_validationfilter was added to
ShortUrlto generate a random slug if requested. This is done before validation to ensure that the slug validations are still used
This release also adds
app.json to automate deployment tasks on Dokku.
Challenges for a learning dev
This was actually a lot of fun to work on, and quite challenging. I spent the most time on two things:
- figuring out how to generate random slugs
- hooking the slug generator into the existing application
Which… really is most of the application I guess, but both of these things took quite a bit of work.
Random slug generation lead to infinite loops
My first pass at this wasn’t too bad actually. I threw all the available chars for redirects into an array, then
.sampled it a number of times equal to the random slug’s requested length. It was a nice 2 or 3 line method that worked great… until the
uniqueness: true validation on the slugs kicked in and some started failing.
First fix to this was to recursively recall the method if the slug was taken. However, if you had requested a slug of, say 1
char, then there are only 36 possible options (I’ve disallowed the 37th char, ‘-‘, from being used in random slugs). Which means that if you try to recursively call
ShortUrl.random_slug(1) and all 36 options are taken, you get into an infinite loop. Which
Minitest pointed out nice and quick.
At this point I considered writing in a fallover so that if the requested length wasn’t available, the generator would automatically increase it. But the inifinite loop issue had also made me aware that it was super inefficient to be trying to write to the database on every pass. Time for a new approach…
Tracking current random slug with an app setting
So, instead, the app is maintaining a DB table for
AppConfig. Which I’m intending to use more for other settings later (because it seems silly to have a table with just 1 row). I’m using the
rails-settings-cached gem for this, and it makes it nice and easily. One
db:migrate and you’re up.
This is an integer number, and when the app needs to create a random slug, it turns this number into a a base-37 number using the
radix gem. Which allows me to create a custom base-37 alphabet to use in the standard
base_37_alphabet = %w(0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v x y z -) AppConfig.current_random_slug.b(10).to_s(base_37_alphabet)
The only difference between standard Ruby and
radix is that
.b(base) method–it tells
radix what base you’re converting from, and then opens up the custom conversion methods after.
Hooking the generator into the app
So… this actually had me flummoxed for a bit. The previous stuff was complicated, and
radix helped save me a lot of time, but I could have written my own base conversion tools from scratch if necessary. But hooking it into the app caused me to get in a few fights with ‘Rails Magic (TM)’.
First I wrote some tests, because at the least I knew what it needed to do–return slugs of the right length, return unique slugs, and return valid slugs. I’ve also written a case where a slug requested of a particular length gets elongated if necessary, but that’s just for kicks.
First pass I put the logic into the controller, but no matter what I did the validations weren’t passing.
So I put some logic into the validations, but they were never getting the right params. So I added a
:random_slug form element in, and then Rails started bitching at me about unknown methods. A quick Stack Overflow search pointed me to creating a ‘virtual attribute’ with
attr_accessor :random_slug in the model, which helped… but then I ran afoul of the validations again.
The logic was trying to replace an empty
:slug with a random one when randoms were requested, but I couldn’t get it to fire at all. It took me longer than I’d care to admit to figure out I should try another filter, this time
before_validation instead of
_save, which is what finally fixed the issue. It was a lot of commenting out code and seeing what errors were thrown with each change.
But, we got there in the end!
This site has turned off cross-site user tracking for Disqus, as much as is possible. If you want to know more about it, Afterpad has a decent post about it, explaining what is tracked, and what a site opting out means.