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