Simple Short URLs

Creating short URLs
I want to share with you an idea, or how to reinvent the wheel, if you need a short URL, but can’t use side solutions, such as goo.gl, and have a short domain. You can make a URL shorter on your own. First, we need to create a model with a destination path, parameter fields, and an appropriate controller. After that, we can route.
1 | <span class="n">get</span> <span class="s1">'/:id'</span> <span class="o">=></span> <span class="s1">'short_url#show'</span> |
Then, we can make a redirect to a required path with saved parameters. If you need to create a very short URL for a tweet, for example, a digital :id
can eat too many symbols in the result URL. To make it shorter, we can convert an :id
number to a string (what, actually, URL shorters do). I have resolved this issue with the help of the Base64 standart. In our case, it’s just an array with characters and their IDs (codes). To convert an :id
number to a short string, first, we convert it into a URL with Base64. This is the example of the ShortUrl
model.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | <span class="k">class</span> <span class="nc">ShortUrl</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span> <span class="no">SYMBOLS</span> <span class="o">=</span> <span class="s2">"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"</span> <span class="no">DEFAULT_URL</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"http://"</span><span class="p">,</span> <span class="no">APP_CONFIG</span><span class="p">[</span><span class="s1">'host'</span><span class="p">],</span> <span class="p">(</span><span class="no">APP_CONFIG</span><span class="p">[</span><span class="s1">'port'</span><span class="p">].</span><span class="nf">present?</span> <span class="p">?</span> <span class="s2">":</span><span class="si">#{</span><span class="no">APP_CONFIG</span><span class="p">[</span><span class="s1">'port'</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span> <span class="p">:</span> <span class="s1">''</span><span class="p">)].</span><span class="nf">join</span> <span class="n">serialize</span> <span class="ss">:params</span> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">find_by_code</span> <span class="n">string</span> <span class="n">find</span> <span class="n">decode</span><span class="p">(</span><span class="n">string</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">generate</span> <span class="n">path</span><span class="p">,</span> <span class="n">params</span> <span class="n">short_url_record</span> <span class="o">=</span> <span class="n">create</span> <span class="k">do</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span> <span class="n">t</span><span class="p">.</span><span class="nf">path</span> <span class="o">=</span> <span class="n">path</span> <span class="n">t</span><span class="p">.</span><span class="nf">params</span> <span class="o">=</span> <span class="n">params</span> <span class="n">t</span><span class="p">.</span><span class="nf">save</span> <span class="k">end</span> <span class="k">if</span> <span class="n">short_url_record</span><span class="p">.</span><span class="nf">id</span> <span class="p">[</span><span class="no">DEFAULT_URL</span><span class="p">,</span> <span class="s1">'/'</span><span class="p">,</span> <span class="n">encode</span><span class="p">(</span><span class="n">short_url_record</span><span class="p">.</span><span class="nf">id</span><span class="p">)].</span><span class="nf">join</span> <span class="k">else</span> <span class="kp">nil</span> <span class="k">end</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">full_path</span> <span class="p">[</span><span class="no">DEFAULT_URL</span><span class="p">,</span> <span class="n">path</span><span class="p">].</span><span class="nf">join</span> <span class="k">end</span> <span class="kp">private</span> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">encode</span> <span class="n">number</span> <span class="n">base</span> <span class="o">=</span> <span class="no">SYMBOLS</span><span class="p">.</span><span class="nf">length</span> <span class="n">res</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">begin</span> <span class="n">code</span> <span class="o">=</span> <span class="n">number</span> <span class="o">%</span> <span class="n">base</span> <span class="n">number</span> <span class="o">/=</span> <span class="n">base</span> <span class="n">res</span> <span class="o"><<</span> <span class="no">SYMBOLS</span><span class="p">[</span><span class="n">code</span><span class="p">]</span> <span class="k">end</span> <span class="k">until</span> <span class="n">number</span> <span class="o">==</span> <span class="mi">0</span> <span class="n">res</span> <span class="o"><<</span> <span class="s1">'s'</span> <span class="n">res</span><span class="p">.</span><span class="nf">reverse</span><span class="p">.</span><span class="nf">join</span> <span class="k">end</span> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">decode</span> <span class="n">string</span> <span class="k">begin</span> <span class="n">string</span> <span class="o">=</span> <span class="n">string</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="sr">/\A[s](.{1,5})\z/</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span> <span class="n">i</span> <span class="o">=</span> <span class="n">string</span><span class="p">.</span><span class="nf">length</span> <span class="o">-</span> <span class="mi">1</span> <span class="n">res</span> <span class="o">=</span> <span class="mi">0</span> <span class="n">string</span><span class="p">.</span><span class="nf">each_char</span> <span class="k">do</span> <span class="o">|</span><span class="n">c</span><span class="o">|</span> <span class="n">res</span> <span class="o">+=</span> <span class="p">(</span><span class="no">SYMBOLS</span><span class="p">.</span><span class="nf">index</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="no">SYMBOLS</span><span class="p">.</span><span class="nf">length</span> <span class="o">**</span> <span class="n">i</span><span class="p">))</span> <span class="n">i</span> <span class="o">-=</span> <span class="mi">1</span> <span class="k">end</span> <span class="n">res</span> <span class="k">rescue</span> <span class="kp">nil</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> |
If you want to leave both route variants (a digital ID and a string ID), you can put a prefix before an encoded string (in this case, it’s the s
character) and use regular expression constraints in the route.
1 | <span class="n">get</span> <span class="s1">'/:id'</span> <span class="o">=></span> <span class="s1">'short_url#show'</span><span class="p">,</span> <span class="ss">constraints: </span><span class="p">{</span> <span class="ss">id: </span><span class="sr">/[s].{1,5}/</span> <span class="p">}</span> |
The number of characters is limited to five, because you can encode a really big number (1073741823) with five characters that can never be used. These are some examples of encoding.
1 2 3 4 5 6 7 8 9 10 | <span class="n">pry</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="no">ShortUrl</span><span class="p">.</span><span class="nf">encode</span> <span class="mi">12345</span> <span class="o">=></span> <span class="s2">"sDA5"</span> <span class="n">pry</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="no">ShortUrl</span><span class="p">.</span><span class="nf">encode</span> <span class="mi">1000000</span> <span class="o">=></span> <span class="s2">"sD0JA"</span> <span class="n">pry</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="no">ShortUrl</span><span class="p">.</span><span class="nf">encode</span> <span class="mi">1056698302</span> <span class="o">=></span> <span class="s2">"s-----"</span> <span class="n">pry</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="no">ShortUrl</span><span class="p">.</span><span class="nf">encode</span> <span class="mi">1827200481836</span> <span class="o">=></span> <span class="s2">"altoros"</span> |
Then, you should decode.
1 2 | <span class="n">pry</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="no">ShortUrl</span><span class="p">.</span><span class="nf">decode</span> <span class="s2">"sFFFFF"</span> <span class="o">=></span> <span class="mi">85217605</span> |
So, you can generate short URLs.
1 | <span class="no">ShortUrl</span><span class="p">.</span><span class="nf">generate</span><span class="p">(</span><span class="n">edit_user_path</span><span class="p">(</span><span class="vi">@screener</span><span class="p">),</span> <span class="ss">reply_to: </span><span class="n">reply_to</span><span class="p">)</span> |
The result should look like this.
1 | <span class="o">=></span> <span class="s2">"http://ru.by/sKa"</span> |
Further reading
- Some Tricks on How to Optimize an Auto-Complete Combobox Using ZK and Java
- Two Powerful Iterator Methods
About the author
Nikolai Sharangovich is an experienced Ruby on Rails developer with a deep understanding of the object-oriented design and modern software principles. He likes to collaborate with product people to achieve a maximum impact. Find him on GitHub.