Ruby block - NoMethodError: undefined method `-' for nil:NilClass - ruby

Getting started with Rails and I'm stuck with a block operation. I want to loop through a model and update integer values for a new field by calculating difference between two datetime fields. Doing this in the console:
MyModel.all.each do |m|
m.new_integer_field = m.existing_datetime_field - m.parent_object.existing_datetime_field
m.save!
end
The result is
NoMethodError: undefined method `-' for nil:NilClass
It works on one record if I do:
m = MyModel.find(1)
m.new_integer_field = m.existing_datetime_field - m.parent_object.existing_datetime_field
m.save
I guess it's a basic syntax thing, but couldn't find obvious explanations. Grateful to get some pointers forward.

The m.existing_datetime_field in one of the instances was nil and therefore the operation didn't work. You need to check for nil values if not sure:
MyModel.all.each do |m|
unless m.existing_datetime_field == nil
m.new_integer_field = m.existing_datetime_field - m.parent_object.existing_datetime_field
m.save!
end

As I already said probably one of the attributes of the instances are nil.
To check this use #inspect on the instances:
MyModel.all.each {|i| puts i.existing_datetime_field.inspect }
It seems one of these objects is nil and then the - method is not defined.
As you suggested you can check the operands before to calculate the differences.

Related

chef reassigning attributes from another namespace in recipe cause undefined method error in 4 levels array

I have a dependency on community consul cookbook from my cookbook.
Because I have consul in server and client modes I want to separate namespaces.
In this case, I can use following construction in my recipe:
node.default['consul']['config']['server'] = node['consul-server']['config']['server'] if node['consul-server']['config']['server']
node.default['consul']['config']['ui'] = node['consul-server']['config']['ui'] if node['consul-server']['config']['ui']
node.default['consul']['config']['ports']['http'] = node['consul-server']['config']['ports']['http'] if node['consul-server']['config']['ports']['http']
node.default['consul']['config']['ports']['https'] = node['consul-server']['config']['ports']['https'] if node['consul-server']['config']['ports']['https']
first two lines forks as expected, but two last lines generating a following error:
================================================================================
Recipe Compile Error in /tmp/kitchen/cache/cookbooks/eax-consul-cluster/recipes/default.rb
================================================================================
NoMethodError
-------------
undefined method `[]' for nil:NilClass
why this happens on same construction and the only difference between them is array one level deeper?
This is because at some level, the [] method returns nil. For example, suppose node.default['consul'] returns nil, then node.default['consul']['config'] should raise the error you saw.
If node.default is a hash of hashes of hashes of ..., then for reading values, you can try Hash#dig, for example:
h = {'a' => {'b' => 'c'} }
h.dig('a', 'b') #=> c
h.dig('x', 'y') #=> nil
For setting values, you have to make sure the parent level has already been set.
Switch that to if if node.read('consul-server', 'config', 'ports', 'http'). That automatically checks for nil in intervening keys in a similar way to Hash#dig.
On a side note since your naming conventions line up exactly below the first level merging them seems more appropriate and avoids all the conditionals, depth checking, etc.
For Example:
# Essentially a railsesque #deep_merge!
merge_proc = Proc.new do |_k,old,new|
if old.is_a?(Hash) && new.is_a?(Hash)
old.merge(new,&merge_proc)
else
new
end
end
node.default['consul'].merge!(node['consul-server'], &merge_proc)
Hash#merge! because a Mash (Chef's subclass of a Hash) is still a Hash

Using .match() inside of a block

I want to read a file and create an array of twitter handles. The file is a random collection of trending tweets, a snippet would be:
#David_Cameron #britishchambers #BCCConf Mention of pay rises in the
NHS. But what about all the redundancies to enable these pay rises?
in which case the code should return
["#David_Cameron", "#britishchambers"]
Placing /#\w+/.match(word)[0] works in irb, but as soon as I put it in a block with each or map:
def read_file(file_path)
text = File.open(file_path, 'r'){ |f| f.read}.split(" ")
text.each{|word| /#\w+/.match(word)[0] }
end
then I receive the error:
NoMethodError: undefined method `[]' for nil:NilClass
What am I doing wrong? Also, if I can do this inside the file.open block, that would be preferable.
What am I doing wrong?
By placing [] after /#\w+/.match(word), you are assuming that word always matches /#\w+/, thereby returning a MatchData object, which is not true. For example #BCCConf, does not match /#\w+/, in which case /#\w+/.match(word) is nil. [] is not defined on nil.
def read_file(file_path)
text = File.read(file_path)
text.scan(/#\w+/).flatten
end
Read the file, then use #scan to extract all the occurrences.

Understanding strange output in multidimensional array

I am new to ruby and I was trying to iterate over a 2d array. I made a mistake in my code. From that mistake, I noticed some unexpected output.
s = [["ham", "swiss"], ["turkey", "cheddar"], ["roast beef", "gruyere"]]
i = 0;
s.each{
|array| so = array[i] # pin
puts so[i]
}
Due to #pin, if i = 0, output is h t r. i = 1 gives w h r. i > 1 gives an error:
C:/Ruby.rb in `block in <main>': undefined method `[]' for nil:NilClass (NoMethodError)
from C:/Ruby.rb:3:in `each'
from C:/Ruby.rb:3:in `<main>'
If I use |array| so = array # pin, then my code does not produce strange output. I'll just fix the remaining stuff to make my code iterate for all values that 'i' can have.
Please explain this.
PS: Working code is here
s = [["ham", "swiss"], ["turkey", "cheddar"], ["roast beef", "gruyere"]]
s.each{
|array| so = array
array.each{
|str| puts str
}
}
For each type of sandwich, when i is 0, so is the 1st element, which is the meat. so[0] is the first letter of the meat.
When i is 1, which is the 2nd element, which is the cheese. so[1] is the second letter of the cheese.
When i is 3, there is no third component to the sandwich. so so is nil. so[2] is asking for the nil[2].
nil is a class, like everything in ruby. But nil does not implement the [] method, as arrays and other classes that implement the Enumerable module do.
Since nil does not support the [] method, then you get the undefined method error.
Even operations that are built into other languages, like +, [], and == are methods that can be overridden in Ruby.
To understand exactly what's happening, try this bit of code:
class NilClass
def [] (i)
nil
end
end
Executing that will open up the existing NilClass, and add a method called []. Then you can do nil[1] and it will return nil. Changing an existing class like this is known as monkey patching in the Ruby world.
When you ask for so[2] you are actually asking for the third element, and if it doesn't exist, you'll get an error.
I recommend structuring your blocks like so:
s.each do |pair|
puts pair
end
Note the do/end instead of {} and the placement of the iteration variable inline with the each. Also note that this is equivalent to your "working code" so you don't need the extra iterator in this case.

Inner working of Integer('string') call?

If I want to see if a string is a valid integer, I can do:
puts Integer('1') #=> 1
as any non-integer would raise an error:
puts Integer('a') #=> invalid value for Integer(): "a" (ArgumentError)
If I want to make sure my variable is both a Float and an Integer, I do not want to repeat myself so I try to put the classes in a list:
x = '1'
[Integer, Float].each{|c| puts c(x) } #=> undefined method `c' for main:Object (NoMethodError)
Will someone explain why this does not work, and if there is some way to achieve what I am looking for? What kind of method call is Integer(var)?
Note, I have no real world problem I'm trying to solve here, I'm just curious.
When you do:
Integer('10')
you are essentially calling the Integer class method defined in Kernel, passing it the argument 10.
To achieve what you want you can do:
[:Integer, :Float].each { |c| puts method(c).call(x) }

Is ruby's multidimensional array out of bounds behaviour consistent?

If I have a multidimensional array, I can exceed the bounds in the final dimension and get a nil return, but if I exceed the bounds of a non-final dimension, I receive an error. Is this by design, and if so what is the reasoning?
> ar = [ [00,01,02], [10,11,12], [20,21,22] ]
=> [[0, 1, 2], [10, 11, 12], [20, 21, 22]]
> ar[2][2]
=> 22
> ar[2][3]
=> nil
> ar[3][2]
NoMethodError: undefined method `[]' for nil:NilClass
from (irb):32
from :0
I understand why this is happening, but why isn't nil[] defined to return nil?
In Ruby, there are no multi-dimensional arrays. What you have there is an array, containing arrays as elements. So if you get the first "dimension", you get another array back (or nil if you exceeded the bounds of the outer array).
nil is an object of NilClass that has a finite (and small) set of defined methods. And the [] method, that is called when you use the whatever[:foo] syntax is just not defined on NilClass. Thus it can't return anything.
Generally, it wouldn't make sense to define all possible methods on nil as it would confuse people even more and would introduce a plethora of hard-to-detect bugs.
However if you know what you are doing and are willing to deal with the implications, you can use the try method that is defined by some frameworks (e.g. ActiveSupport for Rails) but is not part of Ruby itself. It catches the NoMethodError and returns nil instead. In your case you could use
> ar[2].try(:[], 2)
=> nil
However, this is generally discouraged as it makes things harder to debug. Instead you should check the bounds before trying to access the array (e.g. by using array.length) or by using include looping constructs like ar.each {|e| puts e}.
I don't know if Matz ever documented why the NilClass is designed the way it is. If it's not so then we can only guess. My guess is that it is based on the behavior of Smalltalk.
nil could either be message eating or exception throwing. Ruby has an exception throwing nil object.
If you have a message eating nil then it's difficult to determine the object that was the first one to return nil in a chained call like arr[1][2][3]. I don't know how often this really is an issue but it seems to be a valid point. As a counterexample Objective-C seems to do just fine with a message eating nil.
You can patch NilClass to become message eating
class NilClass
def method_missing(*)
nil
end
end
or for arrays only
class NilClass
def []
nil
end
end
both makes nil[] return a nil and either can break existing code
nil[][][][]
=> nil

Resources