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?