<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ronny.haryan.to &#187; Web Development</title>
	<atom:link href="http://ronny.haryan.to/archives/category/web-development/feed/" rel="self" type="application/rss+xml" />
	<link>http://ronny.haryan.to</link>
	<description>Print: $9.50 -- Online: free</description>
	<lastBuildDate>Thu, 04 Feb 2010 13:05:28 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>Build Your Own URL Shortening Service</title>
		<link>http://ronny.haryan.to/archives/2009/04/07/build-your-own-url-shortening-service/</link>
		<comments>http://ronny.haryan.to/archives/2009/04/07/build-your-own-url-shortening-service/#comments</comments>
		<pubDate>Tue, 07 Apr 2009 11:11:29 +0000</pubDate>
		<dc:creator>ronny</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://ronny.haryan.to/?p=191</guid>
		<description><![CDATA[Everyone seems to have their own favourite URL shortening service, TinyURL, is.gd, tr.im, pendek.in, and so on. Some even do more than just shorten URLs, e.g. tr.im can track stats. It&#8217;s relatively easy to roll own your own URL shortening service once you know the magic behind it. I&#8217;m going to explain the basic idea [...]]]></description>
			<content:encoded><![CDATA[<p>Everyone seems to have their own favourite URL shortening service, <a href="http://tinyurl.com">TinyURL</a>, <a href="http://is.gd">is.gd</a>, <a href="http://tr.im">tr.im</a>, <a href="http://pendek.in">pendek.in</a>, and so on. Some even do more than just shorten URLs, e.g. tr.im can track stats.</p>

<p>It&#8217;s relatively easy to roll own your own URL shortening service once you know the magic behind it. I&#8217;m going to explain the basic idea so you can start rolling out your own.</p>

<p><ins datetime="2009-04-13T08:35:39+00:00">UPDATE</ins>: check out <a href="http://github.com/jacobian/django-shorturls/tree/master">django-shorturls</a>, a Django app recently created by Simon Willison and Jacob Kaplan-Moss, it uses a similar base 62 approach.</p>

<p><span id="more-191"></span></p>

<h2>How it works</h2>

<p>The basic idea of how these services work is by storing URLs in a database, each with its own
unique ID, which is normally an integer. The ID is then magically converted into a series of
characters which can be used to make up a complete URL that people can pass around to people.</p>

<p>When someone opens the shortened URL with the code, the server will convert the code back into
the unique ID so that it can retrieve the URL in the database. Once the URL is retrieved then
the server will send a HTTP redirect response to the browser.</p>

<h2>The magic</h2>

<p>It&#8217;s not magic, really.</p>

<p>The conversion function from an integer to the short code must produce as short code as possible
while still being practical: the characters in the code must be valid for URLs, no &#8220;funny&#8221;
characters (&#8220;?&#8221;, &#8220;/&#8221;, &#8220;&amp;&#8221;, &#8220;%&#8221;, &#8220;+&#8221; are probably a bad idea).</p>

<p>One of the easiest functions to use for this purpose is to encode the integer (decimal,
base 10 number) into base 36.</p>

<p>Normally we use base 10 to count. In base 10 numbers we only have 10 possibilities (0 to 9) for each
digit before we need add another digit in front of it and roll over to the starting symbol
(&#8220;10&#8243; is two digits, because we have no more possibile symbol to use after &#8220;9&#8243;).</p>

<p>In base 16 (hexadecimal) we add 6 more symbols (&#8220;ABCDEF&#8221;) so that we can count to &#8220;F&#8221; (or 15)
before we need to add a digit in front and roll over to the starting symbol (&#8220;10&#8243;, or 16 in
hexadecimal). If you&#8217;re having trouble following, imagine having 16 fingers and 16 toes to
count with.</p>

<p>Same idea for base 36, but we add the whole 26 Roman alphabets, &#8220;A&#8221; to &#8220;Z&#8221;. So &#8220;Z&#8221; is 35
in base 36, and &#8220;10&#8243; is 36.</p>

<h2>How short is short?</h2>

<p>With a 3-digit base 10 numbers, we can only have 1000 possible combinations (0 to 999).
If I remember my high school math correctly, the formula is N<sup>D</sup> where N is the number of
possibilities for each digit (or the base number, 10 in this case), and D is the number
of digits (3 in this case), so 10<sup>3</sup> = 1000.</p>

<p>The goal is to have as many combinations as possible with as little number of digits as possible.
Since the number of digits will grow over time (as we store more URLs) then the only way
we can increase the number of possible combinations is by increasing the base number.</p>

<p>That&#8217;s why converting from base 10 to base 36 works.</p>

<p>So why stop at 36, you ask. As a matter of fact, we can make use of both uppercase and lowercase
letters of the alphabets so that we can use base 62. If you&#8217;re willing to have non-alphanumeric
characters in your short codes, you can even push it further with a few more characters. In fact,
this is what essentially base 64 encoding does. Be careful though, the commonly found <a href="http://en.wikipedia.org/wiki/Base64">base 64</a> encoding
normally works on <em>strings</em> instead of <em>numbers</em>, converting 6 bits of data at a time, and it
can add paddings, the output is 33.3% longer than the input. Quite different from what we want.</p>

<p>I would probably use base 62 because it&#8217;s slightly more pragmatic and it has less chance of screwing up, but if you insist on using
base 64 (for numbers, not strings), then I suggest you use &#8220;-&#8221; and &#8220;=&#8221; as the additional symbols.
I&#8217;d avoid underscores (&#8220;_&#8221;) because they can be hard to see in links, which are normally shown
underlined.</p>

<p>The following table shows the number of possibilities for some of the bases:</p>

<div class="scrollview">
<table id="comparison-table" border="0">
<thead>
<tr>
    <th>Digits</th>
    <th>Base 10</th>
    <th>Base 26</th>
    <th>Base 36</th>
    <th>Base 62</th>
    <th>Base 64</th>
</tr>
</thead>
<tbody>
<tr>
    <th>2</th>
    <td>100</td>
    <td>676</td>
    <td>1,296</td>
    <td>3,844</td>
    <td>4,096</td>
</tr>
<tr>
    <th>3</th>
    <td>1,000</td>
    <td>17,576</td>
    <td>46,656</td>
    <td>238,328</td>
    <td>262,144</td>
</tr>
<tr>
    <th>4</th>
    <td>10,000</td>
    <td>456,976</td>
    <td>1,679,616</td>
    <td>14,776,336</td>
    <td>16,777,216</td>
</tr>
<tr>
    <th>5</th>
    <td>100,000</td>
    <td>11,881,376</td>
    <td>60,466,176</td>
    <td>916,132,832</td>
    <td>1,073,741,824</td>
</tr>
<tr>
    <th>6</th>
    <td>1,000,000</td>
    <td>308,915,776</td>
    <td>2,176,782,336</td>
    <td>56,800,235,584</td>
    <td>68,719,476,736</td>
</tr>
</tbody>
</table>
</div>

<p>Say, you add 1 million new URLs every month on average. With base 62, it would take a little over a year to use up all the possible 4 digits, and after that it would take another 75 years to use up
all the possible 5 digits, and after that it would take another 4,658 years to use up all the
possible 6 digits, and so on.</p>

<p>It would probably take even longer to use up the digits if you don&#8217;t add URLs that are already in
the database. Conversely, you might use up the digits faster if you want to do per-user tracking,
for example.</p>

<h2>Sample code</h2>

<p>Here&#8217;s a simple Python code that you can use to convert an integer to a base N &#8220;number&#8221;
(actually string), and vice versa.</p>

<pre><code># Code adapted from: http://en.wikipedia.org/wiki/Base_36#Python_Conversion_Code
# This is base 62:
ALPHABETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

def base_n_decode(s, alphabets=ALPHABETS):
    n = len(alphabets)
    rv = pos = 0
    charlist = list(s)
    charlist.reverse()
    for char in charlist:
        rv += alphabets.find(char) * n**pos
        pos += 1
    return rv

def base_n_encode(num, alphabets=ALPHABETS):
    n = len(alphabets)
    rv = ""
    while num != 0:
        rv = alphabets[num % n] + rv
        num /= n
    return rv
</code></pre>

<h2>Can&#8217;t we just use MD5?</h2>

<p>No. We can&#8217;t use hash functions like MD5, because hash functions are one-way, irreversible, meaning
we can&#8217;t get the original input just from knowing the hash code. Also, hash functions can have
collisions, meaning from one hash code there is a remote possibility that it &#8220;maps&#8221; back to two
or more URLs. Plus, the output of hash functions are normally long-ish fixed-length strings.</p>

<h2>So, what are you waiting for?</h2>

<p>Now that the magic is revealed, you can create your own URL shortening service. Be creative and
innovative so you can compete with all the other URL shortening services out there.</p>

<p>As a bonus, you can also use this technique to shorten the permalinks or absolute URLs for your
objects in your <a href="http://djangoproject.com">Django</a> (or Rails, etc.) websites. So instead of <code>http://example.com/post/12345</code>
it would be <code>http://example.com/post/3D7</code>. Note that this has nothing to do with security
whatsoever. The coded ID can still be reversed with some effort. And you shouldn&#8217;t rely on
obfuscation to protect anything anyway.</p>
]]></content:encoded>
			<wfw:commentRss>http://ronny.haryan.to/archives/2009/04/07/build-your-own-url-shortening-service/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Bad Usability: IndosatM2 Member Site</title>
		<link>http://ronny.haryan.to/archives/2009/01/23/bad-usability-indosatm2-member-site/</link>
		<comments>http://ronny.haryan.to/archives/2009/01/23/bad-usability-indosatm2-member-site/#comments</comments>
		<pubDate>Fri, 23 Jan 2009 09:26:55 +0000</pubDate>
		<dc:creator>ronny</dc:creator>
				<category><![CDATA[Usability]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://ronny.haryan.to/?p=161</guid>
		<description><![CDATA[Many people mistakenly think that &#8220;web design&#8221; means making a website looks nice. Well, that&#8217;s only half of the equation. It&#8217;s so much more than just layout. One of the major elements in web design is usability, which roughly means making a website feels nice. The other half of the equation that many people tend [...]]]></description>
			<content:encoded><![CDATA[<p>Many people mistakenly think that &#8220;web design&#8221; means making a website <strong>looks</strong> nice. Well, that&#8217;s only half of the equation. It&#8217;s so much more than just layout. One of the major elements in web design is usability, which roughly means making a website <strong>feels</strong> nice. The other half of the equation that many people tend to forget. A successful web design normally requires balancing of these two major elements.</p>

<p><span id="more-161"></span></p>

<p>Bad usability, especially obvious ones, is one of my biggest pet peeves in web development. So when a website that I &#8212; and probably many others &#8212; frequently visit has a terrible usability, I have a mixed feeling of anger and frustration that I can only hold for so long. In the spirit of positive thinking, I&#8217;d like to channel out that anger and frustration through this (hopefully) constructive criticism.</p>

<h2>A Closer Look at the Problem</h2>

<p>Let&#8217;s take a look at <a href="http://www.indosatm2.com/">IndosatM2</a>&#8216;s <a href="https://www.indosatm2.com/my-account.php/login">member site</a> as an example.</p>

<p>First, I&#8217;d like to ask a question. What&#8217;s the main reason people go to their own ISP&#8217;s website? I bet checking their usage is one of them. It&#8217;s probably and arguably the number one reason, if not the only reason. It is for me at least, since my ISP doesn&#8217;t provide a Dashboard widget and no one has yet to write such widget to work with <a href="http://www.indosatm2.com/">IndosatM2</a>.</p>

<p>The most commonly used function should be as easy to reach and as quickly displayed as possible. It wouldn&#8217;t make sense to force the user to jump through many hoops just to get to the most frequently wanted information. So let&#8217;s count how many steps are required to check the usage for an IM2 subscriber.</p>

<h3>Step 1: Logging In</h3>

<p>The login form is not on the main indosatm2.com website, requiring users to click a link that goes to a separate login page. (Of course, one can bookmark the page for quicker future access.)</p>

<p><a href="http://ronny.haryan.to/wp-content/uploads/2009/01/1-login.jpg"><img src="http://ronny.haryan.to/wp-content/uploads/2009/01/1-login.jpg" alt="IM2 Login" title="IM2 Login" width="450" height="230" class="alignnone size-full wp-image-164" /></a></p>

<p>Things to note:</p>

<ul>
<li><a href="http://en.wikipedia.org/wiki/CAPTCHA">CAPTCHA</a>. On a login form? What&#8217;s the point?</li>
<li>The size and the location of the login form compared to the text on the side.</li>
<li>The order of the points in the text. The &#8220;Usage History&#8221; point is last.</li>
</ul>

<p>How to improve:</p>

<ul>
<li>Remove the CAPTCHA. To prevent or deter brute force login attacks, use
  delayed response on failed login attempts within a short period of time.
  Block the IP address (either temporarily or permanently) after 50 repeated
  failed logins within 5 minutes, or whatever is reasonable.</li>
<li>Make the login form bigger and place it in the center of the page. It <strong>is</strong> a login
  page after all, so the login form should be the center of attention. Move the text
  below the login form. Show the Usage History as the first item.</li>
</ul>

<h3>Step 2: Closing Useless Popup</h3>

<p><a href="http://ronny.haryan.to/wp-content/uploads/2009/01/2-popup.jpg"><img src="http://ronny.haryan.to/wp-content/uploads/2009/01/2-popup.jpg" alt="IM2 Useless Popup" title="IM2 Useless Popup" width="450" height="224" class="alignnone size-full wp-image-168" /></a></p>

<p>Things to note:</p>

<ul>
<li>Needs to click on OK to make it go away.</li>
<li>It serves no useful purpose.</li>
</ul>

<p>How to improve:</p>

<ul>
<li>Remove it altogether. We would know if the login is successful or not. Only tell us
  when something is <strong>not</strong> working, not when something is working. It&#8217;s like
  a little child yelling proudly everytime they managed to accomplish the smallest
  things: it&#8217;s cute at first but it gets annoying after 3 times. Yes,
  Mr(s). Programmer, I know you have mad Javascript skills, but who are you
  trying to impress? Let me guess, you got the inspiration from Windows&#8217;
  popup balloons?</li>
</ul>

<h3>Step 3: The Home Page</h3>

<p><a href="http://ronny.haryan.to/wp-content/uploads/2009/01/3-firstpage.jpg"><img src="http://ronny.haryan.to/wp-content/uploads/2009/01/3-firstpage.jpg" alt="IM2 member home" title="IM2 member home" width="450" height="268" class="alignnone size-full wp-image-170" /></a></p>

<p>Things to note:</p>

<ul>
<li>The size of the image.</li>
<li>How the image serves no useful purpose.</li>
<li>Needs to scroll down to see the actual contents.</li>
<li>How &#8220;Last Invoice&#8221; and &#8220;All Invoice&#8221; (sic) functions are highlighted in the home page.</li>
</ul>

<p>How to improve:</p>

<ul>
<li>There&#8217;s already a menu on the right side. Make it hierarchical so that the user
  can get straight to the function they want rather than requiring them to see
  an extra page that only contain the submenus.</li>
<li>The &#8220;Last Invoice&#8221; and &#8220;All Invoice&#8221; (sic) buttons can go away. They are no longer
  necessary. Instead, put the contents of step 4 and 5 directly into this page. Read
  more below.</li>
<li>Remove the obnoxious image. At least, don&#8217;t make it so big, or move it somewhere
  else, don&#8217;t make it the first thing I see when I log in.</li>
<li>This step can be eliminated.</li>
</ul>

<h3>Step 4: The Usage Page</h3>

<p><a href="http://ronny.haryan.to/wp-content/uploads/2009/01/4-usage.jpg"><img src="http://ronny.haryan.to/wp-content/uploads/2009/01/4-usage.jpg" alt="IM2 Useless Usage Page" title="IM2 Useless Usage Page" width="450" height="276" class="alignnone size-full wp-image-172" /></a></p>

<p>Things to note:</p>

<ul>
<li>It already knows what month and year it is now (they are shown as the default).</li>
<li>Needs to click &#8220;OK&#8221; to get the information for that month.</li>
<li>Hmm. What does &#8220;Cancel&#8221; do?</li>
<li>The obnoxious image that serves no useful purpose.</li>
<li>Needs to scroll down to see the actual contents.</li>
</ul>

<p>How to improve:</p>

<ul>
<li>Show the month selection box straight in the home page, instead of in a separate page.</li>
<li>Make last month as the default value. See &#8220;How to improve&#8221; in step 5 for the reasoning.</li>
<li>This step can be eliminated.</li>
</ul>

<h3>Step 5: Finally</h3>

<p><a href="http://ronny.haryan.to/wp-content/uploads/2009/01/5-finally.jpg"><img src="http://ronny.haryan.to/wp-content/uploads/2009/01/5-finally.jpg" alt="IM2 Usage" title="IM2 Usage" width="450" height="277" class="alignnone size-full wp-image-175" /></a></p>

<p>Things to note:</p>

<ul>
<li>Again, the obnoxious image and the scrolling down.</li>
<li>The inconsistent use of English and Indonesian.</li>
<li>There&#8217;s no easy way to see data for other months, must go back to step 4.</li>
</ul>

<p>How to improve:</p>

<ul>
<li>Show the results for the current month straight away in the home page. This is
  what most people want to see. And since you already know what month it is now,
  why don&#8217;t you just show me right away?</li>
<li>The system can detect what day of the month it is now. If it&#8217;s still early in the month
  the system can also show the total usage for last month in this page.</li>
<li>The month selection box in step 4 is always shown at the top (and optionally at
  the bottom) of the page with the default value set to last month.</li>
</ul>

<h2>Conclusion</h2>

<p>The steps can be reduced down from 5 to only 2 without sacrificing any functionalities. The most useful and frequently used features are highlighted and shown as reasonable and sensible defaults. Logging in should be easy (username and password, that&#8217;s it). Then the home page, containing the current month&#8217;s usage (probably the most commonly requested information), is shown right away, without any annoying popup. If the user needs to see data for other months, then the month selection box should already be accessible from the same page, showing the next commonly wanted information (last month&#8217;s usage).</p>

<h2>Other Examples?</h2>

<p>There is another public website that I use quite often and I find that it has plenty of rooms for improvement in terms of usability: <a href="https://ibank.klikbca.com/">KlikBCA</a>. But one can write a whole book on that website alone discussing what&#8217;s wrong and how to improve it.</p>

<p>Have you seen other public websites that can be improved in its usability?</p>
]]></content:encoded>
			<wfw:commentRss>http://ronny.haryan.to/archives/2009/01/23/bad-usability-indosatm2-member-site/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>psycopg2 and 64-bit Apache on Leopard</title>
		<link>http://ronny.haryan.to/archives/2008/09/12/psycopg2-and-64-bit-apache-on-leopard/</link>
		<comments>http://ronny.haryan.to/archives/2008/09/12/psycopg2-and-64-bit-apache-on-leopard/#comments</comments>
		<pubDate>Fri, 12 Sep 2008 11:27:07 +0000</pubDate>
		<dc:creator>ronny</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[Tips]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://ronny.haryan.to/archives/2008/09/12/psycopg2-and-64-bit-apache-on-leopard/</guid>
		<description><![CDATA[The Apache httpd that comes with Mac OS X Leopard runs in 64-bit mode, so everything else that&#8217;s loaded, including mod_wsgi and psycopg2 must be able to run in 64-bit mode as well. Graham Dumpleton explained this behaviour in a post to the django-users mailing list. Compiling psycopg2 using MacPorts as well as from the [...]]]></description>
			<content:encoded><![CDATA[<p>The Apache httpd that comes with Mac OS X Leopard runs in 64-bit mode, so everything else that&#8217;s loaded, including <a href="http://code.google.com/p/modwsgi/">mod_wsgi</a> and <a href="http://initd.org/pub/software/psycopg/">psycopg2</a> must be able to run in 64-bit mode as well. Graham Dumpleton explained this behaviour in <a href="http://groups.google.com/group/django-users/msg/1c2537650eb3c79b">a post to the django-users mailing list</a>.</p>

<p>Compiling psycopg2 using MacPorts as well as from the tarball would result in something like this when loaded from a web application that runs on Apache:</p>

<pre><code>ImproperlyConfigured: Error loading psycopg2 module: dlopen(/Library/Python/2.5/site-packages/psycopg2/_psycopg.so, 2): no suitable image found.  Did find: /Library/Python/2.5/site-packages/psycopg2/_psycopg.so: no matching architecture in universal wrapper
</code></pre>

<p>So all I needed to do was force psycopg2 extension to be built for 64-bit as well as 32-bit. After extracting the latest psycopg2 source, do this instead:</p>

<pre><code>LDFLAGS="-arch ppc -arch i386 -arch x86_64" CFLAGS="-arch ppc -arch i386 -arch x86_64" python setup.py build
</code></pre>

<p>Then <code>sudo python setup.py install</code>. After that, check the .so file, it should look something like this:</p>

<pre><code>$ file /Library/Python/2.5/site-packages/psycopg2/_psycopg.so 
/Library/Python/2.5/site-packages/psycopg2/_psycopg.so: Mach-O universal binary with 3 architectures
/Library/Python/2.5/site-packages/psycopg2/_psycopg.so (for architecture i386): Mach-O bundle i386
/Library/Python/2.5/site-packages/psycopg2/_psycopg.so (for architecture ppc7400):      Mach-O bundle ppc
/Library/Python/2.5/site-packages/psycopg2/_psycopg.so (for architecture x86_64):       Mach-O 64-bit bundle x86_64
</code></pre>

<p>Don&#8217;t forget to restart Apache, and that should be it.</p>

<p>Update: It happened to <a href="http://www.pythonware.com/products/pil/">PIL</a> too, it was giving this error: <code>The _imaging C module is not installed</code>, so I had to do the same thing as above: <code>LDFLAGS=... CFLAGS=... python setup.py build</code> followed by <code>sudo python setup.py install</code>.</p>
]]></content:encoded>
			<wfw:commentRss>http://ronny.haryan.to/archives/2008/09/12/psycopg2-and-64-bit-apache-on-leopard/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SQL Injection Kid</title>
		<link>http://ronny.haryan.to/archives/2007/10/10/sql-injection-kid/</link>
		<comments>http://ronny.haryan.to/archives/2007/10/10/sql-injection-kid/#comments</comments>
		<pubDate>Wed, 10 Oct 2007 06:13:59 +0000</pubDate>
		<dc:creator>ronny</dc:creator>
				<category><![CDATA[Funny]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://ronny.haryan.to/archives/2007/10/10/sql-injection-kid/</guid>
		<description><![CDATA[xkcd always cracks me up. As usual, link for the uninitiated.]]></description>
			<content:encoded><![CDATA[<div class="scrollview"><a href="http://xkcd.com/327/" class="noborder"><img src="http://imgs.xkcd.com/comics/exploits_of_a_mom.png" alt="Exploits of a Mom" width="450" class="noborder" /></a></div>

<p><a href="http://xkcd.com/">xkcd</a> always cracks me up.</p>

<p>As usual, <a href="http://en.wikipedia.org/wiki/SQL_injection">link</a> for the uninitiated.</p>
]]></content:encoded>
			<wfw:commentRss>http://ronny.haryan.to/archives/2007/10/10/sql-injection-kid/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Revision Control for Web Application Content</title>
		<link>http://ronny.haryan.to/archives/2006/06/26/revision-control-for-web-application-content/</link>
		<comments>http://ronny.haryan.to/archives/2006/06/26/revision-control-for-web-application-content/#comments</comments>
		<pubDate>Mon, 26 Jun 2006 12:52:53 +0000</pubDate>
		<dc:creator>ronny</dc:creator>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://ronny.haryan.to/archives/2006/06/26/revision-control-for-web-application-content/</guid>
		<description><![CDATA[Revision control is a wonderful concept. Using revision control has helped me personally in managing content and source code. Traditionally revision control is used to track changes in source code files, but people has since extended revision control to manage other things such as system configuration files, web pages, term papers, and so on. In [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://en.wikipedia.org/wiki/Revision_control">Revision control</a> is a
wonderful concept. Using revision control has helped me personally in
managing content and source code. Traditionally revision control is used to
track changes in source code files, but people has since extended revision
control to manage other things such as system configuration files, web
pages, term papers, and so on. In this article, I will focus on applying the
revision control concept in managing content in web applications, for
example, like how <a href="http://en.wikipedia.org/">Wikipedia</a> keeps track of
changes in the articles
(<a href="http://en.wikipedia.org/w/index.php?title=Revision_control&amp;action=history">example</a>).</p>

<h3>Why?</h3>

<p>Having a revision control is really useful when we want to keep a history of
contents in our web applications. This history data can be used to revert
changes to a certain revision, find out by whom and when certain changes are
made, and visualise the differences between revisions. Accidental
overwriting of content is no longer a problem with revision control in
place. Aside from history, revision control can also help in a multi-user
environment where many people may potentially work on the same content. In
my opinion, these are the main benefits of having revision control.</p>

<p><span id="more-99"></span></p>

<h3>Cons</h3>

<p>The only main drawback of having revision control is that extra storage
space is needed to keep the revision data. The actual requirements vary,
depending on the implementation. If done inefficiently (e.g. storing the
whole revision content instead of just the differences), then the space
requirement could be significant. However, storage is cheap nowadays, and in
many cases the benefits far outweigh the drawbacks.</p>

<h3>Traditional vs. web app.</h3>

<p>Traditional revision control systems such as
<a href="http://subversion.tigris.org/">Subversion</a> or
<a href="http://www.nongnu.org/cvs/">CVS</a> operate on files. Web application contents
are normally stored in a database instead of files. This is one major
difference that should be kept in mind.</p>

<h3>Requirements</h3>

<p>I&#8217;m going to give an example of two simple implementation approaches of revision control
for web application content. But first I&#8217;m going to list the requirements
for this simple implementation. Let say I already have a simple web
application that is used by my family to manage our notes (think of a
<strong>very</strong> simple family wiki). This is what I want to get from adding a
revision control to the system:</p>

<ul>
<li><strong>content history</strong>, ability to store, retrieve and rollback to certain
revision, as well as to show differences between revisions</li>
<li><strong>audit</strong>, I want to know who did what and when </li>
<li><strong>all database</strong>, everything is stored in the database itself, no external
files</li>
<li><strong>no cheating</strong>, I can&#8217;t use subversion or cvs behind the scene</li>
<li><strong>simple</strong>, minimal and straightforward modifications in the database
structure and code</li>
<li><strong>generic</strong>, the design should be database- and language-agnostic</li>
<li><strong>storage</strong>, in this case I&#8217;m willing to pay the price of space for having
a history of my notes, so storage space size is not a concern</li>
</ul>

<h3>Existing system</h3>

<p>Let say this is the (simplified) existing database structure:</p>

<pre><code>users:
    + id
    - name

notes:
    + id
    - status
    - title
    - content
    - created_by
    - created_at
    - last_modified_by
    - last_modified_at
</code></pre>

<h3>First approach: the simple method</h3>

<p>I think one of the simplest tricks that can be used to implement revision
control is to add a <code>revision_id</code> column, and make it part of the primary
key. And that is exactly what I&#8217;m going to do first. Here&#8217;s the modified
database structure:</p>

<pre><code>users:
    + id
    - name

notes:
    + id
    + rev_id
    - status
    - title
    - content
    - rev_user_id
    - rev_time
</code></pre>

<p>The <code>users</code> table is unchanged because I don&#8217;t need to put anything there
under revision control. The primary key of the <code>notes</code> table now becomes a
composite key <code>(id, rev_id)</code>. The audit information now becomes the revision
metadata, <code>rev_user_id</code> and <code>rev_time</code>, because now we can track who did
what and when for <em>each</em> revision, not just the first and the last ones.</p>

<p>This method is the simplest that I can think of. There is a limitation,
however, that every column in that table becomes under revision control.
That&#8217;s probably fine for most needs, but there could be situations where
only some of the columns should be under revision control, for example when
storage space is a concern.</p>

<p>Now comes the more interesting part. How can we actually achieve common
revision control tasks? I&#8217;m going to provide some example MySQL statements to
illustrate how to do certain things:</p>

<ul>
<li><p>To list all published notes:</p>

<pre>SELECT DISTINCT id
FROM notes
WHERE status='published'</pre></li>
<li><p>To retrieve the latest revision of note 5:</p>

<pre>SELECT *
FROM notes
WHERE id=5
  AND rev_id=(SELECT MAX&#x28;rev_id)
      FROM notes
      WHERE id=5)</pre></li>
<li><p>To create a new revision of note 5 by user 3:</p>

<pre>BEGIN;
    SELECT @newid := MAX&#x28;rev_id) + 1 FROM notes WHERE id=5;
    INSERT INTO notes
    (id, rev_id, status, title, content, rev_user_id, rev_time)
        VALUES (5, @newid, 'published', '...', '...', 3, NOW());
COMMIT;</pre></li>
<li><p>To edit a revision: simply create a new revision. Editing or deleting a
revision is beating the purpose of having revision control in the first
place. By never editing or deleting any revision we can rollback to a
particular revision if we need to.</p></li>
<li>To delete a revision: simply create a new revision with status set to
&#8216;deleted&#8217; and exclude the deleted notes from your view. Again, we never
edit or delete any revisions, this way we can &#8220;resurrect&#8221; deleted notes by
simply rollback to a previous revision.</li>
<li><p>To list all revision metadata of note 5:</p>

<pre>SELECT rev_id, rev_user_id, rev_time
FROM notes
WHERE id=5</pre></li>
<li>To rollback note 5 to revision 2: create a new revision for note 5 by
copying the content of revision 2 (excluding the revision metadata
<code>rev_user_id</code> and <code>rev_time</code>) into the new revision.</li>
<li>To show differences between revision 2 and 3 for note 5: retrieve both
revisions and pass them into a <code>diff</code> program or its equivalent</li>
</ul>

<h3>Second approach: an alternative method</h3>

<p>There is an alternative approach using a separate table to store revisions.
This method practically addresses the aforementioned limitation of the
simple method. Using the same example, the modified database structure now
becomes:</p>

<pre><code>users:
    + id
    - name

notes:
    + id
    - title

notes_revisions:
    + id
    + notes_id
    - status
    - content
    - rev_user_id
    - rev_time
</code></pre>

<p>In the above structure the <code>title</code> column is not under revision control.
Although <code>title</code> may not be the best example to illustrate the storage space
restriction requirement, I used it as an example to show that we can put
certain columns outside revision control. The <code>id</code> column of
<code>notes_revisions</code> is the revision id. If you&#8217;re using MySQL, then you can&#8217;t
make <code>notes_revisions(id)</code> column as <code>auto_increment</code> in this case.
The <code>notes_id</code> column is a foreign key referencing <code>notes(id)</code> and is part
of the composite primary key.</p>

<p>And here&#8217;s how you do common revision control tasks using this alternative
method:</p>

<ul>
<li><p>To list all published notes:</p>

<pre>SELECT n.*
FROM notes n
LEFT OUTER JOIN notes_revisions nr
    ON nr.notes_id = n.id
WHERE nr.status='published'
  AND nr.id=( SELECT MAX&#x28;id)
        FROM notes_revisions
        WHERE notes_id=nr.notes_id)</pre></li>
<li><p>To retrieve the latest revision of note 5:</p>

<pre>SELECT n.*, nr.*
FROM notes n
LEFT OUTER JOIN notes_revisions nr ON nr.notes_id = n.id
WHERE n.id=5
  AND nr.id=(SELECT MAX&#x28;id)
      FROM notes_revisions
      WHERE notes_id=5)</pre></li>
<li><p>To create a new revision of note 5 by user 3:</p>

<pre>BEGIN;
SELECT @newrid := MAX&#x28;id) + 1 FROM notes_revisions WHERE notes_id=5;
INSERT INTO notes_revisions
    (id, notes_id, status, content, rev_user_id, rev_time)
        VALUES (@newrid, 5, 'published', '...', 3, NOW());
COMMIT;</pre></li>
<li><p>To list all revision metadata of note 5:</p>

<pre>SELECT id, rev_user_id, rev_time
FROM notes_revisions
WHERE notes_id=5</pre></li>
</ul>

<p>Other tasks are done in a similar manner to the simple method.</p>

<h3>Addressing simultaneous edits</h3>

<p>Many times it is useful to allow simultaneous editing of the same content without locking. The basic idea is to allow users to edit anyway and let the system merge the differences between simultaneous editing sessions when creating new revisions. To do this properly, we need the help of a <code>diff</code> program and a <code>patch</code> program (or their equivalents) to merge differences. I think this could make my application too complicated for my purposes. So I&#8217;ll settle for a less complicated workaround: during save, if a simultaneous edit is detected, then the application will deny the save request. Although I have to admit that this workaround is not better than locking. Maybe I will write another article in the future that discusses this specifically in more details.</p>

<p>How to detect simultaneous edits? When presenting a note for editing, the revision number must be attached to the form (e.g. as a hidden input field). After submitting the edited note and before creating a new revision, the system must first check that the revision number in the database is not higher than the current revision. If it is higher, then that means somebody else has edited the same note.</p>

<h3>Conclusions</h3>

<p>There are more to revision control than what is written in this article, and
it could be a very complicated discussion topic. But <em>basic</em> revision
control is good to have, and easy enough to implement in most web
applications.</p>

<h3>Related information</h3>

<ul>
<li><a href="http://cvsbook.red-bean.com/">Open Source Development with CVS, 3rd Edition</a></li>
<li><a href="http://svnbook.red-bean.com/">Version Control with Subversion</a></li>
</ul>

<h3>Acknowledgements</h3>

<p>The alternative approach was inspired by <a href="http://www.mediawiki.org/">MediaWiki</a>.
Thank God for open source!</p>
]]></content:encoded>
			<wfw:commentRss>http://ronny.haryan.to/archives/2006/06/26/revision-control-for-web-application-content/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Membuat Kamus Online</title>
		<link>http://ronny.haryan.to/archives/2005/07/15/membuat-kamus-online/</link>
		<comments>http://ronny.haryan.to/archives/2005/07/15/membuat-kamus-online/#comments</comments>
		<pubDate>Fri, 15 Jul 2005 06:16:38 +0000</pubDate>
		<dc:creator>ronny</dc:creator>
				<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://ronny.haryan.to/?p=75</guid>
		<description><![CDATA[Seringkali saya punya ide untuk membuat suatu aplikasi yang mungkin bisa berguna buat orang banyak, misalnya membuat kamus online bahasa Indonesia&#60;->Inggris yang professional, bukan sekedar terjemahan kata per kata saja tapi juga contoh penggunaan, konteks yang berbeda-beda, part of speech / fungsi kata, idiom, padanan kata, etymology, dan referensi atau link ke kata atau sumber [...]]]></description>
			<content:encoded><![CDATA[<p>Seringkali saya punya ide untuk membuat suatu aplikasi yang mungkin bisa berguna buat orang banyak, misalnya membuat kamus online bahasa Indonesia&lt;->Inggris yang professional, bukan sekedar terjemahan kata per kata saja tapi juga contoh penggunaan, konteks yang berbeda-beda, part of speech / fungsi kata, idiom, padanan kata, etymology, dan referensi atau link ke kata atau sumber lain. Saya juga ingin kamus online ini mempunyai API web services misalnya lewat SOAP sehingga bisa diintegrasikan ke aplikasi lain (e.g. <a href="http://www.apple.com/macosx/features/dashboard/">Dashboard</a> widget atau <a href="http://www.gnome.org">GNOME</a> applet), atau word of the day lewat RSS misalnya. Kamus ini juga nantinya bisa menampung masukan dari sumber-sumber lain yang berbeda-beda, mirip seperti <a href="http://dictionary.com">dictionary.com</a>. Sukur-sukur bisa dibuat cukup generik sehingga aplikasinya bisa digunakan kembali untuk keperluan lain (misalnya kamus Indonesia-Indonesia, atau Indonesia-Jawa, dsb.). Idealnya aplikasinya akan berlisensi free dan open source, begitu pula dengan isinya kurang lebih akan menggunakan lisensi seperti Creative Commons atau GNU FDL.</p>

<p>Karena saya orang teknis, saya cenderung mempunyai ide yang berawal dari design aplikasinya dahulu ketimbang memikirkan bagaimana saya akan mendapatkan contentnya. Saya sadar bahwa pada akhirnya aplikasi yang secanggih dan seuser-friendly apa pun jika tidak mempunyai isi yang bernilai kepada penggunanya sama saja bohong. Saya rasa ini adalah masalah klasik yang dihadapi web developers yang cenderung technical.</p>

<p>Membuat kamus dari scratch (nol) itu bukan pekerjaan mudah, dikerjakan full-time oleh seorang professional saja bisa memakan waktu bertahun-tahun barangkali. Alternatif yang ada adalah menggunakan sumber yang sudah ada dengan meminta ijin kepada pemiliknya (lebih kecil kemungkinannya tapi tidak ada salahnya dicoba), dan/atau membuat sendiri rame-rame a la Wiki. (Catatan teknis: Ini berarti ada dua lagi feature dari kamus online ini, yaitu bisa diedit oleh siapa saja (setelah login, dan mungkin menggunakan captcha), dan ada versioning control untuk jaga-jaga kalau kena vandalisme.) Sumber yang sudah ada yang bisa diadaptasi idealnya adalah yang berkualitas tinggi, seperti kamus edisi cetak yang dijual di toko-toko buku (walaupun tidak berarti semua kamus edisi cetak yang dijual di toko buku berkualitas tinggi), bukan sekedar kamus online buatan si Panjul atau si Unyil. Sejujurnya, dan cukup disayangkan, sampai saat ini saya belum menemukan satu pun kamus Inggris&lt;->Indonesia online yang isinya berkualitas mendekati kamus edisi cetak.</p>

<p>Saya sudah punya gambaran garis besarnya untuk design aplikasinya untuk saat ini. Yang saya rasa lebih penting adalah memikirkan bagaimana mendapatkan isi kamus yang berkualitas. Ada ide atau saran lain barangkali?</p>

<p><strong>Update</strong>: <a href="http://www.seasite.niu.edu/Indonesian/">SEAsite dari Pusat Studi Asia Tenggara, Northern Illinois University</a> menyediakan kamus Inggris-Indonesia yang mendekati apa yang saya cari. Mudah-mudahan jika waktu dan pemilik kamusnya mengijinkan saya akan konversi ke database dan interface yang seperti saya ceritakan di atas.</p>
]]></content:encoded>
			<wfw:commentRss>http://ronny.haryan.to/archives/2005/07/15/membuat-kamus-online/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Ruby on Rails</title>
		<link>http://ronny.haryan.to/archives/2005/06/19/ruby-on-rails/</link>
		<comments>http://ronny.haryan.to/archives/2005/06/19/ruby-on-rails/#comments</comments>
		<pubDate>Sun, 19 Jun 2005 04:29:18 +0000</pubDate>
		<dc:creator>ronny</dc:creator>
				<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://ronny.haryan.to/?p=67</guid>
		<description><![CDATA[I finally got a chance to play around with Ruby on Rails. I looked around for a simple and straightforward tutorial that covers the basic, and settled with Curt Hibbs&#8217; Rolling with Ruby on Rails on ONLamp.com. It&#8217;s a two-part article. Not overly long to follow, like 30 minutes or so (including searching and reading [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.rubyonrails.org/" class="noborder"><img src="/files/rails_logo_remix.gif" alt="Rails logo" class="alignleft" /></a>I finally got a chance to play around with <a href="http://www.rubyonrails.org/">Ruby on Rails</a>. I looked around for a simple and straightforward tutorial that covers the basic, and settled with <a href="http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html">Curt Hibbs&#8217; Rolling with Ruby on Rails</a> on ONLamp.com. It&#8217;s a two-part article. Not overly long to follow, like 30 minutes or so (including searching and reading some <a href="http://api.rubyonrails.org">API documentation</a>), and I got a functional application at the end of the tutorial. All without too much knowledge about <a href="http://www.ruby-lang.org/">Ruby</a> language, just have some prior understanding of the MVC (model-view-controller) model general concept and you&#8217;re all set.</p>

<p><strong>Ruby on Rails is what J2EE should have been.</strong></p>

<p>Oh, man, was I ever excited! After finishing that tutorial, I immediately searched for information about the existance of an Oracle adapter for ActiveRecord. It turned out that <a href="http://wiki.rubyonrails.com/rails/show/HowtoConnectToOracle">there <em>is</em> one</a>, it&#8217;s included in recent versions of ActiveRecord and is based on the <a href="http://rubyforge.org/projects/ruby-oci8/">ruby-oci8</a> driver. I started my development Oracle server and immediately generated a scaffold for a test model. There were two minor problems. The first problem was that my table and column names did not follow the proper naming conventions expected by Rails, so I had to override it in the model class with <code>set_table_name</code> (<a href="http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M000671">doc</a>) and <code>set_primary_key</code> (<a href="http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M000672">doc</a>). No worries. The second problem was that the web server seemed to freeze after about 5-10 seconds of inactivity. It must have been something to do with the oci8 driver or ActiveRecord&#8217;s oci8 implementation, because it worked flawlessly with mysql. I didn&#8217;t have time to investigate further. It was good enough for me for now. I&#8217;m sure if it really was a problem with the driver then someone more knowledegable will catch it real soon and we&#8217;ll have a fix.</p>

<p>So, in conclusion, I wish Ruby on Rails had existed many years ago therefore, it could save me many weeks worth of tedious and error-prone coding. It beats PHP and J2EE any day. I will definitely use Ruby on Rails for my next web development project. And to summarize for those of you that are lazy or don&#8217;t have the time to follow the tutorial, install Ruby on Rails, then create a table in your database called <code>products</code> with these columns: <code>id</code>, <code>name</code>, <code>description</code>. Then create the rails structure with <code>rails productlist</code>, go inside the newly-created productlist  directory, edit <code>config/database.yml</code> accordingly then run <code>ruby script/generate scaffold Product</code>. Then run the built-in WEBrick web server with <code>ruby script/server</code> then open your browser and go to <a href="http://127.0.0.1:3000/products">http://127.0.0.1:3000/products</a>. Voilà! That was &#8216;hard&#8217;. Now you can concentrate on what&#8217;s important, your application and its design, not tedious, repetitive and error-prone coding.</p>
]]></content:encoded>
			<wfw:commentRss>http://ronny.haryan.to/archives/2005/06/19/ruby-on-rails/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Things I Learned Today</title>
		<link>http://ronny.haryan.to/archives/2005/04/30/tilt-2005-04-29/</link>
		<comments>http://ronny.haryan.to/archives/2005/04/30/tilt-2005-04-29/#comments</comments>
		<pubDate>Fri, 29 Apr 2005 14:24:53 +0000</pubDate>
		<dc:creator>ronny</dc:creator>
				<category><![CDATA[Cooking]]></category>
		<category><![CDATA[Internet]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[TILT]]></category>
		<category><![CDATA[Tech]]></category>
		<category><![CDATA[Web Development]]></category>

		<guid isPermaLink="false">http://ronny.haryan.to/archives/2005/04/30/things-i-learned-today/</guid>
		<description><![CDATA[I finally read this article (which had been sitting for a long time in my thick pile of printed &#8220;read-later&#8221; articles) from Apple Developer Connection about XMLHttpRequest which plays a major part in AJAX, which has been discussed a lot lately in the web development area and is used by popular services such as Gmail, [...]]]></description>
			<content:encoded><![CDATA[<ol>
<li><p>I finally read <a href="http://developer.apple.com/internet/webcontent/xmlhttpreq.html">this article</a> (which had been sitting for a long time in my thick pile of printed &#8220;read-later&#8221; articles) from Apple Developer Connection about <code>XMLHttpRequest</code> which plays a major part in <a href="http://www.adaptivepath.com/publications/essays/archives/000385.php">AJAX</a>, which has been discussed a lot lately in the web development area and is used by popular services such as <a href="http://gmail.google.com">Gmail</a>, <a href="http://maps.google.com">Google Maps</a>, <a href="http://www.google.com/webhp?complete=1&amp;hl=en">Google Suggest</a> and many more.</p></li>
<li><p>Maybe I should have named this category &#8220;Things I Learned Yesterday&#8221; since I usually post after midnight.</p></li>
<li><p>There are still a lot of people who are so naive and believe everything they got from the Internet or from forwarded emails, especially urban-legend or scarelore kind of stories. I hate those.</p></li>
<li><p><a href="http://www.cebit.com.au">CeBIT Australia</a> exists and it will be held from 24 to 26 May 2005 at Sydney Exhibition Centre in Darling Harbour. I&#8217;ll be there if I don&#8217;t have any exams or anything like that during that time. Free online registration.</p></li>
<li><p>Need to put less ginger and more salted fish for my spicy eggplant with minced pork.</p></li>
<li><p>A second look on iSCSI and <a href="http://www.cuddletech.com/articles/iscsi/">iSCSI on Linux</a>, and a first introduction to <a href="http://www.vinumvm.org/">Vinum Volume Manager</a> after reading <a href="http://ask.slashdot.org/article.pl?sid=05/04/27/1520246&amp;tid=222&amp;tid=198&amp;tid=230&amp;tid=4">a Slashdot post</a> about Firewire storage.  </p></li>
<li><p>There are several available options for backup solutions that are POSIX ACL aware, including using <a href="http://cdrecord.berlios.de/old/private/star.html">star</a>, proper backup program like <a href="http://www.arkeia.com/">Arkeia</a>, patched NFS (out-of-the-box with SuSE), rsync with ACL patch, etc. Info from <a href="http://lists.samba.org/archive/samba/2004-October/094149.html">this post</a>, googled after answering <a href="http://www.mail-archive.com/tanya-jawab@linux.or.id/msg23774.html">a mailing list post</a>.</p></li>
<li><p>&#8220;IT Research Methods&#8221; is such a boring and useless (at least for me) subject. It&#8217;s hard to force myself to do the assignments for this subject. I wouldn&#8217;t have taken the subject if it weren&#8217;t mandatory. Oh, what the hell, might as well make something useful out of it since I&#8217;ve already paid for it.</p></li>
<li><p>Evolution 2.0.x <a href="http://bugzilla.gnome.org/show_bug.cgi?id=260853">doesn&#8217;t provide an alarm</a> for contact items such as for contact birthdays and anniversaries. I haven&#8217;t looked at the source code yet so I don&#8217;t know how hard it is to implement this. It&#8217;s surprising since I thought many people would have wanted this useful feature. </p></li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://ronny.haryan.to/archives/2005/04/30/tilt-2005-04-29/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
