undefined method `artist' for nil:NilClass (NoMethodError) - ruby

I am getting this error when I run the following code:
#!/usr/bin/env ruby -rubygems
require File.join(File.dirname(__FILE__), 'authentication')
require "csv" # faster_csv (ruby 1.9)
lines = CSV.read(File.join(File.dirname(__FILE__), 'karaoke.csv')) # Exported an Excel file as CSV
lines.slice!(0) # remove header line
collection = StorageRoom::Collection.find('collection ID')
Song = collection.entry_class
lines.each do |row|
karaoke = Song.new(:artist => row[0], :song => row[1], :genre => row[2], :file => StorageRoom::File.new_with_filename("#{karaoke.artist}#{karaoke.song}.mov"))
if karaoke.save
puts "Misuero Karaoke Latino saved: #{karaoke.artist}, #{karaoke.song}, #{karaoke.genre}"
else
puts "Misuero Karaoke Latino could not be saved: #{karaoke.errors.join(', ')}"
end
end
And the error is:
import_csv.rb:15:in `block in <main>': undefined method `artist' for nil:NilClass (NoMethodError)
from import_csv.rb:14:in `each'
from import_csv.rb:14:in `<main>'
I'm interested in learning why this error occurred as well as the solution. Thanks in advance!

Look at line 15 (import_csv.rb:15 tells you where to search for the issue):
karaoke = Song.new(:artist => row[0], :song => row[1], :genre => row[2],
:file => StorageRoom::File.new_with_filename("#{karaoke.artist}#{karaoke.song}.mov"))
In the right part of assignment expression you use karaoke.artist and karaoke.song to construct :file part of your Song, but karaoke variable is uninitialized yet (it appears on the left). In fact ruby interpreter defined karaoke variable when it saw the assignment operator and started evaluation of right-hand part of assignment expression (to initialize variable) and failed, because defined variable has nil value.

It appears that the problem lies in your assignment of the karaoke variable. When you are assigning anything to a new variable, the right side of the assignment is computed before the left side. So, at the moment your code gets to line 15, the karaoke variable is nil.
So, when you use the karaoke variable in the method StorageRoom::File.new_with_filename, it is a nil object. karaoke does not contain anything until the entire right side of the assignment is computed. Then it is tied to the karaoke variable.
You should consider using something like row[0] and row[2] instead of karaoke.artist and karaoke.genre.

It means you are calling artist method on a nil/Null
possibly try replacing #{karaoke.artist}#{karaoke.song} with #{row[0]}#{row[1]})
karaoke = Song.new(:artist => row[0], :song => row[1], :genre => row[2], :file => StorageRoom::File.new_with_filename("#{row[0]}#{row[1]}.mov"))

You can't use karaoke object in initializing of itself.
In ruby when you write(assuming that you've never used "a" variable before)
a = some_expression_or_value
the interpreter calculates the value of the "right part" which is some expression or value and then assigns it to the variable. Your variable karaoke hasn't been used before which means that it's value is nil. That's why you get this error.

Related

Behaviours of a Ruby local variable shadowing an instance method

I recently read a blog post about Ruby's behaviours with regards to a local variable shadowing a method (different to, say, a block variable shadowing a method local variable, which is also talked about in this StackOverflow thread), and I found some behaviour that I don't quite understand.
Ruby's documentation says that:
[V]ariable names and method names are nearly identical. If you have not assigned to one of these ambiguous names ruby will assume you wish to call a method. Once you have assigned to the name ruby will assume you wish to reference a local variable.
So, given the following example class
# person.rb
class Person
attr_accessor :name
def initialize(name = nil)
#name = name
end
def say_name
if name.nil?
name = "Unknown"
end
puts "My name is #{name.inspect}"
end
end
and given what I now know from reading the information from the links above, I would expect the following:
The name.nil? statement would still refer to the name instance method provided by attr_accessor
When the Ruby parser sees the name = "Unknown" assignment line in the #say_name method, it will consider any reference to name used after the assignment to refer to the local variable
Therefore, even if the Person had a name assigned to it on initialisation, the name referenced in the final line of #say_name method would be nil
And it looks like this can be confirmed in an irb console:
irb(main):001:0> require "./person.rb"
true
# `name.nil?` using instance method fails,
# `name` local variable not assigned
irb(main):002:0> Person.new("Paul").say_name
My name is nil
nil
# `name.nil?` using instance method succeeds
# as no name given on initialisation,
# `name` local variable gets assigned
irb(main):003:0> Person.new.say_name
My name is "Unknown"
nil
However, if I do some inline debugging and use Pry to attempt to trace how the referencing of name changes, I get the following:
irb(main):002:0> Person.new("Paul").say_name
From: /Users/paul/person.rb # line 13 Person#say_name:
10: def say_name
11: binding.pry
12:
=> 13: p name
14: if name.nil?
15: name = "Unknown"
16: end
17:
18: puts "My name is #{name.inspect}"
19: end
[1] pry(#<Person>)> next
"Paul"
Okay, that makes sense as I'm assuming name is referring to the instance method. So, let's check the value of name directly...
From: /Users/paul/person.rb # line 14 Person#say_name:
10: def say_name
11: binding.pry
12:
13: p name
=> 14: if name.nil?
15: name = "Unknown"
16: end
17:
18: puts "My name is #{name.inspect}"
19: end
[2] pry(#<Person>)> name
nil
Err... that was unexpected at this point. I'm currently looking at a reference to name above the assignment line, so I would have thought it would still reference the instance method and not the local variable, so now I'm confused... I guess somehow the name = "Unknown" assignment will run, then...?
[3] pry(#<Person>)> exit
My name is nil
nil
Nope, same return value as before. So, what is going on here?
Was I wrong in my assumptions about name.nil? referencing the name instance method? What is it referencing?
Is all this something related to being in the Pry environment?
Something else I've missed?
For reference:
➜ [ruby]$ ruby -v
ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16]
Edit
The example code in this question is meant to be illustrative of the (I think) unexpected behaviour I'm seeing, and not in any way illustrative of actual good code.
I know that this shadowing issue is easily avoided by re-naming the local variable to something else.
Even with the shadowing, I know that it is still possible to avoid the problem by specifically invoking the method, rather than reference the local variable, with self.name or name().
Playing around with this further, I'm starting to think it's perhaps an issue around Pry's environment. When running Person.new("Paul").say_name:
From: /Users/paul/person.rb # line 13 Person#say_name:
10: def say_name
11: binding.pry
12:
=> 13: p name
14: if name.nil?
15: name = "Unknown"
16: end
17:
18: puts "My name is #{name.inspect}"
19: end
At this point, the p statement hasn't run yet, so let's see what Pry says the value of name is:
[1] pry(#<Person>)> name
nil
This is unexpected given that Ruby's documentation says that since no assignment has been made yet, the method call should be invoked. Let's now let the p statement run...
[2] pry(#<Person>)> next
"Paul"
...and the value of the method name is returned, which is expected.
So, what is Pry seeing here? Is it modifying the scope somehow? Why is it that when Pry runs name it gives a different return value to when Ruby itself runs name?
Once Ruby has decided that name is a variable and not a method call that information applies to the totality of the scope it appears within. In this case it's taking it to mean the whole method. The trouble is if you have a method and a variable with the same name the variable only seems to take hold on the line where the variable has been potentially assigned to and this re-interpretation affects all subsequent lines within that method.
Unlike in other languages where method calls are made clear either by some kind of prefix, suffix or other indicator, in Ruby name the variable and name the method call look identical in code and the only difference is how they're interpreted at "compile" time proior to execution.
So what's happening here is a little confusing and subtle but you can see how name is being interpreted with local_variables:
def say_name_local_variable
p defined?(name) # => "method"
p local_variables # => [:name] so Ruby's aware of the variable already
if name.nil? # <- Method call
name = "Unknown" # ** From this point on name refers to the variable
end # even if this block never runs.
p defined?(name) # => "local-variable"
p name # <- Variable value
puts "My name is #{name.inspect}"
end
I'm quite surprised that, given how obnoxiously particular Ruby can be with the -w flag enabled, that this particular situation generates no warnings at all. This is likely something the'll have to emit a warning for, a strange partial shadowing of methods with variables.
To avoid method ambiguity you'll need to prefix it to force it to be a method call:
def say_name
name = self.name || 'Unknown'
puts "My name is #{name.inspect}"
end
One thing to note here is that in Ruby there are only two logically false values, literal nil and false. Everything else, including empty strings, 0, empty arrays and hashes, or objects of any kind are logically true. That means unless there's a chance name is valid as literal false then || is fine for defaults.
Using nil? is only necessary when you're trying to distinguish between nil and false, a situation that might arise if you have a three-state checkbox, checked, unchecked, or no answer given yet.
What looks like inconsistent return values for name during runtime and while debugging doesn't seem to related to Pry, but more about binding itself encapsulating the entire execution context of a method, versus the progressive change in what shadowed variables reference at runtime. To build on the example method with some more debugging code:
def say_name
puts "--- Before assignment of name: ---"
puts "defined?(name) : #{defined?(name).inspect}"
puts "binding.local_variable_defined?(:name) : #{binding.local_variable_defined?(:name).inspect}"
puts "local_variables : #{local_variables.inspect}"
puts "binding.local_variables : #{binding.local_variables.inspect}"
puts "name : #{name.inspect}"
puts "binding.eval('name') : #{binding.eval('name').inspect}"
if name.nil?
name = "Unknown"
end
puts "--- After assignment of name: ---"
puts "defined?(name) : #{defined?(name).inspect}"
puts "binding.local_variable_defined?(:name) : #{binding.local_variable_defined?(:name).inspect}"
puts "local_variables : #{local_variables.inspect}"
puts "binding.local_variables : #{binding.local_variables.inspect}"
puts "name : #{name.inspect}"
puts "binding.eval('name') : #{binding.eval('name').inspect}"
puts "My name is #{name.inspect}"
end
Now, running Person.new("Paul").say_name outputs:
--- Before assignment of name: ---
defined?(name) : "method"
binding.local_variable_defined?(:name) : true
local_variables : [:name]
binding.local_variables : [:name]
name : "Paul"
binding.eval('name') : nil
--- After assignment of name: ---
defined?(name) : "local-variable"
binding.local_variable_defined?(:name) : true
local_variables : [:name]
binding.local_variables : [:name]
name : nil
binding.eval('name') : nil
My name is nil
which shows that binding never references the method call of name and only ever the eventually-assigned name variable.

Ruby: how to determine an object type in irb and binding.pry?

I have just begun adding binding.pry after my objects in order to start determining what they evaluate to. However, now I want to know what type of object the output is.
How can I do this in irb? How can I do this in binding.pry?
EDIT:
Here is what I have tried to determine the type of object H. I know it is a hash, but sometimes it is less obvious in the console:
irb(main):001:0> H = Hash["a" => 100, "b" => 200]
=> {"a"=>100, "b"=>200}
irb(main):002:0> H
=> {"a"=>100, "b"=>200}
irb(main):003:0> type(H)
NoMethodError: undefined method `type' for main:Object
from (irb):3
from /Users/macbook/.rbenv/versions/2.3.0/bin/irb:11:in `<main>'
irb(main):004:0> object.class(H)
NameError: undefined local variable or method `object' for main:Object
Did you mean? object_id
from (irb):4
from /Users/macbook/.rbenv/versions/2.3.0/bin/irb:11:in `<main>'
irb(main):005:0> object.is_a?(H)
NameError: undefined local variable or method `object' for main:Object
Did you mean? object_id
from (irb):5
from /Users/macbook/.rbenv/versions/2.3.0/bin/irb:11:in `<main>'
You can get the class with
h = {a: 100, b: 200}
h.class
# Hash
You can also check if something is of a particular class
h.is_a? Hash
# true
h.is_a? String
# false
The Ruby Language has no concept of "type". Or, to be more precise: in Ruby, types only exist latently in the mind of the programmer, they are not manifest in the program. Ergo, there is no way to get an object's type from the program, you can only get it from the programmer.
Sometimes, types are written down in the documentation or in comments. Some types are basically community folklore, not written down anywhere but passed down from generation to generation.
You can ask an object about its class, you can ask it about its methods, you can ask it whether it responds to a specific message, but you can not ask it about its type.
Note that older versions of Ruby had a type but that method was removed because it was misleading: it didn't actually return the type (I explained above why that is impossible), it returned the class, which is something completely different.

How to serialize Exception

According to ruby-doc and apidock, you can serialize and deserialize an exception using to_json and json_create.
But after having wasted some time trying to use them, I still haven't found a way.
Calling exc.to_json gives me an empty hash, and Exception.json_create(hash) gives me this error: undefined method 'json_create' for Exception:Class
I guess I could easily recreate those functions since the source is available, but I'd rather understand what I'm doing wrong... Any idea?
The JSON module doesn't extend Exception by default. You have to require "json/add/exception". I'm not sure if this is documented anywhere:
require "json/add/exception"
begin
nil.foo
rescue => exception
ex = exception
end
puts ex.to_json
# => {"json_class":"NoMethodError","m":"undefined method `foo' for nil:NilClass","b":["prog.rb:5:in `<main>'"]}
Check out ext/json/lib/json/add in the Ruby source to see which classes work this way. If you do require "json/add/core" it will load JSON extensions for Date, DateTime, Exception, OpenStruct, Range, Regexp, Struct, Symbol, Time, and others.
The answer from Jordan is correct. If you have a case eg. that you need to serialize an Exception and send to to ActiveJob where you want to reconstruct it, then you need to use .as_json method.
require "json/add/exception"
begin
nil.foo
rescue => exception
ex = exception
end
puts ex.to_json.class
# => String
string = ex.to_json
puts Exception.json_create(string).message
# => m
puts ex.as_json.class
# => Hash
hash = ex.as_json
puts Exception.json_create(hash).message
# => undefined method `foo' for nil:NilClass
You need to read the source code to understand why Exception.json_create(string).messagereturns m :)
It's also important to note that the Exception.json_create example doesn't keep the error class.
Exception.json_create(JSON.parse(ArgumentError.new('error').to_json))
# => #<Exception: error>
Try instead:
require "json/add/exception"
def deserialize_exception(json)
hash = JSON.parse(json)
hash['json_class'].constantize.json_create(hash)
end

Array.find method problem

I find this line in the ZenTest source code:
result = #test_mappings.find { |file_re, ignored| filename =~ file_re }
The #test_mappings and result here are both Array object, but I didn't found 'find' method on Array class in ruby doc. I also tried it on irb:
irb(main):014:0> Array.respond_to? :find
=> false
irb(main):015:0> [1,2,3].find
LocalJumpError: no block given
from (irb):15:in `find'
from (irb):15:in `each'
from (irb):15:in `find'
from (irb):15
irb(main):016:0> [1,2,3].find{|x| x>1}
=> 2
Could any one explain it to me? How could find method also return an Array object? thanks in advance.
Array includes the Enumerable module, which adds the find method.
In your example you tested Array.respond_to. This will only return true for class methods of Array. find is an instance method, so respond_to? must be invoked on an instance of the class.
>> a = Array.new
=> []
>> a.respond_to? :find
=> true
Another sometimes useful trick is calling the 'methods' function which lists all the methods available to the instance of the object and using the grep method to filter out for something specific. It also gives you a good picture of what standard methods are provided by base classes without referring to docs.
a = Array.new
=> []
>> a.methods.grep /find/
=> ["find", "find_all"]

Create a ruby method that accepts a hash of parameters

I don't know how to create a ruby method that accepts a hash of parameters. I mean, in Rails I'd like to use a method like this:
login_success :msg => "Success!", :gotourl => user_url
What is the prototype of a method that accepts this kind of parameters? How do I read them?
If you pass paramaters to a Ruby function in hash syntax, Ruby will assume that is your goal. Thus:
def login_success(hsh = {})
puts hsh[:msg]
end
A key thing to remember is that you can only do the syntax where you leave out the hash characters {}, if the hash parameter is the last parameter of a function. So you can do what Allyn did, and that will work. Also
def login_success(name, hsh)
puts "User #{name} logged in with #{hsh[:some_hash_key]}"
end
And you can call it with
login_success "username", :time => Time.now, :some_hash_key => "some text"
But if the hash is not the last parameter you have to surround the hash elements with {}.
With the advent of Keyword Arguments in Ruby 2.0 you can now do
def login_success(msg:"Default", gotourl:"http://example.com")
puts msg
redirect_to gotourl
end
In Ruby 2.1 you can leave out the default values,
def login_success(msg:, gotourl:)
puts msg
redirect_to gotourl
end
When called, leaving out a parameter that has no default value will raise an ArgumentError
Use one single argument. Ruby will transform the named values into a hash:
def login_success arg
# Your code here
end
login_success :msg => 'Success!', :gotourl => user_url
# => login_success({:msg => 'Success!', :gotourl => user_url})
If you really want to make sure you get a hash, instead of the default ruby duck typing, then you would need to control for it. Something like, for example:
def login_success arg
raise Exception.new('Argument not a Hash...') unless arg.is_a? Hash
# Your code here
end

Resources