<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss'><id>tag:blogger.com,1999:blog-6799088</id><updated>2009-12-09T23:26:45.779Z</updated><title type='text'>Instant Badger</title><subtitle type='html'>Rants, raves and random thoughts on Coldfusion MX, Java, methodologies, and development in general. And too much alliteration.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default?start-index=26&amp;max-results=25'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>166</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6799088.post-4795107464542068189</id><published>2009-12-08T20:54:00.003Z</published><updated>2009-12-08T22:28:46.766Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='invalidation'/><category scheme='http://www.blogger.com/atom/ns#' term='naming'/><category scheme='http://www.blogger.com/atom/ns#' term='memcached'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='cache'/><title type='text'>Memcached Cache Invalidation Made Easy</title><content type='html'>&lt;p&gt;&lt;q&gt;There are only two hard problems in computer science - cache invalidation, and naming things&lt;/q&gt;&lt;br /&gt;Phil Karlton&lt;/p&gt;&lt;br /&gt;It's an oft-quoted truism that brings a knowing smile to most hardened programmers, but it's oft-quoted precisely because it's true - and during a recent enforced rush job to implement a cache, I came across a nifty solution to the first problem by judicious use of the second.&lt;br /&gt;&lt;br /&gt;First, the problem - someone posted &lt;a href="http://cragwag.com"&gt;Cragwag&lt;/a&gt; on &lt;a href="http://stumbleupon.com"&gt;StumbleUpon&lt;/a&gt;, which led to an immediate spike in traffic on top of the slow increase I've been getting since I made it &lt;a href="http://twitter.com/cragwag"&gt;Tweet the latest news&lt;/a&gt;. All the optimisation work that I knew I needed to do at some point was more than a few hours work, and I had to get something out quickly - enter &lt;a href="http://memcached.org/"&gt;memcached&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Memcached is a simple, distributed-memory caching server that basically stores whatever data you give it in memory, associated with a given key. Rails has a built-in client that you can use simply as follows:&lt;br /&gt;&lt;br /&gt;&lt;code class="ruby"&gt;my_data = Cache.get(key) do { &lt;br /&gt;  ... do stuff to generate data&lt;br /&gt;}&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;If the cache has an entry for the given key, it will return it straight from the cache. If not, the block will be called, and whatever is returned from the block will be cached with that key.&lt;br /&gt;&lt;br /&gt;So far so good - but what exactly should you cache, and how should you do it?&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;The Complicated Way To Do It&lt;/h4&gt;&lt;br /&gt;A common pattern is to cache ActiveRecord objects, say by wrapping the finder method in a cache call, and generating a key of the class name and primary key. But this only works for single objects, which are usually pretty quick to retrieve anyway, and is no use for the more expensive queries, such as lists of objects plus related objects and metadata, or - often particularly slow - searches.&lt;br /&gt;&lt;br /&gt;So you could extend that simple mechanism to cache lists of objects and search results, say by using the method name and the given parameters. But then you have an all-new headache - an object might be cached in many different collections, so how do you know which cache keys to purge? You have two options:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Try and keep track of which cache keys are caching which objects? Eep - that's starting to sound nasty - you're effectively creating a meta-index of cached entries and keys, which would almost certainly be comparable in size to your actual cache... and where's that index going to live and how are you going to make sure that it's faster to search this potentially large and complex index than to just hit the damn database?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Sidestep the invalidation problem by invalidating the entire cache whenever data is updated. This is much simpler, but there doesn't seem to be a "purge all" method - so you'd need to keep track of what keys are generated somewhere, then loop round them and delete them individually. You could do this with, say, an ActiveRecord class and delete the cache keys on a destroy_all - but still, that's icky.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;The Easy Way To Do It&lt;/h4&gt;&lt;br /&gt;After a few minutes Googling, I found &lt;a href="http://blog.leetsoft.com/2007/5/22/the-secret-to-memcached"&gt;this post&lt;/a&gt; on the way &lt;a href="http://shopify.com"&gt;Shopify&lt;/a&gt; have approached it, and suddenly it all became clear. You can solve the problem of Cache Invalidation by being cunning about Naming Things - in particular, your cache keys.&lt;br /&gt;&lt;br /&gt;The idea is very simple - Be Specific about exactly what you're caching. Read that post for more details, or read on for how I've done it.&lt;br /&gt;&lt;br /&gt;So I ripped out all of my increasingly-over-complicated caching code from the model, and went for a simple approach of caching the generated html in the controllers. At the start of each request, in a before_filter, I have one database hit - load the current CacheVersion - which just retrieves one integer from a table with only one record. Super fast - and if the data is cached, that's the only db hit for the whole request. &lt;br /&gt;&lt;br /&gt;The current cache version number is stored as an instance variable of the application controller, and prepended to all cache keys. The rest of the key is generated from the controller name, the action, and a string constructed out of the passed parameters. Any model methods that aren't just simple retrievals but affect data, can just bump up the current cache version, and hey presto - everything then gets refreshed on next hit, and the old version just gets expired on the least-recently-used-goes-first rule.&lt;br /&gt;&lt;br /&gt;This has a few very nice architectural benefits:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The caching code is then in the "right" place - in the bit you want to speed up - i.e. the interface&lt;/li&gt;&lt;li&gt;You also eliminate the overhead of rendering any complicated views - you just grab the html (or xml, or json) straight from the cache and spit it back.&lt;/li&gt;&lt;li&gt;It utilises, and fits in with, one of the fundamental ideas of resource-based IA - that the URL (including the query string) should uniquely identify the resource(s) requested&lt;/li&gt;&lt;li&gt;The application controller gives you a nice central place to generate your keys&lt;/li&gt;&lt;li&gt;If you have to display different data to users, no problem - just put the user id as part of the key.&lt;/li&gt;&lt;li&gt;Rails conveniently puts the controller and action names into the params hash, so your cache key generation is very simple&lt;/li&gt;&lt;li&gt;The admin interface can then easily work off up-to-date data&lt;/li&gt;&lt;li&gt;You can also provide an admin "Clear the cache" button that just has to bump up the current cache version number.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Etc etc - I could go on, but I won't. The net result is that pages which used to take several seconds to render now take just a few milliseconds, it's much much simpler and more elegant this way, and if you're not convinced by now, just give it a try. &amp;lt;mrsdoyle&amp;gt;Go on - ah go on now, ah you will now, won't you Father?&amp;lt;/mrsdoyle&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;app/models/cache_version.rb&lt;/h4&gt;&lt;br /&gt;&lt;pre&gt;&lt;code class="ruby"&gt;class CacheVersion &lt; ActiveRecord::Base&lt;br /&gt;    def self.current&lt;br /&gt;      CacheVersion.find(:last) || CacheVersion.new(:version=&gt;0)&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    def self.increment&lt;br /&gt;      cv = current &lt;br /&gt;      cv.version = cv.version + 1&lt;br /&gt;      cv.save&lt;br /&gt;    end &lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;app/controllers/application_controller.rb&lt;/h4&gt;&lt;br /&gt;&lt;pre&gt;&lt;code class="ruby"&gt;require 'memcache_util'&lt;br /&gt;&lt;br /&gt;class ApplicationController &lt; ActionController::Base&lt;br /&gt;  # load the current cache_version from the db&lt;br /&gt;  # this is used to enable easy memcache "expiration"&lt;br /&gt;  # by simply bumping up the current version whenever data changes&lt;br /&gt;  include Cache&lt;br /&gt;  before_filter :get_current_cache_version&lt;br /&gt;&lt;br /&gt;private&lt;br /&gt;&lt;br /&gt;  def cache_key&lt;br /&gt;    "#{@cache_version.version}_#{params.sort.to_s.gsub(/ /, '_')}"&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def get_current_cache_version&lt;br /&gt;    @cache_version = CacheVersion.current&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def with_cache( &amp;block )&lt;br /&gt;    @content, @content_type = Cache.get(cache_key) do&lt;br /&gt;      block.call&lt;br /&gt;      [@content, @content_type]&lt;br /&gt;    end&lt;br /&gt;    render :text=&gt;@content, :content_type=&gt;(@content_type||"text/html") &lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;in your actual controller:&lt;/h4&gt;&lt;br /&gt;&lt;pre&gt;&lt;code class="ruby"&gt;  def index &lt;br /&gt;    with_cache {&lt;br /&gt;      # get data&lt;br /&gt;      # NOTE: you must render to string and store it in @content&lt;br /&gt;      respond_to do |format|&lt;br /&gt;        format.html { &lt;br /&gt;          @content = render_to_string :action =&gt; "index", :layout =&gt; "application" &lt;br /&gt;        }&lt;br /&gt;        format.xml {&lt;br /&gt;          @content_type = "text/xml"&lt;br /&gt;          @content = render_to_string :xml =&gt; @whatever, :layout=&gt; false &lt;br /&gt;        }&lt;br /&gt;      end&lt;br /&gt;    }&lt;br /&gt;  end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-4795107464542068189?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/4795107464542068189/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=4795107464542068189' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/4795107464542068189'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/4795107464542068189'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/12/memcached-cache-invalidation-made-easy.html' title='Memcached Cache Invalidation Made Easy'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-4478224402426388593</id><published>2009-10-26T15:57:00.003Z</published><updated>2009-10-26T16:04:04.807Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='timezone'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='utc'/><category scheme='http://www.blogger.com/atom/ns#' term='time'/><category scheme='http://www.blogger.com/atom/ns#' term='date'/><title type='text'>Specs failing with Daylight Saving Time change?</title><content type='html'>So I've been banging my head for the past hour or so,  trying to work out why some of our specs have suddenly started failing without the code having been touched, and it comes down to an inconsistency in how Rails is handling UTC offsets when Time objects are manipulated:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt; Time.now&lt;br /&gt; =&gt; Mon Oct 26 15:55:20 0000 2009&lt;br /&gt;&gt;&gt; Time.now.utc&lt;br /&gt;=&gt; Mon Oct 26 15:55:26 UTC 2009&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;OK, that's fine. Now let's try a calculation:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt; (Time.now - 1.day).utc&lt;br /&gt;[Sun Oct 25 14:55:41 UTC 2009&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;What? Where did that one hour offset come from??&lt;br /&gt;&lt;br /&gt;It turns out THAT is the source of all the specs which suddenly failed for no apparent reason this morning.&lt;br /&gt;&lt;br /&gt;The good news is, it's easy to fix:&lt;br /&gt;&lt;br /&gt;&gt;&gt; Time.now.utc - 1.day&lt;br /&gt;=&gt; Sun Oct 25 15:56:08 UTC 2009&lt;br /&gt;&lt;br /&gt;MUCH better!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-4478224402426388593?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/4478224402426388593/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=4478224402426388593' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/4478224402426388593'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/4478224402426388593'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/10/specs-failing-with-daylight-saving-time.html' title='Specs failing with Daylight Saving Time change?'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-3677227190033472654</id><published>2009-10-26T13:41:00.003Z</published><updated>2009-10-26T13:46:15.483Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='award'/><category scheme='http://www.blogger.com/atom/ns#' term='social software'/><category scheme='http://www.blogger.com/atom/ns#' term='innovation'/><category scheme='http://www.blogger.com/atom/ns#' term='idc'/><title type='text'>Ooh, we're in IDC's "10 most innovative software companies"!</title><content type='html'>Oooh, shiny! We just got named as one of IDC's &lt;a href="http://www.reuters.com/article/pressRelease/idUS108772+26-Oct-2009+BW20091026"&gt;10 most innovative sub-$100m software companies to watch&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I guess we scored more highly on the "Web 2.0-like functionality moves into the enterprise" category more than the other two, and by itself it might not mean much, but it's still great to be thought of in those terms. Makes me feel all warm and fuzzly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-3677227190033472654?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/3677227190033472654/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=3677227190033472654' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/3677227190033472654'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/3677227190033472654'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/10/ooh-were-in-idcs-10-most-innovative.html' title='Ooh, we&apos;re in IDC&apos;s &quot;10 most innovative software companies&quot;!'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-1384708022344101481</id><published>2009-10-13T01:08:00.008+01:00</published><updated>2009-10-21T07:58:04.408+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lasagne'/><category scheme='http://www.blogger.com/atom/ns#' term='recipe'/><category scheme='http://www.blogger.com/atom/ns#' term='cooking'/><title type='text'>Al's Ultimate Lasagne Recipe</title><content type='html'>OK, ok, so I've never posted a recipe on here before. BUT, pretty much everyone I've ever made this lasagne for has said &lt;q&gt;wow, you've GOT to give me the recipe for that!&lt;/q&gt; - the most recent example being a guy who had spent years living in Italy, no less - and I can't sleep tonight, so here goes.&lt;br /&gt;&lt;br /&gt;Last year I added some refinements from &lt;a href="http://www.npr.org/templates/story/story.php?storyId=6530258"&gt;Heston Blumenthal's 'Perfect' bolognese&lt;/a&gt;, but mercifully this version takes about 3-4hrs, rather than 3 days. You can add whatever embellishments you like, here I'm just going to describe the basic sauce preparation.&lt;br /&gt;&lt;br /&gt;You will need:&lt;br /&gt;&lt;h4&gt;Pans&lt;/h4&gt;&lt;ul&gt;&lt;br /&gt;  &lt;li&gt;A heavy-bottomed frying pan (our &lt;a href="http://www.amazon.co.uk/Creuset-Satin-Black-Interior-Frying/dp/B000RK2DA8/ref=sr_1_9?ie=UTF8&amp;s=kitchen&amp;qid=1255393075&amp;sr=8-9"&gt;Le Creuset&lt;/a&gt; pan was perfect for this) that can get really hot&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;A large sauce pot / casserole dish&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;A large lasagne dish - deep enough for at least 3 layers of sauce, plus about half an inch of bechamel sauce&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;A medium sauce pan for the bechamel sauce&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h4&gt;Ingredients&lt;/h4&gt;&lt;p&gt;&lt;em&gt;NOTE: all quantities are approximate. Don't be the kind of cook who has to measure everything to the 3rd significant figure - taste often and see what you think it needs, it's much more fun!&lt;/em&gt;&lt;/p&gt;&lt;ul&gt;&lt;br /&gt;  &lt;li&gt;About 1kg of lean minced beef (preferably organic, or at least free range - we like Waitrose's, and 2 of their &lt;a href="http://www.ocado.com/webshop/product/Organic-Lean-Ground-Beef-10-Fat-Waitrose/26383011"&gt;500g packs&lt;/a&gt; works nicely)&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;About 400g of lardons (again, 2 packs of the &lt;a href="http://www.shopwiki.co.uk/Free+Range+Smoked+Dry+Cure+Lardons+Waitrose"&gt;Waitrose Free-Range lardons&lt;/a&gt; work nicely)&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;1 red onion, 1 white onion&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;1 stick of celery&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;1 or 2 carrots, depending on size - we're aiming for roughly equal quantities of diced carrot, red onion and celery, so adjust as needed&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;3 tins of chopped tomatoes&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;3 or 4 mushrooms - we like &lt;a href="http://www.ocado.com/webshop/product/Organic-Portabellini-Mushrooms-Waitrose/18872011?parentContainer=SEARCHmushrooms"&gt;Portabellini&lt;/a&gt;&lt;br /&gt;  &lt;li&gt;a bulb of garlic, the fresher the better&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;2 large bay leaves&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;2 large/4 small star anise&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;about 1tbsp Thai fish sauce (we like &lt;a href="http://www.squidbrand.com/squideng/product.php#"&gt;Squid Brand&lt;/a&gt;, which you should be able to get from any good Chinese food store)&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;about 1tbsp Lea &amp; Perrins Worcester Sauce&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;about a third of a bottle of red wine&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Maldon sea salt &amp; freshly ground black pepper&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;A tablespoon of Marmite&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Butter. Probably about 100g, maybe a bit more&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Olive oil - it's best to NOT use extra virgin, you're going to be frying with it - but really, it doesn't make that much difference in the end&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Lasagne sheets&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;A decent handful of basil leaves (MUST be fresh - dried is no good here)&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;A decent handful of fresh oregano (ditto)&lt;/li&gt; &lt;br /&gt;  &lt;li&gt;The &lt;em&gt;vine&lt;/em&gt; from some vine tomatoes (that's where most of the smell comes from, not the tomato itself)&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;For the bechamel sauce - maybe 50g of plain flour and about 1/3 pint of milk, plus about 50g of good mature cheddar (&lt;a href="http://www.ocado.com/webshop/product/Cathedral-City-Extra-Mature-Cheddar/27789011?parentContainer=SEARCHcathedral%20city"&gt;Cathedral City Extra Mature&lt;/a&gt; works well).&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h4&gt;Preparation (20 mins)&lt;/h4&gt;&lt;ol&gt;&lt;br /&gt;  &lt;li&gt;Dice the carrot, the red onion and the celery. We're aiming for equal quantities of each, in equal-sized pieces.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Chop the white onion - these pieces don't need to be the same size as the previous lot, they can be bigger and rougher.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Slice the mushrooms, so they're maybe half a centimetre thick&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Lightly crush and peel all the cloves of garlic. I use a good whack with my fist on top of a large knife on top the clove. It doesn't need to be obliterated, just kind of half-crushed, so the skin comes off easily.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Take half of the semi-crushed garlic and chop it finely.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;In a small bowl, crush (with your fingers is fine) a good tablespoon of sea salt and grind about an equal quantity of black pepper (you're going to be handling raw beef next - you don't want the juices from your hands to hang around on the pepper mill, do you?)&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Season the beef - I tip one pack of beef onto the other, then separate the strands back into the empty pack. Each time you make a full layer, sprinkle on some salt and pepper&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;h4&gt;Cooking stage 1 (20 mins)&lt;/h4&gt;&lt;ol&gt;&lt;br /&gt;  &lt;li&gt;In the big pot, pour a good layer of olive oil. Heat over a low-to-medium heat.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Add the semi-crushed (not chopped) garlic cloves. Cook until they're just going golden but still soft (burnt garlic is bitter and grim), then remove them and save for later&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Increase the heat slightly, and add the diced red onion, carrot, and celery. Stir these occasionally while they soften.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Put the heavy-bottom frying pan over a medium-to-high heat, and add some olive oil&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;In the frying pan, add the chopped white onion and star anise. Fry until the onions are caramelising - i.e. going golden brown. Then tip the contents of the frying pan into the big pot (&lt;strong&gt;before&lt;/strong&gt; the onions start to burn and go bitter.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;In the frying pan, turn the heat up to full and start to sear the beef. Do this layer-by-layer - ALL the beef must be touching the pan, otherwise it'll broil instead of searing, so just do a little at a time. Keep it moving until it's browned outside but pink in the middle, then tip into the big pot. Repeat until all the beef is seared.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;...remember to keep stirring the big pot every so often!&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;In the frying pan, add a big lump of butter - probably 50g or so. This will sizzle and spit for a while. Once it's stopped sizzling, all the water has gone out of it, so then add the chopped garlic and mushrooms and saute these until they're golden brown at the edges but still plump and juicy. Then tip them into the large pot (the butter will help give the sauce a nice sheen)&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Make sure the frying pan is hot, then sear the lardons. You're looking for golden crispy edges, but plump and juicy pinkness. These will probably release a fair amount of fat into the pan - this is fine, it's all good for the sauce. When done, tip everything into the big pot.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Now de-glaze the frying pan by tipping the red wine into it and keep stirring it and scraping the tasty bits off the bottom until the wine is reduced by about half. Tip into the large pot. You can now wash the frying pan - everything from now on takes place in the large pot (except the bechamel sauce)&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;In the large pot, add the tomatoes and stir well.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Add the Thai fish sauce and Worcester sauce, stir, and reduce the heat to low-to-medium&lt;/li&gt; &lt;br /&gt;  &lt;li&gt;Crack the bay leaves in half, and add to the pot&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Chop and add the lightly-browned garlic that you used to flavour the oil with, right back at the start&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;h4&gt;Reducing the sauce (1hr-2hrs)&lt;/h4&gt;&lt;ol&gt;&lt;br /&gt;  &lt;li&gt;Leave the sauce pot simmering with the lid half-on for about an hour, stirring occasionally and adding pepper if needed. (Taste!)&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Sometimes I add a tablespoon of Marmite if the sauce needs more depth, sometimes not - depends on the ingredients&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;It's done when it's done - i.e. when it looks and tastes like really good bolognese-style sauce, and isn't too runny or too dry.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;When it *is* done, turn the heat off and leave it to cool with the lid on for about 45mins&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Once it's cool, chop the basil and oregano and stir through. At this point, you can add the vine of the tomatoes and leave it to rest and infuse for about half an hour, then take the vine out again.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;h4&gt;Layer the lasagne (5 mins)&lt;/h4&gt;&lt;ol&gt;&lt;br /&gt;  &lt;li&gt;Put the oven onto 180 degrees C to heat up&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Put a layer of sauce in the bottom of the lasagne dish, then add a layer of lasagne&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Repeat at least once, preferably twice (depending on the size of your dish and how much sauce you have) until you've used all the sauce - but make sure that your top layer is sauce, not pasta!&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;h4&gt;Bechamel Sauce (5 mins)&lt;/h4&gt;&lt;ol&gt;&lt;br /&gt;  &lt;li&gt;In the saucepan, melt about 50g of butter over a medium heat&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Add the flour and stir quickly until you have a good consistency - still "smearable", not a dry lump&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Begin adding milk a little at a time and stirring until absorbed. Make sure you don't add too much too quickly or it'll curdle.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;When you have a nice medium-thick-but-still-easily-pourable sauce, add the grated cheese and grind some more pepper into it.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Stir until the cheese is all melted, then pour over the top of your lasagne, making sure it's all covered&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Grate a bit more cheese on top&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;h4&gt;Final baking (45 mins)&lt;/h4&gt;&lt;ol&gt;&lt;br /&gt;  &lt;li&gt;Bake the lasagne in the oven at 180 degrees C for about 45 mins, or until the top is golden brown and ever so slightly crispy around the edges.&lt;/li&gt;&lt;br /&gt;  &lt;li&gt;Remove from the oven and leave to rest for about ten minutes before serving&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;And that's it! Quality of ingredients counts for a lot, but the biggest clincher is the care taken to make sure that each ingredient is individually well prepared and cooked before adding to the pot. Enjoy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-1384708022344101481?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/1384708022344101481/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=1384708022344101481' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/1384708022344101481'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/1384708022344101481'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/10/als-ultimate-lasagne-recipe.html' title='Al&apos;s Ultimate Lasagne Recipe'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-4052475235707126676</id><published>2009-10-03T10:03:00.003+01:00</published><updated>2009-10-03T11:33:17.273+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='atom'/><category scheme='http://www.blogger.com/atom/ns#' term='simple-rss'/><category scheme='http://www.blogger.com/atom/ns#' term='github'/><category scheme='http://www.blogger.com/atom/ns#' term='cragwag'/><category scheme='http://www.blogger.com/atom/ns#' term='coding'/><title type='text'>Blogspot ATOM in Feed-normalizer / Simple-RSS</title><content type='html'>Both &lt;a href="http://cragwag.com"&gt;Cragwag&lt;/a&gt; and &lt;a href="http://sybilline.com"&gt;Sybilline&lt;/a&gt; are using the excellent &lt;a href="http://feed-normalizer.rubyforge.org/"&gt;Feed-normalizer&lt;/a&gt; for parsing RSS and ATOM feeds, but there's been a &lt;a href="http://code.google.com/p/feed-normalizer/issues/detail?id=30"&gt;niggling problem with the ATOM generated by Blogger / Blogspot in particular&lt;/a&gt; - the resulting links on each entry end up pointing to the comments, not the post itself.  &lt;br /&gt;&lt;br /&gt;So I just forked simple-rss at github and fixed this.&lt;br /&gt;&lt;br /&gt;Turns out that simple-rss is just taking the first link tag that it comes across and using that as the link for a post, which in the case of Blogspot ATOM is the comments link.&lt;br /&gt;&lt;br /&gt;On inspection of the &lt;a href="http://www.ietf.org/rfc/rfc4287.txt"&gt;ATOM RFC&lt;/a&gt; it says (section 4.2.7.2) :&lt;br /&gt;&lt;br /&gt;&lt;q&gt;atom:link elements MAY have a "rel" attribute that indicates the link relation type.  If the "rel" attribute is not present, the link element MUST be interpreted as if the link relation type is "alternate".&lt;/q&gt;&lt;br /&gt;&lt;br /&gt;Looking at the Blogspot ATOM, it looks like every element has a link rel="alternative" that points to the URL you would see if you navigated to the post from the blog homepage, so I've made it choose that link if it exists.&lt;br /&gt;&lt;br /&gt;Github should build the gem automatically - but it's taking a long time to do it, so in the meantime, you can download it from &lt;a href="http://github.com/aldavidson/simple-rss"&gt;http://github.com/aldavidson/simple-rss&lt;/a&gt; and build it locally:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;gem uninstall simple-rss&lt;br /&gt;cd (source root)&lt;br /&gt;rake gem&lt;br /&gt;cd pkg&lt;br /&gt;gem install -l simple-rss&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;That should fix the problem&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-4052475235707126676?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/4052475235707126676/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=4052475235707126676' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/4052475235707126676'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/4052475235707126676'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/10/blogspot-atom-in-feed-normalizer-simple.html' title='Blogspot ATOM in Feed-normalizer / Simple-RSS'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-6139783032381003496</id><published>2009-09-08T19:44:00.004+01:00</published><updated>2009-11-07T18:18:06.487Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='scripts'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='bash'/><category scheme='http://www.blogger.com/atom/ns#' term='amazon'/><category scheme='http://www.blogger.com/atom/ns#' term='ec2'/><category scheme='http://www.blogger.com/atom/ns#' term='hosting'/><category scheme='http://www.blogger.com/atom/ns#' term='ami'/><category scheme='http://www.blogger.com/atom/ns#' term='instance'/><title type='text'>How to create and save an AMI image from a running instance</title><content type='html'>One snag I encountered early on in my migration of &lt;a href="http://cragwag.com"&gt;Cragwag&lt;/a&gt; and &lt;a href="http://sybilline.com"&gt;Sybilline&lt;/a&gt; to Amazon's EC2 Cloud, was that I needed to take a snapshot of my running instance and save it as a new Amazon Machine Image (AMI).&lt;br /&gt;&lt;br /&gt;I'd created a bare-bones Debian image from a public AMI (32-bit Lenny, 5.0, not much else) and then installed a few standard software packages on it - mysql, ruby, apache, etc etc etc. Once I'd got them configured the way I wanted, it had taken a couple of hours (I'll go into the configuration relating to EBS in a separate post) so I wanted to snapshot this instance as a new AMI image. That way, if and when I needed to create a new instance, all of this work would already have been done.&lt;br /&gt;&lt;br /&gt;It actually took a fair amount of time to find out (well, more than a few seconds Googling, which is just &lt;em&gt;eternity&lt;/em&gt; these days, y'know?) so I'll save you the pain and just give you the solution.&lt;br /&gt;&lt;br /&gt;First, install Amazon's AMI tools, and API tools:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code class="bash"&gt;&lt;br /&gt;export $EC2_TOOLS_DIR=~/.ec2 #(or choose a directory here)&lt;br /&gt;cd $EC2_TOOLS_DIR&lt;br /&gt;mkdir ec2-ami-tools&lt;br /&gt;cd ec2-ami-tools&lt;br /&gt;wget http://s3.amazonaws.com/ec2-downloads/ec2-ami-tools.zip&lt;br /&gt;unzip ec2-ami-tools.zip&lt;br /&gt;ln -s ec2-ami-tools-* current&lt;br /&gt;cd ..&lt;br /&gt;mkdir ec2-api-tools&lt;br /&gt;cd ec2-api-tools&lt;br /&gt;wget http://s3.amazonaws.com/ec2-downloads/ec2-api-tools.zip&lt;br /&gt;unzip ec2-api-tools.zip&lt;br /&gt;ln -s ec2-api-tools-* current&lt;br /&gt;&lt;br /&gt;echo "export EC2_AMITOOL_HOME=`dirname $EC2_TOOLS_DIR`/ec2-ami-tools/current" &gt;&gt; ~/.bashrc&lt;br /&gt;echo "export EC2_APITOOL_HOME=`dirname $EC2_TOOLS_DIR`/ec2-api-tools/current" &gt;&gt; ~/.bashrc&lt;br /&gt;echo "export PATH=${PATH}:`dirname $AMI_TOOLS_DIR`/ec2-ami-tools/current/bin:`dirname $AMI_TOOLS_DIR`/ec2-api-tools/current/bin" &gt;&gt; ~/.bashrc&lt;br /&gt;source ~/.bashrc&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next, you'll need to get your security credentials. You can get a reminder of - or create as needed - these on the AWS "Your Account" &gt; "Security Credentials" page.&lt;br /&gt;&lt;br /&gt;I recommend you saving your X.509 certificate and your private key somewhere under /mnt/ - this directory is excluded from the bundled image. Quite important that, as otherwise your credentials would be bundled up in the image - and if you ever shared that image with anyone else, you'd be sharing your credentials too!&lt;br /&gt;&lt;br /&gt;You'll also need to note your AWS access details - especially your access key and secret key - plus your Amazon account ID. &lt;br /&gt;&lt;br /&gt;Now, we're at the main event.&lt;br /&gt;&lt;br /&gt;To take a snapshot of your running instance:&lt;br /&gt;&lt;br /&gt;First, choose a name for your AMI snapshot. We'll call it ami-instance-name :)&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code class="bash"&gt;&lt;br /&gt;# make a directory for your image:&lt;br /&gt;mkdir /mnt/ami-instance-name&lt;br /&gt;&lt;br /&gt;# create the image (this will take a while!)&lt;br /&gt;ec2-bundle-vol -d /mnt/ami-instance-name -k /path/to/your/pk-(long string).pem -c /path/to/your/cert-(long string).pem -u YOUR_AMAZON_ACCOUNT_ID_WITHOUT_DASHES&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Once that's done, you should have a file called image.manifest.xml in your /mnt/ami-instance-name directory, along with all the bundle parts. Sometimes it will say &lt;q&gt;Unable to read instance meta-data for product-codes&lt;/q&gt; - but this doesn't seem to cause any problems, and I've successfully ignored it so far :)&lt;br /&gt;&lt;br /&gt;Next, upload the AMI image to S3. This command will create an S3 bucket of the given name if it doesn't exist - I've found it convenient to call my buckets the same as the instance name: &lt;br /&gt;&lt;br /&gt;&lt;code class="bash"&gt;&lt;pre&gt;ec2-upload-bundle -b ami-instance-name -m /mnt/ami-instance-name/image.manifest.xml -a YOUR_AWS_ACCESS_KEY -s YOUR_AWS_SECRET_KEY&lt;/code&gt;&lt;/pre&gt; &lt;br /&gt;&lt;br /&gt;You should then be able to register the instance. I've done that using the rather spiffy AWS Management Console web UI, but you can also do it from the command line using:&lt;br /&gt;&lt;br /&gt;&lt;code class="bash"&gt;&lt;pre&gt;ec2-register ami-instance-name/image.manifest.xml&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And that's it!&lt;br /&gt;&lt;br /&gt;Of course, you could be cunning and create a script that does it all in one. I've got my AWS/EC2 credentials stored in environment variables from my .bashrc:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code class="bash"&gt;export EC2_PRIVATE_KEY=/mnt/keys/pk-(long string).pem&lt;br /&gt;export EC2_CERT=/mnt/keys/cert-(long string).pem&lt;br /&gt;export AWS_ACCOUNT_ID=(my account id)&lt;br /&gt;export AWS_ACCESS_KEY=(my AWS access key)&lt;br /&gt;export AWS_SECRET_KEY=(my AWS secret key)&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;which means I can make, upload and register an instance in one, by running this script:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code class="bash"&gt;#!/bin/bash&lt;br /&gt;&lt;br /&gt;$AMI_NAME=$1&lt;br /&gt;&lt;br /&gt;ec2-bundle-vol -d /mnt/images/$1 -k $EC2_PRIVATE_KEY -c $EC2_CERT -u $AWS_ACCOUNT_ID&lt;br /&gt;ec2-upload-bundle -b $1 -m /mnt/images/$1/image.manifest.xml -a $AWS_ACCESS_KEY  -s $AWS_SECRET_KEY&lt;br /&gt;ec2-register $1/image.manifest.xml&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;...and giving it a parameter of ami-instance-name. I have that script saved as make_ami.sh, so I can just call, for instance:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code class="bash"&gt;make_ami.sh webserver-with-sites-up-and-running&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;...and go have a cup of coffee while it does it's thing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-6139783032381003496?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/6139783032381003496/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=6139783032381003496' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/6139783032381003496'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/6139783032381003496'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/09/how-to-create-and-save-ami-image-from.html' title='How to create and save an AMI image from a running instance'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-747725458057273569</id><published>2009-09-08T18:43:00.004+01:00</published><updated>2009-09-08T19:42:45.359+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='scaling'/><category scheme='http://www.blogger.com/atom/ns#' term='cloud'/><category scheme='http://www.blogger.com/atom/ns#' term='amazon'/><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><category scheme='http://www.blogger.com/atom/ns#' term='architecture'/><category scheme='http://www.blogger.com/atom/ns#' term='ec2'/><category scheme='http://www.blogger.com/atom/ns#' term='hosting'/><title type='text'>Moving a site to The Cloud</title><content type='html'>Last week I did a lot of reading and research into cloud hosting. The "Cloud" has been a buzzword for a while now, often bandied about by those who know no better as a simple sprinkle-on solution for all of your scale problems - much in the same way as AJAX was touted around a few years ago as a magic solution to all of your interface problems. &lt;br /&gt;&lt;br /&gt;The perception can sometimes seem to be &lt;q&gt;Hey, if we just shift X to The Cloud, we can scale it infinitely!&lt;/q&gt;. The reality, of course, is something rather more qualified. Yes, &lt;em&gt;in theory&lt;/em&gt;, the cloud has all the capacity you're likely to need, unless you're going to be bigger than, say, Amazon - &lt;em&gt;(are you? Are you reeeeeeeally? C'mon, be honest...)&lt;/em&gt;  - provided - and it's a big proviso - that you architect it correctly. You can't just take an existing application and dump it into the cloud and expect to never have a transaction deadlock again, for instance. That's an application usage pattern issue that needs to be dealt with in your application, and no amount of hardware, physical or virtual, will solve it.&lt;br /&gt;&lt;br /&gt;There are also some constraints that you'll need to work around, that may seem a little confusing at first. But once I got it, the light went on, and I became increasingly of the opinion that the cloud architecture is just sheer bloody genius.&lt;br /&gt;&lt;br /&gt;What kind of constraints are they? Well, let's focus on Amazon's EC2, as it's the most well-known....&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Your cloud-hosted servers are instances of an image&lt;/strong&gt;&lt;br/&gt;They're not physical machines - you can think of them as copies of a template Virtual Machine, if you like. Like a VMWare image. OK, that one's fairly straightforward. Got it? Good. Next:&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Instances are transient - they do not live for ever&lt;/strong&gt;&lt;br /&gt;Bit more of the same, this means that you create and destroy instances as you need. The flipside is that there is no &lt;em&gt;guarantee&lt;/em&gt; that the instance you created yesterday will still be there today. It should be, but it might not be. &lt;a href="http://getsatisfaction.com/cohesiveft/topics/why_do_my_google_app_engine_sdk_ec2_instances_keep_dying"&gt;EC2 instances do die&lt;/a&gt;, and when they do, they can't be brought back - you need to create a new one. This is by design. Honestly!&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Anything you write to an instance's disk after creation is &lt;em&gt;non-persistent&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;Now we're getting down to it. This means that if you create an instance of, say, a bare-bones Linux install, and then install some more software onto it, and set up a website, then the instance dies - everything you've written to that instance's disk is &lt;strong&gt;GONE&lt;/strong&gt;. There are good strategies for dealing with this, which we'll come onto next, but this is &lt;em&gt;also&lt;/em&gt; by design. Yes, it is...&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;You can attach &lt;a href="http://aws.amazon.com/ebs"&gt;EBS&lt;/a&gt; persistent storage volumes to an instance - but only to &lt;em&gt;one instance per volume&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;This one is maybe the most obscure constraint but is quite significant. Take a common architecture of two load-balanced web servers with a separate database server. It's obvious that the database needs to be stored on a persistent EBS volume - but what if the site involves users uploading files? Where do they live? A common pattern would be to have a shared file storage area mounted onto both web servers - but if an EBS volume can only be attached to one instance, you can't do that.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Think about that for a few seconds - this has some pretty serious implications for the architecture of a cloud-hosted site. BUT - and here's the &lt;q&gt;sheer bloody genius&lt;/q&gt; - these are the kind of things you'd have to deal with for scaling out a site on physical servers &lt;em&gt;anyway&lt;/em&gt;. Physical hardware and especially disks are not infallible and shouldn't be relied on. Servers can and do go down. Disks conk out. Scaling out horizontally needs up-front thought put into the architecture. The cloud constraints simply force you to accept that, and deal with it by designing your applications with horizontal scaling in mind from the start. And, coincidentally, provide some kick-ass tools to help you do that.&lt;br /&gt;&lt;br /&gt;Take, for example, the last bullet point above - that EBS volumes can only be attached to one instance. So how do you have file storage shared between N load-balanced web servers? Well, the logical thing to do is to have a separate instance with a big persistent EBS volume attached to it, and have the web servers access it by some defined API - WebDAV, say, or something more application-specific.&lt;br /&gt;&lt;br /&gt;But hang on.... isn't that what you should be doing &lt;em&gt;anyway?&lt;/em&gt;. Isn't that a more scalable model? So that when your fileserver load becomes large, you could, say, create more instances to service your file requests, and maybe load-balance &lt;em&gt;those&lt;/em&gt;, and.... &lt;br /&gt;&lt;br /&gt;See? It forces you to &lt;em&gt;do the right thing&lt;/em&gt; - or, at least, put in the thought up front as to how you'll handle it. And if you then decide to stubbornly go ahead and do the &lt;em&gt;wrong&lt;/em&gt; thing, then that's up to you... :)&lt;br /&gt;&lt;br /&gt;So, anyway, I wanted to get my head round it, and thought I'd start by shifting &lt;a href="http://cragwag.com"&gt;Cragwag&lt;/a&gt; and &lt;a href="http://sybilline.com"&gt;Sybilline&lt;/a&gt; onto Amazons EC2 cloud hosting service. I did this over a two day period - most of which, it has to be said, was spent setting up Linux the way I was used to, rather than the cloud config - and I'll be blogging a few small, self-contained articles with handy tips I've learned along the way. Stay tuned....&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-747725458057273569?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/747725458057273569/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=747725458057273569' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/747725458057273569'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/747725458057273569'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/09/moving-site-to-cloud.html' title='Moving a site to The Cloud'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-7417303017685758640</id><published>2009-08-17T22:58:00.015+01:00</published><updated>2009-08-17T23:42:24.294+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='styling'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='css'/><category scheme='http://www.blogger.com/atom/ns#' term='auto_complete'/><title type='text'>Styling the Rails auto_complete plugin</title><content type='html'>Over the weekend I was having a fiddle around with the layout on &lt;a href="http://cragwag.com"&gt;Cragwag&lt;/a&gt;, as the existing design was a bit, shall we say, &lt;q&gt;emergent&lt;/q&gt;. I'd being trying to avoid a LHS column, because everything always seems to end up with one, but in the end I had to give up and just go with it. The auto-tag cloud just makes more sense over there, and there's no point fighting it.&lt;br /&gt;&lt;br /&gt;Anyway, the next question was what else to put there? I was looking for an intermediate stop-gap search until I got round to putting a proper &lt;a href="http://lucene.apache.org"&gt;Lucene&lt;/a&gt;-based search in there, and so the thought struck - I'd been looking for a way to browse tags that weren't necessarily in the top 20 (e.g. &lt;q&gt;show me all the posts about &lt;a href="http://davemcleod.blogspot.com"&gt;Dave McLeod's&lt;/a&gt; 2006 &lt;a href="http://cragwag.com/posts?term=E11"&gt;E11&lt;/a&gt; route), so why not try an auto-suggest tag search? &lt;br /&gt;&lt;br /&gt;So I found DHH's &lt;a href="http://github.com/rails/auto_complete/tree/master"&gt;auto_complete plugin&lt;/a&gt; (it used to be part of Rails core until version 2) and got to work. This should be easy, right?&lt;br /&gt;&lt;br /&gt;Well...&lt;br /&gt;&lt;br /&gt;I'll cut out the frustrations and skip to the solutions. :)&lt;br /&gt;&lt;br /&gt;The documentation on this plugin is virtually non-existent, and there some extra bits you'll need - but I found &lt;a href="http://codeintensity.blogspot.com/2008/02/auto-complete-text-fields-in-rails-2.html"&gt;this post&lt;/a&gt; very helpful.&lt;br /&gt;&lt;br /&gt;One minor irritation I found was that it writes out a bunch of css attributes directly into your HTML inside a style tag - &lt;em&gt;including&lt;/em&gt; a &lt;a href="http://github.com/rails/auto_complete/blob/7b18f05af68dec0f9e45b1a01d6b7c94e306caeb/lib/auto_complete_macros_helper.rb"&gt;hard-coded absolute width of 350px.&lt;/a&gt; (see the auto_complete_stylesheet method)&lt;br /&gt;&lt;br /&gt;&lt;q&gt;Argh!&lt;/q&gt; said I, as my search needed to fit into a 150px width.&lt;br /&gt;&lt;br /&gt;So how can you get round this? Simple - you &lt;em&gt;can&lt;/em&gt; override the inline CSS in your stylesheet, provided you provide a CSS selector with &lt;em&gt;higher specificity&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;Now, &lt;a href="http://www.smashingmagazine.com/2007/07/27/css-specificity-things-you-should-know/"&gt;CSS specificity can be a fairly complicated topic&lt;/a&gt;, but I usually just remember it like this - if you've got two rules that apply to a particular thing, the &lt;em&gt;more specific&lt;/em&gt; rule wins.&lt;br /&gt;&lt;br /&gt;In this case, the inline CSS selector from the plugin:&lt;br /&gt;&lt;pre&gt;&lt;code&gt; div.auto_complete {&lt;br /&gt;  width: 350px;&lt;br /&gt;  background: #fff;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;gets trumped by Cragwag's more specific selector:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;div#content div#lh_sidebar div.auto_complete {&lt;br /&gt;  width: 150px;&lt;br /&gt;  background: #fff;&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The first applies to any div with class="auto_complete", but the second applies only to divs with class="auto_complete" &lt;em&gt;which are inside a div with id="lh_sidebar" which is inside a div with id="content"&lt;/em&gt;. So that's a &lt;em&gt;more specific&lt;/em&gt; rule, so it wins.&lt;br /&gt;&lt;br /&gt;Yay!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-7417303017685758640?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/7417303017685758640/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=7417303017685758640' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/7417303017685758640'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/7417303017685758640'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/08/styling-rails-autocomplete-plugin.html' title='Styling the Rails auto_complete plugin'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-670502300541950025</id><published>2009-08-12T10:53:00.004+01:00</published><updated>2009-08-12T12:26:36.487+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='work'/><category scheme='http://www.blogger.com/atom/ns#' term='activerecord'/><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><category scheme='http://www.blogger.com/atom/ns#' term='jdbc'/><title type='text'>OutOfMemoryError in ActiveRecord-JDBC on INSERT SELECT</title><content type='html'>During some scale testing the other day, we came across this unusual / mildly amusing error in a database-bound command that just funnels INSERT SELECT statements down the ActiveRecord JDBC driver:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;java/util/Arrays.java:2734:in `copyOf': java.lang.OutOfMemoryError: Java heap space (NativeException)&lt;br /&gt; from java/util/ArrayList.java:167:in `ensureCapacity'&lt;br /&gt; from java/util/ArrayList.java:351:in `add'&lt;br /&gt; from com/mysql/jdbc/StatementImpl.java:1863:in `getGeneratedKeysInternal'&lt;br /&gt; from com/mysql/jdbc/StatementImpl.java:1818:in `getGeneratedKeys'&lt;br /&gt; from org/apache/commons/dbcp/DelegatingStatement.java:318:in `getGeneratedKeys'&lt;br /&gt; from jdbc_adapter/JdbcAdapterInternalService.java:668:in `call'&lt;br /&gt; from jdbc_adapter/JdbcAdapterInternalService.java:241:in `withConnectionAndRetry'&lt;br /&gt; from jdbc_adapter/JdbcAdapterInternalService.java:662:in `execute_insert'&lt;br /&gt;  ... 25 levels...&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now, there's a couple of things here that are worth pointing out.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;I &lt;em&gt;REALLY LOVE&lt;/em&gt; the fact that it blew heap space in a method called &lt;q&gt;ensureCapacity&lt;/q&gt;. That makes me smile.&lt;/li&gt;&lt;li&gt;Why is it calling getGeneratedKeys() for an INSERT SELECT?&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;The getGeneratedKeys() method retrieves all the primary keys that are generated when you execute an INSERT statement. Fair enough - BUT the issue here is that we'd specifically structured the process and the SQL statements involved so as to be done with INSERT SELECTS, and hence &lt;em&gt;avoid&lt;/em&gt; great chunks of data being transferred backwards and forwards between the app and the database.&lt;br /&gt;&lt;br /&gt;It turns out that the ActiveRecord JDBC adapter is doing this : &lt;br /&gt;(lib/active_record/connection_adapters/JdbcAdapterInternalService.java)&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;@JRubyMethod(name = "execute_insert", required = 1)&lt;br /&gt;    public static IRubyObject execute_insert(final IRubyObject recv, final IRubyObject sql) throws SQLException {&lt;br /&gt;        return withConnectionAndRetry(recv, new SQLBlock() {&lt;br /&gt;            public IRubyObject call(Connection c) throws SQLException {&lt;br /&gt;                Statement stmt = null;&lt;br /&gt;                try {&lt;br /&gt;                    stmt = c.createStatement();&lt;br /&gt;                    smt.executeUpdate(rubyApi.convertToRubyString(sql).getUnicodeValue(), Statement.RETURN_GENERATED_KEYS);&lt;br /&gt;                    return unmarshal_id_result(recv.getRuntime(), stmt.getGeneratedKeys());&lt;br /&gt;                } finally {&lt;br /&gt;                    if (null != stmt) {&lt;br /&gt;                        try {&lt;br /&gt;                            stmt.close();&lt;br /&gt;                        } catch (Exception e) {&lt;br /&gt;                        }&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        });&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;...in other words, explicitly telling the driver to return all the generated keys.&lt;br /&gt;Hmm, OK, can we get round this by NOT calling the execute_insert method, and instead calling a raw execute method that &lt;em&gt;doesn't&lt;/em&gt; return all the keys?&lt;br /&gt;&lt;br /&gt;Well, no, unfortunately, because it also turns out that the ruby code is doing this:&lt;br /&gt;(activerecord-jdbc-adapter-0.9/lib/active_record/connection_adapters/jdbc_adapter.rb)&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;      # we need to do it this way, to allow Rails stupid tests to always work&lt;br /&gt;      # even if we define a new execute method. Instead of mixing in a new&lt;br /&gt;      # execute, an _execute should be mixed in.&lt;br /&gt;      def _execute(sql, name = nil)&lt;br /&gt;        if JdbcConnection::select?(sql)&lt;br /&gt;          @connection.execute_query(sql)&lt;br /&gt;        elsif JdbcConnection::insert?(sql)&lt;br /&gt;          @connection.execute_insert(sql)&lt;br /&gt;        else&lt;br /&gt;          @connection.execute_update(sql)&lt;br /&gt;        end&lt;br /&gt;      end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;...and the JdbcConnection::insert? method is detecting if something's an insert by doing this:&lt;br /&gt;(JdbcAdapterInternalService.java again)&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;@JRubyMethod(name = "insert?", required = 1, meta = true)&lt;br /&gt;    public static IRubyObject insert_p(IRubyObject recv, IRubyObject _sql) {&lt;br /&gt;        ByteList bl = rubyApi.convertToRubyString(_sql).getByteList();&lt;br /&gt;&lt;br /&gt;        int p = bl.begin;&lt;br /&gt;        int pend = p + bl.realSize;&lt;br /&gt;&lt;br /&gt;        p = whitespace(p, pend, bl);&lt;br /&gt;&lt;br /&gt;        if(pend - p &gt;= 6) {&lt;br /&gt;            switch(bl.bytes[p++]) {&lt;br /&gt;            case 'i':&lt;br /&gt;            case 'I':&lt;br /&gt;                switch(bl.bytes[p++]) {&lt;br /&gt;                case 'n':&lt;br /&gt;                case 'N':&lt;br /&gt;                    switch(bl.bytes[p++]) {&lt;br /&gt;                    case 's':&lt;br /&gt;                    case 'S':&lt;br /&gt;                        switch(bl.bytes[p++]) {&lt;br /&gt;                        case 'e':&lt;br /&gt;                        case 'E':&lt;br /&gt;                            switch(bl.bytes[p++]) {&lt;br /&gt;                            case 'r':&lt;br /&gt;                            case 'R':&lt;br /&gt;                                switch(bl.bytes[p++]) {&lt;br /&gt;                                case 't':&lt;br /&gt;                                case 'T':&lt;br /&gt;                                    return recv.getRuntime().getTrue();&lt;br /&gt;                                }&lt;br /&gt;                            }&lt;br /&gt;                        }&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return recv.getRuntime().getFalse();&lt;br /&gt;    }&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;...in other words, if the sql contains the word &lt;q&gt;INSERT&lt;/q&gt;, then it's an INSERT, and should be executed with an execute_insert call.&lt;br /&gt;&lt;br /&gt;So, looks like we're a bit knacked here. There are two possible solutions:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;The &lt;q&gt;proper&lt;/q&gt; solution - fix the AR JDBC adapter (and, arguably, the MySQL connector/J as well, to stop it blowing heap space), submit a patch, wait for it to be accepted and make it into the next release.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Or, the &lt;q&gt;pragmatic&lt;/q&gt; solution - rewrite the SQL-heavy command as a stored procedure and just call it with an EXECUTE and sod the &lt;a href="http://www.loudthinking.com/arc/2005_09.html"&gt;DHH dogma&lt;/a&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;We went with option 2 :)&lt;br /&gt;&lt;br /&gt;Big kudos to &lt;a href="http://www.drmaciver.com/"&gt;D. R. MacIver&lt;/a&gt; for tracking down the source of the fail in double-quick time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-670502300541950025?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/670502300541950025/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=670502300541950025' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/670502300541950025'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/670502300541950025'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/08/outofmemoryerror-in-activerecord-jdbc.html' title='OutOfMemoryError in ActiveRecord-JDBC on INSERT SELECT'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-4081647300851864037</id><published>2009-08-03T10:24:00.006+01:00</published><updated>2009-08-03T12:13:28.202+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='projects'/><category scheme='http://www.blogger.com/atom/ns#' term='vector'/><category scheme='http://www.blogger.com/atom/ns#' term='tagging'/><category scheme='http://www.blogger.com/atom/ns#' term='nlp'/><category scheme='http://www.blogger.com/atom/ns#' term='news'/><category scheme='http://www.blogger.com/atom/ns#' term='cragwag'/><title type='text'>Introducing Cragwag.com!</title><content type='html'>You know those conversations you end up having in the pub, where after a couple of beers you end up saying "you know what there should be? There should be a site that does X" (where X can be anything at all)&lt;br /&gt;&lt;br /&gt;I've had so many of those over the years, and never quite managed to work up the free time / motivation to actually get on and put the ideas into practice..... (and then what's tended to happen is that a couple of years later, someone else goes and does them and makes a fortune, but that's just sods law) &lt;br /&gt;&lt;br /&gt;Well a few months ago, I decided enough was enough, and the next time I had one of those ideas, I should just stop talking about it and actually &lt;em&gt;do&lt;/em&gt; it - so in my evening and weekends here and there, I've been noodling away on a couple of ideas, mainly just for my own amusement, and to keep me in the habit of actually following through on things.&lt;br /&gt;&lt;br /&gt;So here's one of them - &lt;a href="http://cragwag.com"&gt;Cragwag&lt;/a&gt; - a climbing news aggregator.&lt;br /&gt;&lt;br /&gt;Those of you who know me in person know that I'm a keen amateur climber. I'm under no illusions - I'm &lt;em&gt;definitely&lt;/em&gt; in the "amateur" category for good reason :) - but it struck me that although there's a "definitive" go-to site for uk-centric climbing news ( &lt;a href="http://ukclimbing.com"&gt;UK Climbing.com&lt;/a&gt; ) it's still editorially filtered - an editor keeps himself up to date on everything that's going on in the scene, and then publishes what he thinks is significant.&lt;br /&gt;&lt;br /&gt;That's all well and good, but typically what's significant is what's going on at the forefront of ability. I felt there was also a place for the (admittedly by-now-a-little-cliched) long tail of climbing-related blogs - unfiltered, un-edited, everybody's tales. Whether of heart-stopping epics in the Himalaya, or scrabbling up a Stanage slab. If you felt it enough to blog about it, then someone wants to hear about it.&lt;br /&gt;&lt;br /&gt;Plus it was an experiment in automatic news tagging and cross-relating of posts based on content, so it was kind of techie fun too. Which is important :) I'd like to do something with a crowd-sourced google map and iphone gps too, but that's much more experimental. Need to learn the iPhone SDK first...&lt;br /&gt; &lt;br /&gt;So that's &lt;a href="http://cragwag.com"&gt;Cragwag - all the climbing news from punters and pros alike&lt;/a&gt;. Just for fun, for the sake of an experiment, and - to paraphprase the most famous answer to "why?" of all time - because it wasn't there :) Yay!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-4081647300851864037?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://cragwag.com' title='Introducing Cragwag.com!'/><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/4081647300851864037/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=4081647300851864037' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/4081647300851864037'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/4081647300851864037'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/08/introducing-cragwagcom.html' title='Introducing Cragwag.com!'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-5437725704207527374</id><published>2009-07-29T09:42:00.003+01:00</published><updated>2009-07-29T10:06:37.206+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='crowdfunding'/><category scheme='http://www.blogger.com/atom/ns#' term='trampoline'/><category scheme='http://www.blogger.com/atom/ns#' term='investment'/><category scheme='http://www.blogger.com/atom/ns#' term='work'/><category scheme='http://www.blogger.com/atom/ns#' term='vc'/><title type='text'>VC 2.0 - Crowdfunding is go!</title><content type='html'>The Series B VC market sucks right now. The boom times of the pre-credit-crunch 2.0 world seem very far away. Although there's still enthusiasm to invest in early-stage companies at Angel or Series A rounds (small investment, large slice of equity, potential for large return) and Series C/D+ (where the company is commercialised and probably generating revenue or damn close to it, and therefore the risk is much lower) there's been a huge withdrawal of funds and appetite for Series B investments. &lt;br /&gt;&lt;br /&gt;Which is were we are right now. &lt;br /&gt;&lt;br /&gt;So in typical Trampo style, we've decided not to go down that route... and today we launched &lt;a href="http://crowdfunding.trampolinesystems.com"&gt;Trampoline's Crowdfunding process&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The idea is, simply put, that rather than persuading a few people to part with a large sum of cash, to go the Obama way and offer tranches of shares for much smaller amounts to many more people. This also opens up great possibilities for utilising the power of the extended network of many investors, all the way from getting a large network of potential contacts (it's almost like hiring a hundred part-time salesmen) to exploiting the wisdom of crowdfunders on key strategic decisions.&lt;br /&gt;&lt;br /&gt;Of course, nothing's ever quite as simple as it sounds, and there are many FSA regulations about this kind of thing designed to protect Mom and Pop investors from the plethora of possible boiler room-type scams that could be represented in this light, such as:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;We can't actively solicit investment in any public setting.&lt;br /&gt;So this post is &lt;em&gt;FYI only&lt;/em&gt;, ok? Ok...&lt;/li&gt;&lt;br /&gt;&lt;li&gt;We can't discuss any details with anyone, unless they're&lt;ol&gt;&lt;li&gt;A certified &lt;q&gt;Sophisticated Investor&lt;/q&gt;, or&lt;/li&gt;&lt;li&gt;A certified &lt;q&gt;High Net Worth Individual&lt;/q&gt;, or&lt;/li&gt;&lt;li&gt;A friend or family member (yes, apparently that's ok!)&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;So this is emphatically &lt;strong&gt;NOT&lt;/strong&gt; an exchange-less IPO! 'Cause those are illegal! It's more of a self-build consortium, kind of thing&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;Anyway, bearing in mind the FSA regulations, that's about as much as I can say, apart from point you at &lt;a href="http://www.ft.com/cms/s/0/c037ae5c-7b92-11de-9772-00144feabdc0.html"&gt;Trampoline's Crowdfunding article in todays Financial Times&lt;/a&gt; (also on page 12 of the print edition), and the &lt;a href="http://crowdfunding.trampolinesystems.com"&gt;Crowdfunding Website&lt;/a&gt; if you want more information&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-5437725704207527374?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://crowdfunding.trampolinesystems.com' title='VC 2.0 - Crowdfunding is go!'/><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/5437725704207527374/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=5437725704207527374' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/5437725704207527374'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/5437725704207527374'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/07/vc-20-crowdfunding-is-go.html' title='VC 2.0 - Crowdfunding is go!'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-1656878452067323045</id><published>2009-05-22T10:57:00.003+01:00</published><updated>2009-05-22T11:30:02.478+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='databases'/><category scheme='http://www.blogger.com/atom/ns#' term='transactions'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='fail'/><category scheme='http://www.blogger.com/atom/ns#' term='activerecord'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>This should not be possible - transactional fail?</title><content type='html'>We're having a bizarre problem with MySQL 5.0.32 on Debian in a highly-concurrent environment, using the ActiveRecord JDBC adapter. We've also seen it on 5.0.51 on Ubuntu.&lt;br /&gt;&lt;br /&gt;There's a particular sequence of 3 queries, all executed from within a ActiveRecord::Base.transaction { } block:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;// Statement 1:&lt;br /&gt;  INSERT INTO (InnoDB table 1)&lt;br /&gt;         (type, ...loads of other fields...)&lt;br /&gt;  SELECT 'ProtoMessageItem', ...other fields...&lt;br /&gt;  FROM   (MyISAM table 1) LEFT OUTER JOIN (InnoDB table 2)&lt;br /&gt;  WHERE  (anti-duplication clause)&lt;br /&gt;  GROUP BY (synthetic identifier on MyISAM table 1, to be a unique key on InnoDB table 1)&lt;br /&gt; &lt;br /&gt;// Statement 2:&lt;br /&gt;  INSERT INTO (InnoDB table 2)&lt;br /&gt;  SELECT (fields)&lt;br /&gt;  FROM   (MyISAM table 1) LEFT OUTER JOIN (InnoDB table 3) INNER JOIN (InnoDB table 1)&lt;br /&gt;  WHERE  (anti-duplication clause)&lt;br /&gt;&lt;br /&gt;// Statement 3:&lt;br /&gt;  UPDATE  (InnoDB table 1)&lt;br /&gt;  SET     type = 'MessageItem' &lt;br /&gt;  WHERE   type = 'ProtoMessageItem'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Now, all of this takes place within an explicit transaction, and works well - this approach provides huge performance gains over the more simple and obvious methods.&lt;br /&gt;&lt;br /&gt;The problem is that just very very occasionally, it breaks. We don't know how or why, but once in a blue moon, statement 2 throws a DUPLICATE KEY exception, and the transaction &lt;em&gt;DOESN'T&lt;/em&gt; get rolled back. &lt;strong&gt;Statement 1 still gets COMMITTED&lt;/strong&gt;. We can see ProtoMessageItems in the DB, and this blocks up the pipeline and stops further MessageItems from being created.&lt;br /&gt;&lt;br /&gt;ProtoMessageItems should &lt;em&gt;NEVER&lt;/em&gt; be visible - they're only ever created and then updated to full MessageItems in the same 3-statement transaction. Seeing them from outside that transaction is like seeing a &lt;a href="http://en.wikipedia.org/wiki/Naked_singularity"&gt;naked singularity&lt;/a&gt; - it just shouldn't be possible.&lt;br /&gt;&lt;br /&gt;This is all kinds of wrong in a very wrong way, as it means one of the following - either:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;The DB is getting the correct BEGIN/COMMIT/ROLLBACK statements, but transactional atomicity is not holding for some reason&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The JDBC driver is not sending the correct ROLLBACK statements, but thinks it's in autocommit mode&lt;/li&gt;&lt;br /&gt;&lt;li&gt;There's an obscure concurrency edge case somewhere with the ActiveRecord JDBC adapter, that means once in a while, for some reason, it starts that transaction in autocommit mode.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;All of these possibilities are worrying. We've eliminated the possibility of it being some quirk of the particular data that causes this problem. We've turned on MySQL statement logging and watched it for hours, and as far as we can see, the correct ROLLBACK statements seem to be issued every time. &lt;br /&gt;&lt;br /&gt;Has anyone encountered a similar problem? Any known issues with INSERT/SELECT on combinations of (transactional) InnoDB and (non-transactional) MyISAM tables?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-1656878452067323045?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/1656878452067323045/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=1656878452067323045' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/1656878452067323045'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/1656878452067323045'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/05/this-should-not-be-possible.html' title='This should not be possible - transactional fail?'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-3510703247000378968</id><published>2009-05-19T16:53:00.004+01:00</published><updated>2009-05-20T15:03:20.870+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='humour'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='gruntfuddlr'/><title type='text'>Suggestions for GruntFuddlr !</title><content type='html'>Following on from the previous post on &lt;a href="http://instantbadger.blogspot.com/2009/05/twitter-will-soon-die-second-tipping.html"&gt;The imminent death of Twitter&lt;/a&gt;, after several comments along the lines of "Oh Al, you have SOOOO got to buy that domain" followed by a brief moment of hastiness with the credit card, I am now the proud owner of GruntFuddlr.com !&lt;br /&gt;&lt;br /&gt;So all I need to do now is decide what to &lt;em&gt;do&lt;/em&gt; with it.... add your suggestions to the comments! The winner will win - erm - something to be decided!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-3510703247000378968?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/3510703247000378968/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=3510703247000378968' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/3510703247000378968'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/3510703247000378968'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/05/suggestions-for-gruntfuddlr.html' title='Suggestions for GruntFuddlr !'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-2953682012996859129</id><published>2009-05-15T17:02:00.007+01:00</published><updated>2009-05-18T16:37:09.832+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='facebook'/><category scheme='http://www.blogger.com/atom/ns#' term='evolution'/><category scheme='http://www.blogger.com/atom/ns#' term='twitter'/><category scheme='http://www.blogger.com/atom/ns#' term='tipping point'/><category scheme='http://www.blogger.com/atom/ns#' term='business'/><title type='text'>Twitter Will Soon Die - the second Tipping Point approaches</title><content type='html'>Maybe I'm sticking my neck out, but it's inevitable - hear me out here. By now you're surely familiar with the concept of &lt;a href="http://en.wikipedia.org/wiki/The_Tipping_Point_(book)"&gt;The Tipping Point&lt;/a&gt; as espoused by Malcolm Gladwell - &lt;br /&gt;&lt;q&gt;the moment of critical mass, the threshold, the boiling point&lt;/q&gt;&lt;br /&gt;&lt;q&gt;the point at which momentum for change becomes unstoppable&lt;/q&gt;&lt;br /&gt;&lt;br /&gt;This has been adopted as a central tenet of the Web 2.0+ business model, and is surely now almost as ubiquitous on a web startup's business plan as a long tail reference or hockey stick graph. This post is about the Tipping Point concept specifically as applied to social networking services.&lt;br /&gt;&lt;br /&gt;It's certainly true in my experience that every successful social software service goes through some standard stages of growth:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Early adoption&lt;/strong&gt;&lt;br/&gt;The service gets adopted by a set of keenly curious early users. This is often through an initial restricted beta program, or through the conference circuit, or friends and families of those involved in the development. Many never get past this stage - maybe it's just not a particularly good idea, or useful service, or well-executed, or maybe it's just unlucky. Assuming the service &lt;em&gt;does&lt;/em&gt; get past this stage, they move on to...&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Viral spread&lt;/strong&gt;&lt;br/&gt;The early adopters have judged and found it worthy. Here's where they get to gain geek cool points by name-dropping it amongst their friends, on the conference circuit, in Friday-night bars, etc... often taking advantage of the opportunity to faintly patronise those who are still using an inferior / more mass-market / older service - I mean, how many times have you heard lines like these?&lt;br /&gt;&lt;br /&gt;&lt;q&gt;....Oh Digg &lt;em&gt;totally&lt;/em&gt; jumped the shark, I moved off there to Reddit and now I've started using MangWurdlr &lt;em&gt;(or whatever)&lt;/em&gt;&lt;/q&gt;&lt;br /&gt;&lt;q&gt;...Yeah I use GruntFuddlr for &lt;em&gt;all&lt;/em&gt; my &lt;em&gt;(whatever)&lt;/em&gt;-ing now, it's nice, they've got a really cool UI, and ....etc...etc...&lt;/q&gt;&lt;br /&gt;&lt;q&gt;...Oh, you should have a look at PigNudgr - they're still in beta, but it looks really cool, and they're a great bunch of guys - I used to work with Andy at &lt;em&gt;(name of now-defunct late-nineties web agency, usually some combination of colour and animal)&lt;/em&gt; and he's doing some really neat stuff now...&lt;/q&gt;&lt;br /&gt;etc etc.&lt;br /&gt;&lt;br /&gt;Every time I have one of those conversations I'm reminded of when I was in the sixth form &lt;em&gt;(that's school from 16-18, when you've passed your first exams - sorry I don't know what the US equivalent is)&lt;/em&gt; and people would compete for alpha-cool status by getting into ever-more obscure bands on ever-smaller labels. &lt;br /&gt;&lt;br /&gt;Remember those? Bands like "Anna", "Spontaneous Cattle Combustion", "Generic me-too grunge band #241" - when they appeared in NME for the first time they were the coolest thing since sliced bread, and if you didn't have a copy of their debut EP you were &lt;em&gt;nobody&lt;/em&gt;, maaannn - ? It's the same phenomenon. Web startups / bands - same thing, just slightly older protagonists. Same silly haircuts though. &lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;The first Tipping Point&lt;/strong&gt;&lt;br/&gt;&lt;br /&gt;This is the Holy Grail for earlier-stage web services. This is where you succeed in creating enough word-of-mouth buzz that suddenly, you're unstoppable. You tip over into the mainstream. People's &lt;em&gt;parents&lt;/em&gt; have heard of you (well, those that read the more techno-savvy broadsheets anyway) A whole new wave of joiners is poised to come on-board, often purely because of who is already on there. Prime example - Stephen Fry on Twitter. &lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;Inflation&lt;/strong&gt;&lt;br/&gt;&lt;br /&gt;&lt;em&gt;(apologies to &lt;a href="http://en.wikipedia.org/wiki/Alan_Guth"&gt;Alan Guth&lt;/a&gt;!)&lt;/em&gt;&lt;br /&gt;This is where the whole thing goes crazy. Faster growth than they ever thought possible. All those times the engineers said "Well, that's a high-class problem to have, and we'll just have to deal with that when we get there.." well - they're there right now. Seems like every week there's another digit on the hit counts. Often characterised by regular outages due to scale fail in completely unexpected ways ("Holy crap, we've run out of &lt;em&gt;inodes???&lt;/em&gt; That's &lt;em&gt;possible?&lt;/em&gt;"). They're sticking in new servers by the dozen all the time, burning through all that VC funding and not making any money yet..&lt;br /&gt;&lt;br /&gt;Meanwhile the user base seems to take on a life of its own. It's no longer a community, it instead takes on the characteristics of a population - splits, fragments, in-fighting, single-issue activism - often &lt;em&gt;against&lt;/em&gt; the decisions of the founders, who in their increasingly urgent need to get revenue to support the spiralling infrastructure costs are taking commercially-led decisions and seen as increasingly out-of-touch with the still-trying-to-be-faithful users.&lt;br /&gt;&lt;br /&gt;There's conflicting senses of excitement and disappointment, that it's just not the same as it used to be. And who are all these ****wits joining now anyway? What - my &lt;em&gt;MUM&lt;/em&gt; just joined? And she wants to be my friend? Oh crap, better get rid of all of those tales of drunken debauchery and undeniable photographic evidence. Your boss probably joins up, adding further fuel to the conflict of identity. Is this personal? A work tool? Both?&lt;/li&gt;&lt;br /&gt;&lt;br /&gt;&lt;li&gt;&lt;strong&gt;The second Tipping Point&lt;/strong&gt;&lt;br/&gt;&lt;br /&gt;All good things must come to an end, and this is the end of the line for the early adopters. It's not new, edgy or cool anymore, now that it's talked about on Question Time and even the Government are starting to use it in some god-awful, half-assed but completely cringe-making attempt to be Down With Tha Kids. Meanwhile, you keep getting friend requests from people at school who you weren't friends with in the first place and have no intention of pretending to be friends with now.&lt;br /&gt;&lt;br /&gt;The whole world and his hamster seems to be trying to spam you about it or on it. The number of z-list celebrities on it just keeps growing - Peter Andre? Really? Christ... - and they don't even post themselves, it's done via their PR person, or if they do they're just pushing their warez (yes, &lt;A href="http://twitter.com/WilliamShatner"&gt;@WilliamShatner&lt;/a&gt;, I'm looking at you). &lt;br /&gt;&lt;br /&gt;&lt;em&gt;(sigh)&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;You've had enough. You can barely be bothered to check it any more, and all the third-party apps built on the API are just getting on your nerves, and all the cool kids have moved on to DangDurdlr.com anyway... apparently it's really cool, and apparently Dave from AvocadoAardvaark.com got together with Si from PurplePlatypus to start it up, and they've got a really neat UI, and it &lt;em&gt;doesn't have all of these ****wits on it&lt;/em&gt;...&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;So that's the Second Tipping Point. When all of the people who joined before the first Tipping Point leave, partly due to their constant need for something new, partly because services just naturally evolve, but mostly because of all the people who joined just &lt;em&gt;after&lt;/em&gt; the first. Who, in their turn, had mainly joined because of all the people who were already on it...&lt;br /&gt;&lt;br /&gt;Facebook got there a few months ago. Twitter is rapidly approaching it, and you know what? It's natural. It's part of the inevitable evolution of ideas and the services built upon them. It's not that Twitter has anything fundamentally wrong with it, it's mainly that the problem with software that puts people in touch with people..... is people. The cliques and cooler-than-thouism that ruled school society never went away, they just changed form and went online.&lt;br /&gt;&lt;br /&gt;MySpace took over from first-gen social software like SmartGroups, and while its user numbers may technically still be growing, in new-frontier terms, it's now dying. It was supplanted by Facebook, which has become supplanted by Twitter, which will soon be supplanted by some other new kid on the block. No-one knows what it will be yet, but that's part of the whole fun of this business - natural selection in the full glare of the web, with all the competition, cross-breeding, malformed offspring and occasional apparently-arbitrary successes. I love it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-2953682012996859129?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/2953682012996859129/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=2953682012996859129' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/2953682012996859129'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/2953682012996859129'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/05/twitter-will-soon-die-second-tipping.html' title='Twitter Will Soon Die - the second Tipping Point approaches'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-701749064154847828</id><published>2009-04-07T15:15:00.005+01:00</published><updated>2009-04-09T01:01:56.856+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='footie'/><category scheme='http://www.blogger.com/atom/ns#' term='sonar'/><category scheme='http://www.blogger.com/atom/ns#' term='busy'/><category scheme='http://www.blogger.com/atom/ns#' term='work'/><category scheme='http://www.blogger.com/atom/ns#' term='cragwag'/><title type='text'>Another day, another deployment</title><content type='html'>Posts have been increasingly thin on the ground recently, due to a couple of things: my discovery of a reasonable lightweight API client for Twitter, but mainly lack of time - which itself is down to two things:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;I'm organising my &lt;a href="http://lisaandalswedding.blogspot.com"&gt;wedding&lt;/a&gt; to the ludicrously lovely Lise, and&lt;/li&gt;&lt;li&gt;we've been getting SONAR installed and running at two large corporates recently (who shall remain nameless)&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;It's a good feeling to have seen a product go right through from initial idea on a post-it note to being installed at a super-huge financial client, and it makes all the work worthwhile when you see people actually using it to do their job. I wish I could talk more about the details, but confidentiality dictates not, sadly.&lt;br /&gt;&lt;br /&gt;I've also been meaning to blog about the app I whipped up to manage the football team I organise - being a geek at heart, it's amazing the amount of time and effort I devote to being effectively lazy, and this little Rails app which started out as a quick time-saver ended up being a case study in the power of stupidity as applied to a linear optimisation problem - "given this set of players, each of whom have a set of positions they prefer to play in, what is the best possible line-up ?" I'll get round to blogging about that at some point, honest..&lt;br /&gt;&lt;br /&gt;Another little personal project that I started as a tool for myself and then ended up generalising and meaning to add to whenever I get time &lt;em&gt;(chortle)&lt;/em&gt; is &lt;a href="http://cragwag.com"&gt;Cragwag&lt;/a&gt; - an RSS aggregator, plus some - aggregating many various climbing-related blogs, an only-just-started map of climbing areas plus B&amp;B's plus parking plus convenient deli's, which I may end up mashing up with weather reports to answer questions like "where should we go climbing this weekend?"&lt;br /&gt;&lt;br /&gt;Bleh. So that's why I've ended up micro-blogging rather than macro-blogging these days. I will try to get used to writing in more than 140 characters again soon...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-701749064154847828?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/701749064154847828/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=701749064154847828' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/701749064154847828'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/701749064154847828'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/04/another-day-another-deployment.html' title='Another day, another deployment'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-1895543455969417212</id><published>2009-03-24T10:36:00.001Z</published><updated>2009-03-24T10:36:06.836Z</updated><title type='text'>And a jolly good time was had by all</title><content type='html'>&lt;div style="float: right; margin-left: 10px; margin-bottom: 10px;"&gt;&lt;a href="http://www.flickr.com/photos/drsnooks/3382080064/" title="photo sharing"&gt;&lt;img src="http://farm4.static.flickr.com/3598/3382080064_b162a31114_m.jpg" alt="" style="border: solid 2px #000000;" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-size: 0.9em; margin-top: 0px;"&gt;&lt;a href="http://www.flickr.com/photos/drsnooks/3382080064/"&gt;Karters&lt;/a&gt;&lt;br /&gt;Originally uploaded by &lt;a href="http://www.flickr.com/people/drsnooks/"&gt;Dr Snooks&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;Stag do - survived!&lt;br /&gt;Physically unscathed.&lt;br /&gt;Some of the mental scars may take a little longer to expunge....&lt;br clear="all" /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-1895543455969417212?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/1895543455969417212/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=1895543455969417212' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/1895543455969417212'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/1895543455969417212'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/03/and-jolly-good-time-was-had-by-all.html' title='And a jolly good time was had by all'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-6309511470925604742</id><published>2009-01-29T17:20:00.003Z</published><updated>2009-01-29T17:29:47.084Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='flattery'/><category scheme='http://www.blogger.com/atom/ns#' term='imitation'/><category scheme='http://www.blogger.com/atom/ns#' term='sonar'/><category scheme='http://www.blogger.com/atom/ns#' term='work'/><category scheme='http://www.blogger.com/atom/ns#' term='ibm'/><title type='text'>What was that about imitation / flattery?</title><content type='html'>So, way back in April 2008, Techcrunch ran this story about us: &lt;a href="http://www.techcrunch.com/2008/04/24/the-enterprise-social-network-auto-generated-and-visually-mapped/"&gt;The Enterprise Social Network Auto-Generated and Visually Mapped&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Imagine our surprise here when we discovered this post today on TechcrunchIT:&lt;br /&gt;&lt;a href="http://www.techcrunchit.com/2009/01/29/when-ibm-beats-facebook-and-twitter-discover-relevant-people-within-your-network/"&gt;When IBM Beats Facebook and Twitter&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;So IBM are now &lt;em&gt;also&lt;/em&gt; doing a product called SONAR, that does almost exactly the same thing as our existing product called &lt;a href="http://www.trampolinesystems.com/products/SONAR/benefits"&gt;SONAR&lt;/a&gt; (Social Networks And Relevance)??? Spooky!&lt;br /&gt;The article says that it's called SAND (Social Networks And Discovery), but the screenshots clearly show SONAR everywhere, and that's how it's referred to on the &lt;a href="http://www.haifa.ibm.com/projects/imt/sonar/index.shtml"&gt;IBM Research Labs page&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I felt compelled to respond to the claim that "No one else in consumer or enterprise is doing this yet.", so I posted a comment about an hour ago. Hasn't appeared yet though.....&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-6309511470925604742?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/6309511470925604742/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=6309511470925604742' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/6309511470925604742'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/6309511470925604742'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/01/what-was-that-about-imitation-flattery.html' title='What was that about imitation / flattery?'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-892763132935515988</id><published>2009-01-07T12:13:00.003Z</published><updated>2009-01-07T12:17:33.118Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='meme evel knievel humour'/><title type='text'>...but can Evel jump it?</title><content type='html'>Happy New Year to all! &lt;br /&gt;&lt;br /&gt;It's time for my first bold tech prediction&lt;sup&gt;&lt;a href="#disclaimer"&gt;*&lt;/a&gt;&lt;/sup&gt; of 2009 - this year's "....but will it blend?" will be &lt;a href="http://caneveljumpit.com"&gt;"...but can Evel jump it?"&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Get your suggestions in - the more surreal, the better!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;em&gt;&lt;a name="disclaimer"&gt;*&lt;/a&gt; Disclaimer: OK, OK, so for "bold tech prediction", read "blatant attempt to spread a meme and drive some traffic to my mate's site" :)&lt;/em&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-892763132935515988?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://caneveljumpit.com' title='...but can Evel jump it?'/><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/892763132935515988/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=892763132935515988' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/892763132935515988'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/892763132935515988'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2009/01/but-can-evel-jump-it.html' title='...but can Evel jump it?'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-1265544418832321234</id><published>2008-10-02T09:56:00.003+01:00</published><updated>2008-10-02T10:15:19.477+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='football'/><category scheme='http://www.blogger.com/atom/ns#' term='goalkeeper'/><category scheme='http://www.blogger.com/atom/ns#' term='team'/><title type='text'>Goalkeeper wanted!</title><content type='html'>It's off-topic, I know, but then I don't get much time to post anything at all right now, so here goes anyway.&lt;br /&gt;&lt;br /&gt;Those who know me, know I'm a keen footballer. I play for a team in a midweek 11-a-side league, and we're currently &lt;em&gt;DESPERATE&lt;/em&gt; for a goalie. &lt;br /&gt;&lt;br /&gt;The matches are all on astroturf, so there's no studs and very few sliding tackles, and they vary in day, location and kick-off time as follows:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://snipurl.com/cca-map"&gt;Willesden CCA&lt;/a&gt; 8pm kickoff, Mondays or Tuesdays&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://snipurl.com/prg-map"&gt;Paddington Recreation Ground&lt;/a&gt; 7:30pm kickoff, Mondays&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;Very occasionally, we may have a match on a Wednesday or Thursday night at &lt;a href="http://snipurl.com/marketroad-map"&gt;Market Road&lt;/a&gt; or &lt;a href="http://snipurl.com/wws-map"&gt;Linford Christie Stadium&lt;/a&gt; but these are rare.&lt;br /&gt;&lt;br /&gt;The team vary in ages between 20-ish to 40-ish, and there's a similar variance in abilities. No-one is anywhere near Ronaldo standard, put it that way.&lt;br /&gt;&lt;br /&gt;If you're interested, email me directly (address is on the right, obscured for spambots) or add a comment at the bottom. We've got our next match on Monday, so don't hang about!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-1265544418832321234?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/1265544418832321234/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=1265544418832321234' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/1265544418832321234'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/1265544418832321234'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2008/10/goalkeeper-wanted.html' title='Goalkeeper wanted!'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-750669744260594908</id><published>2008-08-06T22:46:00.003+01:00</published><updated>2008-08-06T22:51:56.932+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sonar'/><category scheme='http://www.blogger.com/atom/ns#' term='ux'/><category scheme='http://www.blogger.com/atom/ns#' term='userexperience'/><title type='text'>Holistic User Experience</title><content type='html'>SONAR v1.0 is just around the corner, and we've been thinking a lot about the user experience we offer. One of the key differences this time round is the recognition that User Experience is not just about the interface. Users in this sense are not just end users - they're *anyone* who interacts in any way with your software or your company. They include the sysadmins who have to install your stuff and keep it up and running, the managers who have to make a purchasing decision about whose software to spend their hard-won budget on. &lt;br /&gt;&lt;br /&gt;So we believe that User Experience is about looking at the whole experience the user has in interacting with your software and you. It's about making the whole thing as easy as possible for them, right from the way they hear about it, through purchasing and finding the information they need to make an informed decision, through installation, configuration, administration, ongoing support, right the way through to migrating away from it if they want to.&lt;br /&gt;&lt;br /&gt;Yes, I did say that you have to make it easy for them to migrate away from your &lt;br /&gt;software. No, that's not a dumb thing to do. &lt;br /&gt;&lt;br /&gt;There's two ways of looking at it - self-centred or user-centred. The first way often involves making it difficult to get their data out of your system once it's in there, end-of-lifing old versions and occasionally making new versions incompatible so that they have to keep buying your upgrades or they lose access to their data.&lt;br /&gt;&lt;br /&gt;This is bad, m'kay? It's called Vendor Lock-In, and although it can do good things for your bottom line in the short term, ultimately it can create frustration and resentment among your users - Bad User Experience.&lt;br /&gt;&lt;br /&gt;The thing to remember is that these days, people move around frequently. They change jobs, they change cities and countries at the drop of a hat, so if you create a Bad User Experience your bad reputation will spread along with the people who had it. The democratised web has created a situation where reputation has become &lt;em&gt;more&lt;/em&gt; of a crucial commodity than ever before.&lt;br /&gt;&lt;br /&gt;If you had to make a purchasing decision between system x and system y, and both of them had pretty much the same features for a pretty similar price, what factors would you use to make that decision? The first thing I'd do is ask around for anyone who's used either system before, and see what they thought of it. If I heard stories like "Yeah, it's OK, but once you buy it you're stuck with it" I'd hear alarm bells. I'm far more likely to go with a system that lets me get my data out easily if i change my mind for any reason.&lt;br /&gt;&lt;br /&gt;You'd also look at questions like - whats the support like? Is there an active user community? Do they have forums / wikis / comprehensive FAQs? Do the reported bugs look they get fixed? Is it easy to find the information you need on the website? &lt;br /&gt;&lt;br /&gt;You might give them a call to ask some questions, which opens up a whole new set of potential decision influencers - how quickly did they answer the phone? Did they sound happy to help? Did you have to navigate a labyrinthine set of automated menus for ten minutes before you actually got to speak to someone? Or even worse - did the menus have a voice recognition system that couldn't understand your accent?&lt;br /&gt;&lt;br /&gt;All of the above factors - and many more - contribute to the user's experience of your company and your software, and all are potential factors that influence whether someone decides to spend their money on you. We know we've a long way to go in a lot of these areas, but we recognise this, and we're looking - and that's a great start.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-750669744260594908?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/750669744260594908/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=750669744260594908' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/750669744260594908'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/750669744260594908'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2008/08/holistic-user-experience.html' title='Holistic User Experience'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-3694010560353758635</id><published>2008-06-26T19:22:00.002+01:00</published><updated>2008-06-26T19:24:33.231+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='humour'/><category scheme='http://www.blogger.com/atom/ns#' term='atheism'/><category scheme='http://www.blogger.com/atom/ns#' term='god'/><title type='text'>Atheistic Error Message Of The Day</title><content type='html'>while trying to install &lt;a href="http://god.rubyforge.org"&gt;GOD&lt;/a&gt;, the Ruby process mgmt gem, I got a sudden attack of poignance:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;[sonar@tryfan sonar-solr]$ gem install god&lt;br /&gt;ERROR:  While executing gem ... (Gem::RemoteSourceException)&lt;br /&gt;    HTTP Response 404&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Discuss, in not less than 3000 words...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-3694010560353758635?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/3694010560353758635/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=3694010560353758635' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/3694010560353758635'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/3694010560353758635'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2008/06/atheistic-error-message-of-day.html' title='Atheistic Error Message Of The Day'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-1658990860924623627</id><published>2008-04-03T14:20:00.002+01:00</published><updated>2008-04-03T15:03:03.887+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='windows'/><category scheme='http://www.blogger.com/atom/ns#' term='rake'/><title type='text'>Rake gotcha on Windows</title><content type='html'>We use &lt;a href="http://rake.rubyforge.org"&gt;Rake&lt;/a&gt; as our build system of choice for all of our projects, as it's much more flexible and pleasant to work with than make or ANT, and by and large it's pretty cross-platform. Except..... &lt;br /&gt;&lt;br /&gt;I just spent about two hours tearing my hair out over something which &lt;em&gt;should&lt;/em&gt; have worked transparently on Windows aswell as *NIX, but it just wasn't playing.&lt;br /&gt;&lt;br /&gt;For reasons which would be tedious to go into, inside one particular rake task I needed to cd to a different directory and execute rake in there, to pick up a completely different set of tasks and ActiveRecord model classes, etc, and then resume execution of the containing task. &lt;br /&gt;&lt;br /&gt;The following code worked fine on UNIX, but just completely failed to do anything on Windows:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;Dir.chdir("../sonar-web") { system( "rake", "db:name_of_other_task" ) }&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;No error messages, nothing, it just wasn't doing anything.&lt;br /&gt;&lt;br /&gt;I'll spare you the headbanging frustration and exhaustive list of things I tried that didn't work, and jump straight to the solution - &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;rake&lt;/strong&gt; on Windows needs to execute c:/ruby/bin/&lt;strong&gt;rake.bat&lt;/strong&gt;, not c:/ruby/bin/rake !&lt;br /&gt;&lt;br /&gt;rake.bat will in turn call ruby.exe c:/ruby/bin/rake and pass on the command line parameters.&lt;br /&gt;&lt;br /&gt;So at the top of my rakefile, I just added:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;# on Windows, you can't invoke rake via a "system" cmd, as rake actually should invoke rake.bat&lt;br /&gt;RAKE_CMD      = RUBY_PLATFORM.match(/win/) ? "rake.bat" : "rake"&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;and then changed my system call to :&lt;br /&gt;&lt;br /&gt;&lt;code&gt;system( "#{RAKE_CMD}", "name_of_other_task" )&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;....and everything worked fine.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-1658990860924623627?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/1658990860924623627/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=1658990860924623627' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/1658990860924623627'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/1658990860924623627'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2008/04/rake-gotcha-on-windows.html' title='Rake gotcha on Windows'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-4958984401661940376</id><published>2008-02-26T09:50:00.015Z</published><updated>2008-02-26T12:06:26.909Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='cfoutput'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='cf'/><category scheme='http://www.blogger.com/atom/ns#' term='coding'/><title type='text'>I still miss CFOUTPUT</title><content type='html'>&lt;p&gt;It's been about a year now since I last coded CF in anger, and what coding I've done since then has been mainly in Java and Ruby On Rails. These days, most of my coding is done in my spare time, which is a resource in increasingly short supply - so coding actually tends to be done during my random bouts of insomnia. Like many similar turncoats, I've found RoR development to be settling into a fairly steady cycle :&lt;/p&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Do some really complicated stuff really quickly.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Nod appreciatively and make coo-ing noises.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Stop yourself just before standing up and announcing that your name is Boris and &lt;a href="http://www.youtube.com/watch?v=cKO0RrM7LvI&amp;feature=related"&gt;you are invincible&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Try to do something ostensibly simple, get stuck&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Spend ages scouring the web for the simple solution that surely must be out there somewhere&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Get pissed off because you found a blog post telling you that you shouldn't want to do that because it's &lt;a href="http://web.archive.org/web/20060418215514/http://www.loudthinking.com/arc/000516.html"&gt;"not the rails way"&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Figure out a really ugly cumbersome way of doing it step-by-step&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Fail to get to sleep because it's just bugging you that something so simple should be so hard in a framework that makes so many other more complicated things so simple&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Two days later, discover entirely by accident that the fifty lines of hackery could have been done in one long line of twenty method calls &lt;em&gt;( things.each do{ |foo| foo.do.some.other.thing }.and.then.do.some.thing.else( bar ) )&lt;/em&gt; if only you'd known that the method you needed was called &lt;em&gt;(insert counter-intuitive method name here)&lt;/em&gt; and that there was a plugin called &lt;em&gt;(insert name of obscure plugin here)&lt;/em&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Go back and redo your hack with the one long line of twenty method calls. Feel like a l33t h4x0r d00d because you just replaced fifty lines with one.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Discover that somewhere in your one long line of twenty method calls, one of them is returning nil. &lt;/li&gt;&lt;br /&gt;&lt;li&gt;Think "hey, it's ok, I can debug this easily with the console!". Feel smug.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Spend what seems like an aeon starting the console, reproducing the conditions in which you get your nil, changing something, then restarting the console so you can test your change.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Resolve to write better unit tests in future.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Pine a little for the good old days of "make your change then hit f5" to see if something works.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Start a trawl through the source code looking for the cause of the problem&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Reflect that while duck typing is indeed an orgy of sheer loveliness, in this particular case it would be nice if just this once you could know for sure that this particular object is a Foo and therefore all you need to know would be found in foo.rb&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Discover that the cause of your woe is that at least one of your magical plugins doesn't work on Oracle, or SQL Server, or indeed anything other than MySQL.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Go back to the blog on which you found the plugin to see if it's a known problem with a new version&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Discover that the blog is down.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Write a plugin to patch the plugin to work on Oracle&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Feel vaguely uneasy that you now have a chain of umpteen plugins patching plugins patching plugins patching plugins patching ActiveRecord to do something that you apparently shouldn't want to do, but dammit, you needed to get it done by 5pm.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Go out and pull scary faces at small children to make them cry for a while until you feel better.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Come back feeling much better now that you've spread a little frustration around. Reflect that you're probably just not thinking about things in the "right" way.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Adjust your thought-angle, and come at it again&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Repeat from step 1&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;p&gt;To be clear, I do think that Ruby has some wonderful features. Blocks, open classes, method_missing - all of these little niceties make some fantastically cool things possible. The &lt;a href="http://www.ruby-doc.org/core/classes/Enumerable.html#M003151"&gt;Enumerable#sort_by&lt;/a&gt; method in particular was one discovery that just gave me a wonderful warm fuzzy feeling - e.g. &lt;/p&gt;&lt;code&gt;some_collection.sort_by { |element| [ element.method1, element.method2, element.method3 ] }&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Rails, also, has some really great features, and makes some of the donkey work so easy it's almost laughable. But sometimes it feels like all the thinking went into the elegance of the back-end design, and not enough thought went into the templating. RHTML feels like a tacked-on afterthought. HAML is better in some respects, but it still feels awkward. I haven't yet found any templating language that even comes close to the sheer simplicity and ease-of-use of CFOUTPUT.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;The example that triggered this rant was grouped output. Let's keep this simple for the purpose of example - say I have a recordset with three fields:&lt;/p&gt;&lt;br /&gt;&lt;table style="width:90%"&gt;&lt;br /&gt;&lt;thead&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;  &lt;th&gt;Type&lt;/th&gt;&lt;br /&gt;  &lt;th&gt;Sub-type&lt;/th&gt;&lt;br /&gt;  &lt;th&gt;Title&lt;/th&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/thead&gt;&lt;br /&gt;&lt;tbody&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;  &lt;td&gt;Type 1&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Sub-type 1&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Foo 1&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;  &lt;td&gt;Type 1&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Sub-type 1&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Foo 2&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;  &lt;td&gt;Type 1&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Sub-type 1&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Foo 3&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;  &lt;td&gt;Type 1&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Sub-type 2&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Bar 1&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;  &lt;td&gt;Type 1&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Sub-type 2&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Bar 2&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;  &lt;td&gt;Type 1&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Sub-type 2&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Bar 3&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;  &lt;td&gt;Type 2&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Sub-type 3&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Foobar 1&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;  &lt;td&gt;Type 2&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Sub-type 3&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Foobar 2&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;  &lt;td&gt;Type 2&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Sub-type 4&lt;/td&gt;&lt;br /&gt;  &lt;td&gt;Foobar 3&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/tbody&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;- etc etc.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;If you wanted to output these with headers and sub-headers whenever the type or sub-type changed, it would be almost trivially easy in CF:&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;cfoutput query="myRecordset" group="type"&amp;gt;&lt;br /&gt;  &amp;lt;h2&amp;gt;#myRecordset.type#&amp;lt;/h2&amp;gt;&lt;br /&gt;  &amp;lt;cfoutput group="subtype"&amp;gt;&lt;br /&gt;    &amp;lt;h3&amp;gt;#myRecordset.subtype#&amp;lt;/h3&amp;gt;&lt;br /&gt;      &amp;lt;ul&amp;gt;&lt;br /&gt;      &amp;lt;cfoutput&amp;gt;&lt;br /&gt;         &amp;lt;li&amp;gt;#myRecordset.title#&amp;lt;/li&amp;gt;&lt;br /&gt;      &amp;lt;/cfoutput&amp;gt;&lt;br /&gt;      &amp;lt;/ul&amp;gt;&lt;br /&gt;  &amp;lt;/cfoutput&amp;gt;&lt;br /&gt;&amp;lt;/cfoutput&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;- but in Rails? I'm still at step 5. I know I'm not the first person to need to do this, not by a long shot, but I just don't yet know what the method that surely must exist would be called. I know you can use Enumerable#group_by to group an array of &lt;br /&gt;objects into something approximating the raw recordset above, but that's kind of working backwards to me.&lt;br /&gt;&lt;br /&gt;I also know that someone will probably post the answer in a comment, probably with some kind of dismissive one-word instruction like "Read." or "Learn." linking to the relevant part of the docs. And that's all well and good. I'm just in a temporary bout of misty-eyed nostalgia for the things that CF made easy - particularly CFOUTPUT.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-4958984401661940376?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/4958984401661940376/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=4958984401661940376' title='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/4958984401661940376'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/4958984401661940376'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2008/02/i-still-miss-cfoutput.html' title='I still miss CFOUTPUT'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-8075278760981986635</id><published>2008-01-25T13:25:00.000Z</published><updated>2008-01-25T13:49:29.813Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='humour'/><category scheme='http://www.blogger.com/atom/ns#' term='fun'/><category scheme='http://www.blogger.com/atom/ns#' term='office'/><category scheme='http://www.blogger.com/atom/ns#' term='war'/><category scheme='http://www.blogger.com/atom/ns#' term='nationalities'/><title type='text'>Don't Mention The War!</title><content type='html'>Well, in an office containing two Poles, two Germans, two Americans and many Brits, it had to happen sooner or later....&lt;br /&gt;&lt;br /&gt;In response to an email about the &lt;a href="http://petitions.pm.gov.uk/remembermonday/"&gt;petition for a Remembrance Day Bank Holiday&lt;/a&gt;, one of the Poles in the office said :&lt;br /&gt;&lt;q&gt;Personally I think it's a totally lame idea. People who want to go to Iraq and other remote places to shoot others should get a shrink not a holiday&lt;/q&gt;&lt;br /&gt;&lt;br /&gt;At which one of the Brits said:&lt;br /&gt;&lt;q&gt;Hang on, what about the people who went to remote places like Poland and shot Germans?&lt;/q&gt;&lt;br /&gt;&lt;br /&gt;And then the Poles started on about the lack of help they got in the early days of WWII, and just as that was kicking off, in walked one of the German guys.... and then the fun &lt;em&gt;really&lt;/em&gt; started ...!&lt;br /&gt;&lt;br /&gt;Ah, you can't beat a bit of good-natured dredging up national stereotypes from half a century ago to really get that Friday afternoon feeling into a place - it's still going, this is great, it's better than a movie! Where's the popcorn?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-8075278760981986635?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/8075278760981986635/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=8075278760981986635' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/8075278760981986635'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/8075278760981986635'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2008/01/dont-mention-war.html' title='Don&apos;t Mention The War!'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6799088.post-1960803241819116770</id><published>2008-01-23T13:23:00.001Z</published><updated>2008-01-23T13:23:35.170Z</updated><title type='text'>Ironically Apt Screenshot Of The Day</title><content type='html'>&lt;div style="float: right; margin-left: 10px; margin-bottom: 10px;"&gt; &lt;a href="http://www.flickr.com/photos/drsnooks/2213725011/" title="photo sharing"&gt;&lt;img src="http://farm3.static.flickr.com/2399/2213725011_c89c9860c2_m.jpg" alt="" style="border: solid 2px #000000;" /&gt;&lt;/a&gt; &lt;br /&gt; &lt;span style="font-size: 0.9em; margin-top: 0px;"&gt;  &lt;a href="http://www.flickr.com/photos/drsnooks/2213725011/"&gt;Ironically Apt Screenshot Of The Day&lt;/a&gt;  &lt;br /&gt;  Originally uploaded by &lt;a href="http://www.flickr.com/people/drsnooks/"&gt;Dr Snooks&lt;/a&gt; &lt;/span&gt;&lt;/div&gt;Tomasz was trying to install the Java Enterprise Edition SDK. For some strange reason it was only giving him this one single button....&lt;br clear="all" /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6799088-1960803241819116770?l=instantbadger.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://instantbadger.blogspot.com/feeds/1960803241819116770/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=6799088&amp;postID=1960803241819116770' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/1960803241819116770'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6799088/posts/default/1960803241819116770'/><link rel='alternate' type='text/html' href='http://instantbadger.blogspot.com/2008/01/ironically-apt-screenshot-of-day.html' title='Ironically Apt Screenshot Of The Day'/><author><name>Alistair Davidson</name><uri>http://www.blogger.com/profile/07415960315873865766</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='02574450378248388075'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry></feed>