Cory O'Daniel – These are just words Software development, thoughts, and randomness

3Feb/100

Ruby Objective-C like Null Pattern

So, I think the objective c null(nil) pattern is pretty cool. I know that lots of people talk about doing it in different ways. Here is my implementation. I'm not keen on having nil catch method missing, because I'm not sure what havoc that may cause in one of the 10million gems I use. I also like the last link above (which I just found when writing this article) except that I dont want to type "try" a million times.

This is my preferred method when dealing with deeply nested messages. Stick this in your lib.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Object
  def rcall(*method_names)
    _result = self
 
    method_names.collect.each { |mname| 
      if _result.respond_to?(mname)
        _result = _result.send(mname)  
      else
        _result = nil
        break
      end
    }
 
    _result
  end
end

Wanna use it?

1
2
3
4
5
6
7
8
9
10
 
# In this example, if an of the messages return nil, i'll get a MethodMissing error and I'll be scorned.
my_object.first_message.second_message.the_message_whos_value_i_want
 
# The problem is, I dont care if its nil, I have a default, the method is still missing.
value = my_object.first_message.second_message.the_message_whos_value_i_want || "Im ok with this being nil"
 
# If chain_message encounters a nil, it just returns nil and you can use standard language logic to default that value
# You dont have to do a million if-checks.
value = my_object.rcall( :first_message, :second_message, :the_message_whos_value_i_want ) || "Im ok with this being nil"

Another Example:

1
2
3
4
5
6
7
8
9
10
11
# Some cool way of finding purchases, be it AR, Datamapper, or whatever, just get an object already
purchases = Purchase.all( arbitrary_finder_logic )
 
# I need to output the name of who added the product on some arbitrary report no one will read
# yeah, I know. My validators should have made sure the values were there, I'm just making this up.
purchases.each do |purchase|
  buyer = purchase.rcall(:purchaser, :name) || "John Doe"
  seller = purchase.rcall(:product, :listor, :name) || "Jack Sales"
 
  puts "Buyer: #{buyer}, Seller: #{seller}"
end

Yay, Drying up if-else nil checking statements. High Five.

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon