Wednesday, August 11, 2010

Nil-Safe Sorting in Ruby Made Easy

I'm not sure why nil doesn't support <=>. In most sorting cases, nil is less than everything else, so it would seem safe enough if it were extended with that. You've run into this issue if you get:
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.<=>
So, what's the workaround?

Say you want to sort an array of task assignments their project name. My first hack was to write a lot of code like the following to try to get around it:

task_assignment.sort { |x,y|
  a = x.try(:project).try(:name)
  b = y.try(:project).try(:name)
  if a && b
    a <=> b
  elsif !a && !b
    0
  else
    left ? 1 : -1
  end
}
I knew this was a little long, so I spoke with Jim who showed me a much cleaner method:
task_assignment.sort_by { |ta| ta.try(:project).try(:name) || '' }
With this code, nil, a task assignment without a project, or a project without a name are equivalent to a task assignment with a project name of ''. It's not functionally equivalent to the code above it that would weigh nil even less than '', but it's fine.

2 comments:

Chuck said...

Nil-Safe max and min works similarly. For example:

max = series.max {|a,b| (a || 0) <=> (b || 0)}

Gary S. Weaver said...

Thanks, Chuck!