isn't + an operator? why would it not be defined?
here's my code:
Class Song
##plays = 0
def initialize(name, artist, duration)
#name = name
#artist = artist
#duration = duration
#plays = 0
end
attr_reader :name, :artist, :duration,
attr_writer :name, :aritist, :duration
def play
#plays += 1
##plays += 1
"This Song: ##plays play(s). Total ###plays plays."
end
def to_s
"Song: ##name--##artist (##duration)"
end
end
First, this code doesn't even run: class on Line 1 needs to be spelled with a lowercase c, and you can't have a comma after the last item in a statement (your attr_reader line). I don't get a NoMethodError after fixing those and running Song.new or Song#play or Song#to_s.
Anyway, you will always get that NoMethodError when you try adding anything to a nil value:
>> nil + 1
NoMethodError: undefined method `+' for nil:NilClass
from (irb):1
>> nil + nil
NoMethodError: undefined method `+' for nil:NilClass
from (irb):2
>> # #foo is not defined, so it will default to nil
?> #foo + 2
NoMethodError: undefined method `+' for nil:NilClass
from (irb):4
So you might be trying to add something to an uninitialized instance variable... or it could be anything. You always need to post full, minimal code to duplicate an error if you want to be helped properly.
+ is defined on numbers (among other things). However, as the error message says, it is not defined on nil. This means you can't do nil + something and why would you?
That being said, you're actually not calling nil + something anywhere in the code you've shown (you're initializing both #plays and ##plays to 0, and you're not setting them to nil at any point). And as a matter of fact your code runs just fine once you remove the two syntax error (Class should be class and there should be no comma after :duration). So the error is not in the code you've shown.
maybe you should include ##plays = 0 in your initialize method?
Related
I have a Author class and need to make some validations before initializing
class Author
include Validations
attr_accessor :name, :biography
def initialize(name, biography = nil)
validate_author
#name = name
#biography = biography
end
def to_s
"#{name}\n#{biography}"
end
end
I use module for this
module Validations
def validate_author
raise ::StandardError, 'Name is required' if name.strip.empty?
end
end
And i get this error
extentions/validations.rb:8:in `validate_author': undefined method `strip' for nil:NilClass (NoMethodError)
It's ruby application
The error gives you the clue, "for nil:NilClass (NoMethodError)", you can't apply the method .split to a Nil, what's happening that you are not passing anything so #name is nil by default, i don't know the rest of the code, but one think that you can do is use the "||" conditional writing something like.
raise ::StandardError, 'Name is required' if name.strip.empty? || name == nil
Name is not yet assigned
class Author
include Validations
attr_accessor :name, :biography
def initialize(name, biography = nil)
#name = name
#biography = biography
validate_author
end
def to_s
"#{name}\n#{biography}"
end
end
Or you can pass name to validate_author(name)
I think because the validate_author runs before setting the name instance variable. So if you move validate_author after setting instance variables, then it should work. However undefined method strip' for nil:NilClass (NoMethodError)` will still be raised as long name is nil.
I have a method that looks like this:
def calculate_the_thing(hsh)
hsh[:x] + hsh[:y] + hsh[:z]
end
which takes something like this:
{:x => 5, :y => nil, :z => 2, :a => 5}
I'd like to patch some classes so that when the + method gets a nil value, it treats it as zero. Seems reasonable. How might I do that?
As #jforberg points out, you can just use the #to_i method which will return 0 for nil.
def calculate_the_thing(hsh)
hsh[:x].to_i + hsh[:y].to_i + hsh[:z].to_i
end
Alternatively, you can also define the hash with an automatic default value...
hsh = Hash.new{0}
But if you have a method that explicitly puts nil as a hash value that will override the default value.
You need to monkey-patch Nilclass to coerce nil to an integer. Here is the code:
class NilClass
def coerce(n)
return [0, n] if n.is_a? Numeric
super
end
end
1 + nil
#=> 1
Have a look at this thread - In Ruby, how does coerce() actually work? - to understand the concept of coerce.
However, there is an issue with above code:
nil + 1
#=> undefined method `+' for nil:NilClass (NoMethodError)
To fix this problem, you will have to define + method on NilClass
class NilClass
def +(param)
param + self
end
end
nil + 1
#=> 1
If we try to get adventurous and try:
nil * 10
#=> undefined method `*' for nil:NilClass (NoMethodError)
By being adventurous, lets handle all such undefined methods by implementing our own method_missing handler.
class NilClass
def method_missing m, *args, &block
args.first.send(m, self, &block) if args.size == 1
end
end
p nil * 1
#=> 0
Next, lets try:
nil + "hello"
# '+': can't convert NilClass to String (NilClass#to_str gives NilClass) (TypeError)
Lets fix this as well
class NilClass
def to_str
""
end
end
nil + "hello"
#=> "hello"
Next, lets try this:
nil + [1,2,3]
#=> '+': can't convert NilClass to Array (NilClass#to_ary gives NilClass) (TypeError)
Lets fix it:
class NilClass
def to_ary
[]
end
end
nil + [1,2,3]
#=> [1, 2, 3]
We now have this version of NilClass:
class NilClass
def coerce(n)
return [0, n] if n.is_a? Numeric
super
end
def method_missing m, *args, &block
args.first.send(m, self, &block) if args.size == 1
end
def to_str
""
end
def to_ary
[]
end
end
Caution:: Above code shows that what you want to do can be done. However, this should be used only for experimental and learning purpose. It is really not feasible to make nil behave like other operand in all operations and you will end up monkey-patching NilClass to no end.
Hence, its better to stay off from this kind of monkey patching to avoid scary surprises to future Rubyists who will be maintaining your code.
The following Ruby code raises the confusing error "no id given" shown at the end. How do I avoid this problem?
class Asset; end
class Proxy < Asset
def initialize(asset)
#asset
end
def method_missing(property,*args)
property = property.to_s
property.sub!(/=$/,'') if property.end_with?('=')
if #asset.respond_to?(property)
# irrelevant code here
else
super
end
end
end
Proxy.new(42).foobar
#=> /Users/phrogz/test.rb:13:in `method_missing': no id given (ArgumentError)
#=> from /Users/phrogz/test.rb:13:in `method_missing'
#=> from /Users/phrogz/test.rb:19:in `<main>'
The core of this problem can be shown with this simple test:
def method_missing(a,*b)
a = 17
super
end
foobar #=> `method_missing': no id given (ArgumentError)
This error arises when you call super inside method_missing after changing the value of the first parameter to something other than a symbol. The fix? Don't do that. For example, the method from the original question can be rewritten as:
def method_missing(property,*args)
name = property.to_s
name.sub!(/=$/,'') if name.end_with?('=')
if #asset.respond_to?(name)
# irrelevant code here
else
super
end
end
Alternatively, be sure to explicitly pass a symbol as the first parameter to super:
def method_missing(property,*args)
property = property.to_s
# ...
if #asset.respond_to?(property)
# ...
else
super( property.to_sym, *args )
end
end
I was playing with the local,class variable and instance variable creation inside the class block as below. But I found something which I failed to explain myself. My confusion has been posted between the two codes below.
class Foo
def self.show
##X = 10 if true
p "hi",##X.object_id,x.object_id
end
end
#=> nil
Foo.show
#NameError: undefined local variable or method `x' for Foo:Class
# from (irb):4:in `show'
# from (irb):7
# from C:/Ruby193/bin/irb:12:in `<main>'
The above erros is expected. But in the below code I have assigned the class variable ##X to 10. But in the p statement I used instance variable #X.Why did the error not throw up like the above code ?
class Foo
def self.show
##X = 10 if true
p "hi",#X.object_id
end
end
#=> nil
Foo.show
"hi"
4
#=> ["hi", 4]
Because of everything is object and no explicit variable declaration is required in Ruby, you code
p #X.object_id
silently introduces an instance variable #X (#X.nil? == true). You can see this magic in irb:
~ irb
> p #x.object_id
# 8
# ⇒ 8
class Card
attr_accessor :number, :suit
def initialize(number, suit)
#number = number
#suit = suit
end
def to_s
"#{#number} of #{#suit}"
end
end
I'm assuming this creates a new array correct?
But why the use of the AT symbol? When should I use it and not use it?
#stack_of_cards = []
#stack << Card.new("A", "Spades")
puts #stack
# => BlackjackGame.rb:17: undefined method `<<' for nil:NilClass (NoMethodError)
Any ideas why this error is firing?
Exactly as it says in error: variable #stack is not defined (or nil).
Did you mean #stack_of_cards << .. instead?
If you had warnings on (ruby -W2 script_name.rb), you would have got a warning that #stack is not merely nil, but undefined. See How do I debug Ruby scripts? for more hints on how to debug.