tabtab - freakin awesome!
November 23rd, 2008
One of the projects to come out of RailsCamp 4 (photos) (well apparently it started on the flight from Birsbane) is Dr Nic's super awesome autocompletions - tabtab.
It's basically context sensitive autocompletion for your ruby (and shell) world (and you can add your own completions). Think git-bash-autocompletion - for everything! To get it installed:
sudo gem install tabtab install_tabtab
Finally, add source ~/.tabtab.bash to the bottom of your ~/.profile
Here's an example of its use - using the github gem to checkout forks of one of my projects.
Adding your own completions
tabtab has a snazy ruby dsl to add completions. I tried adding them for the CI tool garlic and it took a whole minute!.
This was due to the fact you tabtab will parse your help file (if you tell it to) for the command line options, and the fact that I already had an abstraction in garlic to introspect the command-line/rake-tasks. If you don't have the latter it would take maybe 2 whole minutes.
After I pushed garlic, and on reinstall, I just had to run install_tabtab to have the garlic tabtab def automagically appear in my ~/.tabtab.bash.
Check out the comprehensive README. Thanks Dr Nic!
Gems from GitHub, by Doctor Otocat
November 12th, 2008
GitHub has a useful page on how to install gems directly from github.
A recent OS X example is
$ sudo gem install spicycode-rcov
Building native extensions. This could take a while...
Successfully installed spicycode-rcov-0.8.1.5.0
1 gem installed
Installing ri documentation for spicycode-rcov-0.8.1.5.0...
Installing RDoc documentation for spicycode-rcov-0.8.1.5.0...
MateWatch revisited '08
November 8th, 2008
The Background
Recording worked hours can be something of a chore, thankfully help is at hand. Matewatch is a bare-minimum solution to the problem, providing you with the data you need (hours spent on a textmate project) and nothing more. It's free, inherently, in the sense that the Ruby code is being given away.
Ian White produced MateWatch and we use it in-house for time tracking. I haven't really used it yet so can't vouch for it, but a year on I doubt Ray and Ian would be using it if it wasn't doing what they wanted.
For installation and use instructions see the original posting
Gotcha
If you're doing this ~ now you may hit upon issues with the current Macports version of Rubyosa (0.4.0) when trying to start Matewatch.
I hit upon this error:
Library/Ruby/Gems/1.8/gems/rubyosa-0.4.0/lib/rbosa.rb:530: [BUG] Bus Error
ruby 1.8.6 (2008-03-03) [universal-darwin9.0]
Abort trap
The solution is to install an older version ruby gem and modify a source file for rubyosa to use this package instead of the most recent.
sudo gem install libxml-ruby --version 0.3.8.4
sudo sed -i -e "s|require 'xml/libxml'|gem 'xml/libxml', '= 0.3.8.4'|" /Library/Ruby/Gems/1.8/gems/rubyosa-0.4.0/lib/rbosa.rb
My solution is from here and the issue is bug-reported here.
Another trivial update bash script
October 24th, 2008
A bash script to update macports system & installed ports, and the same for ruby gems.
Read the rest of this entryA trivial bash script
October 4th, 2008
Over the summer I was working with another company doing some build system work, which involved a lot of shell scripting (it was more to do with integration than make).
I’ve not got to the stage where I use Ruby on a daily basis for everything yet, which is where I used to be with Java (which was horrible for it!). Currently the weapon of choice is bash scripting, alas, I don’t have my handy O’Reilly book to reference anymore and I’m starting to forget things. Man pages and Google go a long way, but the reason I got the book in the first place was to save time!
Anyway, here’s what I’m using to update all my textmate bundles, which are a mixture of svn and git repos. Usual disclaimer: Y.M.M.V., don’t use it to baby-sit your kids while they play in traffic, etc. An obvious flaw with it is if git or svn fails you won’t get any feedback about it, and if the svn message changes it’ll stop working!
#!/bin/bash
cd ~/Library/Application\ Support/TextMate/Bundles/
export LC_CTYPE=en_GB.UTF-8
for f in ./*; do
cd "$f" #enter bundle dir
echo "Updating $f"
git pull &> /dev/null \
|| svnres=$(svn update) \
&& [[ $svnres = "Skipped '.'" ]] \
&& echo "*** Failed to update $f (is it a local setting not a repo?)"
cd .. #return to bundles dir
done
Feel free to comment / improve / re-implement in Ruby.
Updating inherit_views for edge rails
September 23rd, 2008
Rails edge is getting lots of changes under the hood, consequently garlic has been kicking up a fuss for response_for and inherit_views...
So, I've rewritten them! Both plugins are now a lot cleaner, and have less LOC, due to some great changes under the actionpack hood.
inherit_views | response_for
inherit_views is a pretty simple concept. When you subclass a controller in rails, it would be great if you controller inherited its views from the the view path of the superclass controller. Here's how to use it (or you can just Skip ahead to the rewrite).
Using inherit_views
If you want every controller to inherit it's views from parent controllers, then do this:
class ApplicationController < ActionController::Base
inherit_views
I think this is generally a bad idea, I use inherit views when I need to (when I subclass a controller):
class AccountImagesController < ImagesController
inherit_views 'images'
When you use inherit_views, all view types are supported, as are partials. This means that with the following setup:
images/_form.html.erb
images/edit.html.erb
account_images/_form.html.erb
rendering 'edit' from AccountImages controller will render account_images/_form.html.erb inside images/edit.html.erb.
Rewriting inherit_views for edge
The inherit_views plugin used to acheive this by alias_method_chaining render on the controller and the view, and it would substitute an inherited view if the standard view couldn't be found. Along the way, it got patched for various changes in the rails internal, and well... it got pretty messy.
Edge rails has had a lot of template love recently, and one of the great things is that the notion of picking a template to render now has a single point of entry: ActionView::Base#_pick_template. (there is also _pick_partial_template, but that just massages the filename into the partial format, then calls _pick_template).
This made me stoked because all of the shennanigans in inherit_views could be swept away, and replaced with #_pick_template_from_inherit_view_paths. The only thing left for the plugin to do is to manage the inherit view paths (these are stored on the controller).
A note for current inherit_views users, I've removed some of the API which I never used (render_parent, and some other things). Let me know if you really need that stuff...
inherit_views | response_for
Updating response_for for edge rails
September 23rd, 2008
Rails edge is getting lots of changes under the hood, consequently garlic has been kicking up a fuss for response_for and inherit_views...
So, I've rewritten them! Both plugins are now a lot cleaner, and have less LOC, due to some great changes under the actionpack hood.
inherit_views | response_for
Summary: response_for lets you specify a default response for an action. If that action (or a before_filter) doesn't perform a render or redirect, then the default response is rendered. You can also 'stack' responses - the latter ones override the former ones.
To get the full benefit of response_for, you need to write your actions with it in mind. It's not allways appropriate - but for some things it's great. if you have a set of actions that you use a lot (say if you're a resources_controller user), or if you use superclass controllers, then you'll probably get some benefits form response_for.
Before talking about the rewrite, I'll just give a before and after example of a common use case for response_for - subclassed controller action. If you want to skip this just head straight to the section regarding the rewrite.
Before
Here is a fairly standard controller with an action that does some stuff, then conditionally renders a response.
class SuperclassController < ApplicationController
# the action does some stuff, then conditionally renders a repsonse
# for :html, and :js
def the_action
do_some_stuff
did_yet_more_stuff = if do_some_more_stuff
do_yet_more_stuff
else
do_the_other_thing
end
respond_to do |format|
if did_yet_more_stuff
format.html do
flash[:notice] = "Well done you"
redirect_to the_done_url
end
format.js
else
format.html do
flash[:notice] = "Bad boy"
redirect_to the_bad_url
end
format.js { render :action => 'the_action' }
end
end
end
end
There are two main reasons to override this action in a subclassed controller, (i) to change the response, and (ii) to change the logic. Both of these require a rewrite of the entire action! This is painful.
Code speaks louder than words, so here are two subclassed controllers, both of which add authentication, one which chnages the html response on success, the other which changes the logic. Click here to skip the code.
class Subclass1Controller < SuperclassController
# We add authentication, and change the response
before_filter :require_login
def the_action
do_some_stuff
did_yet_more_stuff = if do_some_more_stuff
do_yet_more_stuff
else
do_the_other_thing
end
respond_to do |format|
if did_yet_more_stuff
format.html do
# We change the response
flash[:notice] = "Well done #{current_user.display_name}"
redirect_to the_done_url
end
format.js
else
format.html do
flash[:notice] = "Bad boy"
redirect_to the_bad_url
end
format.js { render :action => 'the_action' }
end
end
end
end
class Subclass2Controller < SuperclassController
# We add authentication, and change the logic
before_filter :require_login
def the_action
do_some_stuff
# Here we change the logic, but we have to write out the response again
did_yet_more_stuff = if do_some_more_stuff_for_user(current_user)
do_yet_more_stuff
else
do_the_other_thing
end
respond_to do |format|
if did_yet_more_stuff
format.html do
flash[:notice] = "Well done you"
redirect_to the_done_url
end
format.js
else
format.html do
flash[:notice] = "Bad boy"
redirect_to the_bad_url
end
format.js { render :action => 'the_action' }
end
end
end
end
After..
Here's the same effect, with response for. We do need to write the superclass action a little differently first. We just take the response out of the action. We need to add an instance variable, so the response can conditionally render different things.
class SuperclassController < ApplicationController
# this time with response_for
def the_action
do_some_stuff
@did_yet_more_stuff = if do_some_more_stuff
do_yet_more_stuff
else
do_the_other_thing
end
end
response_for :the_action do |format|
if @did_yet_more_stuff
format.html do
flash[:notice] = "Well done you"
redirect_to the_done_url
end
format.js
else
format.html do
flash[:notice] = "Bad boy"
redirect_to the_bad_url
end
format.js { render :action => 'the_action' }
end
end
end
Now, our two subclassed controllers look like this:
class Subclass1Controller < SuperclassController
# We add authentication, and change the response
before_filter :require_login
# we only override the repsonse that has changed
response_for :the_action do |format|
if @did_yet_more_stuff
format.html do
flash[:notice] = "Well done #{current_user.display_name}"
redirect_to the_done_url
end
end
end
end
class Subclass2Controller < SuperclassController
# We add authentication, and change the logic
before_filter :require_login
def the_action
do_some_stuff
# Here we change the logic, we don't need to touch the response
@did_yet_more_stuff = if do_some_more_stuff_for_user(current_user)
do_yet_more_stuff
else
do_the_other_thing
end
end
end
The rewrite
The original response_for had to do some pretty tricky messin about to kick in once the action had done its thing. Things got a whole lot easier when actionpack introduced the default_render hook.
default_render is called after your action if a response has not explicitly been rendered (or redirected). You can use this yourself in your controllers if you want to do something other than just rendering the default template. response_for alias_method_chains it to render any declared responses..
If you're a previous response_for user
Along with the rewrite, I changed the way repsonse_for works. It used to intercept respond_to, so that you could override any repsonse declared in your action. This resulted in a bunch of weird edge cases, and so I decided to ditch it (read more about this at the end of the response_for README). If you need the old functionality use the 0.1-stable branch.
inherit_views | response_for
Leopard, autotest, growl, and Bob the Builder
May 23rd, 2008
Update: check out Nick's latest post for up to date info.
There's a bunch of articles that helpfully diagnose and solve the GRRRRowl + autotest + Leopard snafu.
In order to save me, and my cohorts, some time I've bundled it all up in a README + script + icons.
Being a new father, currently at my in-laws, I've reached for the nearest toy, and made bob the builder icons. I think they might even challenge hookercookerman's set (sadly, not on the web as afar as I can see - step up hookercookerman and post them).



Grab it
here (tarball, installation instructions inside)
P.S.
I made the screenshots with Photo booth's 'backdrop' feature, and a real toy. Finally, I can do composites (I just knew not learning photoshop would pay off).
git hosting with Leopard
May 19th, 2008
So you wanna host your own git repos? Got Leopard? Got Git? Read On...
Assumptions: You have git installed.
Create a git server on leopard with gitosis
Download and install gitosis
mkdir src git clone git://eagain.net/gitosis.git cd gitosis sudo python setup.py install
Create a git user and group on the server
Create a unix user and group for git, using dscl: leopard's directory service cli
1. Find an unused uid and gid
sudo dscl . list /Users uid sudo dscl . list groups gid
(check that, say, 401, is unused in both)
2. Create the git group
sudo dscl . create groups/git sudo dscl . create groups/git gid 401
3. Create the git user
sudo dscl . create users/git sudo dscl . create users/git uid 401 sudo dscl . create users/git NFSHomeDirectory /Users/git sudo dscl . create users/git gid 401 sudo dscl . create users/git UserShell /bin/bash sudo dscl . create users/git Password '*'
4. Create the git home directory (make this location match the end of line 3 above)
sudo mkdir /Users/git sudo chown git /Users/git sudo chgrp git /Users/git
Create an ssh key, and copy it to the server
1. If you don't already have one, create an ssh key, on your local machine if it is not the server
ssh-keygen -t rsa
2. Copy the public key to /tmp on the server
(if your local machine is the server)
cp ~/.ssh/id_rsa.pub /tmp/my_key.pub
(if the server is different from your local machine)
scp ~/.ssh/id_rsa.pub your.server.com:/tmp/my_key.pub
Setup gitosis in git user's home directory
1. Initialise gitosis [on the git server]
sudo -H -u git gitosis-init < /tmp/my_key.pub(you should see something like this:)
Initialized empty Git repository in ./ Reinitialized existing Git repository in ./
2. Make sure git's paths are set to your current ones (where you can see gitosis and git)
sudo su git (enter your password) echo "export PATH=$PATH" > ~/.bashrc exit
Update: drwestco advises that with git 1.6 you should append the following line to the git user's .bash_rc.
export PATH=$(git --exec-path):$PATH
Clone the gitosis repo to your local machine
git clone git@your.server.com:gitosis-admin.git
If you see something like this, then you're all set
remote: Counting objects: 5, done. remote: Compressing objects: 100% (4/4), done. remote: Total 5 (delta 0), reused 5 (delta 0) Receiving objects: 100% (5/5), done.
What next?
Get familiar with gitosis. scie.nti.st has a great writeup for *nix systems, which I used as a reference point. The end of that blog has some general intro to gitosis.
Troubles? Make sure that you can ssh to the server as git (make sure that Leopard's ssh settings allow any user to login, or edit /etc/sshd_config). Otherwise post your troubles here in the comments.
IE8 lagging in standards support (comparatively speaking)
May 11th, 2008
I don’t know how I missed this (well, I do, I've been ridiculously busy), but about a month ago Microsoft issued a statement about planned CSS standards support in the upcoming IE8. The good news is that CSS 2.1 support will be almost complete—good, but not great, considering that this standard is rather old now, as it’s been a “candidate recommendation since 2004”.
The bad news is the level of support for CSS3 elements. Look through that list & try to keep afloat in the sea of red “no”s that signify their CSS3 support—or lack thereof. Of course, CSS3 is still very much a work in progress and is unlikely to be a candidate recommendation for another decade, judging from the pace of “Last Calls” for various components of the CSS3 spec. Nevertheless, the developers behind Safari & Firefox have been implementing a lot of the spec. There’s some fantastic stuff in it (multi-column support! colours with opacity!), that I for one would like to use immediately—and which we do, on our own site & some others we’re currently developing. But for the foreseeable future, if you’re using IE, you won’t see these things.
Given the clusterfuck of discontent that was IE7, and that Safari & Firefox have cleaned their clocks with current & emerging standards support, one might think that Microsoft would take the opportunity to really push IE8 to be a 1st-class browser. But no, they’ve just decided to tread so softly, so slowly, that the net result will be that IE8 will be that much farther in the distance. I mean, IE8b has just passed the Acid2 test. Hooray. Safari & Opera have worked on complete compliance with Acid3—and have succeeded.
This sucks. There are fantastic things taking place in CSS, in particular CSS3—which you can experience right now in Firefox & especially Safari. And I’d like all my site’s viewers to be able to see these effects in action.
resources_controller: things
April 27th, 2008
RC's got a github home now
The subversion repo will still continue to be maintained for the foreseeable future.
RC's also reported as being one of the things under the hood at naked.
Lastly, I've been cooking my CI with garlic.
Two CSS Debugging Tips
April 25th, 2008
Ian may claim to be doing nothing but git-work (and what an unfortunate name that is), but the fact of the matter is that we’re both eyebrow-deep in client work. This business has been running since April 1999 & we’ve never been so busy.
When you’re so busy, the last thing you want is a series of bugs that won’t seem to go away. Since most of my work is in the HTML/CSS/Rails templates realm, I’m speaking mostly of visual bugs, things that seemingly won’t budge no matter how much time you put into them. I thought I’d share two tips which have helped me squash a considerable number of visual bugs, one created by someone else & the other merely a process that I follow.
CSS Debugging Tip 1: Burn it all to the ground
First, the work supplied by someone else: Eric Meyer’s CSS Reset. If you’re unfamiliar with it, go & read up on it ASAP. For those unfamiliar with why you’d need to reset something your CSS, the answer is simple: different browsers have different default sizes for fonts, lists, buttons & a hell of a lot else. By resetting all these items via CSS, you help ensure that all your browsers—Safari, Firefox, Opera & the thorniest of them all, Internet Explorer—start from a common vantage point.
The idea is not new. For years I’ve employed a very basic, simple reset:
html * { margin: 0; padding: 0; }
The problem is that this didn’t reset nearly enough things—and it often didn’t even reset everything it was supposed to! Eric’s reset is a total razing to the ground of all browser-specific sizing quirks & as such should be adopted by every designer. In my experience, it’s particularly useful for a project that you’re just starting up: my IE-specific stylesheets are a fraction of their former size. For pre-existing sites, however, it’s not going to be a magic bullet. You still assembled your CSS based on different assumptions & short of starting from scratch, it will be of more limited use (although still recommended in my book).
CSS Debugging Tip 2: Build it back up brick by brick
The second tip is merely a process that I’ve been following. I’m surely not the first person to do this, but since it doesn’t appear to be widely discussed I thought I’d write it up here.
I’m going to take a real-world example from a site we’re building. In this site, we’re using some Scriptaculous effects to aid in the creation of a visually compact secondary navigation for a database of imagery. The markup is simple:
<h4>Section Heading</h4>
<a href="subsection/1">Subsection 1</a>
<a href="subsection/2">Subsection 2</a>
...
Clicking on the H4 tag will cause the subnavigation links to reveal themselves, or hide themselves if they’re already revealed.
Unless you’re viewing in Internet Explorer 7, that is—no, the problem for once isn’t IE6! In IE7, the links were invisible—they were clickable, but you couldn’t see them.
I tore out what little hair I had left, for weeks, trying to figure out this bug. I even thought it might have been Scriptaculous’ fault. It wasn’t, however & eventually I realised I was compounding the problem by adding CSS declarations instead of removing them—instead of trying to figure out what was triggering the bug, I was trying to pave over the bug. If you’re adding more declarations in order to fix a bug, you may very well be creating a shifting target, which is definitely not what you want to be doing. Particularly when you have a lot of other work to do.
The answer is just a process & a very simple one at that: take your CSS declarations & strip them out. Add them incrementally, viewing in the target browser every time, until you trigger the bug.
In the example above, removing a declaration of a:link { display: block; } rendered the links visible again in IE7. I don’t know why—I’ve used this declaration countless times with no problems—but nevertheless the declaration wasn’t necessary & it was causing a big problem.
Again, though, it’s the process of stripping away your CSS & then re-implementing your declarations which is the key. It’s all too easy to add more & more to your declarations, but remember: you may only be making the job of fixing your problems worse.
Continuous Integration with Garlic
April 24th, 2008
So I've been curled up in a ball, riding the git avalanche, trying to sort out my rails plugins - making sure they're getting tested against the latest and greatest.
Inspired by this ticket for rspec, git's coolness, some menthol snuff, and a lot of coffee, I came up with garlic.
It's an extremely lightweight set of rake tasks that let you test your plugins or app against various version of rails, and other dependencies.
If you want to see it in action (on one of my plugins), do this:
git clone git://github.com/ianwhite/inherit_views cd inherit_views rake cruise
Sit back, watch it download all the dependencies, then create rails apps for each set, and run the rcov task for the plugin in each one... (the download only happens the first time you do it).
You configure it using a little dsl, like this:
garlic do
# default paths are 'garlic/work', and 'garlic/repos'
work_path "tmp/work"
repo_path "tmp/repos"
# repo, give a url, specify :local to use a local repo (faster
# and will still update from the origin url)
repo 'rails', :url => 'git://github.com/rails/rails' #, :local => "~/dev/vendor/rails"
repo 'rspec', :url => 'git://github.com/dchelimsky/rspec'
repo 'rspec-rails', :url => 'git://github.com/ianwhite/rspec-rails'
repo 'inherit_views', :url => '.'
# for target, default repo is 'rails', default branch is 'master'
target 'edge'
target '2.0-stable', :branch => 'origin/2-0-stable'
target '2.0.2', :tag => 'v2.0.2'
all_targets do
prepare do
plugin 'rspec'
plugin 'rspec-rails', :branch => 'origin/aliased-render-partial' do
sh "script/generate rspec -f"
end
plugin 'inherit_views'
end
run do
cd "vendor/plugins/inherit_views" do
sh "rake spec:rcov:verify"
end
end
end
end
Notice that I'm using my fork of rpsec-rails, and the plugin specifies that it should use a particular branch 'aliased-render-partial'. The reason for this is that I have some outstanding tickets on rspec, which haven't been resolved. In the meantime, I can just use my patched version. If the patch gets accepted, I can just change the url, and garlic will inform me that I need to remove and run rake garlic:install_repos to get the new one. This is just making use of the awesomely cool coolness of git.
Also notice the block passed to the 'rspec-rails' plugin. This will be executed inside the rails target after the plugin has been installed. Finally the run block says what should happen for the actual CI. In this case cding into the plugin and running an rcov task.
It's new stuff
So it probably has bugs and stuff.
dynamic resolution + prototype
February 27th, 2008
You may have heard of the Dynamic CSS resolution switcher from Particletree, it's a neat bit of js that allows you to specify different css files depending on the browser resolution.
We love this script, but we had problems running it with Safari, and we are using prototype, so we wanted to make use of its goodness for the browser independent stuff.
So, if you're already using prototype, the following script achieves the same effect as the original particletree script. It also uses the dom:ready event, which fires after the dom is loaded, but before the screen is drawn, so you shouldn't see any 'twitch'.
Example
In the following example, we have three stylesheets that correspond to browser widths as follows:
thin up to 1020 wide 1021...1400 widest above 1400
For non js users we want to default to 'wide' (the middle one). In order for this to work properly, we disable all of the non-default stylesheet links.
In your html
In your html you need to link to the three stylesheets, give them each a title attribute, and disable the ones you don't want non-js users to see.
<link title="wide" href="/stylesheets/wide.css" rel="stylesheet" type="text/css" /> <link title="thin" disabled="true" href="/stylesheets/thin.css" rel="stylesheet" type="text/css" /> <link title="widest" disabled="true" href="/stylesheets/widest.css" rel="stylesheet" type="text/css" />
And link to the script, we'll call it dynamic_css.js
<script src="/javascripts/dynamic_css.js" type="text/javascript"></script>
The script
This is the script, it uses the title attribute of the stylesheets to disable or enable them. It is run when the dom is ready, and when the browser width changes.
// DYNAMIC RESOLUTION SWITCHER
// Originally from ParticleTree
// Simplified with Prototype by Ian White of Argument from Design 2008
// include prototype.js (>=1.6) before this file
// you need to edit this function as per your situation
function applyDynamicLayout() {
var width = document.viewport.getWidth();
if (width <= 1020 ) { applyStylesheet("thin") }
if (width > 1020 && width <= 1400) { applyStylesheet("wide") }
if (width > 1400) { applyStylesheet("widest") }
}
// you shouldn't need to edit past here
function applyStylesheet(title) {
var i, stylesheet;
for(i=0; (stylesheet = document.getElementsByTagName("link")[i]); i++) {
// is it a stylesheet with a title attribute?
if(stylesheet.getAttribute("rel").indexOf("style") != -1 && stylesheet.getAttribute("title")) {
stylesheet.disabled = true;
if (stylesheet.getAttribute("title") == title) {
stylesheet.disabled = false;
}
}
}
}
//Run applyDynamicLayout function when window is ready and when it resizes.
Event.observe(document, 'dom:ready', applyDynamicLayout);
Event.observe(window, 'resize', applyDynamicLayout);
That's it!
That's it, it works for us in Safari, Opera, FF and IE 6 or greater.
Also, you can have as many stylesheet links as you like with the same title. For example, let's say you have some IE6 specific kludges for the 'thin' layout. You would do this:
<!--[if lt IE 7]> <link title="thin" disabled="true" href="/stylesheets/thinie6.css" rel="stylesheet" type="text/css" /> <![endif]-->
Ruby muckin
February 12th, 2008
So I was writing a Rakefile and found myself doing stuff like this:
cmd = "svn co #{src} #{dest}"
puts cmd
system cmd
Everytime I wrote lines like this, a little corner of my heart died.
I dreamt of something like this:
"svn co #{src} #{dest}".to :puts, :system
But it's hard to do this properly, because you need to get hold of the receiver (in the above case main, or some Rake task) or binding, so that the methods can be sent where they ought. With crippled Kernel#caller, the the demise of Binding.of_caller (Ruby-debug solved the problem but there's significant overhead) it was looking like I should just wait for the next ruby release.
But I frowned like Hiro and came up with this:
pass("svn co #{src} #{dest}").to :puts, :system
Briefly, Object gets #pass which creates a PassProxy (basic object). It keeps hold of the receiver, but acts like the object (in this case the string). When you send #to(:method) to this PassProxy, it calls the original receiver with the object.
This is just sugar, but its nice sugar in some cases. Compare:
# plain ruby
msg = "oooh!"
MyObj.foo msg
MyObj.far msg
MyObj.faz msg
msg
# => 'oooh!'
# returning (ActiveSupport)
returning "oooh!" do |s|
MyObj.foo s
MyObj.far s
MyObj.faz s
end
# => 'oooh!'
# (pass) (this code)
MyObj.pass("oooh!").to :foo, :far, :faz
# => 'oooh!'
But eat too much sugar and your teeth will fall out: I realised that this probably has limited usefulness (how often do you need to send the same object to a bunch of different methods?). But at least I learned some things in that last couple of hours of coding.
Go get the code from pastie if you're interested.
Thanks for reading