Last week, while I was writing the following expression for like the 1000th time, it struck me…


name = Product.find_by_code('code') ? Product.find_by_code('code').name : nil 

WTH am I doing writing this crazy, long, duplication bloated one-liner in ruby ? YES, in RUBY ! That ‘s just plain WRONG!
There just HAS to be some great, DRYer, magic, aaaah’s and oooooh’s generating, no-longer-than-5-characters expression that handles this!

So instead of bashing my laptop against the wall I IM’d my colleague and local ruby-guru Peter.

Well, he wasn’t really surprised by my question.

Apparently it’s something that comes up quite often in the ruby community / forums, and there is not really a perfect solution for it.

Big disappointment at first, but we started talking about possible alternatives (well, he talked, I mostly listened ;-).

I wanted a solution for 2 slightly different scenarios:


# EXPR #1: 
name = Product.find_by_code('code') ? Product.find_by_code('code').name : nil 

# EXPR #2: 
name = Product.find_by_code('code') ? Product.find_by_code('code').name : 'something else' 


h3. SOLUTION #1: and


prod = Product.find_by_code('code') and name = prod.name 

The part after and will only be executed if prod != nil

And even if the second part doesn’t get executed, the variable name will still be initialized at nil ! Great !

For EXPR #2, this method becomes a bit too complicated, on to solution #2 !


h3. SOLUTION #2: rescue


name = Product.find_by_code('code').name rescue nil 

name = Product.find_by_code('code').name rescue 'something else' 

Short and clean, but the rescue clause will catch anything and might hide some other problem like this.

So it should be used with care, it is definitely not always a good solution.

Ok, time for the aaaaaah’s and oooooooh’s now.


h3. SOLUTION #3: blocks

For this we first need to define one or two methods that will help clean up our expression.

I’m in Rails, and by injecting the 2 methods in the Object class, I can call them from anywhere I want :


class Object 
  def use 
    yield self 
  end 

  def chain 
    yield self if self 
  end 
end 

EXPR #1 now becomes:


name = Product.find_by_code('code').use {|e| e ? e.name : nil} 
# or with the chain method: 
name = Product.find_by_code('code').chain {|e| e.name } 

EXPR #2 gives:


name = Product.find_by_code('code').use {|e| e ? e[0] : 'something else'} 

And it works also with the chain method, if you make some changes :


def chain(default=nil) 
  self ? yield self : default 
end 

name = Product.find_by_code('code').chain('something else') {|e| e.name } 

Waw, that’s cool, no ?

But I still wasn’t completely satisfied, so I had another question for Peter.

Wouldn’t it be even cooler if I could write the following:


name = Product.find_by_code('code').chain.name 
#or 
name = Product.find_by_code('code').chain('something else').name 

Well, it is possible, and they even have a name for it: magic dot syntax


h3. Solution #4: Magic dot syntax


class Chainer 
  def initialize(obj, default) 
    @obj = obj 
    @default = default 
  end 

  def method_missing(m, *a, &b) 
    if @obj 
      @obj.send(m, *a, &b) 
    else 
      @default 
    end 
  end 
end 

class Object 
  def chain(default = nil) 
    Chainer.new(self, default) 
  end 
end 

You might need a minute to figure it out…I know I did :-)

I’ll try to explain it a bit:

  • calling the chain method, creates a new Chainer object, by calling the Chainer#initialize method
  • the name method, called on this Chainer object will be catched by method_missing
  • method_missing will call the name method on your product unless the product was nil, in that case it will just return nil (or the object you passed to the chain method).

Isn’t that just great !

And with such a cute name, how can you not use it ? :-)


p. This probably isn’t that spectacular for the more experienced Ruby developer,
but I do think it’s another good example of the power and flexibility of Ruby !

blog comments powered by Disqus

Entries per category

  1. 9 pages are tagged with documentum
  2. 12 pages are tagged with events
  3. 14 pages are tagged with rails
  4. 32 pages are tagged with ruby
  5. 13 pages are tagged with sharepoint

Recent Comments

Popular Threads