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

<channel>
	<title>Erik A. Hanson</title>
	<atom:link href="http://www.eahanson.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.eahanson.com</link>
	<description>My weblog</description>
	<lastBuildDate>Thu, 06 Jan 2011 02:27:45 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Palbum: My New iPad App</title>
		<link>http://www.eahanson.com/2011/01/05/palbum-my-new-ipad-app/</link>
		<comments>http://www.eahanson.com/2011/01/05/palbum-my-new-ipad-app/#comments</comments>
		<pubDate>Thu, 06 Jan 2011 02:27:45 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[My Software]]></category>
		<category><![CDATA[Objective C]]></category>
		<category><![CDATA[iOS]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=147</guid>
		<description><![CDATA[I wrote an iPad app called Palbum. It&#8217;s a gallery of the photos your Facebook friends have shared with you. It&#8217;s very simple now, but I hope to add more fun features in the future. Check out the Palbum web site, or download it for free right now from the app store.]]></description>
			<content:encoded><![CDATA[<p>I wrote an iPad app called Palbum. It&#8217;s a gallery of the photos your Facebook friends have shared with you. It&#8217;s very simple now, but I hope to add more fun features in the future.</p>
<p>Check out the <a href="http://www.eahanson.com/palbum/">Palbum web site</a>, or download it for free right now from the <a href="http://itunes.apple.com/us/app/palbum/id412801073?mt=8&#038;ls=1">app store</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2011/01/05/palbum-my-new-ipad-app/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Expectacular</title>
		<link>http://www.eahanson.com/2010/11/27/expectacular/</link>
		<comments>http://www.eahanson.com/2010/11/27/expectacular/#comments</comments>
		<pubDate>Sat, 27 Nov 2010 22:59:49 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[My Software]]></category>
		<category><![CDATA[Objective C]]></category>
		<category><![CDATA[Unit Testing]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=134</guid>
		<description><![CDATA[I started working on a matcher library for Cocoa/Objective-C called Expectacular. My main goals were to have a syntax that I prefer over OCHamcrest&#8216;s syntax, and to have something that XCode&#8217;s code sense feature will understand so it can autocomplete my code. Here are some examples: [Expect int:[myArray count] toEqual:4]; [Expect object:user.name toEqual:@"Fred"]; [Expect array:validTokens [...]]]></description>
			<content:encoded><![CDATA[<p>I started working on a matcher library for Cocoa/Objective-C called Expectacular. My main goals were to have a syntax that I prefer over <a href="https://github.com/360/OCHamcrest">OCHamcrest</a>&#8216;s syntax, and to have something that XCode&#8217;s code sense feature will understand so it can autocomplete my code.</p>
<p>Here are some examples:</p>
<pre>
[Expect int:[myArray count] toEqual:4];

[Expect object:user.name toEqual:@"Fred"];

[Expect array:validTokens toContainObject:user.token];

[Expect block:^{
    [fluxCapacitor rethread];
} toThrowExceptionWithReason:@"Uncharged capacitors cannot be rethreaded."];

[Expect blockToNotThrowException:^{
    [fluxCapacitor charge];
    [fluxCapacitor rethread];
}];
</pre>
<p>and here are some screenshots of the code completion in action:</p>
<p><a href="http://www.eahanson.com/weblog/wp-content/uploads/2010/11/Screen-shot-2010-11-27-at-2.44.33-PM.png"><img src="http://www.eahanson.com/weblog/wp-content/uploads/2010/11/Screen-shot-2010-11-27-at-2.44.33-PM.png" alt="" title="Completion" width="456" height="24" class="aligncenter size-full wp-image-138" /></a></p>
<p><a href="http://www.eahanson.com/weblog/wp-content/uploads/2010/11/bbb.png"><img src="http://www.eahanson.com/weblog/wp-content/uploads/2010/11/bbb.png" alt="" title="completion2" width="464" height="118" class="aligncenter size-full wp-image-140" /></a></p>
<p>Check out the <a href="https://github.com/eahanson/Expectacular">GitHub repo</a> or the <a href="https://www.pivotaltracker.com/projects/152569">public Pivotal Tracker project</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2010/11/27/expectacular/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Connecting Airport Extreme and Airport Express using WPS</title>
		<link>http://www.eahanson.com/2010/09/02/connecting-airport-extreme-and-airport-express-using-wps/</link>
		<comments>http://www.eahanson.com/2010/09/02/connecting-airport-extreme-and-airport-express-using-wps/#comments</comments>
		<pubDate>Fri, 03 Sep 2010 04:26:00 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Apple]]></category>
		<category><![CDATA[wifi]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=130</guid>
		<description><![CDATA[I have an older Airport Express (802.11g, not the new 802.11n one), and a new Airport Extreme (802.11n). I spent a long time trying to figure out how to replicate my previous setup of having the Express extend the wireless network provided by the Extreme using WPS, and I thought I&#8217;d post my solution here. [...]]]></description>
			<content:encoded><![CDATA[<p>I have an older Airport Express (802.11g, not the new 802.11n one), and a new Airport Extreme (802.11n). I spent a long time trying to figure out how to replicate my previous setup of having the Express extend the wireless network provided by the Extreme using WPS, and I thought I&#8217;d post my solution here.</p>
<p>I <a href="http://discussions.apple.com/message.jspa?messageID=11169439#11169439">found the solution</a> after a bunch of Googling: it turns out that the WPS option is <strong>hidden</strong> in the latest Airport Extreme configuration software. <strong>To see it, hold down the option key while selecting the &#8220;Wireless Mode&#8221;</strong> (in the &#8220;Wireless&#8221; section of the manual setup). Once you choose that, you can set up WDS pretty easily.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2010/09/02/connecting-airport-extreme-and-airport-express-using-wps/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SeleniumQuery</title>
		<link>http://www.eahanson.com/2010/05/23/seleniumquery/</link>
		<comments>http://www.eahanson.com/2010/05/23/seleniumquery/#comments</comments>
		<pubDate>Mon, 24 May 2010 04:49:46 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Selenium]]></category>
		<category><![CDATA[jQuery]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=125</guid>
		<description><![CDATA[I wrote a tiny library to make Selenium testing in Ruby a little easier by helping you easily build a jQuery string that can be executed in the browser during a test. This: # this is ruby jquery = SeleniumQuery.new(selenium) id = jquery.find("td:nth(5).user:visible").closest("tr").attr!("id") executes this in the browser: // this is Javascript selenium .browserbot .getCurrentWindow() [...]]]></description>
			<content:encoded><![CDATA[<p>I wrote a tiny library to make Selenium testing in Ruby a little easier by helping you easily build a jQuery string that can be executed in the browser during a test. </p>
<p>This:</p>
<pre>
# this is ruby
jquery = SeleniumQuery.new(selenium)
id = jquery.find("td:nth(5).user:visible").closest("tr").attr!("id")
</pre>
<p>executes this in the browser:</p>
<pre>
// this is Javascript
selenium
  .browserbot
  .getCurrentWindow()
  .jQuery(selenium.browserbot.getCurrentWindow().document)
  .find("td:nth(5).user:visible")
  .closest("tr")
  .attr("id");
</pre>
<p>Check it out here: <a href="http://github.com/eahanson/seleniumquery">http://github.com/eahanson/seleniumquery</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2010/05/23/seleniumquery/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A Ruby Client for Angular</title>
		<link>http://www.eahanson.com/2009/11/19/a-ruby-client-for-angular/</link>
		<comments>http://www.eahanson.com/2009/11/19/a-ruby-client-for-angular/#comments</comments>
		<pubDate>Fri, 20 Nov 2009 04:29:08 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[My Software]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=120</guid>
		<description><![CDATA[Here&#8217;s a little Ruby client for storing data in Angular, a system for creating simple database-backed web apps extremely easily. #!/usr/bin/ruby require 'rubygems' require 'httpclient' require 'json' class Angular def initialize(username, password, library) @http = HTTPClient.new @library = library post("/login", :email => username, :password => password) end def libraries get("/data") end def store(database, document, data) [...]]]></description>
			<content:encoded><![CDATA[<p>Here&#8217;s a little Ruby client for storing data in <a href="http://www.getangular.com">Angular</a>, a system for creating simple database-backed web apps extremely easily. </p>
<pre>
#!/usr/bin/ruby

require 'rubygems'
require 'httpclient'
require 'json'

class Angular
  def initialize(username, password, library)
    @http = HTTPClient.new
    @library = library
    post("/login", :email => username, :password => password)
  end

  def libraries
    get("/data")
  end

  def store(database, document, data)
    post("/data/#{database}/#{document}", data.to_json)
  end

  private

  def post(path, params)
    request(:post, path, params)
  end

  def get(path, params=nil)
    request(:get, path, params)
  end

  def request(method, path, params)
    uri = (path.index("http") == 0) ? path : ("http://#{@library}.getangular.com" + path)
    response = @http.send(method, uri, params)
    if response.code == 302
      get(response.header["Location"][0])
    elsif response.code == 200
      parsed = parse(response)
      status_code = parsed['$status_code']
      raise "#{uri.to_s} responded with #{status_code}" if status_code &#038;&#038; status_code != 200
      parsed
    else
      raise "#{uri.to_s} responded with #{response.code}"
    end
  end

  def parse(response)
    begin
      JSON.parse(response.body.content)
    rescue
      {}
    end
  end
end
</pre>
<p>Also <a href="http://github.com/eahanson/angular-ruby">available on GitHub</a> for your forking pleasure.</p>
<p>Maybe in the future I&#8217;ll write up a blog post about how I&#8217;m using Angular.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2009/11/19/a-ruby-client-for-angular/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Solutions To Slow Test Suites</title>
		<link>http://www.eahanson.com/2009/06/05/solutions-to-slow-test-suites/</link>
		<comments>http://www.eahanson.com/2009/06/05/solutions-to-slow-test-suites/#comments</comments>
		<pubDate>Fri, 05 Jun 2009 18:23:54 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Selenium]]></category>
		<category><![CDATA[Unit Testing]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=79</guid>
		<description><![CDATA[As a proponent of test driven development, I&#8217;m naturally a big fan of writing automated tests. Often, clients who are new to writing automated tests start out by writing big integration tests (with tools like Selenium). This is an easy way to start testing an app that wasn&#8217;t written with testability in mind. Unfortunately, these [...]]]></description>
			<content:encoded><![CDATA[<p>As a proponent of <a href="http://en.wikipedia.org/wiki/Test-driven_development">test driven development</a>, I&#8217;m naturally a big fan of writing automated tests. </p>
<p>Often, clients who are new to writing automated tests start out by writing big integration tests (with tools like <a href="http://seleniumhq.org/">Selenium</a>). This is an easy way to start testing an app that wasn&#8217;t written with testability in mind.</p>
<p>Unfortunately, these sorts of tests tend to be slow and brittle, and when they fail they don&#8217;t always point directly to a broken line of code in the way that a unit test would.</p>
<p>Over the years, I&#8217;ve come across a number of ways to help clients speed up and otherwise improve their test suites and I thought I&#8217;d enumerate them here.</p>
<h3>Better Feedback</h3>
<h4>Continuous Builds</h4>
<p>Continuous builds are vital to any tested application. I&#8217;ve used a few of them, but the simplest one I&#8217;ve used is <a href="http://cruisecontrolrb.thoughtworks.com/">CruiseControl.rb</a>, a version of CruiseControl written in Ruby. It can run tests written in any language, and it is about 1000 times easier to deal with than the legacy version of CruiseControl.</p>
<p>I&#8217;ve also had a few clients who wrote their own continuous build systems. Two of them were simple (in a good way), and one seemed horribly complex. If you&#8217;re going to write your own, keep it simple!</p>
<h4>Visible Continuous Build Monitor</h4>
<p>If you&#8217;re lucky enough to have all your developers in a single room, you should consider displaying the continuous build results somewhere. Some clients have set up some hardware to show the status: red/green lava lamps, &#8220;pass&#8221;/&#8221;fail&#8221; neon signs, and Ambient Orbs that change color from red to green. A simpler solution is to display the status on a big TV screen or monitor. An old iMac seems like a really good way to display the status. There are now digital picture frames that can be updated over wifi &#8212; I wonder if those might be a cheap solution.</p>
<h4>Fast-Failing Continuous Build</h4>
<p>I helped one client who had written their own continuous build system to modify it so that the build turned red as soon as any test failed. They were usually able to check in a fix for the failing test before the suite finished running so that the fix was included in the very next build.</p>
<h3>Encourage Unit Tests</h3>
<p>While I believe that integration tests are important, the majority of a project&#8217;s tests should be unit tests. Unit tests are faster, less brittle, and when they fail, they often point directly at the code that&#8217;s not working.</p>
<h4>When A Bug Is Found, Write a Unit Test</h4>
<p>One way to build up a suite of unit tests for a program that doesn&#8217;t have very many is to commit to writing a unit test to expose any bug that&#8217;s found in the system. Sometimes in a non-test-driven codebase, it&#8217;s hard to write a really good unit test for a bug, but just adding coverage for part of the issue is a good step.</p>
<h4>When An Integration Test Fails, Write A Unit Test</h4>
<p>When an integration test fails, it&#8217;s often hard to tell exactly what went wrong. This is a good opportunity to write a unit test so that the next time something similar fails, you&#8217;ll have a very narrow test that finds it.</p>
<h4>Ashcroft</h4>
<p>There is a tool for Java called <a href="http://docs.codehaus.org/display/ASH/Home">Ashcroft</a> which will cause your test to fail if it does things that a strict unit test shouldn&#8217;t: start a thread, access the filesystem or network, etc. </p>
<p>I&#8217;ve helped clients separate their test suite into a &#8220;slow test&#8221; suite and a fast Ashcroft-enforced unit test suite. On the projects that have had this, developers would often try first to make an Ashcroft-compliant test and only settle for a slower integration test if they ran into trouble. (Sometimes on a project that wasn&#8217;t completely test driven, certain unit tests are just going to take too long to write.)</p>
<p>The Ashcroft suites usually run thousands of tests in a few seconds, whereas the slow suites usually run hundreds of tests in an hour.</p>
<h4>Fake Services</h4>
<p>You can often speed up tests by replacing some services with fake versions. For example, you might use an in-memory database such as <a href="http://hsqldb.org/">HSQL</a> or <a href="http://mayfly.sourceforge.net/">Mayfly</a>. Or you might fake out a custom service with a faster one. For example, I have an app that stores files on Amazon S3 by calling a simple storage class I wrote. The tests use a different storage class that implements the same interface but stores the files in memory.</p>
<h3>Remove Unnecessary Waiting</h3>
<p>I&#8217;ve run across many Selenium tests that perform some action, wait a number of seconds, and then perform another action. All that waiting really adds up and leads to very long test suites.</p>
<p>An easy solution is to use Selenium&#8217;s <a href="http://wiki.openqa.org/display/SEL/waitForCondition">waitForCondition</a> command. For example, if your test clicks on a button that fires an asynchronous event and then shows the result on the page, instead of clicking, waiting 5 seconds and then asserting that the result is visible, just click wait for the result to become visible.</p>
<p>A similar approach is to create a waitForCondition-type method in your own code which calls a function repeatedly until it is true or until a timeout occurs.</p>
<h3>Distribute Tests</h3>
<p>One way to speed up a test suite is to throw hardware at it.</p>
<h4>Multiple Builds</h4>
<p>The simplest way to throw hardware at the problem is to split your continuous build into multiple suites, each of which run on its own server. You could write a script to check the results of each continuous build and tell you if the whole project is red or green, or just look at all the results and consider the entire project red if any of the suites are red. (Hopefully you have some sort of multiple-project continuous build dashboard like <a href="http://ci.pivotallabs.com/">this one</a>.)</p>
<h4>MapReduce</h4>
<p>One of my former clients distributes their tests using MapReduce. I don&#8217;t think they use <a href="http://aws.amazon.com/elasticmapreduce/">Amazon&#8217;s MapReduce</a> service, but that does seem like an easy way to throw a bunch of computers at your problem.</p>
<h4>Selenium Farm</h4>
<p>One reason that Selenium tests are so slow is that they run tests in a browser. Often, a team will want to test against different browsers at once, resulting in an even longer test run. I&#8217;ve helped clients speed up their test suites by putting together a farm of servers that run different browsers, and then running a bunch of tests in parallel. I found that on an ordinary developer box, I could easily run tests in 4 threads if the work of launching and running browsers was offloaded onto the farm.</p>
<p>I&#8217;ve also helped a couple clients run their JsUnit tests in Selenium to let them take advantage of the Selenium infrastructure. Basically, it amounts to writing a Selenium test that opens the JsUnit test runner page, clicks &#8220;run&#8221; and parses the results. </p>
<p>Setting up a Selenium farm is not trivial. Luckily, some folks have written and released an open source library called <a href="http://selenium-grid.seleniumhq.org/">Selenium Grid</a> to help. I haven&#8217;t used it, but it looks promising.</p>
<h4>Test Running Services</h4>
<p>A company called <a href="http://saucelabs.com/">Sauce Labs</a> is working on a service that runs Selenium tests. It doesn&#8217;t seem to be available yet, but the idea sounds very promising.</p>
<h3>Conclusion</h3>
<p>You can greatly improve your test suite run time, especially if you combine multiple solutions, if you don&#8217;t mind putting some time and money into the problem. Considering the costs of a slow test suite, I think it&#8217;s almost always worth the investment.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2009/06/05/solutions-to-slow-test-suites/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Running Jasmine Tests Through Jaxer</title>
		<link>http://www.eahanson.com/2009/06/04/running-jasmine-tests-through-jaxer/</link>
		<comments>http://www.eahanson.com/2009/06/04/running-jasmine-tests-through-jaxer/#comments</comments>
		<pubDate>Fri, 05 Jun 2009 00:51:39 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Jasmine]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Jaxer]]></category>
		<category><![CDATA[Unit Testing]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=98</guid>
		<description><![CDATA[Jasmine? Jaxer? Jasmine is a new Javascript testing framework from my pals at Pivotal Labs. Jaxer is an Apache module that wraps Firefox&#8217;s Javascript and rendering engine, providing a server-side Javascript environment that&#8217;s fast and compatible with any JS that works in Firefox (including all kinds of DOM manipulation and libraries like Prototype, jQuery, etc.). [...]]]></description>
			<content:encoded><![CDATA[<h3>Jasmine? Jaxer?</h3>
<p><a href="http://github.com/pivotal/jasmine/tree/master">Jasmine</a> is a new Javascript testing framework from my pals at <a href="http://pivotallabs.com/">Pivotal Labs</a>. </p>
<p><a href="http://www.aptana.com/jaxer">Jaxer</a> is an Apache module that wraps Firefox&#8217;s Javascript and rendering engine, providing a server-side Javascript environment that&#8217;s fast and compatible with any JS that works in Firefox (including all kinds of DOM manipulation and libraries like Prototype, jQuery, etc.).</p>
<h3>Running Jasmine Tests Through Jaxer</h3>
<p>Part of an app I&#8217;m writing uses Jaxer and I wanted to write my tests in Jasmine, so I wrote a test runner and reporter to bridge the two, and then I wrote a rake task to run the tests.</p>
<p>So now I can run &#8220;rake spec&#8221; and my JS tests will run, in Firefox, but without having to actually have Firefox open up (or be installed).</p>
<p>So:</p>
<pre>
rake spec
</pre>
<p>returns:</p>
<pre>
.    foo should do something.
.    foo should do something else.
.    foo should do yet another thing.

3 of 3 specs passed
</pre>
<p>And the whole thing takes <b>0.37 seconds</b>. It should be noted that those are trivial tests, but running a JS test suite with only 0.37 seconds of overhead is pretty neat.</p>
<p>My rake task looks like:</p>
<pre>
task :spec do
  results = `curl -s http://127.0.0.1:8081/jaxer-service/jasmine-jaxer`
  print results
  fail "spec failures" if results.match(/FAILURES!/)
end
</pre>
<h3>Download</h3>
<p>The JS code that hooks Jasmine to Jaxer is here: <a href="http://www.eahanson.com/code/jasmine-jaxer.js">jasmine-jaxer.js</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2009/06/04/running-jasmine-tests-through-jaxer/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Testing File Uploads To A Rack Server</title>
		<link>http://www.eahanson.com/2009/03/28/testing-file-uploads-to-a-rack-server/</link>
		<comments>http://www.eahanson.com/2009/03/28/testing-file-uploads-to-a-rack-server/#comments</comments>
		<pubDate>Sat, 28 Mar 2009 19:28:46 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Ruby]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=78</guid>
		<description><![CDATA[I wrote a web app on top of Rack that includes the ability to accept file uploads. It took some time to figure out how to successfully test the file upload using Rack::Test. The trick was to encode the file exactly correctly, and then post it with the correct headers. I wrote a simple post_file [...]]]></description>
			<content:encoded><![CDATA[<p>I wrote a web app on top of <a href="http://rack.rubyforge.org/">Rack</a> that includes the ability to accept file uploads. It took some time to figure out how to successfully test the file upload using <a href="http://www.brynary.com/2009/3/5/rack-test-released-a-simple-testing-api-for-rack-based-frameworks-and-apps">Rack::Test</a>. </p>
<p>The trick was to encode the file exactly correctly, and then post it with the correct headers. I wrote a simple <code>post_file</code> method to do this.</p>
<pre>
  def post_file(url, param_name, file_path, content_type)
    file_name = File.basename(file_path)
    boundary = "AaB03x"
    data = "--#{boundary}\r\n" +
           "Content-Disposition: form-data; name=\"#{param_name}\"; filename=\"#{file_name}\"\r\n" +
           "Content-Type: #{content_type}\r\n" +
           "\r\n" +
           "#{File.read(file_path)}\r\n" +
           "--#{boundary}--\r\n" +
           "\r\n"
    post(url, {}, {
      "CONTENT_TYPE" => "multipart/form-data, boundary=\"#{boundary}\"",
      "CONTENT_LENGTH" => data.length, :input => data })
  end
</pre>
<p>I call it from my test like this:</p>
<pre>
...
post_file("/my_app/upload", "uploaded_image", "../test-images/originals/loki.jpg", "image/jpeg")
...
</pre>
<p>and my app reads it like this:</p>
<pre>
...
request = Rack::Request.new(env)
...
uploaded_image = request.params["uploaded_image"][:tempfile]
...
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2009/03/28/testing-file-uploads-to-a-rack-server/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>AppleScripts for Creating Podcast Playlists in iTunes</title>
		<link>http://www.eahanson.com/2009/03/16/applescripts-for-creating-podcast-playlists-in-itunes/</link>
		<comments>http://www.eahanson.com/2009/03/16/applescripts-for-creating-podcast-playlists-in-itunes/#comments</comments>
		<pubDate>Tue, 17 Mar 2009 03:02:10 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[AppleScript]]></category>
		<category><![CDATA[My Software]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=70</guid>
		<description><![CDATA[I have a 2nd generation iPod Shuffle that I use with the super-awesome Arriva iPod headphones: I love these headphones because I can wear them at the gym without having to deal with wires. I actually bought the shuffle because of these headphones. At the gym, I like to start out by listening to a [...]]]></description>
			<content:encoded><![CDATA[<p>I have a <a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&#038;location=http%3A%2F%2Fwww.amazon.com%2Fs%3Fie%3DUTF8%26rs%3D172282%26ref%255F%3Dsr%255Fnr%255Fn%255F2%26keywords%3Dapple%2520ipod%2520shuffle%2520%25282nd%2520Generation%2529%26bbn%3D172623%26qid%3D1237257133%26rnid%3D493964%26rh%3Di%253Aaps%252Ck%253Aapple%2520ipod%2520shuffle%2520%25282nd%2520Generation%2529%252Ci%253Aelectronics%252Cn%253A172282%252Cp%255F4%253AApple%252Cn%253A172623&#038;tag=wshlst-20&#038;linkCode=ur2&#038;camp=1789&#038;creative=390957">2nd generation iPod Shuffle</a><img src="https://www.assoc-amazon.com/e/ir?t=wshlst-20&#038;l=ur2&#038;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /> that I use with the super-awesome <a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&#038;location=http%3A%2F%2Fwww.amazon.com%2Fs%3Fie%3DUTF8%26x%3D0%26ref%255F%3Dnb%255Fss%255Fgw%26y%3D0%26field-keywords%3Darriva%2520ipod%2520shuffle%26url%3Dsearch-alias%253Daps&#038;tag=wshlst-20&#038;linkCode=ur2&#038;camp=1789&#038;creative=390957">Arriva iPod headphones</a><img src="https://www.assoc-amazon.com/e/ir?t=wshlst-20&#038;l=ur2&#038;o=1" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />:<br />
<a href="http://www.amazon.com/gp/redirect.html?ie=UTF8&#038;location=http%3A%2F%2Fwww.amazon.com%2Fs%3Fie%3DUTF8%26x%3D0%26ref%255F%3Dnb%255Fss%255Fgw%26y%3D0%26field-keywords%3Darriva%2520ipod%2520shuffle%26url%3Dsearch-alias%253Daps&#038;tag=wshlst-20&#038;linkCode=ur2&#038;camp=1789&#038;creative=390957"></p>
<p><img src="http://www.eahanson.com/weblog/wp-content/uploads/2009/03/arriva.jpg" alt="arriva" title="arriva" width="199" height="145" class="aligncenter size-full wp-image-71" /></a></p>
<p>I love these headphones because I can wear them at the gym without having to deal with wires. I actually bought the shuffle because of these headphones. At the gym, I like to start out by listening to a few news podcasts while I warm up and then I like to hear some random music.</p>
<p>Unfortunately, version 8.1 of Apple&#8217;s iTunes removed the ability to autofill podcasts on the shuffle. (See <a href="http://discussions.apple.com/thread.jspa?threadID=1937830&#038;tstart=0">this thread on Apple&#8217;s support page</a> for example.)</p>
<p>Luckily, it can still be done with AppleScript. Here&#8217;s a script that copies all of the songs from my &#8220;Gym&#8221; playlist, including podcasts, to my shuffle:</p>
<pre>
tell application "iTunes"
  repeat with thisSource in sources
    if the name of thisSource = "Erik's Shuffle" then set myIpod to thisSource
  end repeat

  set destinationPlaylist to the first playlist in myIpod
  delete tracks in destinationPlaylist

  set sourcePlaylist to playlist "Gym"
  if the number of tracks in sourcePlaylist is greater than 0 then
    set shufflable of (every track of sourcePlaylist) to true
    duplicate every track of sourcePlaylist to destinationPlaylist
  end if
end tell
</pre>
<p>And here&#8217;s the full script that I use these days. It deletes everything on the shuffle, then adds the contents of three of my podcast playlists (which are &#8220;smart playlists&#8221; that only contain the most recent unplayed episode), and then adds one song from a short list of songs I like to start my workout with, and then finally adds 25 random songs from a collection of music that&#8217;s suitable for the gym.</p>
<pre>
on addRandomSongs(sourcePlaylistName, destinationPlaylist, songCount)
  tell application "iTunes"
    set sourcePlaylist to playlist sourcePlaylistName
    set alreadyUsedTrackNumbers to {}
    set numberOfTracksInSourcePlaylist to the number of tracks in sourcePlaylist
    repeat songCount times
      set trackNumber to (random number from 1 to numberOfTracksInSourcePlaylist)
      if alreadyUsedTrackNumbers does not contain trackNumber then
        duplicate (track trackNumber of sourcePlaylist) to destinationPlaylist
        copy trackNumber to the end of alreadyUsedTrackNumbers
      end if
    end repeat
  end tell
end addRandomSongs

on addPodcast(sourcePodcastName, destinationPlaylist)
  tell application "iTunes"
    set sourcePodcast to playlist sourcePodcastName
    if the number of tracks in sourcePodcast is greater than 0 then
      set shufflable of (every track of sourcePodcast) to true
      duplicate every track of sourcePodcast to destinationPlaylist
    end if
  end tell
end addPodcast

on findDevice(deviceName)
  tell application "iTunes"
    repeat with thisSource in sources
      if the name of thisSource = deviceName then return thisSource
    end repeat
  end tell
end findDevice

--
-- begin
--

set myShuffle to findDevice("Erik's Shuffle")

-- delete all tracks from the shuffle
tell application "iTunes"
  set shufflePlaylist to the first playlist in myShuffle
  delete tracks in shufflePlaylist
end tell

-- add podcasts
addPodcast("NPR Hourly News Summary", shufflePlaylist)
addPodcast("California Report", shufflePlaylist)
addPodcast("NPR Story Of The Day", shufflePlaylist)

-- add songs
addRandomSongs("Gym Kickoff", shufflePlaylist, 1)
addRandomSongs("Gym Music", shufflePlaylist, 25)
</pre>
<p>To use either of these scripts, fire up ScriptEditor, paste the script in, choose &#8220;Save As&#8221; from the &#8220;File&#8221; menu and choose &#8220;Application&#8221; as the file format, and then save it in your Library/Scripts/Applications/iTunes folder. A script menu will appear in the right portion of your menu bar when you&#8217;re in iTunes.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2009/03/16/applescripts-for-creating-podcast-playlists-in-itunes/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>A Bookmarklet To Reload A Page&#8217;s CSS</title>
		<link>http://www.eahanson.com/2009/02/28/a-bookmarklet-to-reload-a-pages-css/</link>
		<comments>http://www.eahanson.com/2009/02/28/a-bookmarklet-to-reload-a-pages-css/#comments</comments>
		<pubDate>Sun, 01 Mar 2009 02:46:05 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=62</guid>
		<description><![CDATA[Sometimes when I&#8217;m developing, I want to change some stylesheets and reload them without reloading the whole page. Often, I use Firebug or the Safari Web Inspector to play with individual attributes, but sometimes I have to edit the CSS files. So I wrote a bookmarklet to reload the files. Here it is: reload CSS [...]]]></description>
			<content:encoded><![CDATA[<p>Sometimes when I&#8217;m developing, I want to change some stylesheets and reload them without reloading the whole page. Often, I use Firebug or the Safari Web Inspector to play with individual attributes, but sometimes I have to edit the CSS files.</p>
<p>So I wrote a bookmarklet to reload the files. Here it is:</p>
<p><a href="javascript:(function()%20%7Bvar%20links%20=%20document.getElementsByTagName(%22link%22);for%20(var%20i%20=%200;%20i%20%3C%20links.length;%20i++)%20%7Bif%20(links%5Bi%5D.rel%20===%20%22stylesheet%22)%20%7Bif%20(links%5Bi%5D.href.indexOf(%22?%22)%20===%20-1)%20%7Blinks%5Bi%5D.href%20+=%20%22?%22;%7Dlinks%5Bi%5D.href%20+=%20%22x%22;%7D%7D%7D)()">reload CSS</a></p>
<p>(Just drag that into your bookmarks bar. Tested in Safari and Firefox.)</p>
<p>It works by appending the letter &#8220;x&#8221; after a &#8220;?&#8221; at the end of each stylesheet reference. That causes the browser to reload the stylesheet. Here&#8217;s the code:</p>
<pre>
var links = document.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
  if (links[i].rel === "stylesheet") {
    if (links[i].href.indexOf("?") === -1) {
      links[i].href += "?";
    }
    links[i].href += "x";
  }
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2009/02/28/a-bookmarklet-to-reload-a-pages-css/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Introducing JS Dev Tools</title>
		<link>http://www.eahanson.com/2009/02/28/introducing-js-dev-tools/</link>
		<comments>http://www.eahanson.com/2009/02/28/introducing-js-dev-tools/#comments</comments>
		<pubDate>Sun, 01 Mar 2009 02:05:35 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[jsdevtools.com]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=57</guid>
		<description><![CDATA[I created a site called JS Dev Tools (jsdevtools.com) to keep track of useful Javascript development tools and libraries. Check out the site, subscribe to the feed, and suggest your favorite tools and libraries.]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.jsdevtools.com"><img src="http://www.eahanson.com/weblog/wp-content/uploads/2009/02/jsdevtools.png" alt="jsdevtools" title="jsdevtools" width="400" height="193" class="aligncenter size-full wp-image-59" /></a></p>
<p>I created a site called JS Dev Tools (<a href="http://www.jsdevtools.com">jsdevtools.com</a>) to keep track of useful Javascript development tools and libraries.</p>
<p>Check out <a href="http://www.jsdevtools.com">the site</a>, subscribe to <a href="http://www.jsdevtools.com/rss.xml">the feed</a>, and <a href="mailto:submissions@jsdevtools.com">suggest your favorite tools and libraries</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2009/02/28/introducing-js-dev-tools/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>WordPress 2.7 Theme Development on Mac OS X 10.5 (Leopard)</title>
		<link>http://www.eahanson.com/2008/12/17/wordpress-27-theme-development-on-mac-os-x-105-leopard/</link>
		<comments>http://www.eahanson.com/2008/12/17/wordpress-27-theme-development-on-mac-os-x-105-leopard/#comments</comments>
		<pubDate>Wed, 17 Dec 2008 07:03:29 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Howto]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=41</guid>
		<description><![CDATA[I wanted to create/edit a WordPress theme on my local computer, so I had to install WordPress. It wasn&#8217;t quite as straightforward as I would have hoped. Here are some instructions: MySQL I had already downloaded and installed MySQL, so I didn&#8217;t have to do anything. If I remember, installing MySQL was straightforward. Apache Apache [...]]]></description>
			<content:encoded><![CDATA[<p>I wanted to create/edit a WordPress theme on my local computer, so I had to install WordPress. It wasn&#8217;t quite as straightforward as I would have hoped. Here are some instructions:</p>
<h3>MySQL</h3>
<p>I had already <a href="http://dev.mysql.com/downloads/">downloaded</a> and installed MySQL, so I didn&#8217;t have to do anything. If I remember, installing MySQL was straightforward.</p>
<h3>Apache</h3>
<p>Apache comes pre-installed on Mac OS X, but I had to turn it on in System Preferences &gt; Sharing &gt; Web Sharing.</p>
<h3>PHP</h3>
<p>PHP 5 comes pre-installed on Mac OS X, but I had to enable it in Apache by editing the conf file: </p>
<pre>
sudo vi /etc/apache2/httpd.conf
</pre>
<p>and uncommenting the line:</p>
<pre>
LoadModule php5_module
</pre>
<h3>WordPress</h3>
<p>I <a href="http://wordpress.org/download/">downloaded</a> WordPress and extracted it into:</p>
<pre>
~/Sites/wordpress
</pre>
<p>I followed the <a href="http://codex.wordpress.org/Installing_WordPress">installation instructions</a>, but <b>I had to use &#8220;Localhost&#8221; instead of &#8220;localhost&#8221; as my database host name</b>.</p>
<p>I created a symlink from the directory where I was editing my theme to the WordPress themes directory: </p>
<pre>
ln -s ~/Development/my-theme ~/Sites/wordpress/wp-content/themes/my-theme
</pre>
<p>Apache wasn&#8217;t set up to follow symlinks in my user directory, so I had to edit my personal conf file:</p>
<pre>
sudo vi /private/etc/apache2/users/ehanson.conf
</pre>
<p>and set the options line to:</p>
<pre>
Options Indexes MultiViews FollowSymLinks
</pre>
<p>Then I logged into the WordPress admin console at <code>http://localhost/~ehanson/wordpress/wp-admin</code>, set the theme to my new theme, and everything worked.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2008/12/17/wordpress-27-theme-development-on-mac-os-x-105-leopard/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Relative dates in Javascript</title>
		<link>http://www.eahanson.com/2008/12/04/relative-dates-in-javascript/</link>
		<comments>http://www.eahanson.com/2008/12/04/relative-dates-in-javascript/#comments</comments>
		<pubDate>Thu, 04 Dec 2008 22:10:09 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/?p=37</guid>
		<description><![CDATA[A while ago, I posted some Javascript code that produces a nice relative date string (e.g., &#8220;4 days ago&#8221; or &#8220;18 minutes ago&#8221;) given two dates. I looked at the code again and noticed that it was pretty long and needlessly required Prototype. So I cleaned it up a bit: /** * Simple relative date. [...]]]></description>
			<content:encoded><![CDATA[<p>A while ago, I <a href="http://www.eahanson.com/2007/10/31/my-time-zone-solution-relativedatejs-and-servertimejs/">posted some Javascript code</a> that produces a nice relative date string (e.g., &#8220;4 days ago&#8221; or &#8220;18 minutes ago&#8221;) given two dates.</p>
<p>I looked at the code again and noticed that it was pretty long and needlessly required <a href="http://prototypejs.org/">Prototype</a>. So I cleaned it up a bit:</p>
<pre>
/**
 * Simple relative date.
 *
 * Returns a string like "4 days ago". Prefers to return values >= 2. For example, it would
 * return "26 hours ago" instead of "1 day ago", but would return "2 days ago" instead of
 * "49 hours ago".
 *
 * Copyright (c) 2008 Erik Hanson http://www.eahanson.com/
 * Licensed under the MIT License http://www.opensource.org/licenses/mit-license.php
 */
function relativeDate(olderDate, newerDate) {
  if (typeof olderDate == "string") olderDate = new Date(olderDate);
  if (typeof newerDate == "string") newerDate = new Date(newerDate);

  var milliseconds = newerDate - olderDate;

  var conversions = [
    ["years", 31518720000],
    ["months", 2626560000 /* assumes there are 30.4 days in a month */],
    ["days", 86400000],
    ["hours", 3600000],
    ["minutes", 60000],
    ["seconds", 1000]
  ];

  for (var i = 0; i < conversions.length; i++) {
    var result = Math.floor(milliseconds / conversions[i][1]);
    if (result >= 2) {
      return result + " " + conversions[i][0] + " ago";
    }
  }

  return "1 second ago";
}
</pre>
<p>Try it out: <!-- use prototype just for this demo code -- it's not needed for relativeDate.js --><script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js"></script><script type="text/javascript" src="/code/RelativeDate2/relativeDate.js"></script><script type="text/javascript">function demo() { $('resultArea').update(relativeDate($('older').value, $('newer').value)) }</script></p>
<div style="background-color: #ddd; padding: 1em; margin: 1em">
  Older date:<br />
<input type="text" style="width: 50em" id="older">
  <br/><br />
  Newer date:<br />
<input type="text" style="width: 50em" id="newer">
  <script type="text/javascript">
    $('older').value = new Date(new Date() - 1000000).toString();
    $('newer').value = new Date().toString();
  </script></p>
<p/>
  Relative date: <a name="result"><span id="resultArea">&nbsp;</span></a> (<a href="#result" onclick="demo()">Update</a>)
</div>
<p><script type="text/javascript">demo();</script></p>
<p>Raw code and tests:<br />
<a href="/code/RelativeDate2/relativeDate.js">relativeDate.js</a><br />
<a href="/code/RelativeDate2/relativeDateTest.html.txt">relativeDateTest.html</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2008/12/04/relative-dates-in-javascript/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>New Test Runner for JsUnit</title>
		<link>http://www.eahanson.com/2008/05/18/new-test-runner-for-jsunit/</link>
		<comments>http://www.eahanson.com/2008/05/18/new-test-runner-for-jsunit/#comments</comments>
		<pubDate>Mon, 19 May 2008 02:01:45 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[JsUnit]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2008/05/18/new-test-runner-for-jsunit/</guid>
		<description><![CDATA[Christian Williams created a new test runner for JsUnit. More info here.]]></description>
			<content:encoded><![CDATA[<p>Christian Williams created a new test runner for JsUnit. <a href="http://pivots.pivotallabs.com/users/kelly/blog/articles/450-standup-5-7-2008">More info here</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2008/05/18/new-test-runner-for-jsunit/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A Rake Task to Concatenate and Compress Javascript Files</title>
		<link>http://www.eahanson.com/2008/03/02/a-rake-task-to-concatenate-and-compress-javascript-files/</link>
		<comments>http://www.eahanson.com/2008/03/02/a-rake-task-to-concatenate-and-compress-javascript-files/#comments</comments>
		<pubDate>Mon, 03 Mar 2008 06:15:52 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[wshlst.com]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2008/03/02/a-rake-task-to-concatenate-and-compress-javascript-files/</guid>
		<description><![CDATA[A while ago, I wrote about a Ruby script to concatenate and compress Javascript files for my wshlst application. I&#8217;ve changed things around a bit since that article, so I thought it was time for an update. Background wshlst uses a lot of Javascript. When developing, I want to have all of my JS files [...]]]></description>
			<content:encoded><![CDATA[<p>A while ago, I wrote about <a href="http://www.eahanson.com/2007/10/16/a-ruby-script-to-concatenate-and-compress-javascript-files/">a Ruby script to concatenate and compress Javascript files</a> for my <a href="http://www.eahanson.com/category/wshlstcom/">wshlst</a> application. I&#8217;ve changed things around a bit since that article, so I thought it was time for an update.</p>
<h3>Background</h3>
<p><a href="http://www.eahanson.com/category/wshlstcom/">wshlst</a> uses <a href="http://www.eahanson.com/2007/10/25/wshlstcom-a-webapp-with-a-pure-javascript-ui/">a lot of Javascript</a>. When developing, I want to have all of my JS files listed in my <code>index.html</code> page. But in production mode, I want a single JS file. So I wrote a script to read in my <code>index.html</code> file, find all the references to my Javascript files, and write out a new HTML file that contains a reference to a single JS file that was created by concatenating and compressing all the JS files.</p>
<p>As an example, have a look at what my <code>index.html</code> page looks like in <a href="http://eahanson.com/code/wshlst-index.txt">development mode</a> and in <a href="http://eahanson.com/code/wshlst-index-mini.txt">production mode</a>.</p>
<h3>Ruby Script &rarr; Rake</h3>
<p>When a server move prompted me to upgrade to Subversion and Capistrano, I decided to turn my Ruby script into a Rake task that I could trigger from my Capistrano deploy: <a href="http://www.eahanson.com/code/minify.rake">minify.rake</a></p>
<h3>Capistrano</h3>
<p>I added a line to my Capistrano <code>deploy.rb</code> file to run <code>rake minify</code> on the server after updating from Subversion:</p>
<pre>run "cd #{release_path} &#038;&#038; rake minify RAILS_ENV=#{rails_env}"</pre>
<h3>Apache</h3>
<p>I also added a few lines to my <code>httpd.conf</code> file to support this. First, since the Rake task combines all the JS files into a single JS file but Capistrano deploys the entire app, I wanted to restrict access to everything in the <code>javascripts</code> directory:</p>
<pre># Don't allow access to the javascripts directory (all JS should be in minified js file)
RewriteRule ^/javascripts/ - [F]</pre>
<p>Then, because the Rake task reads <code>index.html</code> and outputs a file called <code>index-mini.html</code>, I wanted to send all requests for <code>/</code> and <code>/index.html</code> to <code>/index-mini.html</code>:</p>
<pre>
# Rewrite index.html to index-mini.html
RewriteRule ^/index.html$ /index-mini.html [QSA]

# Rewrite index to check for static
RewriteRule ^/$ /index-mini.html [QSA]
</pre>
<h3>Why Not Use AssetPackager?</h3>
<p>For a normal Rails application, <a href="http://synthesis.sbecker.net/pages/asset_packager">AssetPackager</a> is probably the right solution to this problem. However, <a href="http://www.eahanson.com/category/wshlstcom/">wshlst</a> doesn&#8217;t use Rails to generate the UI (<a href="http://www.eahanson.com/2007/10/25/wshlstcom-a-webapp-with-a-pure-javascript-ui/">it&#8217;s all done in Javascript</a>), so AssetPackager wouldn&#8217;t work.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2008/03/02/a-rake-task-to-concatenate-and-compress-javascript-files/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Firing mouse events in tests</title>
		<link>http://www.eahanson.com/2008/01/01/firing-mouse-events-in-tests/</link>
		<comments>http://www.eahanson.com/2008/01/01/firing-mouse-events-in-tests/#comments</comments>
		<pubDate>Wed, 02 Jan 2008 04:17:48 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[JsUnit]]></category>
		<category><![CDATA[Unit Testing]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2008/01/01/firing-mouse-events-in-tests/</guid>
		<description><![CDATA[The Bad News Sending mouse events such as click and mouseover in JsUnit tests can be really hard. More Bad News Prototype doesn&#8217;t make it any easier. Sam Stephenson says: We would very much like to support it in the future. It&#8217;s fairly complicated to implement native event firing across all supported browsers, so in [...]]]></description>
			<content:encoded><![CDATA[<h4>The Bad News</h4>
<p>Sending mouse events such as <code>click</code> and <code>mouseover</code> in <a href="http://jsunit.net">JsUnit</a> tests can be really hard.</p>
<h4>More Bad News</h4>
<p><a href="http://prototypejs.org">Prototype</a> doesn&#8217;t make it any easier. Sam Stephenson <a href="http://groups.google.com/group/prototype-core/browse_thread/thread/9fec287978138250">says</a>:</p>
<blockquote><p>
We would very much like to support it in the future.  It&#8217;s fairly complicated to implement native event firing across all supported browsers, so in 1.6.0, fire works with custom events only.
</p></blockquote>
<h4>YUI To The Rescue</h4>
<p><a href="http://developer.yahoo.com/yui/yuitest/#useractions">YAHOO.util.UserActions</a> can simulate some user actions. Unfortunately, calls to YUI can look a bit clunky in a Prototype-heavy codebase:</p>
<pre>
var element = new Element("div").insert("Hi");
var offset = element.cumulativeOffset();
YAHOO.util.UserAction.click(element, { shiftKey: true });
</pre>
<h4>YUI + Prototype FTW</h4>
<p>A little mixin magic:</p>
<pre>
Element.addMethods({
  simulateClick: YAHOO.util.UserAction.click.bind(YAHOO.util.UserAction),
  simulateDblClick: YAHOO.util.UserAction.dblclick.bind(YAHOO.util.UserAction),
  simulateMousedown: YAHOO.util.UserAction.mousedown.bind(YAHOO.util.UserAction),
  simulateMouseup: YAHOO.util.UserAction.mouseup.bind(YAHOO.util.UserAction),
  simulateMouseover: YAHOO.util.UserAction.mouseover.bind(YAHOO.util.UserAction),
  simulateMouseout: YAHOO.util.UserAction.mouseout.bind(YAHOO.util.UserAction),
  simulateMousemove: YAHOO.util.UserAction.mousemove.bind(YAHOO.util.UserAction)
});
</pre>
<p>and now our test code looks nicer:</p>
<pre>
var element = new Element("div").insert("Hi");
var offset = element.cumulativeOffset();
myElement.simulateClick({ shiftKey: true });
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2008/01/01/firing-mouse-events-in-tests/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>My time zone solution: RelativeDate.js and ServerTime.js</title>
		<link>http://www.eahanson.com/2007/10/31/my-time-zone-solution-relativedatejs-and-servertimejs/</link>
		<comments>http://www.eahanson.com/2007/10/31/my-time-zone-solution-relativedatejs-and-servertimejs/#comments</comments>
		<pubDate>Wed, 31 Oct 2007 21:29:05 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[wshlst.com]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2007/10/31/my-time-zone-solution-relativedatejs-and-servertimejs/</guid>
		<description><![CDATA[For my wshlst.com project, I needed to show when each item or comment was created or edited. My initial implementation was to just show the time and date of the change, but people who aren&#8217;t in the same time zone as my server didn&#8217;t like the fact that it showed a different time zone. I [...]]]></description>
			<content:encoded><![CDATA[<p>For my <a href="http://www.eahanson.com/category/wshlstcom/">wshlst.com</a> project, I needed to show when each item or comment was created or edited. My initial implementation was to just show the time and date of the change, but people who aren&#8217;t in the same time zone as my server didn&#8217;t like the fact that it showed a different time zone.</p>
<p>I contemplated having a per-user time zone setting, but that seemed complicated. So I decided to just show how old the item is. For that, I needed to write two things. I&#8217;m posting them here in case anyone else finds them useful.</p>
<h3>RelativeDate.js</h3>
<p>First, I needed a simple relative date formatter, so I wrote a simple one. Given two dates, it returns a string like </p>
<pre>
4 days ago
</pre>
<p>or </p>
<pre>
26 hours ago
</pre>
<p>Here&#8217;s the code and the <a href="http://jsunit.net">JsUnit</a> test:</p>
<p><a href="/code/RelativeDate/RelativeDate.js">RelativeDate.js</a><br />
<a href="/code/RelativeDate/RelativeDateTest.html.txt">RelativeDateTest.html</a></p>
<h3>ServerTime.js</h3>
<p>The timestamps of the items I&#8217;m displaying are stored in the database, so they are based on the clock of my database server. The relative dates are calculated in Javascript, so they are based on the clock of the user, which might be hours or just minutes off from the database server time. </p>
<p>The first thing my app does when it loads is checks to see if the user is logged in, so I decided to piggyback the current server time along with the response. Since the client and server talk using JSON, it&#8217;s simple to add another property to the response:</p>
<pre>
def current
  ...
  render_json({
    :success => true,
    :user => user,
    :servertime => dbtime()
  }.to_json)
end

...

def dbtime
  ActiveRecord::Base
    .connection
    .select_all("select unix_timestamp(now()) as now")[0]["now"]
    .to_i
end
</pre>
<p>I wrote a ServerTime class on the client side and I initialize it when the app receives the current user response from the server:</p>
<pre>
this.serverTime = new ServerTime(json.servertime);
</pre>
<p>Here&#8217;s the code (it&#8217;s very simple):</p>
<p><a href="/code/ServerTime.js">ServerTime.js</a></p>
<h3>Putting It Together&#8230;</h3>
<p>And now to display the relative time, my app only has to do this:</p>
<pre>
var updated = new Date(item.updated_at * 1000);
element.insert(new RelativeDate(updated, app.serverTime.get()).toString());
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/10/31/my-time-zone-solution-relativedatejs-and-servertimejs/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>wshlst.com: A Webapp With A Pure-Javascript UI</title>
		<link>http://www.eahanson.com/2007/10/25/wshlstcom-a-webapp-with-a-pure-javascript-ui/</link>
		<comments>http://www.eahanson.com/2007/10/25/wshlstcom-a-webapp-with-a-pure-javascript-ui/#comments</comments>
		<pubDate>Thu, 25 Oct 2007 22:51:08 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[wshlst.com]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2007/10/25/wshlstcom-a-webapp-with-a-pure-javascript-ui/</guid>
		<description><![CDATA[I created a little wish list website called wshlst.com for my family to use. (Read more about it here.) I worked on it a little bit before Christmas last year, and a little bit again this year. It&#8217;s been a lot of fun to write, and one reason is that the entire UI is written [...]]]></description>
			<content:encoded><![CDATA[<p>I created a little wish list website called <a href="http://www.wshlst.com/">wshlst.com</a> for my family to use. (<a href="http://www.eahanson.com/2007/10/24/wshlstcom/">Read more about it here</a>.) I worked on it a little bit before Christmas last year, and a little bit again this year.</p>
<p>It&#8217;s been a lot of fun to write, and one reason is that the entire UI is written in Javascript. No HTML, no CSS. There&#8217;s no HTML generation on the server-side either; it&#8217;s all client-side Javascript. I&#8217;d really like to write more apps like this.</p>
<h3>The Server</h3>
<p>There is of course a server-side component. I decided to write the server in Rails because I had just finished working on a Rails project at <a href="http://www.pivotallabs.com/">Pivotal Labs</a> and had Rails on the brain. Plus, my web host, <a href="http://www.dreamhost.com/r.cgi?29771">DreamHost</a>, supports Rails and doesn&#8217;t support Java, which is the language I&#8217;ve written all my other web apps in.</p>
<p>DreamHost&#8217;s support of Rails is a bit limited though: they kill any long-running processes, which means that a response from your Rails app could take 10 seconds if no other requests have been made lately. It&#8217;s good enough for a little project like Wshlst with only a few users and for initial development, but not much beyond that. (If I were to do it over again, I&#8217;d probably just use Ruby CGI, or maybe even PHP. Or use a VPS from <a href="http://www.rimuhosting.com/">Rimu</a>, but that costs money.)</p>
<p>Because all of the UI is in Javascript, the Rails side of things doesn&#8217;t have to generate any HTML. Instead, it takes standard Rails requests and returns <a href="http://json.org/">JSON</a> using <a href="http://json.rubyforge.org/">JSON for Ruby</a>. For example, the client sends the following AJAX request:</p>
<pre>
GET http://wshlst.com/user/list
</pre>
<p>and the server uses ActiveRecord magic to find all the users that are &#8220;friends&#8221; with the current user and uses JSON for Ruby to convert that list to JSON, which it returns back to the client like this:</p>
<pre>
[{name: "Andy Acorn", id: 12, email: "andy@example.com"},
{name: "Betty Beets", id: 13, email: "betty@example.com"},
{name: "Chad Carrot", id: 14, email: "chad@example.com"},
{name: "Donna Donut", id: 15, email: "donna@example.com"}]
</pre>
<p>which Prototype automatically converts to Javascript objects.</p>
<p>The server is of course also responsible for sending static files, but those are all handled by DreamHost&#8217;s Apache servers.</p>
<h3>The Client</h3>
<p>Here&#8217;s where it gets interesting. The app consists of an HTML file that does little more than include the Javascript files. (In development mode, there are multiple Javascript files, but when I deploy to production, there&#8217;s just one file. <a href="http://www.eahanson.com/2007/10/16/a-ruby-script-to-concatenate-and-compress-javascript-files/">Read this blog post on concatenating and compressing Javascript files with Ruby</a>.)</p>
<p>The app relies heavily on <a href="http://prototypejs.org/">Prototype</a>, and also uses <a href="http://www.berniecode.com/writing/animator.html">Animator.js</a> for a few visual effects. Everything else I wrote from scratch. I like to write a lot of code from scratch when I&#8217;m working on a personal project so I can get a good understanding of what&#8217;s really going on and be able to better evaluate pre-written libraries when I&#8217;m programming for money.</p>
<p>The app&#8217;s main class is called Application and acts as a controller. There are many view classes which are combined to form the app&#8217;s UI. For example, the &#8220;Change Password&#8221; box is implemented by a ChangePasswordForm class and consists of a ModalBox with a FormPanel inside. The FormPanel contains instances of Buttons and Fields. Writing a Field class of course takes more time than putting <tt>&lt;input type="text"&gt;</tt> in some template file somewhere, but it allows for easy reuse and the ability to add a feature to all fields in just one place. (I hope to post some of the code from Wshlst on this blog in the future.)</p>
<p>Responding to user actions is just a matter of one Javascript method directly calling another method. No parsing of parameters, no layers of actions and filters, no guessing client state on the server side. It&#8217;s very liberating. </p>
<h3>Lessons Learned</h3>
<p><b>Test early and often in lots of browsers.</b> This is obviously important when doing any web development, but when it&#8217;s 100% Javascript, there&#8217;s even more chance of incompatibilities. My <a href="http://jsunit.net/">JsUnit</a> tests caught some issues (<tt>Date.now</tt> turns out to be Firefox-only), but most issues I had were visual.</p>
<p><b>MVC is good.</b> Again, I already knew this, but I didn&#8217;t do nearly a good enough job with it in this app, probably because it was the first time I wrote an entire UI in Javascript. Next time, I&#8217;ll have multiple controllers and keep app logic out of the views as much as possible.</p>
<p><b>Events are probably the way to go.</b> Wshlst suffers from some tight coupling of objects because they directly notify each other of actions. Next time, I&#8217;ll try having all my objects send custom events to and receive custom events from some global event source (perhaps <tt>document.body</tt>), like I did back in the days when I wrote Mac software.</p>
<p><b>UI is hard.</b> I&#8217;m glad I did all the UI myself, but next time I&#8217;d like to build on the work of others. I&#8217;m still looking for a good Javascript widget library. Some of the widget libraries I&#8217;ve seen are trying to make web apps look application-y, and I&#8217;d prefer my web app to look web-y. Also, some libraries don&#8217;t have great support for calling everything from Javascript. I think I&#8217;ll look into <a href="http://dojotoolkit.org/">Dojo</a> again now that version 1.0 has been released, and more importantly, now that there&#8217;s more documentation.</p>
<h3>Try It Out</h3>
<p>There&#8217;s no way yet to sign up for Wshlst (if there&#8217;s enough interest I&#8217;ll do it), but the front page of the site lists some demo users you can use to play around: <a href="http://www.wshlst.com/">www.wshlst.com</a>.</p>
<p><i><a href="http://www.eahanson.com/category/wshlstcom/">Read more articles about wshlst.com</a></i></p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/10/25/wshlstcom-a-webapp-with-a-pure-javascript-ui/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Introducing wshlst.com</title>
		<link>http://www.eahanson.com/2007/10/24/wshlstcom/</link>
		<comments>http://www.eahanson.com/2007/10/24/wshlstcom/#comments</comments>
		<pubDate>Wed, 24 Oct 2007 19:06:57 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[My Software]]></category>
		<category><![CDATA[wshlst.com]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2007/10/24/wshlstcom/</guid>
		<description><![CDATA[wshlst.com is a wish list website I created for my family to use. It differs from other wish list sites in a few ways: it&#8217;s group-oriented, it allows people to add items to other peoples&#8217; wish lists (secretly, if desired), and it allows people to discuss wish list items (again, secretly, if desired). The result [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.wshlst.com/">wshlst.com</a> is a wish list website I created for my family to use. </p>
<p>It differs from other wish list sites in a few ways: it&#8217;s group-oriented, it allows people to add items to other peoples&#8217; wish lists (secretly, if desired), and it allows people to discuss wish list items (again, secretly, if desired). The result is a site that lets a group of people plot and scheme behind each others&#8217; backs to make gift buying easier.</p>
<h3>Site Tour</h3>
<p><img src="/images/wshlst/overview.jpg" width="400" height="220" class="articleImage"></p>
<p>The site&#8217;s main page is divided into three panels. The first is called &#8220;People&#8221; and lists all the people you share wish lists with. For me, it lists everyone in my family and everyone in my wife&#8217;s family. </p>
<p><img src="/images/wshlst/people.jpg" width="311" height="197" class="articleImage"></p>
<p>Clicking on a person loads their wish list in the next panel, the &#8220;Wish List&#8221; panel. As you&#8217;d expect, you can add and remove items from your wish list. But you can also add and remove items from other peoples&#8217; wish lists. By default, when you add an item to someone else&#8217;s list, it&#8217;s not visible to that person. Items can also be marked as &#8220;Claimed&#8221; so two people don&#8217;t end up buying the same gift. </p>
<p><img src="/images/wshlst/wishlist.jpg" width="313" height="289" class="articleImage"></p>
<p>Clicking on a wish list item loads comments about the item in the third panel, called &#8220;Discussion&#8221;. Comments in the discussion panel are also hidden from the item&#8217;s intended recipient by default. </p>
<p><img src="/images/wshlst/discussion.jpg" width="312" height="150" class="articleImage"></p>
<h3>Try It Out</h3>
<p>There&#8217;s no way to sign up to use the site (yet&mdash;if there&#8217;s interest from anyone outside my family, I might take the time to add the ability), but there are demo users you can use to try the site out. Details are on the front page of the site: <a href="http://www.wshlst.com/">www.wshlst.com</a>.</p>
<p><i><a href="http://www.eahanson.com/category/wshlstcom/">Read more posts about wshlst.com</a></i></p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/10/24/wshlstcom/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A Ruby Script to Concatenate and Compress Javascript Files</title>
		<link>http://www.eahanson.com/2007/10/16/a-ruby-script-to-concatenate-and-compress-javascript-files/</link>
		<comments>http://www.eahanson.com/2007/10/16/a-ruby-script-to-concatenate-and-compress-javascript-files/#comments</comments>
		<pubDate>Wed, 17 Oct 2007 06:07:38 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2007/10/16/a-ruby-script-to-concatenate-and-compress-javascript-files/</guid>
		<description><![CDATA[I&#8217;ve got an HTML page that includes a bunch of Javascript files, which makes development easy but which hurts performance in production. The standard solution is to concatenate all the Javascript files into one big file and then compress it. I wanted it to be automated of course, and I wanted to do it at [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve got an HTML page that includes a bunch of Javascript files, which makes development easy but which <a href="http://yuiblog.com/blog/2006/11/28/performance-research-part-1/">hurts performance</a> in production.</p>
<p>The standard solution is to concatenate all the Javascript files into one big file and then compress it. I wanted it to be automated of course, and I wanted to do it at deploy-time, not at run-time. </p>
<p>So I wrote a little script that figures out what Javascript files are being included in the HTML page, compresses them with the <a href="http://developer.yahoo.com/yui/compressor/">YUI Compressor</a>, concatenates them into a file named after the newly-compressed and -concatenated Javascript&#8217;s MD5 hash, and creates a copy of the original HTML page that references the new Javascript file instead of the old ones.</p>
<p>It&#8217;s pretty specific to my app&#8217;s needs, but with some minor tweaking, it should be useable on other projects. Check it out: <b><a href="http://www.eahanson.com/code/minify.rb">minify.rb</a></b></p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/10/16/a-ruby-script-to-concatenate-and-compress-javascript-files/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Big New Feature In JsUnit 2.2 alpha 25</title>
		<link>http://www.eahanson.com/2007/08/13/big-new-feature-in-jsunit-22-alpha-25/</link>
		<comments>http://www.eahanson.com/2007/08/13/big-new-feature-in-jsunit-22-alpha-25/#comments</comments>
		<pubDate>Mon, 13 Aug 2007 08:21:33 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[JsUnit]]></category>
		<category><![CDATA[Unit Testing]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2007/08/13/big-new-feature-in-jsunit-22-alpha-25/</guid>
		<description><![CDATA[In order to run all test functions in a test page, JsUnit naturally needs to know what those test functions are. It's able to get a list of those functions from Firefox, Safari and Opera, but it's not able to get them from Internet Explorer. So in IE, JsUnit has to resort to searching the text of all scripts for strings that look like test function names. Unfortunately, it doesn't understand comments so commented-out test functions will end up running anyway. ]]></description>
			<content:encoded><![CDATA[<p>In order to run all test functions in a test page, <a href="http://www.jsunit.net/">JsUnit</a> naturally needs to know what those test functions are. It&#8217;s able to get a list of those functions from Firefox, Safari and Opera, but it&#8217;s not able to get them from Internet Explorer. </p>
<p>So in IE, JsUnit has to resort to searching the text of all scripts for strings that look like test function names. Unfortunately, it doesn&#8217;t understand comments so commented-out test functions will end up running anyway. </p>
<p>Recently, Dean McNamee and <a href="http://pupius.co.uk/">Dan Pupius</a> alerted <a href="http://edwardh.com/">Edward Hieatt</a> and me to an undocumented Internet Explorer Javascript function called <tt>RuntimeObject</tt> and suggested JsUnit might be able to use it to discover test functions. </p>
<p>I replaced the text searching with <tt>RuntimeObject</tt> and tagged it as version 2.2 alpha 25. (I also added an <tt>assertEqualsIgnoringOrder</tt> function while I was making changes.)</p>
<p>Browse the new version <a href="http://jsunit.svn.sourceforge.net/viewvc/jsunit/tags/v2_2alpha25/jsunit/">at SourceForge</a> or check it out like this:</p>
<div class="code">svn co https://jsunit.svn.sf.net/svnroot/jsunit/tags/v2_2alpha25/jsunit/ jsunit</div>
<p>Try it out and <a href="http://tech.groups.yahoo.com/group/jsunit/">let us know</a> if there are any problems.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/08/13/big-new-feature-in-jsunit-22-alpha-25/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>An AppleScript to Clean Up My Downloads Folder</title>
		<link>http://www.eahanson.com/2007/08/11/an-applescript-to-clean-up-my-downloads-folder/</link>
		<comments>http://www.eahanson.com/2007/08/11/an-applescript-to-clean-up-my-downloads-folder/#comments</comments>
		<pubDate>Sat, 11 Aug 2007 18:55:02 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[AppleScript]]></category>
		<category><![CDATA[Mac OS X]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2007/08/11/an-applescript-to-clean-up-my-downloads-folder/</guid>
		<description><![CDATA[My downloads folder always gets so full after a while that I have a hard time finding things. I like to keep stuff around for a little while in case I need it again, and if I have the space, I like to keep things around for a long time, just in case. So I wrote a simple AppleScript to move old files to an "Old" subdirectory.]]></description>
			<content:encoded><![CDATA[<p>My downloads folder always gets so full after a while that I have a hard time finding things. I like to keep stuff around for a little while in case I need it again, and if I have the space, I like to keep things around for a long time, just in case.</p>
<p>So I wrote a simple AppleScript to move old files to an &#8220;Old&#8221; subdirectory. I thought I&#8217;d post it here in case anyone else needed something similar. It&#8217;s a simple script, but AppleScript is hard for me because I write scripts so rarely that I forget everything about it before I need to write another script.</p>
<p>(Many years ago, I actually got paid to write a few AppleScripts.)</p>
<pre>
set folderName to "Macintosh HD:Users:<em>your-user-name</em>:Temp:"

repeat with fileName in list folder folderName
  set tempFile to (folderName as string) &amp; contents of fileName
  set modificationDate to modification date of (info for alias tempFile)
  set age to ((current date) - modificationDate) / days

  if age &gt; 7 and fileName is not " Clean Up Temp Dir" then
    tell application "Finder"
      move alias tempFile to alias (folderName &amp; " Old")
    end tell
  end if
end repeat
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/08/11/an-applescript-to-clean-up-my-downloads-folder/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Minor New Features In JsUnit 2.2alpha24</title>
		<link>http://www.eahanson.com/2007/08/09/minor-new-features-in-jsunit-22alpha24/</link>
		<comments>http://www.eahanson.com/2007/08/09/minor-new-features-in-jsunit-22alpha24/#comments</comments>
		<pubDate>Fri, 10 Aug 2007 01:46:36 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[JsUnit]]></category>
		<category><![CDATA[Unit Testing]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/2007/08/09/minor-new-features-in-jsunit-22alpha24/</guid>
		<description><![CDATA[I added a few new features to JsUnit and tagged it as version 2.2 alpha 24: new log button, new URL parameters, and easier suite creation.]]></description>
			<content:encoded><![CDATA[<p>I added a few new features to <a href="http://jsunit.net">JsUnit</a> and tagged it as version 2.2 alpha 24. Browse the new version <a href="http://jsunit.svn.sourceforge.net/viewvc/jsunit/tags/v2_2alpha24/jsunit/">at SourceForge</a> or check it out like this:</p>
<div class="code">
  svn co https://jsunit.svn.sf.net/svnroot/jsunit/tags/v2_2alpha24/jsunit/ jsunit
</div>
<h3>Show Log</h3>
<p>There&#8217;s now a &#8220;Show Log&#8221; button that opens a window with a log of what JsUnit has been up to.</p>
<p><a href="http://www.eahanson.com/weblog/wp-content/uploads/2007/08/testrunner.jpg" title="New Log Button"><img src="http://www.eahanson.com/weblog/wp-content/uploads/2007/08/testrunner.thumbnail.jpg" alt="New Log Button" /></a></p>
<p>New log button (click image for larger view)</p>
<p><a href="http://www.eahanson.com/weblog/wp-content/uploads/2007/08/log.gif" title="New Log Window"><img src="http://www.eahanson.com/weblog/wp-content/uploads/2007/08/log.thumbnail.gif" alt="New Log Window" /></a></p>
<p>New log window (click image for larger view)</p>
<h3>New URL Parameters</h3>
<p>There are three new URL parameters:</p>
<ul>
<li><tt>suppressDialogs=true</tt> will suppress JsUnit&#8217;s dialogs in interactive mode</li>
<li><tt>setupPageTimeout=<em>n</em></tt> will change the timeout for the setupPage call</li>
<li><tt>pageLoadTimeout=<em>n</em></tt> will change the page load timeout</li>
</ul>
<h3>Easier Suite Creation</h3>
<p>The <tt>jsUnitTestSuite</tt> constructor now accepts test pages and subsuites as parameters. So instead of this:</p>
<pre>
function suite() {
  var suite = new jsUnitTestSuite();
  suite.addTestPage("/foo/barTest.html");
  suite.addTestPage("/foo/bazTest.html");
  return suite;
}</pre>
<p>you can do this:</p>
<pre>
function suite() {
  return new jsUnitTestSuite("/foo/barTest.html", "/foo/bazTest.html");
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/08/09/minor-new-features-in-jsunit-22alpha24/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A Ruby Script To Generate a JsUnit Suite</title>
		<link>http://www.eahanson.com/2007/05/26/a-ruby-script-to-generate-a-jsunit-suite/</link>
		<comments>http://www.eahanson.com/2007/05/26/a-ruby-script-to-generate-a-jsunit-suite/#comments</comments>
		<pubDate>Sat, 26 May 2007 20:38:29 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[JsUnit]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=16</guid>
		<description><![CDATA[I've got a bunch of JsUnit tests for a project I'm working on and I wanted an easy way to create a JsUnit suite that can run all the tests but also run any individual test page. Also, I wanted to be able to view any test page so I could debug the tests with Firebug. I also didn't want to run a web server all the time, so I wanted a script that would create a static HTML file.]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve got a bunch of JsUnit tests for a project I&#8217;m working on and I wanted an easy way to create a JsUnit suite that can run all the tests but also run any individual test page.</p>
<p>Also, I wanted to be able to view any test page so I could debug the tests with Firebug (as explained <a href="http://www.eahanson.com/weblog/?p=11">here</a>).</p>
<p>I also didn&#8217;t want to run a web server all the time, so I wanted a script that would create a static HTML file.</p>
<p>Here&#8217;s what I came up with. It&#8217;s not configurable, so you&#8217;ll have to modify it for your own needs.</p>
<p align="center"><strong><a href="/code/JsUnitSuiteBuilder.rb">JsUnitSuiteBuilder.rb</a></strong></p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/05/26/a-ruby-script-to-generate-a-jsunit-suite/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Element.stylize via Prototype.js</title>
		<link>http://www.eahanson.com/2007/05/25/elementstylize-via-prototypejs/</link>
		<comments>http://www.eahanson.com/2007/05/25/elementstylize-via-prototypejs/#comments</comments>
		<pubDate>Fri, 25 May 2007 20:13:18 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=15</guid>
		<description><![CDATA[I often want to add a bunch of styles to an element, so I wrote a simple stylize method.]]></description>
			<content:encoded><![CDATA[<p>I often want to add a bunch of styles to an element, so I wrote a <tt>stylize</tt> method so I can do something like this:</p>
<pre>
$("foo").stylize({
  color: red,
  background-color: green,
  text-decoration: underline
});</pre>
<p>Implementing <tt>stylize</tt> is easy with <a href="http://prototypejs.org/">Prototype</a>&#8216;s <a href="http://prototypejs.org/api/element/addMethods">Element.addMethods()</a> method:</p>
<pre>
var ElementExtensions = {
  stylize: function(element, styles) {
    for (var s in styles) {
      element.style[s] = styles[s];
    }
    return element;
  }
}
Element.addMethods(ElementExtensions);</pre>
<p><b>Update:</b> <a href="http://prototypejs.org/2007/8/15/prototype-1-6-0-release-candidate">Prototype 1.6</a> added a similar <tt>setStyle</tt> method which works similarly but also allows CSS string-type style declaration (e.g., <tt>myElement.setStyle("color: blue; border: 3px")</tt>). Still, the above is a nice example of <tt>Element.addMethods()</tt>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/05/25/elementstylize-via-prototypejs/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Installing termios gem on Mac OS X</title>
		<link>http://www.eahanson.com/2007/02/03/installing-termios-gem-on-mac-os-x/</link>
		<comments>http://www.eahanson.com/2007/02/03/installing-termios-gem-on-mac-os-x/#comments</comments>
		<pubDate>Sun, 04 Feb 2007 03:13:54 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Mac OS X]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=13</guid>
		<description><![CDATA[When installing Capistrano, you should install the termios gem so that your password isn't echoed when you type it. Easier said than done.]]></description>
			<content:encoded><![CDATA[<p>When installing <a href="http://manuals.rubyonrails.com/read/chapter/97">Capistrano</a>, you should install the <strong>termios</strong> gem so that your password isn&#8217;t echoed when you type it.</p>
<h4>Easier said than done</h4>
<p>Here&#8217;s the error I got:</p>
<div class="code">$<span style="color: darkgreen">sudo gem install termios</span><br />
Attempting local installation of &#8216;termios&#8217;<br />
Local gem file not found: termios*.gem<br />
Attempting remote installation of &#8216;termios&#8217;<br />
Updating Gem source index for: http://gems.rubyforge.org<br />
Building native extensions.  This could take a while&#8230;<br />
can&#8217;t find header files for ruby.<br />
ERROR:  While executing gem &#8230; (RuntimeError)<br />
ERROR: Failed to build gem native extension.<br />
Gem files will remain installed in /usr/lib/ruby/gems/1.8/gems/termios-0.9.4 for inspection.<br />
ruby extconf.rb install termios\n
</div>
<p>It was looking for header files in <tt>/usr/lib/ruby/1.8/powerpc-darwin8.0</tt>, but they weren&#8217;t there; they were in <tt>/usr/lib/ruby/1.8/universal-darwin8.0</tt>.</p>
<h4>The Solution</h4>
<p>The solution was simple enough: create symlinks to the header files:</p>
<pre>$<span style="color: darkgreen">sudo ln -s ../universal-darwin8.0/*.h ./</span>
$<span style="color: darkgreen">sudo gem install termios</span></pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/02/03/installing-termios-gem-on-mac-os-x/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Free Website Monitoring</title>
		<link>http://www.eahanson.com/2007/01/14/free-website-monitoring/</link>
		<comments>http://www.eahanson.com/2007/01/14/free-website-monitoring/#comments</comments>
		<pubDate>Mon, 15 Jan 2007 02:31:17 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Services]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=12</guid>
		<description><![CDATA[I was looking around for a good website monitoring service or script, and I found Montastic which is a free website monitoring service.]]></description>
			<content:encoded><![CDATA[<p>I was looking around for a good website monitoring service or script, and I found <a href="http://www.montastic.com/">Montastic</a> which is a free website monitoring service.</p>
<p>There&#8217;s even a <a href="http://widgets.yahoo.com/gallery/view.php?widget=38681">Monstatic Widget</a> for <a href="http://widgets.yahoo.com/">Yahoo Widgets</a>.</p>
<p>I only wish it had <a href="http://www.jabber.org">Jabber</a> notification in addition to email notification.</p>
<p>I also read about <a href="http://nubyonrails.topfunky.com/articles/2006/03/29/surviving-rails-1-1-with-server-monitoring">dwatch</a> but I figured a service would be easier.</p>
<p><strong>Update:</strong> I got an email from Arun who works for <a href="http://www.site24x7.com">Site24x7.com</a>. It seems to have more features than Montastic, such as being able to look for a particular keyword in the response (or not in the response), and nice looking reports. I&#8217;ll be trying them out alongside Montastic. (Unrelatedly, the makers of Site24x7 also make <a href="http://www.zoho.com/">Zoho</a>, a suite of productivty apps like a word processor, spreadsheet, etc. Somehow I&#8217;d never heard of them.)</p>
<p><strong>Update 2</strong>: My pals at <a href="http://pivotallabs.com/">Pivotal Labs</a> have released <a href="http://monitwitter.com/">Monitwitter</a>, which uses Twitter to show you the status of your servers. Brilliant!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2007/01/14/free-website-monitoring/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Debugging a JsUnit Test With Firebug</title>
		<link>http://www.eahanson.com/2006/11/16/debugging-a-jsunit-test-with-firebug/</link>
		<comments>http://www.eahanson.com/2006/11/16/debugging-a-jsunit-test-with-firebug/#comments</comments>
		<pubDate>Thu, 16 Nov 2006 07:36:53 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[JsUnit]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=11</guid>
		<description><![CDATA[Sometimes I find that I want to debug a JsUnit test because I'm not quite sure what's going on in the browser. FireBug has a nice debugger, but I couldn't get it to work with the JsUnit test runner. I could set breakpoints in JsUnit itself, but any breakpoints in my test would be ignored. And then it hit me: ditch the test runner.]]></description>
			<content:encoded><![CDATA[<p>Sometimes I find that I want to debug a <a href="http://www.jsunit.net/">JsUnit</a> test because I&#8217;m not quite sure what&#8217;s going on in the browser.</p>
<p><a href="http://getfirebug.com/">FireBug</a> has a nice debugger, but I couldn&#8217;t get it to work with the JsUnit test runner. I could set breakpoints in JsUnit itself, but any breakpoints in my test would be ignored.</p>
<p>And then it hit me: ditch the test runner. I opened my test page directly in Firefox, opened FireBug, clicked the Debugger tab, and chose my test from the &#8220;Scripts&#8221; menu. Here&#8217;s a simplified version:</p>
<pre>
function testUpdate() {
  var listPanel = new ListPanel();
  listPanel.update(["one", "two", "three"]);

  var children = $("content").childNodes;
  assertEquals("one", children[0]);
  assertEquals("two", children[1]);
  assertEquals("three", children[2]);
}</pre>
<p>I put a breakpoint at the first <tt>assertEquals</tt> and switched to the Console tab and typed <tt>testUpdate()</tt>. I saw the ListPanel getting drawn in the browser (something you don&#8217;t see in the JsUnit test runner) and in the Debugger tab I could inspect all my variables.</p>
<p>But even better, I could go back to the Console tab and start evaluating Javascript.</p>
<pre>
&gt;&gt;&gt; children.length
3</pre>
<p>Good.</p>
<pre>
&gt;&gt;&gt; children[0]
div</pre>
<p>Bad.</p>
<pre>
&gt;&gt;&gt; children[0].firstChild
[Text] "one"</pre>
<p>Better.</p>
<pre>
&gt;&gt;&gt; children[0].firstChild.nodeValue
"one"</pre>
<p>Perfect.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2006/11/16/debugging-a-jsunit-test-with-firebug/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>A Bold Prompt in Bash</title>
		<link>http://www.eahanson.com/2006/02/12/a-bold-prompt-in-bash/</link>
		<comments>http://www.eahanson.com/2006/02/12/a-bold-prompt-in-bash/#comments</comments>
		<pubDate>Mon, 13 Feb 2006 04:20:29 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Howto]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=8</guid>
		<description><![CDATA[I wanted my Bash prompt to be bold and red so I could find my one line of input amongst the million lines of output in my terminal. I'm no Unix nerd, so I don't really know offhand where to look for this info. After a bunch of Googling, I found something that works for me.]]></description>
			<content:encoded><![CDATA[<p>I wanted my Bash prompt to be bold and red so I could find my one line of input amongst the million lines of output in my terminal. I&#8217;m no Unix nerd, so I don&#8217;t really know offhand where to look for this info. After a bunch of Googling, I found something that works for me. The shell variable for the prompt is naturally called <tt>PS1</tt>. The original value was:</p>
<pre>PS1='\h:\w \u\$ '</pre>
<p>The code for starting a font and color change is <tt>\e[</tt>. The code for bold red is <tt>1;31</tt>. The code for closing the starting code for a font and color tag is <tt>m</tt>. The closing code for a font and color change is <tt>\e[m</tt>. Bash thinks that these color commands will increase the length of the prompt, which will cause odd line wrapping behavior. The solution is to wrap all the color codes in brackets (<tt>\[</tt> and <tt>\]</tt>).</p>
<p>
So, the entire prompt looks like this:</p>
<pre>PS1='\[\e[1;31m\]\h:\w \u\$\[\e[m\] '</pre>
<p>Eeech. Add that to your <tt>.bashrc</tt> file and you&#8217;re all set. If you don&#8217;t want the red, the prompt would be:</p>
<pre>PS1='\[\e[1m\]\h:\w \u\$\[\e[m\] '</pre>
<p>
More colors are listed at the bottom of <a href="http://networking.ringofsaturn.com/Unix/Bash-prompts.php">this page</a>.</p>
<p>
<b>Update:</b> Somewhere along the way, all the backslashes got replaced by double backslashes. I&#8217;ve fixed that.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2006/02/12/a-bold-prompt-in-bash/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Join Tables with Attributes in Rails</title>
		<link>http://www.eahanson.com/2006/02/12/join-tables-with-attributes-in-rails/</link>
		<comments>http://www.eahanson.com/2006/02/12/join-tables-with-attributes-in-rails/#comments</comments>
		<pubDate>Mon, 13 Feb 2006 04:19:39 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Ruby on Rails]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=7</guid>
		<description><![CDATA[I had a little trouble today with join tables that contain attributes.]]></description>
			<content:encoded><![CDATA[<p>I had a little trouble today with join tables that contain attributes. </p>
<p><h4>Creating the join table</h4>
<p>Starting with tables <tt>foos</tt> and <tt>bars</tt>, I created a join table <tt>bars_foos</tt>. (Rails expects the join table&#8217;s name to be a concatenation of the two table names <i>in alphabetical order</i>). </p>
<p><h4>Reading attributes</h4>
<p>I added an attribute <tt>baz</tt> to <tt>bars_foos</tt>. I had originally tried to access it with:</p>
<pre class="wrong">myFoo.baz</pre>
<p>but that didn&#8217;t work. It turns out that I needed to force Rails to do the join before accessing the attribute, which makes sense when you think about it (but it took me a while before my little brain thought about it correctly):</p>
<pre class="right">myFoo.bars.baz</pre>
<p><h4>Adding attributes</h4>
<p>Use the well-documented <tt>push_with_attributes</tt> method:</p>
<pre>myFoo.bars.push_with_attributes(bar, :baz => &quot;fez&quot;)</pre>
<p><h4>Updating attributes</h4>
<p>Apparently you can&#8217;t. Luckily, in my app the attribute is immutable. Other people have suggested deleting and re-inserting the row which I imagine shouldn&#8217;t be a problem for most applications. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2006/02/12/join-tables-with-attributes-in-rails/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Setting XSL Parameters from Javascript in AJAXSLT</title>
		<link>http://www.eahanson.com/2005/10/23/setting-xsl-parameters-from-javascript-in-ajaxslt/</link>
		<comments>http://www.eahanson.com/2005/10/23/setting-xsl-parameters-from-javascript-in-ajaxslt/#comments</comments>
		<pubDate>Sun, 23 Oct 2005 23:34:22 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[XSL]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=6</guid>
		<description><![CDATA[Recently, I was trying to pass some info from my Javascript into my XSL stylesheet. Here's how I finally did it.]]></description>
			<content:encoded><![CDATA[<p>Google&#8217;s <a href="http://goog-ajaxslt.sourceforge.net/">AJAXSLT</a> library is very nice, but it is lacking in documentation and sometimes hard to figure out.</p>
<p>
Recently, I was trying to pass some info from my Javascript into my XSL stylesheet. Here&#8217;s how I finally did it:</p>
<p>
<i>(Quick note: in real life, I don&#8217;t name variables &quot;myThis&quot; and &quot;myThat&quot;; I&#8217;m just doing it here to make it clear what is my code and what is part of XSL, AJAXSLT, Javascript, etc.)</i></p>
<p><h4>The XSL</h4>
<pre>
&lt;xsl:stylesheet version=&quot;1.0&quot;
    xmlns:xsl=&quot;http://www.w3.org/1999/XSL/Transform&quot;&gt;
  &lt;xsl:param name=&quot;myParam&quot; select=&quot;&apos;myDefault&apos;&quot;/&gt;
  &lt;xsl:template match=&quot;/&quot;&gt;
    myParam: &lt;xsl:value-of select=&quot;$myParam&quot;/&gt;
  &lt;/xsl:template&gt;
&lt;/xsl:stylesheet&gt;
</pre>
<p><h4>The Javascript</h4>
<pre>
var myContext = new ExprContext(myXml);
myContext.setVariable(&quot;myParam&quot;,
    <b>new StringValue(&quot;the value for myParam&quot;)</b>);
xsltProcessContext(myContext, myXsl, myRootElement);
</pre>
<h4>The Trick</h4>
<p>So the big trick, which I figured out after reading lots of the AJAXSLT code, was to set the parameter to a <tt>StringValue</tt>, rather than a regular Javascript String. <tt>StringValue</tt> is part of AJAXSLT. <b>Update:</b> Will, in the comments below, mentions that there are four total types: <tt>StringValue</tt>, <tt>NumberValue</tt>, <tt>BooleanValue</tt> and <tt>NodeSetValue</tt>.</p>
<h4>Troubleshooting</h4>
<p>Don&#8217;t forget to declare your parameter with <code>&lt;xsl:param.../&gt;</code> before using it with <code>&lt;xsl:value-of select=&quot;$...&quot;/&gt;</code>, otherwise Ajaxslt will blow up in the middle of rendering.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2005/10/23/setting-xsl-parameters-from-javascript-in-ajaxslt/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>A Mac OS X Web Browser for JavaScript Testing</title>
		<link>http://www.eahanson.com/2005/10/02/writing-a-mac-os-x-web-browser-for-javascript-testing/</link>
		<comments>http://www.eahanson.com/2005/10/02/writing-a-mac-os-x-web-browser-for-javascript-testing/#comments</comments>
		<pubDate>Sun, 02 Oct 2005 21:34:22 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[My Software]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=5</guid>
		<description><![CDATA[I was playing with a very JavaScript-heavy web page and wanted my automated tests to use a real web browser. I tried running them inside Safari, but it's a bit annoying to have my tests take control of my browser. Plus, there were caching issues. So I wrote a very simple web browser.]]></description>
			<content:encoded><![CDATA[<p>I was playing with a very JavaScript-heavy web page and wanted my automated tests to use a real web browser. I tried running them inside Safari, but it&#8217;s a bit annoying to have my tests take control of my browser. Plus, there were caching issues. So I wrote a very simple web browser with the following features:</p>
<ul>
<li>It uses WebKit so it works just like Safari</li>
<li>It doesn&#8217;t do any caching</li>
<li>It takes a URL command-line parameter and loads it</li>
<li>It quits immediately <b>[update: I added a noquit paramter in case you don't want it to quit immediately]</b></li>
</ul>
<p>It currently has the following problems:</p>
<ul>
<li>It&#8217;s uses AppKit, so it has a UI. I really wanted it to be a command-line-only app, but I haven&#8217;t yet figured out how to do that</li>
<li>Invoking it is painful: <tt>/Applications/wkget.app/Contents/MacOS/wkget -url http://www.apple.com</tt></li>
<li>It only tests WebKit; one day I might make a version that uses Firefox&#8217;s JavaScript engine</li>
</ul>
<p>I called it <b>wkget</b> and you can <a href="/sw/wkget.zip">download it here</a>. [<b>Update:</b> I had an incorrectly-built version up here for a while. It's fixed now.]</p>
<p>Here&#8217;s the code (sorry for the formatting; I had to wrap everything for this very narrow column):</p>
<pre>
@implementation WkgetController
- (void)awakeFromNib {
  [self gotoUrl:self];
  [webView setFrameLoadDelegate:self];
}

- (IBAction)gotoUrl:(id)sender {
  NSString *url = [[NSUserDefaults standardUserDefaults]
      stringForKey:@"url"];

  if (url == nil) {
    url = @"http://www.apple.com/";
  }

  NSURLRequest *request = [NSURLRequest
      requestWithURL:[NSURL URLWithString:url]
      cachePolicy:NSURLRequestReloadIgnoringCacheData
      timeoutInterval:1.0];

  [[webView mainFrame] loadRequest:request];
}

- (void)webView:(WebView *)sender
  didFinishLoadForFrame:(WebFrame *)frame {
  if (![[NSUserDefaults standardUserDefaults]
      boolForKey:@"noquit"]) {
    [NSApp terminate:self];
  }
}
@end
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2005/10/02/writing-a-mac-os-x-web-browser-for-javascript-testing/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>How To Use Google&#8217;s Ajaxslt Library</title>
		<link>http://www.eahanson.com/2005/09/30/how-to-use-googles-ajaxslt-library/</link>
		<comments>http://www.eahanson.com/2005/09/30/how-to-use-googles-ajaxslt-library/#comments</comments>
		<pubDate>Fri, 30 Sep 2005 15:53:48 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[XSL]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=4</guid>
		<description><![CDATA[Google has written an open-source library called Ajaxslt that implements XPath and XSLT in Javascript. It took a bit of playing to get it to work.]]></description>
			<content:encoded><![CDATA[<p><a href="http://search.yahoo.com/search?p=google&#038;sm=Yahoo%21+Search&#038;fr=FP-tab-web-t&#038;toggle=1&#038;cop=&#038;ei=UTF-8">Google</a> has written an open-source library called <a href="http://goog-ajaxslt.sourceforge.net/">Ajaxslt</a> that implements <a href="http://www.w3.org/TR/1999/REC-xpath-19991116">XPath</a> and <a href="http://www.w3.org/TR/1999/REC-xslt-19991116">XSLT</a> in Javascript.</p>
<p>It took a bit of playing to get it to work. Here&#8217;s some sample code. (I forgot to write it up when I did it, so I might have left something out.)</p>
<h3>Setup</h3>
<p>First, include the scripts. <b>Note:</b> make sure to have a closing <tt>&lt;/script&gt;</tt> tag rather than doing <tt>&lt;script ... /&gt;</tt> because that silently fails on some browsers (yay!).</p>
<pre>
&lt;!-- AJAXSLT --&gt;
&lt;script src=&quot;/ajaxslt/misc.js&quot;
    type=&quot;text/javascript&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;/ajaxslt/dom.js&quot;
    type=&quot;text/javascript&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;/ajaxslt/xpath.js&quot;
    type=&quot;text/javascript&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;/ajaxslt/xslt.js&quot;
    type=&quot;text/javascript&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;/ajaxslt/xpathdebug.js&quot;
    type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
</pre>
<h3>Using Ajaxslt for XSL Transformation</h3>
<p>For XSL transformation, you&#8217;ll need three things: some XML, some XSL, and a node (or DocumentFragment presumably) to place the result in. One way to get the XML and XSL is to load them through an XmlHttpRequest. Ajaxslt also provides a couple of handy functions to convert between text and XML:</p>
<pre>
var myText = &quot;...&quot;;
var myXml = xmlParse(myText);
var myTextAgain = xmlText(myXml);
</pre>
<p>Once you&#8217;ve got the XML and XSL, you&#8217;re ready to do the transformation:</p>
<pre>
var myNode = document.getElementById(&quot;myId&quot;);
xsltProcessContext(new ExprContext(myXml), myXsl, myNode);
</pre>
<p>Your node should now contain the result of the transformation.</p>
<h3>Using Ajaxslt for XPath</h3>
<p>You can also use Ajaxslt for XPath evaluation, which is really handy when writing tests on your rendered document:</p>
<pre>
var expr = xpathParse(myXpathString);
var result = expr.evaluate(new ExprContext(myXmlDocument));
var resultAsString = result.stringValue();
</pre>
<h3>Troublshooting</h3>
<p><b>The order of the JavaScript include statements matters:</b> if Ajaxslt is giving strange errors, make sure the scripts are included in the exact same order as  in the Setup section of this page.</p>
<p><b>Make sure you know what you&#8217;re passing to Ajaxslt:</b> if you&#8217;re sending <tt>request.responseXML</tt> to a method, don&#8217;t look at <tt>request.responseText</tt> to see what you&#8217;re passing; use <tt>xmlText(request.responseXML)</tt>.</p>
<p><b>The root element isn&#8217;t selectable:</b> if your XML is <tt>&lt;foo id=&quot;myId&quot;&gt;...&lt;/foo&gt;</tt>, then  you don&#8217;t select foo&#8217;s id with <tt>/foo/@id</tt> but rather with just <tt>@id</tt>.</p>
<p><b>Case matters in xpath:</b> If you&#8217;re evaluating an xpath expression on an HTML DOM, note that the node names are most likely uppercase. So do <tt>/HTML/BODY</tt> instead of <tt>/html/body</tt></p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2005/09/30/how-to-use-googles-ajaxslt-library/feed/</wfw:commentRss>
		<slash:comments>31</slash:comments>
		</item>
		<item>
		<title>Writing a Ruby Test Suite that RDT and Test::Unit Can Understand</title>
		<link>http://www.eahanson.com/2005/09/20/writing-a-ruby-test-suite-that-rdt-and-testunit-can-understand/</link>
		<comments>http://www.eahanson.com/2005/09/20/writing-a-ruby-test-suite-that-rdt-and-testunit-can-understand/#comments</comments>
		<pubDate>Tue, 20 Sep 2005 17:33:10 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Ruby on Rails]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=3</guid>
		<description><![CDATA[I'm working on a Ruby project using Eclipse and RDT. RDT provides a couple useful features, including support for running unit tests with Test::Unit. Unfortunately, it's pretty primitive at the moment. One big problem is that it can't run all your tests. So we wrote a test suite class that runs all tests and can be run from Test::Unit:.]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m working on a <a href="http://www.ruby-lang.org">Ruby</a> project using <a href="http://www.eclipse.org">Eclipse</a> and <a href="http://rubyeclipse.sourceforge.net/">RDT</a>. RDT provides a couple useful features, including support for running unit tests with Test::Unit. Unfortunately, it&#8217;s pretty primitive at the moment.</p>
<p>One big problem is that it can&#8217;t run all your tests. So we wrote a test suite class that runs all tests and can be run from Test::Unit:</p>
<pre>
require 'test/unit'

Dir.chdir(&quot;test&quot;)
@@test_files = Dir.glob(&quot;**/*_test.rb&quot;)
@@test_files.each {|x| require x} 

class TestSuite
  def self.suite
    suite = Test::Unit::TestSuite.new

    @@test_files.each do |filename|
      File.open(filename) do |file|
        file.each_line do |line|
          if match = (/class\s+(\w+Test)/).match(line)
           suite &lt;&lt; eval(match[1] + &quot;.suite&quot;)
          end
        end
      end
    end

    return suite

  end
end
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2005/09/20/writing-a-ruby-test-suite-that-rdt-and-testunit-can-understand/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Installing Ruby on Rails on Mac OS X 10.4 (Tiger)</title>
		<link>http://www.eahanson.com/2005/09/07/instaling-ruby-on-rails-on-mac-os-x-104-tiger/</link>
		<comments>http://www.eahanson.com/2005/09/07/instaling-ruby-on-rails-on-mac-os-x-104-tiger/#comments</comments>
		<pubDate>Wed, 07 Sep 2005 16:55:29 +0000</pubDate>
		<dc:creator>Erik</dc:creator>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Ruby on Rails]]></category>

		<guid isPermaLink="false">http://www.eahanson.com/weblog/?p=2</guid>
		<description><![CDATA[I had a hell of a time getting Ruby on Rails to work on Mac OS X 10.4 (Tiger). Here's what ended up working for me.]]></description>
			<content:encoded><![CDATA[<p>I had a hell of a time getting <a href="http://www.rubyonrails.org/">Ruby on Rails</a> to work on <a href="http://www.apple.com/macosx/">Mac OS X 10.4 (Tiger)</a>.</p>
<p>Here&#8217;s what ended up working for me. It&#8217;s based on the <a href="http://jamie.blogthing.com/2005/05/12/installing-rails-on-a-fresh-tiger/">instructions on Jamie&#8217;s Blogomatic</a>.</p>
<p>1. Install the XCode tools from the Tiger DVD.</p>
<p>2. Download and run the Ruby on Rails installer from: <a href="http://users.tpg.com.au/aarnold/Ruby%20on%20Rails.zip">http://users.tpg.com.au/aarnold/Ruby%20on%20Rails.zip</a>.</p>
<p>3. Download and run the MySQL installer (I used version 4.1.11): <a href="http://dev.mysql.com/downloads/index.html">http://dev.mysql.com/downloads/index.html</a>.</p>
<p>4. Start mysql:  <tt>bash$ <b>sudo /usr/local/mysql/bin/mysqld_safe</b></tt></p>
<p>5. Put mysql into the background: <tt><b>ctrl-z</b></tt> and then <tt>bash$ <b>bg</b></tt></p>
<p>6. Update gems: <tt>bash$ <b>sudo gem update</b></tt></p>
<p>7. Install the mysql gem <b>from the remote source</b>: <tt>bash$ <b>sudo gem install -r mysql -- --with-mysql-dir=/usr/local/mysql</b></tt></p>
<p>8. Compile and install the MySQL API module from here: <a href="http://www.tmtm.org/en/mysql/ruby/">http://www.tmtm.org/en/mysql/ruby/</a>.</p>
<p>9. All done! </p>
]]></content:encoded>
			<wfw:commentRss>http://www.eahanson.com/2005/09/07/instaling-ruby-on-rails-on-mac-os-x-104-tiger/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

