<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
  <id>tag:gwynmorfey.com:categories1-on-the-jobarticles</id>
  <link rel="alternate" type="text/html" href="http://gwynmorfey.com"/>
  <title>Gwyn Morfey: On The Job</title>
  <updated>2010-06-02T11:23:51-04:00</updated>
  <entry>
    <id>tag:gwynmorfey.com:Article193</id>
    <published>2010-06-02T11:23:51-04:00</published>
    <updated>2010-06-02T11:26:16-04:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/193-android-will-make-you-miss-your-flight"/>
    <title>Android Will Make You Miss Your Flight</title>
    <content type="html">I like Android, but it can't tell time.

I'm in London, booking my return flight from Trondheim. Takeoff is at 5:45pm Trondheim time. That's what's on the boarding pass, that's what I'm thinking in, and that's what I'll need to know when this event starts to matter to me. So I put 5:45pm in to google calendar and it syncs to my phone.

Then I land in Trondheim and tell my phone that my timezone has changed from GMT+1 to GMT+2. It updates the local time display - correct. Fine. But it also changes the time of every single event in my calendar.

The calendar says takeoff is at 6:45pm. I miss my flight.

Well, I don't, because I see this coming. But now my schedule has to live in my brain, and I own a phone so that I don't have to do that.

The way I used to handle this was to lie to my phone - I'd tell it &quot;honest, I'm still in GMT+0&quot; and change the master clock. But this breaks Android quite badly; it trusts timestamps that come in on external messages, so it sorts SMSs out of order. It's trying to be too clever and it's getting it wrong. 

What it _wants_ me to do, sitting in London booking the flight, is to think &quot;hrm, takeoff at 5:45pm Trondheim time. What's that in London time? I should put it in my calendar in London time, since that's where I am _now_&quot;. Those kind of back-conversions are unnecessary and error-prone.

And that's yet another case of how adding features to software can actually make it _worse_. 
</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article158</id>
    <published>2008-10-13T08:26:34-04:00</published>
    <updated>2011-12-06T04:34:32-05:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/158-git-and-the-octopus-merge-of-doom"/>
    <title>Git and the Octopus Merge Of Doom</title>
    <content type="html">We now develop in two branches, for bugfixing and features. Merging the two, if we haven't done it for a while (and we now do it every day), is a bit scary. The best tool I've found is p4merge, which does a three-way diff showing me changes in Dev, changes in Master, and the common base. A fourth pane lets me edit the output.

It works seamlessly with git - see &lt;a href='http://blog.logicalrand.com/2008/9/9/use-p4merge-or-diffmerge-with-git-mergetool-on-os-x'&gt;this blog post about integrating p4merge with git mergetool&lt;/a&gt;. If it doesn't work, you have an old version of git - upgrade.
</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article157</id>
    <published>2008-10-13T08:24:02-04:00</published>
    <updated>2008-10-13T08:24:26-04:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/157-testing-block-helpers"/>
    <title>Testing block helpers</title>
    <content type="html">Not as easy as you'd think. &lt;a href='http://rspec.lighthouseapp.com/projects/5645/tickets/376-eval_erb-not-recognizing-instance-variables-in-before'&gt;This bug&lt;/a&gt; is relevant. It seems that not only can you not create your instance variables outside the ERB and pass them in, you can't even reliably create them inside the ERB. I ended up doing this: 
 
[ruby]
def eval_erb(text)
  ERB.new(text).result(binding)
end

describe SomeHelper do
 it &quot;should work&quot; do
 @result = eval_erb &lt;&lt;-ERB
 &lt;% my_helper_name(MyThing.new(:body=&gt;&quot;foo&quot;) do %&gt;
  this stuff is passed as the block text
 &lt;% end %&gt;
ERB
 @result.should == &quot;exactly what I want&quot;
end
end
[/ruby]</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article155</id>
    <published>2008-09-29T11:42:15-04:00</published>
    <updated>2011-08-16T22:50:33-04:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/155-tracing-through-rails-partials"/>
    <title>Tracing through Rails partials</title>
    <content type="html">Looking at HTML source, it can be hard to figure out which partial is generating bad code, and why. This code outputs HTML comments at the start and end of partial rendering, giving the path to the partial and the objects and locals that were passed to it.

script/plugin install git://github.com/gwynm/noisy_partials.git</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article148</id>
    <published>2008-07-31T07:08:20-04:00</published>
    <updated>2009-10-01T09:18:03-04:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/148-tethering-a-nokia-to-osx-via-bluetooth-on-leopard"/>
    <title>Tethering a Nokia to OSX via Bluetooth on Leopard</title>
    <content type="html">I &lt;a href='http://gwynmorfey.com/categories/2-on-the-go/articles/100-miracle-device'&gt;love&lt;/a&gt; my Nokia 6120c. Now I have a new trick - internet access on my Macbook, using the phone's net connection.

It's trivial. Don't google, don't download scripts, don't do anything hard. Just go to System Preferences -&gt; Network, choose Bluetooth, enter the Access Point Name under Telephone Number, and click Set Up Bluetooth Device. Leopard has the scripts already - just select Nokia, 3G. 

To get your Access Point Name, Username and Password, look in your phone's settings. For Three Mobile in the UK, it's &quot;three.co.uk&quot;, blank username/pass.

It Just Works.
</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article146</id>
    <published>2008-07-29T02:54:20-04:00</published>
    <updated>2008-07-29T04:58:16-04:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/146-breach"/>
    <title>Breach</title>
    <content type="html">It's eleven o'clock at night. I'm in a tent ten miles out of Malmesbury. I should _not_ be thinking about this.

Work can wait until Monday; wedding planning can wait a week. Lasertrap can wait forever, if it has to. I certainly don't have to enumerate every little thing I have to do between now and Christmas, but that's what I'm doing.

The hippies I'm hanging out with are telling me to chill out: Funny. I thought I was.

A little introspection reveals what's going on: I no longer trust that everything I need to do is captured somewhere externally, and that I will get to it at the appropriate time. My phone is flat - power's hard to find at WOMAD - and I can neither read my GTD lists, to reassure myself that they exist, nor write to them. So I'm storing things in my head.

But that's not the only problem. Now managing several projects, both business and personal, my lists have become more detailed and I'm having to look ahead multiple actions. I used to get away with avoiding grouping tasks by project, but that's no longer the case.

And instead of fixing the system, I've stopped using it. The thing with GTD is that 100% coverage is an order of magnitude better than 99% coverage - and right now, I'm closer to 50%.

I'll buy some phone batteries. In the meantime, I need to reboot my GTD system: do a full sweep, collect all unclosed loops, take them to next actions, store them and forget them.

I'll put that on the todo list in my head.
</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article145</id>
    <published>2008-07-10T04:45:52-04:00</published>
    <updated>2008-07-12T18:16:36-04:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/145-loyalty-refactored"/>
    <title>Loyalty Refactored</title>
    <content type="html">I will never have a Tesco's Club Card. But every _single_ time, they ask me.

The Apostrophe cafe near New Inn Yard is much smarter. Blank loyalty cards are visible on the counter, but not offered. They don't ask you if you have one to redeem, either - you're expected to present it as you order. I've been going there most mornings for the last week; this morning, they recognised me as a frequent customer and asked me if I wanted one.

Of course I said yes. I've seen this principle at work back in the shareware days - you don't even mention registration for the first week or two - but I think more could be made of it on the web.
</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article144</id>
    <published>2008-07-06T10:15:11-04:00</published>
    <updated>2009-07-30T04:21:53-04:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/144-live-scrum"/>
    <title>Live Scrum</title>
    <content type="html">Scrum can manage any project, not just software projects.

But can it run my life?

I'm going to find out. I have a staggering number of projects - 'features' - in progress. Because of constant priority changes from the product owner - me - I never complete any of them. The ones that are complete aren't 'shippable'. 

This sounds like exactly the kind of problem that Scrum was created to solve.

I have only a single developer available, at around 50% committment. The scrum master will be the same person as the product owner. There's one &lt;a href='http://gwynmorfey.com/images/gwynlyn.jpg'&gt;external stakeholder&lt;/a&gt;. 

I've created a backlog and estimated it. I'll be using two-week sprints, with reviews on Sunday afternoons. 

The first release is scheduled for Sunday, 3 August.
</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article138</id>
    <published>2008-07-01T07:03:34-04:00</published>
    <updated>2008-07-01T07:07:16-04:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/138-calendaring-solved-goosync-syncs-gcal-and-your-phone"/>
    <title>Calendaring Solved: Goosync syncs gCal and your phone</title>
    <content type="html">Short version: Goosync.com solves all your problems for &#163;23/year.

The Problem
========
I have two gCals - one for work, one for home. My work calendar is shared with my colleagues; my home calendar is shared with my fiancee. I don't want my home events appearing on my work calendar. Work events should appear on both. 

I have a Nokia 6120c which must stay in sync with, and allow editting of, both calendars.

iCal is a user-interface disaster and I don't want to go near it. Spanning Sync throws unexplained errors and doesn't appear to be ready for prime-time.

The Solution
========

I set up both calendars so that they have read-write access to the other. 

I created an account at goosync.com, paid &#163;23 to make it premium, then authorised it to my work calendar (I could have used either, since they both have permission to read/write to the other).

Since my work calendar can &quot;see&quot; my personal calendar, goosync can see both. I set up goosync to sync both of them to my phone. The personal calendar uses the tag &quot;[g]&quot;; any event with that in the title goes to the personal calendar only. Otherwise, it goes to the work calendar.

Goosync sent an SMS to my phone to configure it, which worked perfectly. Now, to sync, I do Settings-&gt;Connectivity-&gt;Sync-&gt;Goosync.com-&gt;Sync on the phone. It uses the 3G network to talk to goosync; my macbook isn't involved at all.

Syncing is quite slow - about two minutes - but seems to be very reliable. Nasty edge cases like *changing* the tags on an existing event are handled.

I haven't yet set up scheduled (automatic) syncing on the phone; I don't know if it's possible.
</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article136</id>
    <published>2008-04-22T09:56:50-04:00</published>
    <updated>2008-04-22T09:56:50-04:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/136-tenlet-reverse-telnet"/>
    <title>Tenlet: reverse telnet</title>
    <content type="html">Tenlet is a backwards telnet client. It waits for a connection from the outside, and from then on it behaves like telnet. It's useful for simple protocol debugging - AGI or HTTP are obvious ones.

This code also demonstrates how to do simple listen socket programming in ruby using 'eventmachine'.

[ruby]
#!/usr/bin/env ruby
require 'rubygems'
require 'eventmachine'
require 'termios'

CONVERT_CR_LF = true

module Tenlet
  def post_init
    $con = self
    puts &quot;--- Connected at #{Time.now.to_s} --- &quot;
  end
  def receive_data data
    puts data
  end
  def connection_completed
    puts &quot;--- Disconnected at #{Time.now.to_s} --- &quot;
  end
end

def stdin_buffer( enable )
  return unless defined?( Termios )
  attr = Termios::getattr( $stdin )
  if enable
    attr.c_lflag |= Termios::ICANON | Termios::ECHO
  else
    attr.c_lflag &amp;= ~(Termios::ICANON|Termios::ECHO)
  end
  Termios::setattr( $stdin, Termios::TCSANOW, attr )
end

def check_for_keys
  if IO.select([$stdin],nil,nil,0.01)
    data = $stdin.sysread(1000)
    data.gsub!(&quot;\n&quot;,&quot;\r\n&quot;) if CONVERT_CR_LF
    if $con
      print data
      $stdout.flush
      $con.send_data data
    end 
  end
end

if ARGV[0].nil? or ARGV[1].to_i &lt; 1
  puts &quot;Usage: tenlet [host] [port] - eg tenlet 127.0.0.1 5000&quot;
  puts &quot;Tenlet is a backwards telnet client - it waits for a connection from the outside, and from then on &quot;
  puts &quot;it behaves like telnet. Written by Gwyn Morfey at New Bamboo - gwyn@new-bamboo.co.uk. &quot;
  exit
end

begin
  stdin_buffer false

  EventMachine::run {
    puts &quot;Listening on #{ARGV[0]}:#{ARGV[1]}&quot;
    EventMachine::start_server(ARGV[0],ARGV[1].to_i,Tenlet)
    EventMachine::add_periodic_timer(0.05) {check_for_keys}
  }
ensure
  stdin_buffer true
end
[/ruby]
</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article133</id>
    <published>2008-04-16T06:20:09-04:00</published>
    <updated>2008-08-04T08:04:16-04:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/133-webrat-in-merb"/>
    <title>Webrat in Merb</title>
    <content type="html">I've just forked Webrat to add Merb support. You can get it &lt;a href='http://github.com/gwynm/webrat/tree/master'&gt;here&lt;/a&gt;.

Webrat lets you write integration tests that are even closer to natural language: you can say things like:
[ruby]
visits '/auth/login'
fills_in 'username',:with=&gt;&quot;bob&quot;
fills_in 'password',:with=&gt;&quot;hunter2&quot;
clicks_button &quot;login&quot;
response.should be_successful
...
[/ruby]

To get started, &lt;a href='http://gwynmorfey.com/categories/1-on-the-job/articles/128-git-github-and-rubygems-quickstart'&gt;clone it, build the gem, install it&lt;/a&gt;, then add this to config/environments/test.rb:

[ruby]
Merb::Config.use do |c|
  #Otherwise sessions will be lost.
  c[:session_store] = 'memory' 
end  
dependencies &quot;merb_stories&quot;,&quot;webrat&quot;
[/ruby]
 
</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article132</id>
    <published>2008-04-15T09:25:44-04:00</published>
    <updated>2008-04-16T06:18:25-04:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/132-rspec-stories-in-merb"/>
    <title>RSpec Stories In Merb</title>
    <content type="html">&lt;a href='http://rspec.info/'&gt;RSpec stories&lt;/a&gt; are a way of doing integration and acceptance testing using plaintext executable tests. You can use them in Merb as well as Rails. Here's how.

&lt;a href='http://merbivore.com/get_merb.html#trunk'&gt;Install edge Merb&lt;/a&gt;; the latest gem (0.9.2) will not work. You need merb-core, merb-more, and merb-plugins.

Merb-plugins gives you the merb_stories gem, so you don't need to install that separately.

Add this line to your app's init.rb:

[ruby]dependency &quot;merb_stories&quot; if Merb.environment == &quot;test&quot;[/ruby]

(Note that merb_stories' README file is wrong about this, for now).

Now generate your story:

[ruby]merb-gen story mystory[/ruby]

Now run your story:

[ruby]rake story\[mystory\][/ruby]

Yes, you must include the square brackets, and you have to escape them.

Now fill out your story. There are some differences to Rails' versions. The best places to look for help are in the Merb code itself:
* spec/public/test/controller _matchers _spec.rb
* lib/merb-core/test/helpers
* lib/merb-core/test/matchers

To start you off, here are the steps for a simple integration test:
[ruby]
steps_for(:homepage) do
  When(&quot;I visit the root&quot;) do
    @mycontroller = get(&quot;/&quot;)
  end
  Then(&quot;I should see the home page&quot;) do
    @mycontroller.should respond_successfully
    @mycontroller.body.should contain(&quot;Hello&quot;) 
  end    
end
[/ruby]


As you write your tests, don't trust Merb absolutely. Some things are wrong, don't work, or aren't meant to work [yet]. As part of debugging, look at the Merb source, and &lt;a href='http://gweezlebur.com/2008/2/9/contributing-to-merb-part-2'&gt;fork it and fix it&lt;/a&gt; if needed. 

The problem is not necessarily in _your_ code.
</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article130</id>
    <published>2008-04-08T05:37:45-04:00</published>
    <updated>2011-12-16T13:41:41-05:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/130-five-scottish-beauties"/>
    <title>Five Scottish Beauties</title>
    <content type="html">Here are five of the many things I learnt at Scotland On Rails:

1. *&quot;Is your program written in invisible ink?&quot;*&lt;br/&gt;
The point of being terse is that terse code is often more understandable. When your terseness starts to make your code less understandable, stop. Put empty actions in your controllers, even though you don't have to.
(Thanks to David Black for 'invisible ink').

2. *Plugins are not free.*&lt;br/&gt;
Every line of code is a potential failure point; don't add more than you have to. You may be better using the plugin as a skeleton to write your own functionality. If you do use it, read the code, subscribe to the author's blog, and make sure it's maintained.
(Thanks to Jonathan Weiss)

3. *Reading code is more important than writing it.* &lt;br/&gt;
Practice makes permanent; only perfect practice makes perfect. Reading the Rails source can be particularly valuable. Every time you see syntax you haven't _used_, write something that uses it, just as an exercise.

4. *Move beyond MRI.*&lt;br/&gt;
Rubinius gives you more metaprogramming hooks; JRuby gives you a one-command application server (via Glassfish). Even if you don't use them, look at them.

5. *Write the damn tests.*&lt;br/&gt;
I find your lack of tests &lt;a href='http://www.flickr.com/photos/sebastian_bergmann/2282734669/'&gt;disturbing&lt;/a&gt;.


---
(Have your own? &lt;a href='http://railscasts.com/contest'&gt;Enter the Railscast contest&lt;/a&gt; )
</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article129</id>
    <published>2008-04-03T07:40:45-04:00</published>
    <updated>2011-10-13T19:10:02-04:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/129-facebook-developer-garage-2-april"/>
    <title>Facebook Developer Garage, 2 April</title>
    <content type="html">Many successful facebook applications offer &quot;low-pressure social interaction&quot;. I would, largely, refer to this as &quot;low-content social interaction&quot;. Either way, there's a market for it.

Constantly track your &quot;retention rate&quot;. Do this per cohort: &quot;of the people who joined 1-7 December, how many are still users?&quot;. You must improve your retention rate early, because once people have been lost they won't come back.

The users-vs-time graph often describes a &quot;shark fin&quot;. This is because growth stops when the network becomes saturated. To fix this, seed in multiple networks, increasing the maximum total reach. Also improve your retention rate.

One way to improve your retention rate is &quot;recall&quot; notifications. For example, receiving another &quot;action&quot; from an active user may bring someone back.

Pages should have visual appeal. Pulling in friends' avatars (alongside some kind of data - it doesn't really matter what) helps here.

As an alternative revenue source, tie in-game points to marketing offers (eg Lovefilm).

Check out the 'Nexus' facebook app. It shows connections between your friends, revealing groupings. It's fascinating.</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article128</id>
    <published>2008-03-31T12:07:25-04:00</published>
    <updated>2011-12-02T04:20:16-05:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/128-git-github-and-rubygems-quickstart"/>
    <title>Git, Github, and Rubygems quickstart</title>
    <content type="html">Here's how to go from a rubygem's source code in a git repository to an installed gem on your machine:

1. Make sure you have git installed. Install MacPorts, then do 'sudo port install git-core'.
2. Copy the git repository to your local machine: &lt;em&gt;git clone http://github.com/gwynm/mailtrap/tree/master&lt;/em&gt;.
3. Now you have a new directory, &quot;mailtrap&quot;. &lt;em&gt;cd mailtrap&lt;/em&gt;
4. Build the gem: &lt;em&gt;rake gem&lt;/em&gt;. This step fails if the gem you downloaded isn't using &quot;&lt;a href='http://www.nimblecode.com/articles/2007/01/10/packaging-your-first-gem-with-hoe'&gt;hoe&lt;/a&gt;&quot;, a gem which builds gems.
5. Install the gem: &lt;em&gt;sudo rake install_gem&lt;/em&gt;.</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article127</id>
    <published>2008-03-31T12:01:33-04:00</published>
    <updated>2011-02-21T00:37:47-05:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/127-mailtrap-and-mailshovel-simple-rails-mailer-testing"/>
    <title>Mailtrap and Mailshovel: Simple Rails mailer testing</title>
    <content type="html">Mailtrap is a minimal SMTP server that runs on your development machine. Actionmailer can talk to it. I've just added Mailshovel, a minimal POP3 server, that will talk to your email program. This means that you can:

1. Run 'mailtrap start'
2. Test your Rails app on localhost
3. Receive the sent emails in real email programs

It saves you from setting up dummy email accounts and real SMTP servers, and it's better than tailing the logs, because it allows you to check for formatting problems. I use it to help with complex mime/multipart + attachment messages.

This is based on the work of Matt Mower (matt.blogs.it). Coming soon: a re-released mailtrap, which will mean that you can get this whole thing with just 'gem install mailtrap'.

Until then, grab it from git, build the gem, and install it:

http://github.com/gwynm/mailtrap/tree/master

Patches welcome; it's probably best to fork the git repository, then send me a pull request. I'd like to play with this git thing more.

If &quot;grab it from git, build the gem, and install it&quot; wasn't very helpful, you can check out my &lt;a href='http://gwynmorfey.com/categories/1-on-the-job/articles/128-git-github-and-rubygems-quickstart'&gt;more detailed article on github&lt;/a&gt;.</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article91</id>
    <published>2008-01-01T11:08:00-05:00</published>
    <updated>2011-12-16T12:15:21-05:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/91-database-optimization-for-rails-apps"/>
    <title>Database Optimization for Rails Apps</title>
    <content type="html">&lt;em&gt;First published on the &lt;a href='http://blog.nbwd.co.uk'&gt;New Bamboo Blog&lt;/a&gt;&lt;/em&gt;.

&lt;p&gt;The reporting application I'm working on does a date-range select against a table with ten million rows. It took over two minutes to execute. I cut it down to around two seconds without significantly changing the application. Here's how.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use STRAIGHT_JOIN if MySQL is doing something silly&lt;/li&gt;
&lt;li&gt;MySQL Datetimes are not slow&lt;/li&gt;
&lt;li&gt; MyISAM is much, much faster than InnoDB&lt;/li&gt;
&lt;li&gt; Consider MySQL partitioning; it's not hard&lt;/li&gt;
&lt;li&gt; Watch out for background optimisation and query caching&lt;/li&gt;
&lt;li&gt; Log everything &lt;b&gt;you&lt;/b&gt; do, and &lt;b&gt;why&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;Straight Joins&lt;/h2&gt;

&lt;p&gt;The original SQL statement was 12 lines, featuring three joins, a group-by and a computed column. &lt;/p&gt;

&lt;p&gt;To start with I used EXPLAIN SELECT to look at the execution plan. MySQL had made a bad guess, and was table-scanning a four-million-row related table that could have been joined by ID instead. &lt;/p&gt;

&lt;p&gt;I added STRAIGHT_JOIN to the SQL statement and dropped the execution time to 20 seconds. &lt;/p&gt;

&lt;p&gt;&lt;span class='pullme'&gt;Use STRAIGHT_JOIN if MySQL is doing something silly.&lt;/span&gt;&lt;/p&gt;

&lt;h2&gt;Finding the Weak Point&lt;/h2&gt;

&lt;p&gt;Progress; MySQL was now table-scanning a 10 million row 'events' table to satisfy the WHERE clause, then joining the other three tables by ID, grouping and summing. I cut the query down progressively to a simple &lt;b&gt;&quot;SELECT count(*) FROM events WHERE starttime &lt; '2006-12-30 08:30:00' AND endtime &gt; '2006-12-30 08:15:00&quot;&lt;/b&gt; and found that performance didn't significantly change. &lt;/p&gt;

&lt;p&gt;This isolated the slow part of the query. My original plan had been to denormalize the table structure, but there was no point.&lt;/p&gt;

&lt;p&gt;At this point I built a test database of two million rows on my local machine with only the one table in question, and only the three columns that were needed.&lt;/p&gt;

&lt;h2&gt;Indexes&lt;/h2&gt;

&lt;p&gt;I added indexes on starttime, endtime, and (starttime,endtime), but the MySQL optimiser chose not to use them. When I forced it to use them, using FORCE INDEX, performance deteriorated (!). OPTIMIZE TABLE made no difference.&lt;/p&gt;

&lt;h2&gt;Dead End: Int Conversion&lt;/h2&gt;

&lt;p&gt;The application needs only 15-minute resolution on the date times, so I created start_int and finish_int columns and filled them:&lt;/p&gt;


&lt;pre&gt;update events set start_int = floor(unix_timestamp(starttime)/900) 
update events set finish_int = floor(unix_timestamp(endtime)/900)&lt;/pre&gt;

&lt;p&gt;Then I created indexes on the columns and tested again. The plan here was that, by reducing the resolution, there'd be fewer unique values and the indexes would be more effective.&lt;/p&gt;

&lt;p&gt;It didn't work. Tablescanning against datetimes was &lt;b&gt;faster&lt;/b&gt; than indexed lookup against ints.&lt;/p&gt;

&lt;p&gt;&lt;span class='pullme'&gt;MySQL DateTimes are not slow.&lt;/span&gt;&lt;/p&gt;

&lt;h2&gt;Switching To MyISAM&lt;/h2&gt;

&lt;p&gt;I'd noticed that my test database was performing much faster than the production database, even taking into account the smaller size. Running SHOW CREATE TABLE against the two revealed that I was using MyISAM, but the production database had InnoDB. I converted my local database to InnoDB and took a speed hit on the order of 6x.&lt;/p&gt;

&lt;p&gt;&lt;span class='pullme'&gt;MyISAM is &lt;b&gt;much&lt;/b&gt; faster than InnoDB.&lt;/span&gt; (but &lt;a href='#notes'&gt;see notes&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Note that the conversion process is hideously slow; it's faster to create a new table and load it up with your test data yourself. If you do this, use extended inserts; they're about 25x faster than singular inserts.&lt;/p&gt;

&lt;p&gt;You give up some goodies, including transactions, by making the switch.&lt;/p&gt;

&lt;h2&gt;Partitioning&lt;/h2&gt;

&lt;p&gt;The application never queries over a time range of greater than 15 minutes, and always aligns its requests on 15-minute boundaries. If we had to tablescan, I could use this to at least make sure we didn't have to tablescan all ten million rows.&lt;/p&gt;

&lt;p&gt;I installed MySQL 5.1, which is the first release that supports partitioning, and created a new table using MyISAM, with a partition per 3 months, copying data from the old table in one hit:&lt;/p&gt;

&lt;pre&gt;
CREATE TABLE `events2` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `starttime` datetime DEFAULT NULL,
 `endtime` datetime DEFAULT NULL,
 KEY `id` (`id`),
 KEY `starttime` (`starttime`),
 KEY `endtime` (`endtime`),
 KEY `starttime_and_endtime` (`starttime`,`endtime`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8
PARTITION BY RANGE (TO_DAYS(starttime)) (
	PARTITION p0 VALUES LESS THAN (732312),
	PARTITION p1 VALUES LESS THAN (732677),
	PARTITION p2 VALUES LESS THAN (732767),
...
	PARTITION p17 VALUES LESS THAN MAXVALUE	
) SELECT * FROM events;
&lt;/pre&gt;

&lt;p&gt;I'm using TO_DAYS because partitions by range can only be described in terms of a single integer (not a datetime).&lt;/p&gt;

&lt;p&gt;Now I could adjust my query to address a single partition only:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT count(*) FROM events WHERE starttime &lt; '2006-12-30 08:30:00' 
AND endtime &gt; '2006-12-30 08:15:00&quot; AND starttime &gt; '2006-12-01 00:00:00'.&lt;/CODE&gt;&lt;/pre&gt;

&lt;p&gt;I used EXPLAIN PARTITION SELECT ... to confirm that MySQL was accessing only a single partition.&lt;/p&gt;

&lt;p&gt;The benefit of this particular partitioning strategy is that it's transparent to the application. More advanced approaches involve splitting records across database _servers_, rather than simply changing the 'create table' statement.&lt;/p&gt;

&lt;p&gt;&lt;span class='pullme'&gt;Consider MySQL partitioning; it's not hard.&lt;/span&gt;&lt;/p&gt;

&lt;h2&gt;Results&lt;/h2&gt;

&lt;p&gt;The full query originally took around 152 seconds to run. It's now down to around two seconds, or a 75x speed improvement. This is running essentially the same query, and returning data in the same format; the only change necessary at the application level was to reword the SQL slightly.&lt;/p.

&lt;h2&gt;General Tactics&lt;/h2&gt;
&lt;p&gt;Those were the specific victories. Here are the overall tactics which are more generally applicable:&lt;/p&gt;

&lt;p&gt;I found it helpful to think not about what my application needed to know, but what it &lt;em&gt;didn't&lt;/em&gt; need to know. For example, rules like &quot;we never run a query that does not align on a 15-minute boundary&quot; can simplify the problem. &lt;/p&gt;

&lt;p&gt;Nothing substitutes for benchmarks. Things that ought to be fast are sometimes slow. Things that ought to be slow are sometimes fast.&lt;/p&gt; 

&lt;p&gt;But don't trust the benchmarks. Sometimes I'd return to my machine to find the *same query* running in less than a second instead of over a minute. I was using SQL_NO_CACHE, so I think background optimisation is responsible here. Since the database was very large, it's possible that OSX's virtual memory management was also interfering.&lt;/p&gt;

&lt;p&gt;&lt;span class='pullme'&gt;Watch out for background optimisation and query caching.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;The only reason I caught this was that I kept notes of every single query I ran, which machine I ran it on, the time it took to execute, and anything interesting about the results. It's a simple routine to create or edit the query in Textmate, then paste it into MySQL, instead of working in the MySQL window directly. &lt;/p&gt;

&lt;p&gt;&lt;span class='pullme'&gt;Log everything &lt;b&gt;you&lt;/b&gt; do, and &lt;b&gt;why&lt;/b&gt;.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;I also kept notes about theories and ideas, inline with the sql, and later marked them with PROVED / DISPROVED and the evidence so that I could be sure I'd covered all the bases.&lt;/p&gt;

&lt;h2&gt;Conclusion and Notes&lt;/h2&gt;
&lt;a name='notes'&gt;&lt;/a&gt;

&lt;p&gt;Tweaking the database lets you make huge gains in performance without modifying your complex application. The assertions above are only guidelines based on the evidence I collected on &lt;i&gt;one&lt;/i&gt; project; ultimately it's your own benchmarks that will matter. Simplify the problem, record your investigations, and benchmark the results.&lt;/p&gt;



&lt;!-- From http://home.tampabay.rr.com/bmerkey/examples/css-pullquotes.html --&gt;
&lt;style type=&quot;text/css&quot;&gt;
blockquote {
	width: 24%;
	float: right;
	font-size: 125%;
	font-style: italic;
	font-family: &quot;Palatino Linotype&quot;, Georgia, Caslon, serif;
	line-height: 140%;
	margin: 10px -20px 10px 10px;
	_margin-right: -35px; /* IE box model hack  */
	background: white url(http://dezyne.net/closequote.gif) no-repeat bottom right;
	padding: 2px 15px 2px 2px;
	border: dotted 3px #bcc;
	}

blockquote:first-letter {
	background: url(http://dezyne.net/openquote.gif) no-repeat left top;
	padding: 10px 2px 10px 25px;
	}
	
/* Print Styles*/
@media print {
blockquote {margin-right: -20pt;  border-width: 0 0 2pt 2pt}
blockquote:first-letter {padding: 0px;	font-size: 140%;}
}
&lt;/style&gt;

&lt;script type=&quot;text/javascript&quot;&gt;
function makePullquote()
{
	var spans = document.getElementsByTagName('span');
	for (i = 0;  i &lt; spans.length;  i++) 
	{	
		if(spans[i].className == &quot;pullme&quot;)  
		{
			var pullquote = document.createElement('blockquote');
			pullquote.innerHTML = spans[i].innerHTML;  
			spans[i].parentNode.insertBefore(pullquote,null);
		}	
	}
}
window.onload = makePullquote;
&lt;/script&gt;</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
  <entry>
    <id>tag:gwynmorfey.com:Article90</id>
    <published>2008-01-01T11:03:00-05:00</published>
    <updated>2011-12-16T01:18:03-05:00</updated>
    <link rel="alternate" type="text/html" href="http://gwynmorfey.com/categories/1-on-the-job/articles/90-getting-things-done-at-new-bamboo"/>
    <title>Getting Things Done at New Bamboo</title>
    <content type="html">&lt;em&gt;First published on the &lt;a href='http://blog.nbwd.co.uk/'&gt;New Bamboo Blog&lt;/a&gt;&lt;/em&gt;.

&lt;p&gt;Getting Things Done (GTD) is a way of choosing what to do next. It is more efficient and less stressful than other systems.&lt;/p&gt;

&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;Last year my web development company, consisting of myself and an ever-increasing number of subcontractors, began expanding very quickly. While I stayed mostly on top of the situation, the stress was overwhelming.&lt;/p&gt;

&lt;p&gt;I made the switch to GTD during Christmas 06.&lt;/p&gt;

&lt;p&gt;Now that I'm working full-time as a developer, instead of as a sole trader, my task lists are much shorter. I've therefore made some simplifications. GTD can be used for large-scale project management, but that isn't covered here.&lt;/p&gt;


&lt;h2&gt;Benefits&lt;/h2&gt;

&lt;p&gt;GTD is both a workflow and an attitude. The core concept is that &lt;b&gt;nothing&lt;/b&gt; lives in your brain, except for what you're currently working on. You must be merciless about this.&lt;/p&gt;

&lt;p&gt;The real benefit, and the driver for me, is this: You get a stronger personal focus, as well as a stronger work focus. &lt;/p&gt;

&lt;p&gt;When I'm at work, I'm &lt;i&gt;really&lt;/i&gt; at work. When I'm at home, I'm &lt;i&gt;really&lt;/i&gt; at home. This means more effective recovery, and more energy at work. &lt;/p&gt;

&lt;p&gt;David Allen's book &lt;a href='http://davidco.com/store/product.php?productid=16182'&gt;Getting Things Done&lt;/a&gt;, which is where I started with GTD, says this:&lt;/p&gt;

&lt;p&gt;&quot;In order to hang out with friends or take a long aimless walk and truly have nothing on your mind, you need to know what all your actionable items are, where they are, and that they can wait. And you need to be able to do that in a few seconds, not days.&quot;&lt;/p&gt;


&lt;h2&gt;Selecting Tasks&lt;/h2&gt;
&lt;p&gt;I have &lt;i&gt;everything&lt;/i&gt; that I &lt;i&gt;ever&lt;/i&gt; need to do in &lt;i&gt;one&lt;/i&gt; place. The &lt;i&gt;everything&lt;/i&gt; bit is important.&lt;/p&gt;

&lt;p&gt;These items are described in the form of &quot;next actions&quot;, or &quot;the very next thing I can do to move this forward&quot;. They are very specific. For example, I don't have &quot;deal with tax return&quot;; I have &quot;phone ATO re: missing BAS for FY06-07, 13 28 66, 9-6pm Adelaide time&quot;.&lt;/p&gt;

&lt;p&gt;Because of this precision, I can allocate tasks to &quot;Contexts&quot;, which are sets of prerequisites. For example, I can't perform the &quot;tidy room&quot; task unless I'm at home. I can't call the ATO unless it's Really Early (because they're in Australia).&lt;/p&gt;

&lt;p&gt;This means that tasks are invisible to me unless I can perform them &lt;i&gt;right now&lt;/i&gt;.&lt;/p&gt;

&lt;p&gt;The workflow is:&lt;/p&gt;
&lt;ol&gt;
 &lt;li&gt;Complete current task.&lt;/li&gt;
 &lt;li&gt;Select context.&lt;/li&gt;
 &lt;li&gt;Select a new task from tasks in this context. There's no advance scheduling and prioritising; in the midst of constantly shifting requirements, I trust myself to make the selection just-in-time.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Adding Tasks&lt;/h2&gt;
&lt;p&gt;That's how you work with tasks in the system. What about when a new task appears?&lt;/p&gt;

&lt;p&gt;First, you stash it in one of your inboxes. You have the smallest number of these that you can get away with. &lt;/p&gt;

&lt;p&gt;Your brain is absolutely not allowed to be an inbox. If you're filing things in your brain for even a second, you need another inbox.&lt;/p&gt;

&lt;p&gt;I use two: my email account, and a notepad application on my phone, which is always with me.&lt;/p&gt;

&lt;p&gt;Stashing an item in an inbox is very quick, and doesn't interrupt what you're doing. Whenever you have time - eg whenever you finish a task - &quot;process&quot; your inbox. For each item in it, you have four options:&lt;/p&gt;

&lt;h3&gt;Do it&lt;/h3&gt;
&lt;p&gt;If it's going to take less than two minutes, do it. Do it right now. Then forget about it.&lt;/p&gt;

&lt;p&gt;The &quot;two minutes&quot; is a varying threshold. Anywhere between two and five works for me. I'll initiate system backups, answer quick emails, fill in my timesheets, and occasionally even make phone calls. If I spend ten minutes and kill off five two-minute tasks, I feel _good_.&lt;/p&gt;

&lt;p&gt;I've taken this a step further, and will even break inbox queuing to do this: if a new task appears I'll sometimes stop what I'm doing to kill it off.&lt;/p&gt;

&lt;h3&gt;Delete it&lt;/h3&gt;
&lt;p&gt;GTD requires you to be realistic. Some things you'd like to do, you might have long-term plans to do, but realistically, you're never going to do them.&lt;/p&gt;

&lt;p&gt;Don't feel bad about them. Don't lie to yourself, or to other people. Instead, say &quot;I'm never going to do this&quot;, delete it from your inbox, and forget that it ever existed.&lt;/p&gt;

&lt;p&gt;You can have a &quot;Someday&quot; context for items that will live in your brain if you don't put them somewhere else. I review mine periodically to weed out things that just aren't ever gonna happen.&lt;/p&gt;

&lt;h3&gt;Delegate it&lt;/h3&gt;
&lt;p&gt;If you give the job to someone else, the chances are you're still responsible for it. You need to add it to a &quot;Waiting&quot; context, with a description like &quot;Waiting for ATO to send new form&quot;. Periodically scan this context, and see if any of these things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;has happened, so you can remove it, or&lt;/li&gt;
&lt;li&gt;hasn't happened, and you need to follow it up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you don't put it in the &quot;Waiting&quot; context, it'll still live in your head. You don't want that.&lt;/p&gt;

&lt;h3&gt;Defer it&lt;/h3&gt;
&lt;p&gt;This is your option of last resort. It means &quot;I can't do this now, but I do have to do it, and I can't give it to anyone else&quot;. Here's where you work out the very next action that you need to take in order to move this forward. &lt;b&gt;Do&lt;/b&gt; take the time to calculate the next action; often, it's very small (&quot;sort out my tax&quot; is a huge job, but &quot;email the ATO asking for a form 12&quot; is not), and you will be able to do it now, after all. &lt;/p&gt;

&lt;p&gt;Once you've got a next action, choose a context for it. Store it under that context in whatever system you're using.&lt;/p&gt;

&lt;p&gt;You are now committed to doing this task. &lt;/p&gt;

&lt;h2&gt;Equipment&lt;/h2&gt;
&lt;p&gt;The Book tells you to buy a lot of things. I didn't have to. &lt;/p&gt;

&lt;p&gt;You must, &lt;i&gt;must&lt;/i&gt; have a small notebook that you carry with you all the time, particularly at first when you are dumping the contents of your brain. I use the built-in Notes application on my Symbian phone.&lt;/p&gt;

&lt;p&gt;Until recently, I used &lt;a href='http://www.kaboomerang.com'&gt;Actiontastic&lt;/a&gt;, relying on the fact that I always have my laptop with me. I'm now in the process of migrating to the Notes application on my phone, because I'm using the laptop much less at home. &lt;/p&gt;

&lt;p&gt;I didn't get hung up on syncing. I always have my phone. Why would I need to sync it to anything else?&lt;/p&gt;

&lt;p&gt;I have all my paperwork in a single box - yes, I use shoebox filing. This works because everything I care about is also in iPhoto. I use a digital camera rather than a scanner, because it's orders of magnitude faster and the quality is &quot;good enough&quot;. iPhoto is crap for this, but it's &quot;good enough&quot; - I use the description field to insert keywords.&lt;/p&gt;

&lt;p&gt;I have a single &quot;reference&quot; folder in my email account. I find that gmail's search is good enough that I don't even need to use labels. Anything that I'm not &lt;i&gt;sure&lt;/i&gt; I'll need, I delete. &lt;/p&gt;

&lt;p&gt;The Book covers some other areas, such as the &quot;hard landscape&quot; - tasks that must occur at certain times - and &quot;tickler files&quot; (tasks you'd like to forget about for a while, but do need to revisit). I use my phone's calendar for both. I sync with my partner, friends, and co-workers using the low-tech but highly-effective &quot;what have we got on Tuesday?&quot; method.&lt;/p&gt;


&lt;p&gt;These are the tools you &lt;b&gt;do&lt;/b&gt; need:&lt;/p&gt;

&lt;p&gt;&lt;a href='http://davidco.com/store/product.php?productid=16182'&gt;Getting Things Done&lt;/a&gt;, by David Allen. It's an inspiring read, and packed full of details that are worth knowing about even if you don't use them. GTD is a style, not a religion. &lt;/p&gt;

&lt;p&gt;&lt;a href='http://www.kaboomerang.com'&gt;Actiontastic&lt;/a&gt;, if you're going to keep your GTD lists on your Mac. The Omni Group is working on something called OmniFocus, but it's still in early beta.&lt;/p&gt;

&lt;p&gt;As far as I know there are no GTD tools, or even suitable Todo-list tools, for Symbian phones. If there's enough demand, I'll write one - let me know ('gwyn' at this domain).&lt;/p&gt;


&lt;h2&gt;Making The Switch&lt;/h2&gt;
&lt;p&gt;One of the guys here at New Bamboo had previously tried to switch to GTD, but bailed out when The Book told him to take two days off to sort out his life. You don't &lt;i&gt;have&lt;/i&gt; to do that.&lt;/p&gt;

&lt;p&gt;But you should.&lt;/p&gt;

&lt;p&gt;I read The Book and made the switch in the Christmas-New Year break last year. The timing worked well for me, and allowed me to go through &lt;i&gt;everything&lt;/i&gt;, getting the full clear-head benefit. Yes, I had tasks like &quot;charge my phone&quot; and &quot;tidy my desk&quot;. It's like tidying up; once you start, you go a little crazy. &lt;/p&gt;

&lt;p&gt;Carrying a notebook is critical. I found that once I started the dump, I couldn't stop it. &lt;/p&gt;

&lt;p&gt;Don't get sidetracked into software. Half of New Bamboo reacted to my initial presentation by planning to write software to track their tasks. This defeats the purpose, which is to &lt;i&gt;increase&lt;/i&gt; productivity. This isn't about software, or three-colour stationery and ring binders. This is about habits.&lt;/p&gt;

&lt;h2&gt;Summary&lt;/h2&gt;

&lt;p&gt;To summarize:
&lt;ul&gt;
&lt;li&gt;Get everything in one place.&lt;/li&gt;
&lt;li&gt;Think of your tasks in terms of the Next Actions that are needed to complete them.&lt;/li&gt;
&lt;li&gt;Classify your tasks into Contexts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GTD takes some effort to maintain, but you'll feel it when you start to slip: the stress will come back.&lt;/p&gt;


&lt;h2&gt;Postscript: GTD at New Bamboo&lt;/h2&gt;

&lt;p&gt;The material above is drawn from an internal presentation (&quot;GTD: Task Management For Ninjas&quot;) that I gave for New Bamboo's Talk At Two program. A month later, I did a quick survey to see if it stuck. While there's only one new full-blown implementation, nearly everybody found something useful to incorporate into their existing systems. &lt;/p&gt;

&lt;p&gt;Three people didn't attempt GTD. Two of those weren't here for the presentation.&lt;/p&gt;

&lt;p&gt;Damien tried it, but found that my stripped-down version didn't track larger projects. He's using the &quot;errands&quot; context, though, with a separate list that he looks at on his way out the door.&lt;/p&gt;

&lt;p&gt;Pablo's using a series of paper lists, and is enjoying lower stress and the sense of achievement that comes from crossing things out. He's also been inspired to collect old tasks and to evaluate them realistically.&lt;/p&gt;

&lt;p&gt;Ismael's using GTD's aggressive single-task focus, but finds he can store his lists in his brain without stressing himself out.&lt;/p&gt;

&lt;p&gt;Max is running a full implementation, using Actiontastic, but he's still using his brain as an inbox for when he's not near a computer.&lt;/p&gt;

&lt;p&gt;Matt started using GTD, then found he no longer needed it.&lt;/p&gt;

&lt;p&gt;He stopped because he Got Everything Done.&lt;/p&gt;

</content>
    <author>
      <name>Gwyn Morfey</name>
    </author>
  </entry>
</feed>
