<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;D0IDRX8_fSp7ImA9WxJWGU4.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835</id><updated>2009-06-25T23:12:54.145+10:00</updated><title>My hovercraft is full of eels</title><subtitle type="html">The occasional airing of my not so occasional rants.</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://www.harukizaemon.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>287</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><link rel="self" href="http://feeds.feedburner.com/MyHovercraftIsFullOfEels" type="application/atom+xml" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry gd:etag="W/&quot;D0IEQXk_eCp7ImA9WxJWGU4.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-3303245576931958808</id><published>2009-06-25T16:03:00.008+10:00</published><updated>2009-06-25T23:11:40.740+10:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-25T23:11:40.740+10:00</app:edited><title>Problem Solving</title><content type="html">&lt;span class="Apple-style-span"  style="font-size:medium;"&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;It occurred to me recently that I have this notion of programming as a process that involves breaking a problem down into a sets of smaller and smaller problems until I have something I know how to solve. (I mentioned this to &lt;/span&gt;&lt;/span&gt;&lt;a href="http://iridescenturchin.blogspot.com/"&gt;&lt;span class="Apple-style-span"  style="font-size:medium;"&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;Steve&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span class="Apple-style-span"  style="font-size:medium;"&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt; yesterday which reminded him of a &lt;/span&gt;&lt;/span&gt;&lt;a href="http://www.electronics.dit.ie/staff/sofearghail/mathematicians.htm"&gt;&lt;span class="Apple-style-span"  style="font-size:medium;"&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;joke about an engineer and a mathematician&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span class="Apple-style-span"  style="font-size:medium;"&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;.)&lt;/span&gt;&lt;/span&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:medium;"&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:medium;"&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;I have previously just assumed that I therefore follow this process when I'm actually problem solving however, on reflection, I'n not so sure. More specifically, I'm either not doing it at all or, at the very least, I'm doing it intuitively.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:medium;"&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:medium;"&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;I wonder how many people do (or have done) this as an explicit part of their own problem solving and if so, what effects they've noticed as a consequence.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-3303245576931958808?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/3303245576931958808/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2009/06/problem-solving.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/3303245576931958808?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/3303245576931958808?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/v_eBQX-Zcjw/problem-solving.html" title="Problem Solving" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2009/06/problem-solving.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUIASXg-cSp7ImA9WxJWGU8.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-4743437265562813066</id><published>2009-06-25T12:00:00.013+10:00</published><updated>2009-06-25T19:52:28.659+10:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-25T19:52:28.659+10:00</app:edited><title>Collaboration</title><content type="html">&lt;span class="Apple-style-span"   style="  ;font-family:monospace;font-size:medium;"&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style=" ;font-family:monospace;"&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;I've heard the phrase "Excuse my poor code" (or words to that effect) a number of times recently. I've said it to myself about my own code (if not to others), I've heard two of my work colleagues say it, and I've had a customer's sole developer apologise ad nauseum about his code.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;I'm not here to make any determination as to the veracity of these claims but the thing that I find interesting is that in all cases, the majority of the code in question was written by a single person.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;Now this may be difficult for some to believe but I inherently don't trust my own opinion. I do tend to confidently put ideas forward as bold assertions to be shot down when I'm wrong. The confidence comes from knowing I'll defend my ideas to the hilt and not take it personally when my argument is proven flawed.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;I hate working alone because I don't trust decisions that weren't arrived at through furious debate. I don't like developers working alone no matter how good they are (or think they are) because I don't trust they can be objective enough by themselves. In fact I don't care if they're working in pairs of developers or not, I just want their decisions to be scrutinised by smart people as early as possible.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;The idea that the majority of a developer's work involves going off to think on their own is totally nonsensical to me. It feels like a lot of hocus pocus going on behind closed doors and then *poof* a few days later something is divined. Not only is the process opaque, but the possibility for smart people to scrutinise is left as late as possible. Sure, I like to sit and ponder without the hullabaloo of every man and his dog trying to give me their 2c worth as much as the next but the lack of transparency and scrutiny in this as a process is something I find very difficult to accept.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;Smart people working alone is even worse. They're often implicitly promoted to the position of grand wizard, the seer and knower of all things. They go away, think about a problem and come back with much fanfare (trumpets playing, drums beating) to bestow upon the people their creation who will wonder in amazement at the design, so simple and yet, so complex, that only they can truly grasp the significance of what they have achieved.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;When I write code on my own, I feel personally, individually, responsible. When I write code on my own, the pressures I feel are, mostly, self imposed. They make me lie awake at night. They make me code into the wee hours of the morning, on the train home. I worry that I've missed something. I start to believe my own hype. That I'm good enough to do this on my own. That because I did most of it on my own, I should fix it on my own lest someone else realise how crap my code is. In a vacuum my ideas have no predators. As importantly (&lt;span class="Apple-style-span"  style=" ;font-family:georgia, -webkit-fantasy;"&gt;if not more so)&lt;span class="Apple-style-span"  style=" ;font-family:georgia, fantasy;"&gt;, my priorities have no predators. I race towards a goal without stopping to re-think that goal because I'm stuck in a vicious cycle of self doubt and self confidence. Moreover, when I work alone, the production of code becomes the focus and not, as I believe it should be, thinking!&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;When we write code together we have shared code ownership. I feel like someone has my back. That even if we got the decision "wrong", we decided together and if two smart people can't get it right then maybe it's good enough, maybe there are diminishing returns for adding more people to solving the problem. When I go home at night, sure I might tinker but I know there's not much point to spending a lot of effort because I'll just be re-doing it tomorrow anyway. And if there is something we missed, someone has my back if need be.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;I speak as one who has been, done and experienced others doing all of the above and decided once and for all that it's just not worth working alone. Having more than one person working on something WILL cost more but I assert that the result will be better and will be achieved more quickly and with less churn. Anyone who thinks they're smart enough to do otherwise needs to learn to &lt;/span&gt;&lt;a href="http://teddziuba.com/2009/06/startups-keep-it-in-your-pants.html"&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;keep it in their pants&lt;/span&gt;&lt;/a&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;I have this mental model of software development that is akin to the way I've heard Abraham Lincoln's cabinet described. Apparently he got a whole bunch of really smart people together, even those from opposite sides of politics, and let them argue it out. His (Lincoln's) role was to decide when to stop the debate and which idea(s) to act on.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;I think that's what collaborative development should be.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:georgia;"&gt;YMMV :)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-4743437265562813066?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/4743437265562813066/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2009/06/collaboration.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/4743437265562813066?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/4743437265562813066?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/vP7xRbgaSQA/collaboration.html" title="Collaboration" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2009/06/collaboration.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Dk8EQng5fSp7ImA9WxJXGUg.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-8734573793952587548</id><published>2009-06-14T09:10:00.009+10:00</published><updated>2009-06-14T14:46:43.625+10:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-14T14:46:43.625+10:00</app:edited><title>No, Sleep, Till Bedtime</title><content type="html">Or at least until all those &lt;a href="http://twitter.com/"&gt;Twitter&lt;/a&gt; client developers have fixed their &lt;a href="http://www.twitpocalypse.com/"&gt;Twitpocalypse&lt;/a&gt; bugs. In case you didn't know, a few days ago the ID range used for Twitter's messages exceeded 2^31 (approximately 2 billion) causing any apps that stored them as 32-bit integers to think they were really small negative numbers. &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It's usual--and I say usual because I don't always adhere to it--policy for storing external identifiers is to treat them as text, even when I know they are numbers. Why? Essentially because I consider it a coincidence that they're numbers. That identifier, number though it may be, has no special significance to me over and above being an opaque handle to some entity in another system. As such, I like to treat them as text.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Discussing this with a good friend and colleague of mine, the question of column width came up. Ie. if you're going to make it text, how long should the column be? If you're lucky enough to be using a database such as PostgreSQL, then the answer is: it doesn't matter--there's no performance benefit to artificially limiting the size of the column. For other databases, the common practice is to use something like &lt;code&gt;VARCHAR(255)&lt;/code&gt;. Think about it, even if it is a number that's 10^255!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Twitter claims that its &lt;a href="http://apiwiki.twitter.com/Twitter-API-Documentation"&gt;API&lt;/a&gt; is RESTful. And if to you, REST means nice, predictable URLs with some semantic path possibly followed by a numeric id and returning numeric ids in search results, then yes, it's RESTful. Want to see the most recent messages for a user? There's a simple HTTP request you can make to a nice, semantic (if you speak English) URL that returns a list of them and their identifiers. And, as expected, our Twitter clients have been dutifully squirrelling these ids away in integer fields (probably because that's the default) and all has well until 2 days ago.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Now, without going into too much of an ideological rant, I happen subscribe to the principle that RESTful URLs should be opaque. That is, a URL is a URL is a URL. No slicing, no dicing, no assembling, no joining. If I have a URL to a resource then that's what I use. Period. End of story. (You can find plenty of discussion on this by Roy Fielding using Google.)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, back to our column widths. Assuming we have a text field in our database large enough to accommodate a URL, we could go one step further. Rather than treat the identifier as text and storing that, why not go the whole hog and store the URL instead?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;As far as I can tell, the only reason is that Twitter's API, RESTful though they may claim, sends back numeric identifiers rather than URLs which in turn leads developers to incorrectly assume that they should be storing them as numbers.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;On their own, identifiers are meaningless and in fact, useless. To utilise an identifier requires us to know the system in which it is stored and the collection in which it belongs. If instead each piece of information was identified by a URL we get all that context for free and the power to share information grows phenomenally.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;To me, the beauty and power of the internet is the ability to link together disparate systems in ways no one had previously imagined. More specifically, in ways the publishers of the information never considered.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Opaque URLs combined with idiomatic use of HTTP verbs can help reduce the coupling between producers and consumers by giving back control to producers in how and where they store information and at the same time increasing the freedom for others to share and use that information.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;(That last paragraph reads like an &lt;a href="http://www.amnesty.org/"&gt;Amnesty International&lt;/a&gt; commercial!)&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-8734573793952587548?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/8734573793952587548/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2009/06/no-sleep-till-bedtime.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/8734573793952587548?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/8734573793952587548?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/w2uNu3ZwOSg/no-sleep-till-bedtime.html" title="No, Sleep, Till Bedtime" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://www.harukizaemon.com/2009/06/no-sleep-till-bedtime.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08NQn0-fyp7ImA9WxJRF08.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-2499749709306246199</id><published>2009-05-15T19:26:00.007+10:00</published><updated>2009-05-19T19:38:13.357+10:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-19T19:38:13.357+10:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="gtd runway" /><title>Shameless Self Promotion</title><content type="html">&lt;p&gt;So the past couple of months, I've finally had the luxury of starting to realise my (and &lt;a href="http://www.cogentconsulting.com.au"&gt;Cogent's&lt;/a&gt;) dream of doing product development.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;We just recently launched what we hope is a very simple, easy to use and somewhat opinionated web application for &lt;a href="http://www.runwayapp.com/"&gt;Getting Things Done&amp;trade; (GTD)&lt;/a&gt;. It's a crowded market to be sure but we really believe we understand GTD well enough to deliver a system that is &lt;strong&gt;more than just a to-do list with GTD inspired keywords&lt;/strong&gt;.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Runway is still in the early stages of feature development. For those that know anything about GTD, you'll be happy to hear that we're working on delivering Projects, Artifacts, Agendas and of course an Inbox, to name but a few, in the very near future.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;What you see now is, and will always be, free. at some point we'll be adding pay-for features but we'll also be doing the right thing by all our early adopters. So, if you have 5 or so minutes, we'd love for you to &lt;a href="http://www.runwayapp.com/signup"&gt;sign up&lt;/a&gt;, have a play, and of course, tell us what you think. &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-2499749709306246199?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/2499749709306246199/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2009/05/shameless-self-promotion.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/2499749709306246199?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/2499749709306246199?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/QxvJs_Tr3mA/shameless-self-promotion.html" title="Shameless Self Promotion" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2009/05/shameless-self-promotion.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUQFSXg8fip7ImA9WxVaGU4.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-1303729540279935516</id><published>2009-04-17T11:43:00.008+10:00</published><updated>2009-04-17T13:01:58.676+10:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-04-17T13:01:58.676+10:00</app:edited><title>Web standards and all I got was this lousy website</title><content type="html">&lt;p&gt;Over the Easter long weekend, I had a great break from work and a great opportunity to think about and reflect on my career, my job, and my profession as a whole. It's safe to say I become a bit disheartened and disillusioned. The one striking conclusion I kept arriving at is that we are so technology focused that we spend too much time, money and effort building things that the customer is "happy" with but not blown away by. That we artificially constrain the end user experience based on our notions of "correctness".  In particular, &lt;strong&gt;web application development is largely a bunch of dick-pulling technical masturbation&lt;/strong&gt;, forever re-inventing the wheel at a ridiculously low level of&lt;br /&gt;abstraction shoving our technological solutions down user's throats in the name of "software engineering". &lt;strong&gt;What's worse is that I've been complicit.&lt;/strong&gt; Not only by buying the hype but often by trying to do "the right thing" even when I felt as though I was bashing my head against a brick wall.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Rewind the clock to somewhere between 1996 and 1999. During those years I, along with a good friend and colleague built a desktop application that was delivered to thousands of users across Australia using nothing more than good old-fashioned client-server SQL written in, of all things, PowerBuilder--kinda like VisualBasic. &lt;strong&gt;More than 10 years ago, the user experience was compelling and sophisticated, it performed exceptionally well over 2400 baud dialup modems, and we built the initial release with only 2 people over 3 months from scratch.&lt;/strong&gt; As shameful as it is, especially coming from one so vocal about automated testing as I, we had nothing but manual testing but we also had few bugs and when users did find a problem, we fixed and redeployed within 24 hours--mostly because we didn't want to interrupt users as they worked and so waited until after hours. Over the next 12 months, we were able to adapt to the user's needs immediately. Rarely did we add the features as request but we always managed to produce a solution they actually needed. Fast forward a decade and &lt;strong&gt;I feel like I suck&lt;/strong&gt; because I honestly don't believe I could do the same thing again today. In fact, &lt;strong&gt;I challenge any of us to build the same user experience with our existing technology stack&lt;/strong&gt;.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;To those that know me well, I will no doubt sound like a broken record but I can't help feel we've been trying to coerce HTML &amp; CSS into something they just aren't and doing so for a decade now.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Think about it, HTML: HyperText Markup Language. Does that sound like it has anything to do with layout and design? In fact do you know any designers, even those that call themselves web designers, that do any of their design work in HTML/CSS? No--well none that I've ever heard of. The closest I can think of is a colleague who does his wireframes in OmniGraffle and then generates HTML/CSS. Why? I put it to you it's because &lt;strong&gt;we don't think in HTML/CSS&lt;/strong&gt;. You CAN'T effectively think in HTML/CSS and if a guy who's expertise lies in designing user interfaces can't think in terms of HTML/CSS why the hell do we think we should?&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;strong&gt;HTML was designed for linking documents&lt;/strong&gt; with a modicum of layout and has served that purpose admirably. As a result, the web browser largely won the battle for desktop supremacy and almost everyone has a web browser and regularly uses a number of web sites. Similarly, pretty much everyone has a computer running an 80x86 based CPU and run dozens of applications built specifically for it. &lt;strong&gt;HTML/CSS are the machine language of the web&lt;/strong&gt;.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;For those of us lucky enough to have done any assembler programming, we've also been lucky enough not to have had to do any for a very long time. Instead, we chose to move away from assembler to other languages. C, C++, Java, Smalltalk, Python, Perl, Ruby, literally &lt;strong&gt;dozens of other programming languages that have systematically improved the level of abstraction.&lt;/strong&gt; Many of these languages now run on top of the JVM, LLVM, CLR, etc. themselves abstractions on top of the underlying CPU.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Did we move because the runtime was faster? Hardly. In fact in almost all cases outrageous claims were made early on that poor performance would be the undoing of these languages and in almost all cases these claims ultimately proved unfounded. No, &lt;strong&gt;we moved to these languages because we hoped they would give us a better level of abstraction.&lt;/strong&gt; That we could code more closely to the way we think. That we would one day realise the dream of literally thinking in code.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Even within languages we constantly strive to improve the level of abstraction. In many cases we've created Domain-Specific-Languages in order that we are better able to think IN the language most appropriate to the task at hand rather than needing to perform some contorted mapping process. This is the reason the Ruby community has slowly moved from Test::Unit to RSpec/Shoulda: Test::Unit does the job just fine but it's verbose and "too close to the metal". Just like assembler. &lt;strong&gt;When I'm the most productive I'm literally thinking in code.&lt;/strong&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;strong&gt;We've largely sorted the back-end problems&lt;/strong&gt;: Database access layers, routing, data format conversion, validation, you name it it's all been largely worked out in whatever framework and language combination you can imagine. &lt;strong&gt;The same cannot be said of the front-end WHERE IT ACTUALLY MATTERS.&lt;/strong&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Granted, HTML/CSS has undergone change but to what extent and to what end? We have JSP, ASP, ERB, HAML, SASS, Liquid, blueprint, jQuery, Prototype, MooTools, Dojo, YUI, etc. but none of them appreciably raises the level of abstraction. &lt;strong&gt;Most advances in the world of HTML/CSS are lipstick.&lt;/strong&gt; They're all constrained by the fallacy that HTML/CSS is the holy grail of web design. No, the whole problem with web development is that &lt;strong&gt;we haven't abstracted away the underlying technology&lt;/strong&gt;, instead we've been conned by a bunch of HTML/CSS gurus and boffins who think that designing the perfect machine code is all the world needs. &lt;strong&gt;There is nothing more primitive than HTML+CSS when it comes to the web.&lt;/strong&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;HTML &amp; CSS try to be all things to all people and by doing so, much like J2EE, we ended up with a set of primitive tools that are repetitive, verbose, hard to test, maintain and refactor and ultimately provide a user experience that can best be described as &lt;strong&gt;a tarted up, 24-bit 3270 terminal.&lt;/strong&gt; Don't believe me? Point me at a website where the user experience feels liquid and natural. Where it literally gets out of your way so that you never even realise you're using it? For the most part you can't. The poster children of the Rails world provide at best a rudimentary user experience. I suspect people use them because there is no alternative, not because it's actually a great UX. Why? IMHO because the technology choices are just plain awful. If you can find a website with a rich user experience that just melts away, you'll likely find a bunch of developers who either had nervous breakdowns or spent many years building some superduper framework, or both!&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;To be fair I'm no doubt coming across as though HTML/CSS is to blame for all the world's problems. Not at all. We suffer from similar problems across the board in software development. It just so happens that I've been in the world of web development for a long time now and feeling the effects.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;I'm not advocating the use of any particular technology--that would kinda defeat the purpose of my argument. What I am saying is that I believe we're stuck in a mindset that only allows us to think inside the incredibly narrow bounds of something we're used to, IMHO, only because it's all we're used to.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Rather than embracing the "web paradigm" how about we &lt;strong&gt;embrace the user and their experience and decide what technology would best enable us to deliver that.&lt;/strong&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-1303729540279935516?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/1303729540279935516/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2009/04/web-standards-and-all-i-got-was-this.html#comment-form" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/1303729540279935516?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/1303729540279935516?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/UJ_0weTzNjU/web-standards-and-all-i-got-was-this.html" title="Web standards and all I got was this lousy website" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><feedburner:origLink>http://www.harukizaemon.com/2009/04/web-standards-and-all-i-got-was-this.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkYCRXczcCp7ImA9WxVQFU8.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-6521864846066989396</id><published>2009-02-02T09:39:00.005+11:00</published><updated>2009-02-02T09:42:44.988+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-02T09:42:44.988+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript" /><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><category scheme="http://www.blogger.com/atom/ns#" term="Objective-C" /><title>A Title Case Gem for Ruby</title><content type="html">&lt;p&gt;A project I'm working on called for some "smart" capitalisation of page titles. Essentially I wanted to take a URL &lt;a href="http://en.wikipedia.org/wiki/Slug_(production)"&gt;slug&lt;/a&gt; and generate a page title.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Rails comes with a built-in &lt;code&gt;String#titleize&lt;/code&gt; method that capitalises every word but that looked a little odd when the title was something like: &lt;em&gt;"My Hovercraft Is Full Of Eels"&lt;/em&gt;. So I went on a hunt for something "smarter".&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;After a little search I stumbled upon Marshall Elfstrand's &lt;a href="http://vengefulcow.com/titlecase/"&gt;JavaScript, Ruby, and Objective-C  ports&lt;/a&gt; of &lt;a href="http://daringfireball.net/"&gt;John Gruber's&lt;/a&gt; "Title Case" algorithm and decided to turn it into a &lt;a href="http://github.com/harukizaemon/titleizer"&gt;Gem&lt;/a&gt; that adds &lt;code&gt;String#titleize&lt;/code&gt; and &lt;code&gt;String#titleize!&lt;/code&gt; (aliased as &lt;code&gt;#titlecase&lt;/code&gt;, and &lt;code&gt;#titlecase!&lt;/code&gt; respectively). When used in a Rails environment, this effectively replaces the Rails versions.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Now my page titles look a little more human-like: "My Hovercraft is Full of Eels".&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-6521864846066989396?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/6521864846066989396/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2009/02/title-case-gem-for-ruby.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/6521864846066989396?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/6521864846066989396?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/mxQ82w5Gl9o/title-case-gem-for-ruby.html" title="A Title Case Gem for Ruby" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2009/02/title-case-gem-for-ruby.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0ECSXY7cCp7ImA9WxVREk8.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-8652509510371950975</id><published>2009-01-18T08:46:00.005+11:00</published><updated>2009-01-18T09:01:08.808+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-18T09:01:08.808+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><title>Plugins move</title><content type="html">Following hot on the heels of my &lt;a href="http://www.harukizaemon.com/2009/01/blog-move.html"&gt;blog move&lt;/a&gt;, I've finally moved all my rails plugins off the venerable RubyForge and onto GitHub.&lt;br /&gt;&lt;br /&gt;Since I started working at &lt;a href="http://www.cogentconsulting.com.au"&gt;CogentConsulting&lt;/a&gt;--no we're not "The Company of ex-ThoughtWorkers" unless you count all 3 of us as somehow statistically significant--I've had less and less time and less and less inclination to spend any appreciable effort on &lt;a href="http://www.redhillconsulting.com.au/"&gt;RedHill&lt;/a&gt; related stuff to the point where the company really exists just to support and market &lt;a href="http://www.redhillconsulting.com.au/products/simian"&gt;Simian&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;As a consequence, I've also dropped the RedhillOnRails moniker in favour of publishing the plugins under &lt;a href="http://github.com/harukizaemon/"&gt;my personal account&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-8652509510371950975?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/8652509510371950975/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2009/01/plugins-move.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/8652509510371950975?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/8652509510371950975?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/7b_xlOw5rMM/plugins-move.html" title="Plugins move" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2009/01/plugins-move.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUIMRXg8fyp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-5632432830772578869</id><published>2009-01-17T22:43:00.002+11:00</published><updated>2009-01-17T22:59:44.677+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:59:44.677+11:00</app:edited><title>Blog move</title><content type="html">If you're reading this then the move of my blog was successful and thank-you for putting up with a screwy RSS feed during the transition. No doubt you received double or possibly even triple posts.&lt;br /&gt;&lt;br /&gt;Why the move? Well, even though &lt;a href="http://www.geekisp.com/"&gt;GeekISP&lt;/a&gt; have been a fantastic hosting provider over the years and MovableType has been pretty reliable as a blogging platform, in my never ending quest to Do Less Stuff, I figured it was time to move the pain somewhere else.&lt;br /&gt;&lt;br /&gt;From a technical perspective, the move was fairly easy though not without some pain. There is no direct way to import from MT to Blogger however I did find &lt;a href="http://movabletype2blogger.appspot.com/"&gt;a tool that helped&lt;/a&gt; convert the MT export file into something Blogger could import.&lt;br /&gt;&lt;br /&gt;I also wrote a quick script to replace all internal references with new links as well as generating a new .htaccess file for any links from the outside world. This step was pretty easy although it took some trial and error to work out what how Blogger converts titles into URLs--as near as I can tell it truncates to a maximum of 40 characters with a bias towards word boundaries. The duplicate posts appearing in the RSS feed were as a direct result of me re-creating the entire blog several times fixing little things here and there.&lt;br /&gt;&lt;br /&gt;And so it is that my blog comes to be here on Blogger. The next step is to move all my domain hosting to Google Sites but that's for another day. Hopefully this will be the last move for some time and, with someone else maintaining my blogging software, hopefully less stuffing around on my part.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-5632432830772578869?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/5632432830772578869/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2009/01/blog-move.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/5632432830772578869?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/5632432830772578869?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/PP27sLBkOR4/blog-move.html" title="Blog move" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2009/01/blog-move.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08DQH4_cCp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-5783312490449752402</id><published>2009-01-12T20:26:00.001+11:00</published><updated>2009-01-17T22:31:11.048+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:31:11.048+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><title>Rails, meet Drupal.</title><content type="html">If you've been considering integrating (or replacing) your Drupal application with a Rails application, then &lt;a href="http://github.com/harukizaemon/drupal_fu"&gt;Drupal Fu&lt;/a&gt; may come in handy.&lt;br /&gt;&lt;br /&gt;It's pretty rough-and-ready--I essentially just ripped the code out of an existing application and cobbled it together--with, as yet, no plugin infrastructure, Rakefile, or anything else that might give you a degree of confidence in the quality of the code :)&lt;br /&gt;&lt;br /&gt;That said, the code has been working in a production application for a while and we figured it might help out some others going through the same pain.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-5783312490449752402?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/5783312490449752402/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2009/01/rails-meet-drupal.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/5783312490449752402?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/5783312490449752402?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/oM2dRSLJdWY/rails-meet-drupal.html" title="Rails, meet Drupal." /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2009/01/rails-meet-drupal.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEcERXw5cSp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-3004042081213516999</id><published>2009-01-12T07:32:00.001+11:00</published><updated>2009-01-17T22:33:24.229+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:33:24.229+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><title>Acts As Teapot</title><content type="html">No, it's not April Fools yet but I thought I'd get in early this year. &lt;a href="http://github.com/harukizaemon/acts_as_teapot"&gt;Acts As Teapot&lt;/a&gt; is a Ruby on Rails plugin that ensures your Ruby on Rails applications conform to &lt;a href="http://www.ietf.org/rfc/rfc2324.txt"&gt;RFC2324&lt;/a&gt;. My assumption here is that your application is not a coffee pot and therefore does not understand the Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0). Thus, if ever a BREW request or any other request with the Content-Type set to "application/coffee-pot-command" is received, the server will respond with 418 I’m a teapot.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-3004042081213516999?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/3004042081213516999/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2009/01/acts-as-teapot.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/3004042081213516999?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/3004042081213516999?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/iCoGMYk4-PU/acts-as-teapot.html" title="Acts As Teapot" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2009/01/acts-as-teapot.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEYASH4zfyp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-295947588874612208</id><published>2008-07-25T22:41:00.001+10:00</published><updated>2009-01-17T22:35:49.087+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:35:49.087+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Mac OS X" /><title>TimeMachine FTW!</title><content type="html">Not withstanding the fact that I needed to restore my operating system in the first place--due to an inexplicable and catastrophic failure of the Java installation resulting in segfaults--I was able to restore my entire 100GB system in around 4 hours. For posterity:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Boot off the OS X System Install DVD--hold down option while the system starts&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Connect the external drive with the TimeMachine backup--in my case a TimeCapsule attached via ethernet&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Select "Restore from TimeMachine backup" in the Utilities menu&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Select the specific backup (by timestamp) from which to restore&lt;/li&gt;&lt;br /&gt;&lt;li&gt;And away you go!&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;The disk is then automatically erased and a fully bootable system is restored sans temp directories and cache files. It even managed to restore my PostgreSQL databases that were running at the time--which probably says more about PostgreSQL than anything.&lt;br /&gt;&lt;br /&gt;The one grumble I do have is that the timestamps in the name of the backups were some non-obvious period relative to the actual date the backup was made. The difference wouldn't have been much of an issue had I simply needed to restore the most recent backup but as it turned out I needed to go back a couple of days in order to get a clean system. Thankfully I got lucky on the second attempt :)&lt;br /&gt;&lt;br /&gt;Once I had restored the system I took a look at the backup folders and sure enough there are two timestamps: the one in the folder name, and the created date. The created timestamp was spot on but the one in the folder name--the one presented to you when restoring--was whacky. I honestly didn't spend long enough to calculate if the difference was consistent.&lt;br /&gt;&lt;br /&gt;What is really interesting is that I had SuperDuper! on my list of software to start using but it would appear there is little need--at least in my case.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-295947588874612208?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/295947588874612208/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/07/timemachine-ftw.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/295947588874612208?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/295947588874612208?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/ExOsrN7cduk/timemachine-ftw.html" title="TimeMachine FTW!" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/07/timemachine-ftw.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEIERn8zeyp7ImA9WxVREk8.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-4366290560667862351</id><published>2008-06-07T21:41:00.001+10:00</published><updated>2009-01-18T07:01:47.183+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-18T07:01:47.183+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><title>Prevent accidental deployment with a prompt</title><content type="html">This morning I went to push out a new version of an application to our staging environment on an Engine Yard slice. I knew I had done exactly that last night so I navigated through my bash history and hit enter. Two minutes later the new version had been deployed and I was about to walk out the door to do some chores before coming back to start playing with the app. Thankfully, &lt;a href="htp://steve.cogentconsulting.com.au/"&gt;Steve&lt;/a&gt; came online and informed me that production was broken.&lt;br /&gt;&lt;br /&gt;A quick look through my bash history and it seemed I'd used the deploy to production rather than deploy to staging but, being in a hurry, hadn't looked carefully enough. Of course some might argue that I should have looked more carefully. That I shouldn't have deployed before heading out. All valid points but I very rarely have full control over what's going on around me. So, while it's all very well and good to hope that I will be more careful next time, that's a bit like hoping global warming isn't a reality: we all hope it's not but maybe we should do something about it just in case?&lt;br /&gt;&lt;br /&gt;And so I added the following at the start of the &lt;code&gt;:production&lt;/code&gt; task in my &lt;code&gt;deploy.rb&lt;/code&gt; file:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code class="editor"&gt;  unless Capistrano::CLI.ui.agree("Are you sure you want to deploy to production? (yes/no): ")&lt;br /&gt;puts "Phew! That was a close call."&lt;br /&gt;exit&lt;br /&gt;end&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;For all other environments, the deployment goes through without question. Attempt to deploy to production however, and I'm now forced to be explicit about my intentions.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-4366290560667862351?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/4366290560667862351/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/06/prevent-accidental-deployment-with.html#comment-form" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/4366290560667862351?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/4366290560667862351?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/GdfJJEa_clw/prevent-accidental-deployment-with.html" title="Prevent accidental deployment with a prompt" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/06/prevent-accidental-deployment-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zeSp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-2855812002473895673</id><published>2008-06-07T01:51:00.000+10:00</published><updated>2009-01-17T22:07:24.381+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.381+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><title>Generating lots of little test cases</title><content type="html">&lt;p&gt;When writing code that is largely algorithmic, I find I end up writing specs that sit in a loop repeating the same operations over a set of data.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;This works well enough but it has the downside that the tests abort as soon as a single failing case is detected which can lead to vicious cycles of fixing one case only to find you've broken another. &lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;The solution is of course to break out each expectation--inputs and expected outputs--so they are all run and reported individually. Doing so by hand however, is tedious to say the least so why not generate them on the fly instead:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;describe Fibonacci do&lt;br/&gt;[[0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5], [6, 8]].each do |input, output|&lt;br/&gt;it "should generate #{output} for #{input}" do&lt;br/&gt;Fibonacci.calculate(input).should == output&lt;br/&gt;end&lt;br/&gt;end&lt;br/&gt;end&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;This is such a elegant solution I'm not sure why it only just occurred to me.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-2855812002473895673?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/2855812002473895673/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/06/generating-lots-of-little-test-cases.html#comment-form" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/2855812002473895673?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/2855812002473895673?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/-Jk8tio7Y6k/generating-lots-of-little-test-cases.html" title="Generating lots of little test cases" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/06/generating-lots-of-little-test-cases.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zeSp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-7434556966292734407</id><published>2008-06-03T04:22:00.000+10:00</published><updated>2009-01-17T22:07:24.381+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.381+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><title>Quickly Migrate all database times to UTC</title><content type="html">&lt;p&gt;If you're thinking about updating to Rails 2.1 to get the timezone support, you'll need to update all database records to UTC. Here's a quick migration script to do just that:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;class ConvertTimestampsToUtc &lt; ActiveRecord::Migration&lt;br/&gt;# Assume all times were in UTC+10:00&lt;br/&gt;OFFSET = "interval '10 hours'"&lt;br/&gt;&lt;br/&gt;# Adjust any date/time column&lt;br/&gt;COLUMN_TYPES = [:datetime, :timestamp]&lt;br/&gt;&lt;br/&gt;def self.up&lt;br/&gt;adjust("-")&lt;br/&gt;end&lt;br/&gt;&lt;br/&gt;def self.down&lt;br/&gt;adjust("+")&lt;br/&gt;end&lt;br/&gt;&lt;br/&gt;private&lt;br/&gt;&lt;br/&gt;def self.adjust(direction)&lt;br/&gt;connection = ActiveRecord::Base.connection&lt;br/&gt;connection.tables.each do |table|&lt;br/&gt;columns = connection.columns(table).select { |column| COLUMN_TYPES.include?(column.type) }&lt;br/&gt;updates = columns.map { |column| "#{column.name} = #{column.name} #{direction} #{OFFSET}"}.join(", ")&lt;br/&gt;execute("UPDATE #{table} SET #{updates}") unless updates.blank?&lt;br/&gt;end&lt;br/&gt;end&lt;br/&gt;end&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;As you can see, I've assumed that the dates were previously stored as AEST (UTC+10:00) so you'll likely need to adjustthat and I'm also assuming PostgreSQL for date manipulation though it should be pretty simple to convert to run under MySQL. It may even work asis.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-7434556966292734407?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/7434556966292734407/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/06/quickly-migrate-all-database-times-to.html#comment-form" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/7434556966292734407?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/7434556966292734407?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/DIlYTl-l5zs/quickly-migrate-all-database-times-to.html" title="Quickly Migrate all database times to UTC" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/06/quickly-migrate-all-database-times-to.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zeSp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-4619586112214748217</id><published>2008-05-30T19:07:00.000+10:00</published><updated>2009-01-17T22:07:24.381+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.381+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><title>Deploying branches with Capistrano</title><content type="html">&lt;p&gt;This morning I had occasion to deploy a branch of a git repository to a staging server but hadn't the foggiest idea how. A quick search through the capistrano source code revealed that I could use &lt;code&gt;set :branch "branch_name"&lt;/code&gt; in my deploy script. I tried it and it worked. I then figured I would need to make a similar change across all my branches. Of course, I'm a lazy sod and wondered if there wasn't a better way.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;If you're not familiar with git, the output of the &lt;code&gt;git branch&lt;/code&gt; command is a list of branches with an asterisk marking the one currently checked out on your local machine. For example:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="console"&gt;$ git branch&lt;br/&gt;* drupal_authentication&lt;br/&gt;fragment_caching&lt;br/&gt;master&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;So, I figured, what if I just parsed the output and searched for the branch marked as current:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;set :branch, $1 if `git branch` =~ /\* (\S+)\s/m&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Now I'm able to deploy whatever branch is current on my local machine from a single, shared, deploy script.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-4619586112214748217?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/4619586112214748217/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/05/deploying-branches-with-capistrano.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/4619586112214748217?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/4619586112214748217?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/8gjnU8oGwRU/deploying-branches-with-capistrano.html" title="Deploying branches with Capistrano" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/05/deploying-branches-with-capistrano.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zeSp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-234615076677305338</id><published>2008-05-13T22:27:00.000+10:00</published><updated>2009-01-17T22:07:24.381+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.381+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript" /><title>JavaScript Date Helpers</title><content type="html">&lt;p&gt;It's all the rage these days to have timestamps displayed in words to indicate how long ago some event occurred. You know something like "less than a minute ago" or "about 2 months ago", etc. You'll see plenty of examples on news sites, blog entries, and bug tracking tickets to name but a few.&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;If you've ever had to build this kind of thing in Rails you'll be familiar with all the &lt;a href="http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html"&gt;Date Helper methods&lt;/a&gt; that make the task pretty trivial. The problem is that the result is fixed to whatever the date was when the page was rendered and as a result these timestamps go stale very quickly.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Save refreshing the page every minute--or hour or whatever--just to update the times, I figured what was needed was a little but of client-side action. Rather than send the text in the HTML, I decided to instead send the raw timestamps and have the browser periodically generate the textual representation.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;To this end, I blatantly copied two methods from the afore-mentioned Rails helper-- &lt;code&gt;distance_of_time_in_words(from, to)&lt;/code&gt;, and &lt;code&gt;time_ago_in_words(from)&lt;/code&gt;--and, taking some liberties along the way, converted them to JavaScript:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;  distance_in_milliseconds = to - this;&lt;br/&gt;distance_in_minutes = Math.abs(distance_in_milliseconds / 60000).round();&lt;br/&gt;&lt;br/&gt;if (distance_in_minutes == 0) {&lt;br/&gt;words = "less than a minute";&lt;br/&gt;} else if (distance_in_minutes == 1) {&lt;br/&gt;words = "1 minute";&lt;br/&gt;} else if (distance_in_minutes &lt; 45) {&lt;br/&gt;words = distance_in_minutes + " minutes";&lt;br/&gt;} else if (distance_in_minutes &lt; 90) {&lt;br/&gt;words = "about 1 hour";&lt;br/&gt;} else if (distance_in_minutes &lt; 1440) {&lt;br/&gt;words = "about " + (distance_in_minutes / 60).round() + " hours";&lt;br/&gt;} else if (distance_in_minutes &lt; 2160) {&lt;br/&gt;words = "about 1 day";&lt;br/&gt;} else if (distance_in_minutes &lt; 43200) {&lt;br/&gt;words = (distance_in_minutes / 1440).round() + " days";&lt;br/&gt;} else if (distance_in_minutes &lt; 86400) {&lt;br/&gt;words = "about 1 month";&lt;br/&gt;} else if (distance_in_minutes &lt; 525600) {&lt;br/&gt;words = (distance_in_minutes / 43200).round() + " months";&lt;br/&gt;} else if (distance_in_minutes &lt; 1051200) {&lt;br/&gt;words = "about 1 year";&lt;br/&gt;} else {&lt;br/&gt;words = "over " + (distance_in_minutes / 525600).round() + " years";&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;return words;&lt;br/&gt;};&lt;br/&gt;&lt;br/&gt;Date.prototype.time_ago_in_words = function() {&lt;br/&gt;return this.distance_of_time_in_words(new Date());&lt;br/&gt;};&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Now all I do is periodically invoke a function that calls one or other of these methods and updates the text of whatever display element is appropriate. Even better, because the raw timestamps have timezone information in them, the display doesn't suffer from, in my case here in Australia, always being 10 hours out because the server is sitting in the US with a US date/time.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-234615076677305338?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/234615076677305338/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/05/javascript-date-helpers.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/234615076677305338?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/234615076677305338?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/ySTqyxSZrXo/javascript-date-helpers.html" title="JavaScript Date Helpers" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/05/javascript-date-helpers.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zeip7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-1464155274455308385</id><published>2008-05-13T21:58:00.000+10:00</published><updated>2009-01-17T22:07:24.382+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.382+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript" /><title>Giving the Anchor tag some Ajax Lov'n</title><content type="html">&lt;p&gt;It seems I'm forever needing to submit links using an XMLHttpRequest rather than the default full-page refresh. One approach commonly used in the Rails community is to render each link with the JavaScript already in place. My preferred approach however is to keep the HTML as free from JavaScript as possible and unobtrusively add behaviour using &lt;a href="http://www.danwebb.net/2006/9/3/low-pro-unobtrusive-scripting-for-prototype"&gt;LowPro&lt;/a&gt;.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;LowPro already comes with a built-in behaviour for links but sometimes I need something little more complex than simply submitting the request and so I usually end up doing the following:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;anchor = ...;&lt;br/&gt;new Ajax.Request(anchor.href, { method: "get", parameters: ... });&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Granted that's not a lot of effort but it still felt as though I were repeating myself and that the overall intention of my code was largely obscured by the infrastructure. It then struck me that submitting a form using the &lt;a href="http://www.prototypejs.org"&gt;Prototype JavaScript framework&lt;/a&gt; is almost trivial:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;form = ...;&lt;br/&gt;form.request({ parameters: ... });&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;So I cooked up a version for anchors as well:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;Element.addMethods("A", {&lt;br/&gt;request: function(anchor, options) {&lt;br/&gt;new Ajax.Request(anchor.href, Object.extend({ method : "get" }, options || {}));&lt;br/&gt;}&lt;br/&gt;});&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Now I can submit links in pretty much the same was as I do forms:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;anchor = ...;&lt;br/&gt;anchor.request({ parameters: ... });&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;I'm wondering what other possibilities might occur were I to add a &lt;code&gt;serialize()&lt;/code&gt; method to extract the request parameters.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-1464155274455308385?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/1464155274455308385/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/05/giving-anchor-tag-some-ajax-lov.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/1464155274455308385?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/1464155274455308385?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/oImjdNbpYIw/giving-anchor-tag-some-ajax-lov.html" title="Giving the Anchor tag some Ajax Lov&amp;#39;n" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/05/giving-anchor-tag-some-ajax-lov.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zeip7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-3188722335327552874</id><published>2008-04-22T05:46:00.000+10:00</published><updated>2009-01-17T22:07:24.382+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.382+11:00</app:edited><title>HTTP Ranges for Pagination?</title><content type="html">&lt;p&gt;Would it be a gross perversion to use HTTP ranges for pagination?:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Client asks the server what range types it accepts for people:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;HEAD /people HTTP/1.1&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Server responds:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;Status: 200&lt;br/&gt;Accept-Ranges: pages; records&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Client requests the first page of people:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;GET /people HTTP/1.1&lt;br/&gt;Range: pages=1-1&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Server Responds:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;Status: 206&lt;br/&gt;Content-Range: pages 1-1/13&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-3188722335327552874?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/3188722335327552874/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/04/http-ranges-for-pagination.html#comment-form" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/3188722335327552874?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/3188722335327552874?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/-OGCWDVFKsw/http-ranges-for-pagination.html" title="HTTP Ranges for Pagination?" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/04/http-ranges-for-pagination.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zeip7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-3048210993041781753</id><published>2008-04-18T01:01:00.000+10:00</published><updated>2009-01-17T22:07:24.382+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.382+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><title>Finding the index of an item using a block</title><content type="html">&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; &lt;a href="http://svn.ruby-lang.org/repos/ruby/tags/v1_8_7_preview1/NEWS"&gt;Ruby 1.8.7&lt;/a&gt; also has this.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Ruby 1.9 has it but if you're not that bleeding edge, you can have it now:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;class Array&lt;br/&gt;def index_with_block(*args)&lt;br/&gt;return index_without_block(*args) unless block_given?&lt;br/&gt;each_with_index do |entry, index|&lt;br/&gt;return index if yield(entry)&lt;br/&gt;end&lt;br/&gt;nil&lt;br/&gt;end&lt;br/&gt;alias_method :index_without_block, :index&lt;br/&gt;alias_method :index, :index_with_block&lt;br/&gt;&lt;br/&gt;def rindex_with_block(*args)&lt;br/&gt;return rindex_without_block(*args) unless block_given?&lt;br/&gt;&lt;br/&gt;index = size&lt;br/&gt;reverse_each do |entry|&lt;br/&gt;index -= 1&lt;br/&gt;return index if yield(entry)&lt;br/&gt;end&lt;br/&gt;nil&lt;br/&gt;end&lt;br/&gt;alias_method :rindex_without_block, :rindex&lt;br/&gt;alias_method :rindex, :rindex_with_block&lt;br/&gt;end&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;If you're using Rails you can substitute the two calls each to &lt;code&gt;alias_method&lt;/code&gt; with a single call to &lt;code&gt;alias_method_chain&lt;/code&gt;.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-3048210993041781753?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/3048210993041781753/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/04/finding-index-of-item-using-block.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/3048210993041781753?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/3048210993041781753?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/2vSgg7QHwAY/finding-index-of-item-using-block.html" title="Finding the index of an item using a block" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/04/finding-index-of-item-using-block.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zeip7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-698194271819330915</id><published>2008-04-16T04:59:00.000+10:00</published><updated>2009-01-17T22:07:24.382+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.382+11:00</app:edited><title>It's OK for GET Requests to Update the Database</title><content type="html">&lt;p&gt;We've all been indoctrinated into associating the HTTP request methods POST, GET, PUT and DELETE with the standard database (aka CRUD) operations Create (INSERT), Read (SELECT), Update and Delete respectively. For the most part the analogue holds. When we make a GET request, our &lt;em&gt;intention&lt;/em&gt; is to read whatever the server hands back. When we POST some data, our &lt;em&gt;intention&lt;/em&gt; is to update something.&lt;br/&gt;&lt;br/&gt;&lt;p&gt;The general view however, seems to be that the HTTP methods relate &lt;em&gt;directly&lt;/em&gt; to database operations. In fact many developers seem to think that they are in fact one in the same thing: POST is for INSERTing data, GET for SELECTing, etc. The popularity of which seems to have strengthened with the growing interest in REST and the wide-spread adoption of Rails 2&lt;sup&gt;&lt;a href="#rails"&gt;&lt;small&gt;1&lt;/small&gt;&lt;/a&gt;&lt;/sup&gt;. In fact read of the HTTP/1.1 Method Definitions &lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html"&gt;specification&lt;/a&gt; explicitly states that so-called "Safe Methods" such as GET:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;blockquote&gt;... SHOULD NOT have the significance of taking an action other than retrieval.&lt;/blockquote&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;But what happens when we have a site that tracks where you have visited, updates the "last read date" on each record retrieved, or remembers the last search criteria you used? Each of these features requires recording information into some kind of database--be it relational or simply a log file.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;What people seem to overlook is the paragraph that follows in the same document:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;blockquote&gt;Naturally, it is not possible to ensure that the server does not generate side-effects ... The important distinction here is that the user did not request the side-effects, so therefore cannot be held accountable for them.&lt;/blockquote&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;The HTTP methods should be used to indicate the &lt;em&gt;user's&lt;/em&gt; intention without regard to the underlying implementation. The web application is an abstraction so we need to model the interaction on that abstraction. If the user's intention is to make a change to something then go ahead and use a PUT but if they're only reading some data use a GET even if you know it involves some database writes.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;It may seem somewhat esoteric but spending a bit of time thinking about what the user's intention is exactly has helped me better flesh out an application's API.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;a name="rails"&gt;&lt;/a&gt;&lt;br/&gt;&lt;p&gt;&lt;small&gt;&lt;sup&gt;1&lt;/sup&gt; I don't mean to imply that Rails is the culprit here. Nothing in Rails explicitly makes these assertions. However the fact that the idiom is explicitly referred to as CRUD resources certainly doesn't help.&lt;/small&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-698194271819330915?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/698194271819330915/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/04/it-ok-for-get-requests-to-update.html#comment-form" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/698194271819330915?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/698194271819330915?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/_SsuGbqViYk/it-ok-for-get-requests-to-update.html" title="It&amp;#39;s OK for GET Requests to Update the Database" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/04/it-ok-for-get-requests-to-update.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zeyp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-717601628185818005</id><published>2008-04-15T00:07:00.000+10:00</published><updated>2009-01-17T22:07:24.383+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.383+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript" /><title>Getting Too Fancy with HTTP Response Codes</title><content type="html">&lt;p&gt;As part of my adoption of REST and all its goodness, I've started using HTTP response codes more, responsibly ;-) So, for example, instead of always returning 200 (OK) for just about everything, I'm using 201 (Created) with a Location header set to the new URL after a POST. For PUT, I send back 204 (No Content), a 404 (Not Found) after a GET for a resource that no longer exists, and a good old 200 (OK) after a successful DELETE or GET.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Interestingly, in the system I'm developing at present, an update (PUT) might actually cause a resource to move due to the application of server-side business rules. In this case, the 204 response also sets the Location header so that the client knows where it can be found.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;All this was working beautifully on my local machine using both Safari and Firefox so once I was happy with the result I deployed it into the remote test site and started playing in FireFox. So far so good. Everything checks out. Next let's try Safari...not so great.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Some bits of the application worked just fine but others seemed to have no effect. Then mysteriously things would start working again. Even stranger was the fact that hitting the browser's refresh button had no effect either.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;At first I suspected that nesting Ajax calls might be to blame but as everything seemed to work perfectly in FireFox and a Google search turned up nothing, I decided to do some more investigation.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;I logged in to the server box and tailed the logs for signs of life. Everything looked normal. All the expected requests and responses were there but still nothing client side. Using Safari's new Network Timeline I could see what the browser thought was going on. All the requests and responses were there but something was odd. In all but a few cases, the response code was 204 (No Content). I double checked the server logs but no, the server was definitely sending back the correct responses; a mixture of 204, 200 and 404 as appropriate.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;On a hunch I went back and re-read the &lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html"&gt;HTTP Status Codes&lt;/a&gt; document and in particular the definition for 204:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;blockquote&gt;The server has fulfilled the request but does not need to return an entity-body ... &lt;strong&gt;the client SHOULD NOT change its document view&lt;/strong&gt; from that which caused the request to be sent ...&lt;/blockquote&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;That might actually explain it. If Safari received a 204 and interpreted that to mean "Don't change anything" then hitting refresh would indeed have no effect even if my code subsequently went on to perform more asynchronous requests as a result.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;So, I dutifully changed all the 204s to 200s and voila! Safari started to behave just as expected and FireFox continued to work as had previously.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;I've also noticed a difference in the way both browsers handle 303 (Redirect) from within an XML HTTP Request: Safari performs the redirect and keeps all the headers as per the original, whereas FireFox seems to essentially construct an entirely new request. The upshot is that you can't actually detect (server-side) an XML HTTP Request from FireFox if it is the result of a redirect.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;I'm really not sure why the two browsers have such differing opinions of what the appropriate behaviour should be in either case but I hope this helps some other poor sod keep from pulling their hair out.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-717601628185818005?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/717601628185818005/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/04/getting-too-fancy-with-http-response.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/717601628185818005?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/717601628185818005?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/3s05MXIR3TQ/getting-too-fancy-with-http-response.html" title="Getting Too Fancy with HTTP Response Codes" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/04/getting-too-fancy-with-http-response.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zeyp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-6515321439998329933</id><published>2008-04-12T08:36:00.000+10:00</published><updated>2009-01-17T22:07:24.383+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.383+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript" /><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><title>Drag &amp; Drop Prioritizable Lists</title><content type="html">&lt;p&gt;Yes, it's true, &lt;a href="http://script.aculo.us/"&gt;Scriptaculous&lt;/a&gt; already provides a &lt;a href="http://wiki.script.aculo.us/scriptaculous/show/Sortable.create"&gt;&lt;code&gt;Sortable&lt;/code&gt;&lt;/a&gt; that makes it almost trivial to enable drag'n'drop sorting of your HTML lists. Whenever an item is moved an &lt;code&gt;onUpdate()&lt;/code&gt; event is called (if provided) allowing you to inspect the new order and presumably perform an AJAX request to record the change. In principle, this sounds great but I've never really liked it for a couple of reasons.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;For a start, if you have any appreciable number of items updating each in the database just to re-order one seems somewhat unnecessary. Not withstanding the fact that we need to send all those ids to the server in the first place.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Secondly, if you're doing any kind of filtering, it's difficult at best to take the newly constructed ordering and apply that at the back-end; what happens to all the items that may be lurking in between that aren't presently displayed?&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Enter &lt;code&gt;Prioritizable&lt;/code&gt; (itself built on top of &lt;code&gt;Sortable&lt;/code&gt;).&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;You use it in much the same way as &lt;code&gt;Sortable&lt;/code&gt; with the major difference being that the &lt;code&gt;onUpdate()&lt;/code&gt; event is called with three&lt;br/&gt;arguments: the item that was moved, the sibling relative to which it was moved, and the relative position (&lt;code&gt;"higher"&lt;/code&gt; or &lt;code&gt;"lower"&lt;/code&gt;). And, if like me, you're feeling a bit RESTful, it's pretty easy to turn these arguments into a nice semantic URL and parameters as shown:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;Prioritizable.create($("chores"), {&lt;br/&gt;onUpdate: function(item, position, sibling) {&lt;br/&gt;id = item.substring(6);                           // "chore_17" =&gt; "17"&lt;br/&gt;sibling_id = to.substring(6);                     // "chore_2" =&gt; "2"&lt;br/&gt;url = "/chores/" + sibling_id + "/" + position;   // "/chores/2/higher"&lt;br/&gt;&lt;br/&gt;new Ajax.Request(url, {&lt;br/&gt;method: "post",&lt;br/&gt;parameters: { id: id }&lt;br/&gt;});&lt;br/&gt;}&lt;br/&gt;});&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;When the &lt;code&gt;onUpdate()&lt;/code&gt; event is called we POST the id of the item to be moved to a path constructed from the id of the sibling and the relative position. Assuming the the user moves &lt;code&gt;chore_17&lt;/code&gt; just above &lt;code&gt;chore_2&lt;/code&gt; we would POST &lt;code&gt;"id=17"&lt;/code&gt; to &lt;code&gt;/chores/2/higher&lt;/code&gt;.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;In practice, I combine this client-side behaviour with some server-side code that provides &lt;code&gt;move_higher_than()&lt;/code&gt; and &lt;code&gt;move_lower_than()&lt;/code&gt; methods that efficiently handle all the necessary database updates.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;All the pieces mentioned will eventually be available alongside &lt;a href="http://www.cogentconsulting.com.au/"&gt;Cogent's&lt;/a&gt; other &lt;a href="https://rubyforge.org/projects/cogent-rails/"&gt;Rails plugins&lt;/a&gt; but until then, here's enough of the Javascript side of things to get you going.&lt;p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;var Prioritizable = {&lt;br/&gt;create: function(element) {&lt;br/&gt;options = Object.extend(arguments[1] || {}, {&lt;br/&gt;onChange: Prioritizable.onChange,&lt;br/&gt;onUpdate: Prioritizable.onUpdate&lt;br/&gt;});&lt;br/&gt;&lt;br/&gt;Sortable.create(element, options);&lt;br/&gt;},&lt;br/&gt;&lt;br/&gt;destroy: function(element) {&lt;br/&gt;Sortable.destroy(element);&lt;br/&gt;},&lt;br/&gt;&lt;br/&gt;onChange: function(item) {&lt;br/&gt;Sortable.options(item)._item = item;&lt;br/&gt;},&lt;br/&gt;&lt;br/&gt;onUpdate: function(element) {&lt;br/&gt;options = Sortable.options(element);&lt;br/&gt;item = options._item;&lt;br/&gt;options._item = null;&lt;br/&gt;&lt;br/&gt;sibling = item.previous();&lt;br/&gt;if (other) {&lt;br/&gt;position = "higher";&lt;br/&gt;} else {&lt;br/&gt;sibling = item.next();&lt;br/&gt;position = "lower"&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;options.onUpdate(item, position, sibling);&lt;br/&gt;}&lt;br/&gt;};&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Enjoy!&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-6515321439998329933?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/6515321439998329933/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/04/drag-drop-prioritizable-lists.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/6515321439998329933?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/6515321439998329933?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/h2W_wASvcLA/drag-drop-prioritizable-lists.html" title="Drag &amp;amp; Drop Prioritizable Lists" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/04/drag-drop-prioritizable-lists.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zeyp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-7020560685622199622</id><published>2008-04-10T04:47:00.000+10:00</published><updated>2009-01-17T22:07:24.383+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.383+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><title>Getting Chronic to Parse Non-U.S. Dates</title><content type="html">&lt;p&gt;In an attempt to push yet more behaviour from my Rails controllers into model classes, I was extracting some code into a yet-to-be-published plugin that allows date and time columns to be set using more human-readable values. For examples:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="console"&gt;&gt;&gt; task.completed_at = &lt;strong&gt;"now"&lt;/strong&gt;&lt;br/&gt;&gt;&gt; task.completed_at&lt;br/&gt;=&gt; Wed Apr 09 14:39:12 +1000 2008&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Although my actual requirement was to support &lt;code&gt;"now"&lt;/code&gt; and &lt;code&gt;"today"&lt;/code&gt; I figured it would be rather cool if I could support anything that &lt;a href="http://chronic.rubyforge.org/"&gt;Chronic&lt;/a&gt; does. (If you haven't used Chronic before, it's a natural language date/time parser written in pure Ruby that understands a vast array of expressions including ranges.)&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Naturally (no pun intended), Chronic also supports explicit dates such as "1/2/08". As part of my testing however, I discovered that the date parsing is decidedly US-centric presuming, of course, that dates are specified as "month-day-year". So for anyone living in say, Australia, it can be pretty frustrating to have &lt;code&gt;Chronic.parse("1/2/08")&lt;/code&gt; return &lt;code&gt;"Wed Jan 02 12:00:00 +1100 2008"&lt;/code&gt; rather than the expected &lt;code&gt;"Fri Feb 01 12:00:00 +1100 2008"&lt;/code&gt;.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;The good news is, there is a solution. The bad news is, the solution is far from elegant. But first some context.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Chronic is actually written very nicely and the code is fairly easy to follow. In essence it works as follows: the input string is tokenized; each token is inspected to see if it's a keyword such as a month name, day name, or a number, etc.; and finally tries to matches the sequence of tokens against a pattern such as "a day number followed by a month name and then a year." The problem arises because the pattern for matching "month-day-year" comes before the one for "day-month-year" meaning that unless the first number is greater than 12, Chronic will always consider it to be a month.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;The less than elegant solution is almost trivial and involves switching the order in which the patterns are matched. Doing so returns the desired result:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="console"&gt;&gt;&gt; Chronic.parse("1/2/08")&lt;br/&gt;=&gt; "Fri Feb 01 12:00:00 +1100 2008"&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Which is all very well and good but now we have the reverse problem. What would be better is if we had a more general solution, one that allows us to specify the desired precedence when parsing:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="console"&gt;&gt;&gt; Chronic.parse("1/2/08")                                    # Default to U.S. date formats&lt;br/&gt;=&gt; Wed Jan 02 12:00:00 +1100 2008&lt;br/&gt;&lt;br/&gt;&gt;&gt; Chronic.parse("1/2/08", &lt;strong&gt;:explicit_date_format =&gt; :non_us&lt;/strong&gt;)  # Prefer Non-U.S. formats&lt;br/&gt;=&gt; Fri Feb 01 12:00:00 +1100 2008&lt;br/&gt;&lt;br/&gt;&gt;&gt; Chronic.parse("1/2/08", &lt;strong&gt;:explicit_date_format =&gt; :us&lt;/strong&gt;)      # Prefer U.S. formats&lt;br/&gt;=&gt; Wed Jan 02 12:00:00 +1100 2008&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Among other things, this allows us to have users in Australia enter dates with one format and users in the U.S. another.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;For anyone interested, I've pasted a diff for each approach below.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;A less than elegant solution:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;--- a/chronic-0.2.3/lib/chronic/handlers.rb&lt;br/&gt;+++ b/chronic-0.2.3/lib/chronic/handlers.rb&lt;br/&gt;@@ -13,8 +13,8 @@ module Chronic&lt;br/&gt;Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),&lt;br/&gt;Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),&lt;br/&gt;Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),&lt;br/&gt;-                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),&lt;br/&gt;Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),&lt;br/&gt;+                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),&lt;br/&gt;Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),&lt;br/&gt;Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;A more general solution:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;--- a/chronic-0.2.3/lib/chronic/chronic.rb&lt;br/&gt;+++ b/chronic-0.2.3/lib/chronic/chronic.rb&lt;br/&gt;@@ -43,7 +43,8 @@ module Chronic&lt;br/&gt;default_options = {:context =&gt; :future,&lt;br/&gt;-                         :ambiguous_time_range =&gt; 6}&lt;br/&gt;+                         :ambiguous_time_range =&gt; 6,&lt;br/&gt;+                         :explicit_date_format =&gt; :us}&lt;br/&gt;options = default_options.merge specified_options&lt;br/&gt;&lt;br/&gt;# ensure the specified options are valid&lt;br/&gt;@@ -51,6 +52,7 @@ module Chronic&lt;br/&gt;default_options.keys.include?(key) || raise(InvalidArgumentException, "#{key} is not a valid option key.")&lt;br/&gt;end&lt;br/&gt;[:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")&lt;br/&gt;+      [:us, :non_us].include?(options[:explicit_date_format]) || raise(InvalidArgumentException, "Invalid value ':#{options[:explicit_date_format]}' for :explicit_date_format specified. Valid values are :us and :non_us.")&lt;br/&gt;&lt;br/&gt;# store now for later =)&lt;br/&gt;@now = options[:now]&lt;br/&gt;--- a/chronic-0.2.3/lib/chronic/handlers.rb&lt;br/&gt;+++ b/chronic-0.2.3/lib/chronic/handlers.rb&lt;br/&gt;@@ -3,41 +3,50 @@ module Chronic&lt;br/&gt;class &lt;&lt; self&lt;br/&gt;&lt;br/&gt;def definitions #:nodoc:&lt;br/&gt;-	    @definitions ||=&lt;br/&gt;-      {:time =&gt; [Handler.new([:repeater_time, :repeater_day_portion?], nil)],&lt;br/&gt;+	    if @definitions.nil?&lt;br/&gt;+        us_date = [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),&lt;br/&gt;+                   Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),&lt;br/&gt;+                   Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),&lt;br/&gt;+                   Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),&lt;br/&gt;+                   Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),&lt;br/&gt;+                   Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),&lt;br/&gt;+                   Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),&lt;br/&gt;+                   Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),&lt;br/&gt;+                   Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),&lt;br/&gt;+                   Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),&lt;br/&gt;+                   Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)]&lt;br/&gt;+&lt;br/&gt;+        non_us_date = us_date.dup&lt;br/&gt;+        non_us_date[7] = us_date[8]&lt;br/&gt;+        non_us_date[8] = us_date[7]&lt;br/&gt;+&lt;br/&gt;+	      @definitions =&lt;br/&gt;+        {:time =&gt; [Handler.new([:repeater_time, :repeater_day_portion?], nil)],&lt;br/&gt;&lt;br/&gt;-       :date =&gt; [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),&lt;br/&gt;-                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),&lt;br/&gt;-                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),&lt;br/&gt;-                 Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),&lt;br/&gt;-                 Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),&lt;br/&gt;-                 Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),&lt;br/&gt;-                 Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),&lt;br/&gt;-                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),&lt;br/&gt;-                 Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),&lt;br/&gt;-                 Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),&lt;br/&gt;-                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],&lt;br/&gt;+         :date =&gt; {:us =&gt; us_date, :non_us =&gt; non_us_date},&lt;br/&gt;&lt;br/&gt;-       # tonight at 7pm&lt;br/&gt;-       :anchor =&gt; [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),&lt;br/&gt;-                   Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),&lt;br/&gt;-                   Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],&lt;br/&gt;+         # tonight at 7pm&lt;br/&gt;+         :anchor =&gt; [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),&lt;br/&gt;+                     Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),&lt;br/&gt;+                     Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],&lt;br/&gt;&lt;br/&gt;-       # 3 weeks from now, in 2 months&lt;br/&gt;-       :arrow =&gt; [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),&lt;br/&gt;-                  Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),&lt;br/&gt;-                  Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],&lt;br/&gt;+         # 3 weeks from now, in 2 months&lt;br/&gt;+         :arrow =&gt; [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),&lt;br/&gt;+                    Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),&lt;br/&gt;+                    Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],&lt;br/&gt;&lt;br/&gt;-       # 3rd week in march&lt;br/&gt;-       :narrow =&gt; [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),&lt;br/&gt;-                   Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]&lt;br/&gt;-      }&lt;br/&gt;+         # 3rd week in march&lt;br/&gt;+         :narrow =&gt; [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),&lt;br/&gt;+                     Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]&lt;br/&gt;+        }&lt;br/&gt;+      end&lt;br/&gt;+      @definitions&lt;br/&gt;end&lt;br/&gt;&lt;br/&gt;def tokens_to_span(tokens, options) #:nodoc:&lt;br/&gt;# maybe it's a specific date&lt;br/&gt;&lt;br/&gt;-      self.definitions[:date].each do |handler|&lt;br/&gt;+      self.definitions[:date][options[:explicit_date_format]].each do |handler|&lt;br/&gt;if handler.match(tokens, self.definitions)&lt;br/&gt;puts "-date" if Chronic.debug&lt;br/&gt;good_tokens = tokens.select { |o| !o.get_tag Separator }&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-7020560685622199622?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/7020560685622199622/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/04/getting-chronic-to-parse-non-us-dates.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/7020560685622199622?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/7020560685622199622?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/k-NPfhR3LGg/getting-chronic-to-parse-non-us-dates.html" title="Getting Chronic to Parse Non-U.S. Dates" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/04/getting-chronic-to-parse-non-us-dates.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zfCp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-1250385676425777218</id><published>2008-04-03T00:13:00.000+11:00</published><updated>2009-01-17T22:07:24.384+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.384+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript" /><title>Fixing lowpro form submission failures</title><content type="html">&lt;p&gt;I finally worked out why my forms weren't submitting when the user hits the ENTER key. &lt;a href="http://lowpro.stikipad.com/home/"&gt;lowpro&lt;/a&gt; serializes the button that was clicked along with any other parmeters when submitting a form via AJAX. However, when the user hits enter under FireFox, there is no button and consequently the browser barfs. Safari on the other hand tries to be too helpful and triggers an onclick event for the first submit button (which is why I never noticed it).&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;So anyway, rather than try to be too clever myself, I simply changed the parameter serialization in &lt;code&gt;Remote.Form.onsubmit&lt;/code&gt; to look like:&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;code class="editor"&gt;parameters : this.element.serialize({ submit: this._submitButton ? this._submitButton.name : null })&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Problem solved.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-1250385676425777218?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/1250385676425777218/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/04/fixing-lowpro-form-submission-failures.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/1250385676425777218?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/1250385676425777218?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/XpjsklelCq4/fixing-lowpro-form-submission-failures.html" title="Fixing lowpro form submission failures" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/04/fixing-lowpro-form-submission-failures.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMARX0zfCp7ImA9WxVREUQ.&quot;"><id>tag:blogger.com,1999:blog-8801681572421568835.post-6849758655524798803</id><published>2008-03-20T05:43:00.000+11:00</published><updated>2009-01-17T22:07:24.384+11:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-17T22:07:24.384+11:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript" /><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><title>Culturally Sensitive JavaScript</title><content type="html">&lt;p&gt;JavaScript is a fantastic little language and with the likes of &lt;a href="http://www.prototypejs.org/"&gt;Prototype&lt;/a&gt;, &lt;a href="http://script.aculo.us/"&gt;Scriptaculous&lt;/a&gt;, and my newest favourite, &lt;a href="http://lowpro.stikipad.com/"&gt;lowpro&lt;/a&gt;, you can build some quite frankly, remarkable web applications.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;One area where most web browsers fall down however is in their error-reporting, or lack thereof. A fact that has caused me to waste seemingly countless hours trying to find the source of some problem or other only to realise that a typo that had been staring me in the face the entire time was to blame!&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;Now, like just about any programming library I use these days, most JavaScript libraries use American english. &lt;code&gt;initiali&lt;strong&gt;z&lt;/strong&gt;e&lt;/code&gt;, &lt;code&gt;capitali&lt;strong&gt;z&lt;/strong&gt;e&lt;/code&gt;, you know what I'm talking about.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;For the most part the use of 'z' instead of 's' isn't too much of a problem but just recently I consistently tried to use lowpro's &lt;code&gt;addBehaviour&lt;/code&gt; method, only there isn't one. It's called &lt;code&gt;addBehavior &lt;/code&gt; (sans 'u').&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;So today after about 20 minutes cursing and swearing at the spelling &lt;a href="http://steve.cogentconsulting.com.au/"&gt;Steve&lt;/a&gt; asked "is there anyway you could create an alias?" Being JavaScript the answer is of course "abso-bloody-lutely!":&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;&lt;pre&gt;&lt;blockquote class="code"&gt;Event.addBehavio&lt;strong&gt;u&lt;/strong&gt;r = Event.addBehavior&lt;/blockquote&gt;&lt;/pre&gt;&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;You can alias just about anything this way.&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;No more will my code silently fail due to differences in spelling :)&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8801681572421568835-6849758655524798803?l=www.harukizaemon.com'/&gt;&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.harukizaemon.com/feeds/6849758655524798803/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.harukizaemon.com/2008/03/culturally-sensitive-javascript.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/6849758655524798803?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/8801681572421568835/posts/default/6849758655524798803?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/MyHovercraftIsFullOfEels/~3/8lVfeNIWn_4/culturally-sensitive-javascript.html" title="Culturally Sensitive JavaScript" /><author><name>Simon Harris</name><uri>http://www.blogger.com/profile/10823088714559454377</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="06877301789329501410" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://www.harukizaemon.com/2008/03/culturally-sensitive-javascript.html</feedburner:origLink></entry></feed>
