As I talked about last month when trying to demystify  “UNDEFINED METHOD `[]’ FOR NIL:NILCLASS” error, it can be challenging to make sure all your attributes are set the way you want, or even at all.

One thing that should be detectable is anytime you get nothing (nil) back from a Node[] call, we might as well throw an error now.  Some might want to use abuse the nil by conditional transformed to ‘false’:

puts “More info here” if node[:options][:verbose]

or transformed into empty string:

mypath = #{node[:path_prefix]}/#{node[:destination_folder]}/”

But, you should instead use those classes directly instead:

defaults[:options][:verbose] = false
defaults[:path_prefix] = “”
defaults[:destination_folder] = “”

So, if you agree we should never be using nil as a valid Node[] return- then we can simply automatically detect it in the node[] function itself!  For this we are going make a Chef library by including some Ruby and add functionality to Chef directly.

This is advanced dark-art in Ruby.  We can combing monkey-patching with alias_method to effectively add any code to the beginning or end of anyone else’s function.  Specifically, we want to check the return of Node[] and thrown an error before anyone tried to access a resulting nil.  In this way, we factor out a lot of error checking by putting it into the Node[] function call, and relieving the cookbook & recipe writers from having to write it themselves.

 


safety.rb
1    #Chef::Node.strict_retrieval defaults to true 
2    #Chef::Node.debug defaults to true 
3    #Mash.strict_retrieval defaults to true 
4    #Mash.debug defaults to false 
5     
6    class Chef 
7      class Node 
          # alias_method allows us to make a copy of a function
          # This will copy the old [] operator to function "old_squarebraket"
8        alias_method :old_squarebracket, "[]".to_sym 
9        @@strict_retrieval=true 
10       def self.strict_retrieval=(v) 
11         @@strict_retrieval=v 
12       end 
13       def self.strict_retrieval 
14         @@strict_retrieval 
15       end 
16    
17       @@debug=true 
18       def self.debug=(v) 
19         @@debug=v 
20       end 
21       def self.debug 
22         @@debug 
23       end 
24     
           # This redefinition of the [] operator, I call the original copy operator
           # then add any error checking.
25       def [](v) 
26         oret = self.old_squarebracket(v) 
27         if @@strict_retrieval 
28           raise "Attribute key(#{v}) returned nil. (This error can be over written by setting the Chef::Node.strict_retrieval=false)" if oret.nil? 
29         end 
30         puts "Debug: Attribute key(#{v}) accessed, returning: #{oret.inspect}" if @@debug 
31         oret 
32       end 
33     end #class Node 
34   end #class Chef 
35    
36   class Mash 
37     alias_method :old_squarebracket, "[]".to_sym 
38    
39     @@strict_retrieval=true 
40     def self.strict_retrieval=(v) 
41       @@strict_retrieval=v 
42     end 
43     def self.strict_retrieval 
44       @@strict_retrieval 
45     end 
46    
47     @@debug=false 
48     def self.debug=(v) 
49       @@debug=v 
50     end 
51     def self.debug 
52       @@debug 
53     end 
54    
55     def [](v) 
56       oret = self.old_squarebracket(v) 
57       if @@strict_retrieval 
58         raise "Attribute key(#{v}) returned nil. (This error can be over written by setting the Mash.strict_retrieval=false)" if oret.nil? 
59       end 
60       puts "Debug: Attribute key(#{v}) accessed, returning: #{oret.inspect}" if @@debug 
61       oret 
62     end #[]
63   end #class Mash 
64   

 

The previous code allows us to get this:

RuntimeError

————

Attribute key(my_attribute) returned nil. (This error can be over written by setting the Mash.strict_retrieval=false)

instead of this:

NoMethodError
————-
undefined method `[]’ for nil:NilClass

Which error would you rather get?

Until the robots take over,
Jonathan Malachowski

Leave a comment

Your email address will not be published. Required fields are marked *

X