My attempt at sake task management
I’ve used sake intermittently in my workflow. It competes against me writing helper/admin scripts in my ~/ruby/bin folder. Normally, executable Ruby scripts have won. But I think I have a new solution that could make sake a permanent winner for me.
Ruby scripts are easy to create and execute. You just open new file, change the TextMate grammar to ‘Ruby’, type ‘rb’ and press TAB and you’re off and running (the ‘rb’ snippet generates #!/usr/bin/env ruby or a variation of that). You then make the file executable and BAM! you can run the script from any folder in your environment.
Sake tasks are more annoying to write. After creating a new file, you need to create the namespace and task wrappers for your functionality, such as:
namespace 'foo' do
namespace 'bar' do
desc "This task ..."
task :baz do
end
end
end
Your task isn’t instantly executable either. After each change, you need to uninstall the task (sake -u foo:bar:baz) and then reinstall the sake file (sake -i foo/bar/baz.sake) and then run it (sake foo:bar:baz). Perhaps there’s a way to inline edit a sake task, but I can’t see it from the help options.
But once you’ve got your script installed in sake, you get all the wonders that sake provides: a named list (with summary) of tasks (sake -T) and the ability to run those tasks anywhere. Ok, that’s really only one advantage over standard Ruby scripts. But I like it. Oh, namespacing. The baz task exists in a namespace foo:bar. That’s nice too.
So to make me happy, I need a solution to the dubious “create-install-execute” process above. I also want the raw source for all my sake tasks in one place so I can fix/add/change them, reinstall them and move on with my life. I want simple.
So I’ve forked Chris Wanstrath’s empty sake-tasks repo (mine) and added some infrastructure for managing sake tasks. Of course the repo itself is the repository for my sake tasks (which includes a lot from Luke Melia), but most importantly it has a single rake task to reinstall all the tasks without any manual fuss.
The rest of this article assumes you want to have your own repository for your own sake tasks hosted on github. This paragraph is probably unnecessary, but I don’t want to be accused of not being mildly thorough.
Fork the sake-tasks repo
For thoroughness and a chance to demonstrate some gold-medal git-fu, I’ll show two ways: fork my repo and forking the original repo from Chris and pulling my stuff into yours. It’s git, it’s distributed, you can do anything.
If you want to fork my repo and skip a nifty git lesson, go to my sake-tasks repo and click “fork”. Then follow the clone instructions as you normally do when you are blatantly, systematically duplicating someone else’s hard work, using a command that will look something like:
git clone git@github.com:your-github-username/sake-tasks.git
Now, lazy man, you can skip to the next step.
If you want to flex your git-fu, then go and fork Chris’ repo instead. Again, follow the clone instructions.

Now take a moment to reflect on just how empty your repository is. A fine moment in open-source where you’ve essentially cloned an empty repository. Hardly worth the effort, but since Chris is a creator of github then if he creates an empty repository then who am I to disagree. Empty it shall start.
Now let’s pull in the code and tasks from my repo. My repo could be any git repo anywhere on the tubes.
One way you could pull my code into your local repository is to add my repo as a remote and then pull in the goodness:
git remote add drnic git://github.com/drnic/sake-tasks.git git pull drnic master
This is useful if you ever plan on re-pulling from a target repo again in the future.
If you just want to pull from someone’s repo one time only, then you can merge these two lines together:
git pull git://github.com/drnic/sake-tasks.git master
If you get occasional pull requests for your projects, then the latter option is handy to know.
Your local repo is now different to your remote repo (your fork on github) so push it back to your remote:
git push origin master
Installing the sake tasks
I originally created my sake-tasks fork so I could store a git:manpages:install task. I’ve just upgraded to git 1.6 (note to self: I want an ‘upgrade to latest git version via src’ task; UPDATE the repository now includes a git:src:install task to do this) and found some instructions for installing the pre-built manpages. Then I got over excited and refactored all of Luke Melia’s git+mysql+ssh tasks in to my repo so it looked like I’d done a lot of work.
To install all the tasks, first install sake:
sudo gem install sake
Then run the install task (check below for the list of tasks to be installed):
WARNING: This will uninstall any tasks you already have by the same name.
rake install
Now, check that your sake tasks are installed:
sake -T
Gives you:
sake git:analyze:commits:flog_frequent # Flog the most commonly revised files in the git history sake git:close # Delete the current branch and switch back to master sake git:manpages:install # Install man pages for current git version sake git:open # Create a new branch off master sake git:pull # Pull new commits from the repository sake git:push # Push all changes to the repository sake git:status # Show the current status of the checkout sake git:topic # Create a new topic branch sake git:update # Pull new commits from the repository sake mysql:dump # Dump the database to FILE (depends on mysql:params) sake mysql:load # Load the database from FILE (depends on mysql:params) sake ssh:install_public_key # Install your public key on a remote server.
Sexy.
Adding new recipes/tasks
The installer rake task rake install works by assuming that each .sake file contains one sake task. This allows the rake task to uninstall the tasks from sake first, and then re-install it (sake barfs if you attempt to reinstall an existing task). Without the one-task-per-file rule, the solution would be to load all the sake tasks as rake tasks into memory. But I like one-task-per-file; it seems clean.
So, to create a task foo:bar:baz, you’ll need to add a folder foo/bar and create a file baz.sake inside it. Within that file you would then specify your task using namespace and task method calls:
namespace 'foo' do
namespace 'bar' do
desc "This task ..."
task :baz do
end
end
end
To install new tasks or reinstall modified tasks, just run the rake task (rake install or rake).
TextMate users
The latest Ruby.tmbundle on github includes a task command that generates the above namespace/task snippet based on the path + file name. That is, inside the foo/bar/baz.sake file, make sure your grammar is ‘Ruby’ or ‘Ruby on Rails’ and then type “task” and press TAB. The above snippet will be generated ready for you to specify your task.
Summary
So now I have a single place for all my original sake source and a simple rake task to re-install the tasks if I add or modify them. And because its all in one git repo, if other people fork it and add their own tasks then I can steal them.
Dr Nic for hire
For the last few months I’ve been contracting to three different projects, and then recently two of them finished, with the third being an on-going project for the last two years which will be launched in a months’ time.
So I’m excited to start looking for a new consulting project to work on.
Travel or relocation is an option. Full-time salary is probably not an option. Either full-time or several days-per-week is an option. Making me wear a uniform and serve hamburgers through a small window is not an option. Working in an office is an option. Working on an oil rig in the Indian Ocean is not an option.
My professional background is in telecom billing systems and third-party integrations, including a roving two year stint in various countries between 2005 and 2007 (India, Sweden, Netherlands and Germany). One part of me is excited by the idea of moving overseas again. The other part of me includes two small children and a ten-hour plane flight to get anywhere.
I do have a PhD from the University of Queensland, though the only proof of it I can find on the InterTubes is on my supervisor’s 1990’s-themed website.
In recent years I’ve worked on several Rails projects, including developing and coaching the founders of imindi.com, a next-generation service for individual and collaborative thinking. I’ve run a bunch of Introduction to Rails workshops. I’m with a small group of Australians who recently started soliciting to develop iPhone SDK + Rails applications (under the brand Mocra; also, see Garath Townsend’s free app I Am Here which is in the top 50 free apps at the moment).
I’ve written dozens and dozens of blog articles (see an amusing graph); authored or hijacked or nudged along dozens and dozens of Ruby projects, JavaScript thingies, and TextMate bundles (see my github profile). I’ve even been allowed to talk at various conferences (my favourite talk was the RubiGen session at RubyConf 2007 [video]), including the exciting Rails Summit Latin American in Brazil later this year.
Open Source is for free and Consulting is for money. In both cases I like to work hard, write lots of code, and accumulate and share knowledge.
It would be wonderful if you can share this post with anyone who would benefit from having me on their team.
I can be contacted at drnicwilliams@gmail.com or on +61 412 002 126 in GMT+10 time zone.
Unit Testing iPhone apps with Ruby: rbiphonetest

Everything to love about Ruby: the concise, powerful language; the sexy testing frameworks; and finally, the people.
Everything to love about Objective-C: hmmm; well…; and finally, its the only high-level language you can use to write iPhone apps.
On iPhone 2.0, to arrive on the 11th of July, you cannot run RubyCocoa. But you can run it on your Mac, so let’s use it to unit test your Objective-C classes. This tutorial shows you how to get started using a new project rbiphonetest [GitHub | Lighthouse | Google Group]
If you followed some of my recent tweets, this project was previously called “iphoneruby”. And alas, the screencast also calls it “iphoneruby” but that was a crap name. People thought it was a way to run Ruby on the iphone. I can’t do that yet. So, a far better name is ‘rbiphonetest’. [track on summize]
Even if you’ve never touched Objective-C, Cocoa, the iPhone SDK, nor RubyCocoa I recommend watching the video anyway. It should give you hope that if you make the transition to iPhone development you don’t have to go alone without Ruby: your trusty swiss army knife of language/libraries/tools.
The screencast is also available in high-def video (55Mb QuickTime)
Unit Testing iPhone apps using Ruby from Dr Nic on Vimeo.
Installation and Usage
To summarize the video, but change ‘iphoneruby’ to ‘rbiphonetest’, you install the framework via RubyGems:
sudo gem install rbiphonetest
Then change to your project’s folder and install the test framework:
rbiphonetest .
Finally, for each generic, non-UIKit-framework-using class you want to test:
script/generate model WidgetModel
Then write your tests in test/test_widget_model.rb
Supported Cocoa & iPhone frameworks
The mysterious, magical premise upon which rbiphonetest depends is possibly erroneous: that your Objective-C class can be compiled and tested against your OS X/Intel frameworks, and if your tests pass you assume you can then compile and include your class with the the iPhone/ARM frameworks.
I’m willing to go with this assumption until its proven dangerously flawed by some angry 20-year veteran of NextStep/Cocoa/iPhone. But really, how different could NSString be on the iPhone versus your Mac?
Fortunately there is one way to check for significant differences between your available Mac-based frameworks, such as Cocoa, and the iPhone-based frameworks, such as UIKit. We need to compare the framework names, header files and method signatures.
So for example, you cannot currently unit test any class that depends on/includes the UIKit framework. Why? It doesn’t exist on your Mac, so the Mac/Intel compiler cannot link it in. We’re compiling and running our tests with RubyCocoa, which itself is built against the Mac/Intel frameworks, not the iPhone frameworks. Hell, Laurent doesn’t even own an iPhone
[Laurent is the Apple-employee maintainer of RubyCocoa and the newer MacRuby]
Similarly, its no use including/linking the Cocoa framework into your Objective-C class. Why? It doesn’t exist on the iPhone. It has its own UI frameworks, collectively called ‘UIKit’.
So for the moment we cannot test UI-related, iPhone-API-specific code. But we can test generic Objective-C. That’s better than a kick in the teeth. Surely. I mean, in the teeth… that’d friggin’ hurt.
“Fair enough Dr Nic, so which frameworks can my code use and yet still unit test it with your oh-so-special test library thingy?” Keep your pants on, I’m getting there. [ref]
To the best of my ability, I’ve compared the two sets of frameworks and listed the available Frameworks that are available on both the iPhone and your Mac. There are about a dozen. The most important is called ‘Foundation’. It holds gold nuggets like ‘NSString’.
The list of platform differences is on the wiki as a reference.
Note, this list doesn’t guarantee that any two framework classes - the iPhone and matching Mac framework - will behave the same. This list is compiled by finding the set of Frameworks with the same name on both platforms, e.g. Foundation.
Then it compares the set of public header files (Foundation.framework/Headers/*.h files) This comparison is by method signature. It pulls all lines from each header that start with + or - (+ is a class method and - is an instance method in Objective-C) and compares the two lists. If there is a single difference in the method signatures of the header files in the two platforms it is marked on the wiki page. You’ll need to look at the two header files yourself to see the differences. Some header files are ugly. C-based anything starts ugly and goes down from there, I think.
Python testing of iPhone Objective-C?
In the Python world there is PyObjC, a bridge-based twin to RubyCocoa. If you are a Python developer you could easily port this project to use PyObjC I would think. Ping me if you are attempting this and need any help.
Summary
I think this project can give Ruby developers a happy place to work from as they write their Objective-C/iPhone code. You still need to wire up your UI views and controller classes manually, but if you push all the “oooh that code really needs some tests” classes away from the UI-dependent frameworks then you can hook it up to rbiphonetest and write your tests in Ruby.
Currently the generator creates test/unit test stubs. I personally then add the Shoulda gem into my test_helper.rb for my apps. If an rspec and/or test/spec developer can help with adding support to the generators I’m certain the large rspec user-base would be happy campers.
Similarly, someone might like to investigate using MacRuby to run the tests instead of RubyCocoa. Fast tests vs slow tests. You choose.
What the?
Sometimes I re-read what I’ve written and notice things that don’t seem to make sense, but are in my vocabulary nonetheless. Yep, the things you learn living in Australia.
“Keep your pants on” - this seems to imply that until I mentioned otherwise you were about to take your pants off. Hardly relevant at any stage during this article, we’d both agree. Most code-based blog articles are “pants on”. This phrase means “don’t get upset”. You can try to figure out how you go from “don’t get upset” to “keep your pants on”. I have no idea.
What is *jour and why they are killer apps for RailsCamp08
RailsConf 2008 was a few weeks ago, and I didn’t go. I made babies instead. But if you ignore the whoohaa of MagLev and DHH’s Surplus talk, there was one piece of genuine, new awesomeness that came out of RailsConf. This awesomeness, plus Twitter created at least 6 new projects in the last month. These apps are going to make RailsCamp awesome.
That is if RailsConf never happened this piece of delicious coding happiness never would have occurred. It was built at RailsConf, by RailsConf attendees. It was built to solve a problem that only happens at conferences. Crappy internet.
The answer is gitj… no wait, I have more introductory story. [skip it]
Instead of going to RailsConf this year I “get” to go to RailsCamp, which is like a conference for Rails developers, but without the conference part. What’s left without the conference? Hacking, Guitar Hero3, rejectconf-esque talks, and alcohol. Its’ the Lord’s way of saying “you’re in the right profession.”
RailsCamp has two minor drawbacks, since it is held out in the country. One, there is no internet connection. Two, there is no internet connection. Now I know that is only one draw back but I thought it was such a big one it was worth mentioning twice [oh I miss Red Dwarf].
So, 6 months ago we solved one internet-related problem: we turned to Git and git-svn clone to take all our svn repositories with us camping. 6 months on, git has taken over the Ruby OSS world, thanks mostly to GitHub which made remote hosting of git repos easy peasy. But, Github won’t be with us at RailsCamp. So we’re back to where we were 6 mths ago - being gumbies at hosting remote git repos.
So the question to be solved: how do you host a git repository, or indeed, lots of git repositories from your development machine; and conversely, how do you find someone else’s git repositories on their machine and clone it?
And whilst we’re at it… how do you easily share the RubyGems you have installed on your machine with other people, and how do you easily find gems on other people’s machines that you don’t currently have, if you don’t have access to rubyforge.org or github.com to get them?
And furthermore… if you’re running a fun little web app off your dev machine, how can you easily advertise it and other people find it and use it? “Hey, what’s your machine’s name?” “drnic-mac” “Thanks. ..pause… Um, what port’s the app on?” “4050”…
Same problem. Local devs, running local services, but how to share with everyone in the room?
Bonjour
The core solution is a technology created by the boffins at Apple that is now completely open-source (afaik): Bonjour. From wikipedia:
Bonjour, formerly Rendezvous, is Apple Inc.’s trade name for its implementation of Zeroconf, a service discovery protocol. Bonjour locates devices such as printers, as well as other computers, and the services that those devices offer on a local network using multicast Domain Name System service records.
In the Ruby world there are two RubyGems to integrate Bonjour into your app: dnssd and net-mdns. The former is only for Mac OS X, and the latter is a Ruby-only implementation that can be used anywhere. Bingo.
gitjour
So, Chad Fowler, Evan Phoenix, Rich Kilmer and a cast of others started a humble project to solve the first problem: sharing git repositories across a local network, called gitjour.
This project became a happy hack place for many many people.
NOTE: I’ve included all the installation instructions that should work. If they don’t then try building the gems from src.
On OS X:
sudo gem install dnssd
sudo gem install gitjour
or from source
sudo gem install dnssd
git clone git://github.com/chad/gitjour.git
cd gitjour
rake install_gem
On Linux/Windows:
sudo gem install net-mdns
sudo gem install_gem nogoth-gitjour --source=http://gems.github.com
or from source
sudo gem install net-mdns
git clone git://github.com/nogoth/gitjour.git
cd gitjour
rake install
The gitjour project uses dnssd gem, with mac os x dependencies, and GitHub user ‘nogoth’ has ported the project to use the net-mdns gem for Linux/Windows.
Now, open up two terminal windows so you can emulate both sides of “serving” and “finding” a git repository.
Window 1:
cd a/git/project
gitjour serve
Window 2:
gitjour list
gitjour clone project
In Window 1, you can also serve an entire folder of repositories. Lots of them all at one time. Go into your project’s parent folder and run gitjour serve and you’ll see it Registered dr_nic_magic_models on port 9418. Starting service. a lot. In Window 2, you just gitjour clone xxx the project you want.
See, genius. No mucking around with Apache or setting up local “remote” repositories in your public folder so people can pull from it. Just run gitjour serve from your parent folder and you are instantly hosting ALL of your git repositories. Top Tip: move any private work into another folder first wink
So, that’s the git-sharing problem fixed.
pastejour
After RailsConf, twitter was alive with the coolness of gitjour, and very quickly John Barnette created pastejour to publish and find single pastie’s of code, given that without the internet you don’t have pastie websites.
To install on OS X:
sudo gem install dnssd
sudo gem install jbarnette-pastejour --source=http://gems.github.com
or install from src:
sudo gem install dnssd
git clone git://github.com/jbarnette/pastejour.git
cd pastejour
rake install
On Linux/Windows:
sudo gem install net-mdns
sudo gem install nogoth-pastejour --source=http://gems.github.com
or install from src:
sudo gem install net-mdns
git clone git://github.com/nogoth/pastejour.git
cd pastejour
rake install
Pastejour publishes a single block of text until one person retrieves it. Essentially, you yell at the person that there is a paste for them and they get it.
Window 3:
pastejour <<< "Hello Window 2"
Window 4:
$ pastejour -f
Searching for servers (3 seconds)
Found pastejour at 'drnic'
$ pastejour drnic
(drnic from drnic-mac.local.:42424)
Hello Window 2
gemjour
After John’s announcement on Twitter, I asked “do we have a gemjour yet for sharing gems?”
Later that day, Evan Phoenix replies with “@drnic Enjoy! http://github.com/evanphx/gemjour/tree/master”
This gem serves up all your gems to anyone who wants to install them from you. Don’t have merb but want to try it? Grab it from someone else.
To install on OS X:
sudo gem install dnssd
sudo gem install evanphx-gemjour --source=http://gems.github.com
or install from src:
sudo gem install dnssd
git clone git://github.com/evanphx/gemjour.git
cd gemjour
rake install
On Linux/Windows:
sudo gem install net-mdns
sudo gem install nogoth-gemjour --source=http://gems.github.com
or install from src:
sudo gem install net-mdns
git clone git://github.com/nogoth/gemjour.git
cd gemjour
rake install
Window 5:
gemjour serve
Window 6:
gemjour list
gemjour list someuser
gemjour diff someuser -- shows diff btw you and them
sudo gemjour install someuser newgem -- install newgem from someuser
sudo gemjour install_diff someuser -- installs all gems from someuser you don't have
Go around the room and steal everyone gems in a few minutes.
appjour
Room full of people hacking up cool things? Publish and find them easily with appjour, written by Lachie Cox
To install on OS X:
sudo gem install dnssd
sudo gem install lachie-appjour --source=http://gems.github.com
or install from src:
sudo gem install dnssd
git clone git://github.com/lachie/appjour.git
cd appjour
rake install
To use it, after you’ve started a web app that you want to publish, open another window to run appjour:
Window 7:
cd some/web/app
script/serve -p 4000 # or whatever for your framework
appjour leet_app 4000
Window 8:
$ appjour
Searching for servers (3 seconds)
found web app called 'leet_app'
$ appjour leet_app
... app loaded into browser
Mongrel publishing bonjour events
Courtenay hacked in a few lines to mongrel_rails so that each mongrel publishes itself to Bonjour.
The magic of this is that mongrels, or the applications behind them, could use Bonjour to self-discover each other within a mongrel cluster, and then talk to each other.
One man’s solution is another man’s problem
As new people join the circus, new gems, git repositories, web apps and pasties are added and the new problem is: how to keep in touch with the newness?
First solution: publish them to Growl with dejour by Aaron Patterson, tagged “Discover awesome services near you.”
On Mac OS X:
sudo gem install dnssd
sudo gem install aaronp-dejour --source=http://gems.github.com
or install from src:
sudo gem install dnssd
git clone git://github.com/aaronp/dejour.git
cd dejour
rake install
There is no fork of dejour for Linux/Windows yet.
Window 9:
$ dejour
All your current services should appear as Growl notifications (from Windows 1, 3, 5 and 7 above).
But Growl notifications fly-by pretty quickly. How to keep track?
Finally, *jour to the rescue (starjour)
I really felt that this problem was best solved with a GUI. Mostly cause I was keen to try out RubyCocoa. I don’t think I did productive work for a week, whilst I read books, tutorials and anything I could find on RubyCocoa, Cocoa, Xcode + Interface Builder, Objective-C and after trying and failing a few times starjour was created. By Lachie Cox (who also wrote appjour).
F@#$@#.
Really, I just couldn’t get my head around Cocoa Bindings at the time, but my experience of using RubyCocoa was very positive, once I’d learnt the underlying framework and the thinking-patterns of a Cocoa developer. Best book to read to learn Cocoa/RubyCocoa? The book is by Aaron Hillegass - Cocoa Programming for Mac OS X (3rd ed). After struggling with other people’s books and tutorials this one was wonderful. The “CollectionView” message in the image above was me telling Lachie that I’d figured out and used CollectionViews to replace his TableView. Now i just need to bind the bonjour message type (gitjour/gemjour etc) to an image. And more.
But, nonetheless, starjour (read: *jour as all these apps were collectively named on Twitter) did one job: it sat there showing which services were currently available.
Unlike all the others, this one used the native Cocoa framework for Bonjour, fwiw, and is doomed as an OS X-only app.
On Mac OS X, we don’t really have a place to download and run it, so grab it from src:
git clone git://github.com/lachie/starjour.git
cd starjour
rake run
This requires Xcode and RubyCocoa (available on OS X Leopard or via rubycocoa.com on sourceforge)
Wow! What just happened last month?
Chronologically: gitjour, pastejour, gemjour, dejour, starjour and appjour. And I think there are others. Its incredible.
Firstly, it is going to be an awesome RailsCamp - these tools are killer apps for an internet-less hackfest like RailsCamp, or massive internet-starved conferences like RailsConf.
Secondly, it has been very exciting to watch the creation of these projects over the last few weeks, with the *jour meme bubbling through twitter and irc. Another happy month for our open source communities.
Using Ruby within TextMate snippets and commands
I didn’t know you could run Ruby within TextMate snippets. As a consequence, a lot of the TextMate bundles I work on either have simplistic Snippets or the advanced code is run via Commands with code pushed into explicit Ruby files in the Support folder.
But sometimes I just want a clever snippet. For example, I want the ‘cla’ snippet to use the current filename to create the default class name instead of the current ‘ClassName’ default. I want default foreign key names to be meaningful.
I’ve now figured this out (thanks to Ciaran Walsh), and …
Um, lost already? Ok, let me show you via screencast on Snippets and Commands with Ruby (QuickTime (11Mb)):
TextMate Snippets running Ruby from Dr Nic on Vimeo.
Want to learn more about living with TextMate and Ruby?
The TextMate website has a series of videos, including one by the Ruby.tmbundle’s own James Edward Gray II (JEG2).
In addition, there is the latest TextMate for Rails 2 Peepcode written by myself and spoken by Geoffrey Grosenbach. Its cheap at $9, good value at $15.50, and perhaps overpriced in the $20-$30 range. Lucky its only $9.
The snippets used throughout the video
The current Ruby.tmbundle snippet (activated via ‘cla’):
class ${1:ClassName}
$0
end
An attempt to use regular expressions to convert the filename to a classname:
class ${1:${TM_FILENAME/[[:alpha:]]+|(_)/(?1::\u$0)/g}}
$0
end
The final snippet, with embedded Ruby to do the heavy lifting (note: added ’singluarize’ to the snippet):
class ${1:`#!/usr/bin/env ruby
require 'rubygems'
require "active_support"
puts ENV['TM_FILENAME'].gsub(/\.rb$/, '').camelize.singularize
`}
$0
end
Add this to your own Ruby.tmbundle, or clone mine (which is a clone of the original subversion repo).










