Be Genius

Fork Me
Mugshot

Hi, I'm Bodaniel Jeanes.

I'm a Ruby developer from Brisbane, Australia

I work at Mocra where I hack on awesome code. Follow me, recommend me, and link with me.

Automatically open the last page for failed scenarios

When writing new Cucumber scenarios, developers will usually start with a failing scenario, incrementally making it pass, step by step. If this is a common workflow for you, you’ll probably have noticed a trend of having to re-run it each time a new step is failing, adding something like Then show me the page after the last passing step to see why the story is currently failing.

Chendo and I came up with a nifty little solution in just a few lines that really helps this process of seeing what webrat sees when a scenario fails. Throw this snippet of code in a file in your features/support/ directory:

1
2
3
4
5
After do |scenario| if scenario.status == :failed save_and_open_page end end

64-bit Postgres and Rails on Snow Leopard

Dependencies

Install the GEOS and PROJ frameworks from here

Postgres

Download and Compile

1
2
3
4
http://ftp2.au.postgresql.org/pub/postgresql/source/v8.4.1/postgresql-8.4.1.tar.bz2 | tar xjf - cd postgresql-8.4.1 ./configure make && sudo make install

The files are now all installed in the right places, onwards to making them usable!

Create a postgres User and Group

This is adjusted from the instructions in the comments here).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Find unused Group ID and User ID: export GROUPID=`sudo dscl . -list /Groups PrimaryGroupID | ruby -e 'puts STDIN.read.scan(/\d+/m).map{|i|i.to_i}.uniq.sort.last.succ'` export USERID=`sudo dscl . -list /Users UniqueID | ruby -e 'puts STDIN.read.scan(/\d+/m).map{|i|i.to_i}.uniq.sort.last.succ'` # Create group: sudo dscl . -create /Groups/_postgres sudo dscl . -create /Groups/_postgres PrimaryGroupID $GROUPID sudo dscl . -append /Groups/_postgres RecordName postgres # Create user: sudo dscl . -create /Users/_postgres sudo dscl . -create /Users/_postgres UniqueID $USERID sudo dscl . -create /Users/_postgres PrimaryGroupID $GROUPID sudo dscl . -create /Users/_postgres UserShell /bin/bash sudo dscl . -create /Users/_postgres RealName "PostgreSQL Server" sudo dscl . -create /Users/_postgres NFSHomeDirectory /usr/local/pgsql sudo dscl . -append /Users/_postgres RecordName postgres

That’s the clean way of making a user (so it doesn’t show up in your Accounts preference pane, etc.

Clean up

1
2
3
4
5
6
7
sudo touch /var/log/psql.log sudo mkdir /usr/local/pgsql/data sudo chown -R postgres:postgres /usr/local/pgsql/data /var/log/psql.log # Put Postgres in your path export $PATH="/usr/local/pgsql/bin:$PATH" sudo sh -c 'echo /usr/local/pgsql/bin > /etc/paths.d/pgsql'

Thanks to Apple we have a nice way of setting the PATH across the entire system so every app should now know how to find the Postgres binaries and the data directory is now set up to store the database files — now, we just need to create them.

Initialise the Database

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Become the postgres user sudo su - postgres # If this throws next commant throws an error about shared memory, # try putting "these lines":http://gist.github.com/224815 into # /etc/sysctl.conf, rebooting, and trying again: initdb -D /usr/local/pgsql/data -E UTF8 # Start the database postgres -D /usr/local/pgsql/data >/var/log/psql.log 2>&1 & # Create a test database and check you can connect to it createdb test psql test # Just create a superuser for general dev tasks createuser postgres # we don't want to be the postgres user anymore exit

Your done with the Postgres setup now!

PostGIS

1
2
3
4
curl http://postgis.refractions.net/download/postgis-1.3.7SVN.tar.gz | tar xzf - cd postgis-1.3.7SVN ./configure --with-geosconfig=/Library/Frameworks/GEOS.framework/unix/bin/geos-config --with-projdir=/Library/Frameworks/PROJ.framework/unix make && sudo make install

Phew! That one was easy.

Postgres Ruby Gem

This is the part that everyone cares about the most. If this works your Rails apps should start working like a charm.

1
sudo env ARCHFLAGS='-arch x86_64' gem install pg

Using Fish shell's event system to behave like method_missing

Dr Nic’s latest post, “hash bang cucumber”, reminded me of a piece of hax I whipped up a few weeks ago with Ruby and Fish Shell.

Fish has an event system that allows you to register functions to be auto-run after or during certain events (such as when a particular environment variable is changed). One of this events is called fish_command_not_found. It is triggered whenever you type a non-existant command into the prompt.

With this in mind, you can trigger certain commands to run by matching what fish couldn’t manage to run automatically by catching this event.

For instance:

1
2
3
4
5
function __fish_method_missing --on-event fish_command_not_found method_missing $argv end funcsave method_missing

Given that function is created (simply paste the whole code block into your Fish terminal), I can now create a command called method_missing (or whatever you call inside your __fish_method_missing) and place it somewhere in your $PATH — I like ~/.config/fish/bin.

My method_missing binary is simply something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env ruby command = ARGV.shift def run(cmd) puts "Running #{cmd.inspect} instead" system(cmd) end case command when /^git(@|:\/\/).*\.git$/ run("git clone #{command.inspect}") when /^(?:ftp|https?):\/\/.+\.t(?:ar\.)?gz$/ run("curl #{command.inspect} | tar xzv") else $stderr.puts "No default action defined in #{__FILE__.inspect}" abort end

Each command you want to register just becomes a new when statement. For instance, to implement the functionality Dr Nic was trying to achieve, I simply modify the case block as such:

1
2
3
4
5
case command when /^[a-z0-9_\-\/]+\.feature(:\d+)?$/ run("cucumber #{command}") # ... end

The existing entries I have in my method_missing command will auto-clone the repository of a pasted Git URL and download and expand a URL for a tar file, respectively. Not too shabby, and dead easy to implement

Gotcha with Hash.new when providing a default value

For a project I am working on at the moment, I needed a Hash that returned a different default value, i.e. not nil. Specifically, I needed it to return another Hash, and for that internal Hash’s default value to be an Array, like so:

1
2
3
4
hash = # ... p hash.class #=> Hash p hash[:non_existant_key].class #=> Hash p hash[:another][:non_existant_key].class #=> Array

Naïvely, I threw this into my code, and went along my merry way: entries = Hash.new(Hash.new([])). A few days later, I came back to this code to finish it and realised that something was awry with the values I was getting out of the Hash. It took me a while to figure it out because the values were all integers being summed together and I was never looking or setting the values directly.

However, eventually I saw a pattern — many of the values were the same. For instance, given the following assignments, I should expect p entries[153][:monday] #=> [12.hours]:

1
2
3
4
entries[153][:monday] << 12.hours entries[153][:tuesday] << 3.hours entries[87][:monday] << 7.hours entries[87][:tuesday] << 2.5.hours

However, what I found was the following:

1
2
3
4
5
6
7
p entries[153][:monday] #=> [12.hours, 3.hours, 7.hours, 2.5.hours] p entries[153][:tuesday] #=> [12.hours, 3.hours, 7.hours, 2.5.hours] p entries[87][:monday] #=> [12.hours, 3.hours, 7.hours, 2.5.hours] p entries[87][:tuesday] #=> [12.hours, 3.hours, 7.hours, 2.5.hours] # And in fact: p entries[:any][:thing] #=> [12.hours, 3.hours, 7.hours, 2.5.hours]

Let that soak in for a second. Hash.new({}) uses the same instance of the internal Array as the default value for each of the inner-most keys. It also doesn’t set the key you want so p entries still printed {}. In retrospect, it is blatantly obvious that it does this, but the ramifications are still huge.

The way to get the intended effect is of course to use the block syntax of Hash.new which, while much uglier, definitely works as expected:

1
2
3
4
5
6
7
entries = Hash.new {|hash, key| hash[key] = Hash.new {|h, k| h[k] = [] }} entries[153][:monday] << 12.hours entries[153][:tuesday] << 3.hours entries[87][:monday] << 7.hours entries[87][:tuesday] << 2.5.hours p entries #=> {87=>{:monday=>[7.hours], :tuesday=>[2.5.hours]}, 153=>{:monday=>[12.hours], :tuesday=>[3.hours]}}

Wow. Obvious, but good to remember. I am sure most of you have had this issue before, but I think it is still worthy of mention…

Ultraviolet Tools

A few weeks ago, I was modifying my clone of Enki (my blogging platform) to use Ultraviolet instead of CodeRay, as it supports using both TextMate themes and syntaxes. This means I can extend it fairly limitlessly and use TextMate itself to modify the themes an syntaxes.

However, Ultraviolet doesn’t come with the best tools to turn the TextMate syntax and theme files into files Ultraviolet can use, and the ones that it does provide puts those translated files INTO the gem on your system. This means that you can’t easily deploy or share them.

So, to remedy this I made a small gem Ultraviolet Tools that provides two command line tools: uv-create-syntax and uv-create-theme.

They aren’t very complicated, nor very documented, but they are straight-forward to use:

Install

Install Ultraviolet Tools with sudo gem install bjeanes-ultraviolet-tools -s http://gems.github.com.

Usage

Themes

Run uv-create-theme path/to/theme.tmTheme

Syntaxes

Run uv-create_syntax path/to/syntax.plist or uv-create_syntax path/to/bundle/with/syntaxes.tmBundle

Using the resulting files

Use my fork of ultraviolet as it has an option to change the load path for themes and syntaxes so that you can load them out of your application files.

Using accepts_nested_attributes_for When the Child Object validates_presence_of Parent

I came across an interesting dilemma today in Rails 2.3 whilst trying to use accepts_nested_attributes_for.

The models I have were:

1
2
3
4
5
6
7
8
9
class Organization < ActiveRecord::Base has_many :users accepts_nested_attributes_for :users end class User < ActiveRecord::Base belongs_to :organization validates_presence_of :organization end

I had an “Organization” creation form which allowed an administrator to create an Organization and an initial user for it. However, even though I had filled in all the required fields to pass the validations I was constantly getting the following error: Organization can not be blank.

Digging deeper I found that when an object is added to an association via the association.build method, the parent object isn’t actually set:

1
2
3
4
5
6
>> o = Organization.new(:name => "Test") => #<Organization id: nil, name: "Test"> >> u = o.users.build(:name => "Test User") => #<User id: nil, organization_id: nil, name: "Test User"> >> u.organization => nil

Apparently this flaw also applies to accepts_nested_attributes_for. When I send through the User params through from my form normally with the Organization params, it creates the User object in the users association as you’d expect, but raises a validation error because the User instance doesn’t recognise that it is has a parent Organization

My quick 5 minute fix was to modify the Organization model thusly:

1
2
3
4
5
6
7
8
9
10
class Organization < ActiveRecord::Base has_many :users accepts_nested_attributes_for :users def users_attributes_with_self_assignment=(attributes) self.users_attributes_without_self_assignment = attributes users.each { |u| u.organization = self } end alias_method_chain :users_attributes=, :self_assignment end

Related Rails bugs seem to be here and here. This patch will also solve the problem and is currently in edge, but didn’t make it in time for Rails 2.3.

Using AlterEgo with ActiveRecord

Today after finding the gem AlterEgo and falling in love with the API, @chendo and I came to a sad realisation that its beauty was skin-deep. It seems it is not ActiveRecord-aware and overrides #state and #state= on your models. This means that your state column goes untouched and your models are always in the default state.

To get around this, @chendo and I hacked together this little monkey patch which does two things:

  1. It adds question methods for each of your defined states. So if you have states: active, inactive, and banned on your User model, your instances will have #active?, #inactive?, and #banned? defined.
  2. It properly sets and gets the state of the row and loads it into AlterEgo.

Here is the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
module AlterEgo def self.included(base) base.instance_eval do # This will allow us to define a question method for each state in a class # For instance if user has states :active and :inactive, methods active? and inactive? # will be defined when first called def method_missing_with_states(method, *args, &block) begin method_missing_without_states(method, *args, &block) rescue NoMethodError => e state_found = false result = nil base.states.keys.each do |state| if method.to_s == "#{state}?" && !state_found self.class.send :define_method, "#{state}?" do self.state == state end state_found = true result = self.__send__(method) end end raise(e) unless state_found result end end alias_method_chain :method_missing, :states # state setter to write to database define_method :state= do |value| write_attribute(:state, value.to_s) end # state getter to read from database define_method :state_with_active_record do (read_attribute(:state) || (self.state = state_without_active_record)).to_sym end alias_method_chain :state, :active_record end end end

Unescaping UTF-8 Strings in Ruby 1.9

Today Radar and I encountered a little issue with String encodings in Ruby 1.9. In this project, some Merb UTF-8 params needed to be unescaped. We spent some trying to force strings into UTF-8 encoding but for some reason while the encoding was UTF-8, the actual contents of the strings were getting massacred.

Long story short:

1
2
3
4
5
6
7
# In Ruby 1.9 # Broken puts URI::unescape("Baden-W%C3%BCrttemberg") # => "Baden-Württemberg" # Working puts CGI::unescape("Baden-W%C3%BCrttemberg") # => "Baden-Württemberg"

Another lesson we learnt on our adventures today is the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# In Ruby 1.9 m1 = "Munich" puts = m1.encoding # let's suppose it is something other than UTF-8, such as ASCII-8Bit m2 = "München" puts = m2.encoding # let's suppose we have a UTF-8 encoded string # The following will raise an exception about mismatched encodings m3 = m1 << m2 # The easiest way to get around this is to do the concatenation like so: m3 = m3 = m1 << m2.force_encoding("UTF-8")

Snow Leopard for the Ruby Developer

After the 2009 WWDC release, I decided to give Snow Leopard a try on my production machine. Crossing my fingers, expecting a plethora of broken gems and databases, I wiped my hard drive clean and installed the latest build.

Much to my surprise, most things worked flawlessly and exactly as it did on Leopard.

What Ships with Snow Leopard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$ gem --version 1.3.1 $ ruby --version ruby 1.8.7 (2008-08-11 patchlevel 72) [universal-darwin10.0] $ gem list *** LOCAL GEMS *** actionmailer (2.2.2, 1.3.6) actionpack (2.2.2, 1.13.6) actionwebservice (1.2.6) activerecord (2.2.2, 1.15.6) activeresource (2.2.2) activesupport (2.2.2, 1.4.4) acts_as_ferret (0.4.3) capistrano (2.5.2) cgi_multipart_eof_fix (2.5.0) daemons (1.0.10) dnssd (0.6.0) fastthread (1.0.1) fcgi (0.8.7) ferret (0.11.6) gem_plugin (0.2.3) highline (1.5.0) hpricot (0.6.164) libxml-ruby (1.1.2) mongrel (1.1.5) needle (1.3.0) net-scp (1.0.1) net-sftp (2.0.1, 1.1.1) net-ssh (2.0.4, 1.1.4) net-ssh-gateway (1.0.0) rails (2.2.2, 1.2.6) rake (0.8.3) RedCloth (4.1.1) ruby-openid (2.1.2) ruby-yadis (0.3.4) rubynode (0.1.5) sqlite3-ruby (1.2.4) termios (0.9.4) xmpp4r (0.4)

Quite possibly these default gems and versions will change between now and the final release of Snow Leopard. For instance, this seed comes with Rails 2.2.2, even though we are already at 2.3.2. Snow Leopard doesn’t come out till September and by then we could have an even newer version of Rails that might be bundled.

What doesn’t work, and how to fix it.

Textmate – Everything works fine except for the following shortcuts: ⌘←, ⌘→, ⌘⇧←, and ⌘⇧→. These shortcuts are for moving the text insertion point to beginning and end of line, and selecting to beginning and end of line, respectively. The fix is easy, simply download and double click the TextMate macros attached to this ticket

Nokogiri – Nokogiri installs fine but gives error “native.bundle: mach-o, but wrong architecture” when used. Getting around this is as easy as running sudo gem install nokogiri -s http://tenderlovemaking.com/ to get the latest development release which includes a fix for this.

do_sqlite3 – I haven’t personally used this or tried to fix it but I saw report of it not working by @benlovell

Passenger PrefPane – Preference pane does not load. No known fix (it’s probably an easy fix but I haven’t looked enough into it to try).

RubyCocoa and Keychain access

There have been a few posts asking how to access and manipulate the Keychain from within RubyCocoa but no answers supplied. As someone who is just getting into RubyCocoa (and Cocoa all together for that matter), I thought I’d document once and for all the process I took to get it working.

I am running on Leopard and while 10.5 is supposed to ship with BridgeSupport for most frameworks, the Security framework must have been left out. After playing around with using other frameworks (such as the AddressBook framework) and seeing how they were used, I did a bit of digging in the Apple Developer Documentation (incredible resource for anyone starting out) and stumbled across this gem: Generate Framework Metadata

After reading this, I was accessing the keychain within 5 minutes. Here’s how:

  1. Run the following commands (first one will take some time):

    1
    2
    3
    gen_bridge_metadata -f Security -o Security.bridgesupport mkdir -p /Library/BridgeSupport mv Security.bridgesupport /Library/BridgeSupport
  2. Open an IRB session to test it:

    1
    2
    3
    4
    5
    6
    >> require 'osx/cocoa' => true >> OSX.require_framework 'Security' => true >> defined? OSX::SecKeychainAddGenericPassword() => "method"

Hoorah! Two steps! Well, one because the second one was just testing… Now go make password-able RubyCocoa application! I am not sure how to pull this off with deployable apps but I suggest your apps could ship with the file or run those commands on first-run or if the Security.bridgesupport file doesn’t exist. Note: I have not tested either of those scenarios, but I would guess the latter would be more reliable for cross-version development (i.e. Tiger + Leopard)

How to use these methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Require needed libraries require 'osx/cocoa' include OSX require_framework 'Security' # Set up some relevant variables service = "Test Service" account = "MyUsername" password = "MyPassword" # Add password to default keychain (first param) SecKeychainAddGenericPassword(nil, service.length, service, account.length, account, password.length, password, nil) # Finding a password in default keychain (first param) status, *password = SecKeychainFindGenericPassword(nil, service.length, service, account.length, account) # Password-related data password_length = password.shift # => 10 password_data = password.shift # OSX::ObjcPtr object password = password_data.bytestr(password_length) # The last item is another OSX::ObjcPtr. I haven't figured # out how to cast or use this yet but will post when I've # figured it out keychain_item = password.shift # Yay it works! You can also check the password exists # in the Keychain Access utility. I've confirmed that # this works puts password # => "MyPassword"

[UPDATE 4/8/08]: Below is a fix for the REXML bug some people have been getting (myself included)

There is good news for anyone that has been getting the following error:

1
2
3
undefined local variable or method `trans’ for <UNDEFINED> … </>:REXML::Document Usage: gen_bridge_metadata [options] <headers…> Use the `-h’ flag or consult gen_bridge_metadata(1) for help.

There is a very simple fix. This is caused by a typo in the source of REXML. It’s such a massive bug I can’t believe it made it into the release of Ruby but i did some poking around and it looks like they renamed a method and didn’t change all references to it.

There are two ways to fix it—patching REXML; and patching gen_brige_metadata.

Personally I prefer patching the generator for peace of mind and have submitted the patch at http://bridgesupport.macosforge.org/trac/ticket/1, but I’ll show both fixes.

Patching gen_bridge_metadata

  1. Open /usr/bin/gen_bridge_metadata and find the method definition for generate_xml (should be on line 1531)
  2. Replace 0 in the xml_document.write() with -1 (both occurrences)
  3. Save!

Patching REXML

  1. Open /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rexml/document.rb
  2. Find the line that has ‘if trans’ and change it to ‘if transitive’
  3. Save!