Due to some sloppy coding on my part, I've noticed that undeclared instance variables seem to evaluate nil where undeclared local variables do not. Is this default nill value for instance variables intended behavior that I can exploit (like with a conditional to check whether the variable has been set true) or is this just a quirk that should be left alone?
2.6.6 :001 > puts #test
=> nil
2.6.6 :002 > puts test
Traceback (most recent call last):
2: from (irb):2
1: from (irb):2:in `test'
ArgumentError (wrong number of arguments (given 0, expected 2..3))
Per the Ruby doc:
Instance variables of ruby do not need declaration. This implies a flexible structure of objects. In fact, each instance variable is dynamically appended to an object when it is first referenced.
https://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/instancevars.html
You can also get a list of all previously defined instance variables:
ClassName.instance_variables
or
a = ClassName.new
a.instance_variables #specific to instance
tl;dr Yes, this is documented in Assignment.
Whether a thing is defined,and what it is defined as, can be had with the defined? keyword.
2.6.5 :003 > defined?(fnord)
=> nil
2.6.5 :004 > fnord = 42
=> 42
2.6.5 :005 > defined?(fnord)
=> "local-variable"
2.6.5 :006 > defined?($fnord)
=> nil
2.6.5 :007 > $fnord = 42
=> 42
2.6.5 :008 > defined?($fnord)
=> "global-variable"
2.6.5 :009 > defined?(#fnord)
=> nil
2.6.5 :010 > #fnord = 42
=> 42
2.6.5 :011 > defined?(#fnord)
=> "instance-variable"
2.6.5 :012 > defined?(FNORD)
=> nil
2.6.5 :013 > FNORD = 42
=> 42
2.6.5 :014 > defined?(FNORD)
=> "constant"
This is useful for debugging, but I'd discourage "exploiting" it in application code.
First, let's clear up your example. test is a method of main. It is inherited from Kernel#test.
2.6.5 :004 > defined?(test)
=> "method"
2.6.5 :005 > method(:test)
=> #<Method: main.test>
A proper example looks like this.
2.6.5 :008 > puts #fnord
=> nil
2.6.5 :009 > puts fnord
Traceback (most recent call last):
4: from /Users/schwern/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `<main>'
3: from /Users/schwern/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `load'
2: from /Users/schwern/.rvm/rubies/ruby-2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
1: from (irb):9
NameError (undefined local variable or method `fnord' for main:Object)
Local variables
The error hints at what's happening: ambiguity. fnord could be the local variable fnord or self.fnord. Neither have been declared, Ruby won't guess at what you meant, so you get a NameError.
In Ruby local variable 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.
Declared local variables can be discovered with local_variables.
2.6.5 :012 > foo = 42
=> 42
2.6.5 :013 > Kernel.local_variables
=> [:foo, :_]
Instance variables
An uninitialized instance variable has a value of nil. If you run Ruby with warnings enabled, you will get a warning when accessing an uninitialized instance variable.
There are various methods for introspecting instance variables like instance_variables and instance_variables_defined?. While there are some instances this might be necessary when writing libraries, I strongly discourage exploiting the distinction between nil and "undefined" in application code; it makes things fragile.
Class variables
Accessing an uninitialized class variable will raise a NameError exception.
Global variables
An uninitialized global variable has a value of nil.
Related
This question already has an answer here:
In Ruby, why does nil[1]=1 evaluate to nil?
(1 answer)
Closed 6 years ago.
I've just noticed a very puzzling change in behaviour between Ruby 2.2.4 and Ruby 2.3.0: trying to use [] on nil in an assignment does not raise a NoMethodError anymore.
Ruby 2.2.4:
box:~ jfoeh$ irb
2.2.4 :001 > a = nil
=> nil
2.2.4 :002 > a[:b] = 1
NoMethodError: undefined method `[]=' for nil:NilClass
from (irb):2
from /Users/jfoeh/.rvm/rubies/ruby-2.2.4/bin/irb:11:in `<main>'
In contrast Ruby 2.3.0:
box:~ jfoeh$ irb
2.3.0 :001 > a = nil
=> nil
2.3.0 :002 > a[:b] = 1
=> nil
Is that behaviour expected, or is this a regression of sorts?
We originally noticed this when we found such an assignment seemingly swallowing exceptions in 2.3:
2.3.0 :001 > require 'date'
=> true
2.3.0 :002 > a = nil
=> nil
2.3.0 :003 > a[:b] = Date.parse(nil)
=> nil
whereas Ruby 2.2 would execute the right-hand side first and raise a TypeError as one would expect.
This was a bug introduced in ruby version 2.3.0. It has since been fixed, as of version 2.3.1.
Here is the original issue that was raised, on ruby-lang.org, and here is the commit which resolves the issue.
I am trying to understanding eval and binding contexts in Ruby.
Consinder the following in irb
irb(main):001:0> eval "a = 42"
=> 42
irb(main):002:0> a
NameError: undefined local variable or method `a' for main:Object
from (irb):2
from /Users/niels/.rbenv/versions/2.1.3/bin/irb:11:in `<main>'
irb(main):003:0>
Why is a not defined?
If I declare a prior to evalling, the value 42 is assigned to a.
It appears to me that some sort of block scope applies where local variables are available inside the eval context, but any variables declared are only declared in the block scope.
How do I eval code without creating a new scope?
Why is a not defined?
a is defined within the binding of the eval'd code, but not outside of it. That's just how local variables work. They are local to the scope they are defined in. That's why they are called "local" variables, after all.
If I declare a prior to evalling, the value 42 is assigned to a.
Yes, eval's scope nests, just like block scope.
How do I eval code without creating a new scope?
You can't. In Ruby 1.8 and prior, eval would indeed leak variables into the surrounding scope, but that leak was fixed in 1.9 and onwards.
That is because a is not on the same context than irb.
look at this code
2.2.1 :001 > eval "a = 42"
=> 42
2.2.1 :002 > a
NameError: undefined local variable or method `a' for main:Object
from (irb):2
from /home/hbranciforte/.rvm/rubies/ruby-2.2.1/bin/irb:11:in `<main>'
it's the same as blocks
2.2.1 :001 > 1.times{|a| b=1}
=> 1
2.2.1 :002 > b
NameError: undefined local variable or method `b' for main:Object
from (irb):2
from /home/hbranciforte/.rvm/rubies/ruby-2.2.1/bin/irb:11:in `<main>'
But...
2.2.1 :001 > a=nil
=> nil
2.2.1 :002 > eval "a = 42"
=> 42
2.2.1 :003 > a
=> 42
it's like
2.2.1 :001 > b=nil
=> nil
2.2.1 :002 > 1.times{|a| b=1}
=> 1
2.2.1 :003 > b
=> 1
2.2.1 :004 >
Instances variables work because it is part of "self"
2.2.1 :001 > eval "#b = 42"
=> 42
2.2.1 :002 > #b
=> 42
2.2.1 :003 >
Take a look how to context could be sent to a method. from http://ruby-doc.org/core-2.2.0/Binding.html
def get_binding(param)
return binding
end
b = get_binding("hello")
b.eval("param") #=> "hello"
I don't know if I was clear but, that I want to say is that the real thing that really matters is the context (or scope) where ruby will allocate/deallocate memory and his access.
courtesy of irb:
2.0.0-p0 :006 > #instance_variable = "from an instance variable"
=> "from an instance variable"
2.0.0-p0 :007 > variable = "from a variable"
=> "from a variable"
2.0.0-p0 :008 > instance_variable_get(:#instance_variable)
=> "from an instance variable"
2.0.0-p0 :009 > variable_get(:variable)
NoMethodError: undefined method `variable_get' for main:Object
from (irb):9
from /usr/local/rvm/rubies/ruby-2.0.0-p0/bin/irb:16:in `<main>'
2.0.0-p0 :010 >
I'm simply trying to programmatically 'query' for a variable and return its contents. Exactly like instance_variable_get but for a variable.
It's for a custom rspec matcher. Not some crazy workaround :)
In Ruby 2.1 and later, you can use Binding#local_variable_get.
In prior versions of Ruby, you have to use eval. If you want to do some sanity-checking before evaluating a supposed variable name, you can check whether the named variable is in local_variables.
You could use eval:
irb(main):007:0> variable = "from a variable"
=> "from a variable"
irb(main):008:0> eval 'variable'
=> "from a variable"
Some undeclared variables are nil, some throw an error. How come?
$ irb
1.9.3p0 :001 > asdf # local
NameError: undefined local variable or method `asdf' for main:Object
from (irb):1
from /Users/saizai/.rvm/rubies/ruby-1.9.3-p0/bin/irb:16:in `<main>'
1.9.3p0 :002 >#asdf # instance
=> nil
1.9.3p0 :003 >##asdf # class
NameError: uninitialized class variable ##asdf in Object
from (irb):3
from /Users/saizai/.rvm/rubies/ruby-1.9.3-p0/bin/irb:16:in `<main>'
1.9.3p0 :004 > $asdf # global
=> nil
Class variables must always be assigned or else they will return a NameError when you attempt to use them. I do not currently have the details as to why this is.
Instance and Global variables will return nil even if they are not assigned. However, they will raise a warning if you run the script with the -w flag.
I do, however, have the answer in regards to the local variables. The reason local variables act like this comes in the fact that they do not have any punctuation in front of them. This means the variable could be either a variable or a method call (since Ruby does not require () after a method call with no parameters).
something # could be a variable named 'something' or a method called 'something()'
If there is no value assigned to something variable then the Ruby interpreter assumes it is a method invocation. If there is no method by that name then it raises NameError. That is why you will get this message:
NameError: undefined local variable or method 'something' for main:Object
from (irb):1
from path/to/Ruby/bin/irb:12 in '<main>'
So, it is important for the Ruby interpreter to treat local variables in this manner just in case it is actually a method you are referring to.
As an interesting side note:
There is one quirkâa variable comes into existence when the Ruby
interpreter sees an assignment expression for that variable. This is
the case even if that assignment is not actually executed. A variable
that exists but has not been assigned a value is given the default
value nil.
Which means that:
if false
z = "Something"
end
z.nil? #=> true
never_assigned.nil? #=> NameError
The above quote is from The Ruby Programming Language by David Flanagan and Yukihiro Matsumoto section 4.2
There are operators in Ruby similar to "OrElse"and "AndAlso" in VB.NET?
For example in Ruby NoMethodError exception is raised when active_record is nil:
if active_record.nil? || active_record.errors.count == 0
...
end
In VB.net i can do:
If active_record Is Nothing OrElse active_record.errors.count = 0
...
End
That does not generate an exception because it is only checked the first expression
In this case there will be no exception raised (because only the first term in || will be evaluated). However you might be interested in reading about Object#try from ActiveSupport, which can be helpful when dealing with objects that can be nil.
in ruby, there is a big difference between something that is nil and something that is undefined. Considering the following, from IRB:
ruby-1.9.2-p0 :002 > active_record
NameError: undefined local variable or method `active_record' for main:Object
from (irb):2
from /Users/jed/.rvm/rubies/ruby-1.9.2-p0/bin/irb:16:in `<main>'
ruby-1.9.2-p0 :003 > active_record = nil
=> nil
ruby-1.9.2-p0 :004 > active_record.class
=> NilClass
ruby-1.9.2-p0 :006 > active_record.nil?
=> true
So, an object that is nil is an instance of NilClass and therefore responds to the message nil? will return true, but without declaring the variable (as in your code) Ruby doesn't know what you are calling.
A couple of options here:
Ruby's || operator is a strict operator, whereas the or keyword is less strict, so I don't know where the vb operation compares to these two or flow options.
you could use a neat little gem callled 'andand'
require 'andand'
active_record.andand.errors.count == 0
but, generally when you are dealing with this situation in rails, you would use another means to determine the situation above, consider:
#post = Post.new(:my_key => "my value") #=> an ActiveRecord object
if #post.valid?
# do something meaningful
else
puts #post.errors.full_messages.to_sentence
end
and if you mean to assign something based on if it possibly undefined, you would want to use memoization:
#post ||= Post.new
which will declare the object if undefined or use the existing object
Ruby || is short circuit evaluation operator, so it should evaluate only first condition, therefore your if should not raise any exception.
I assume active_record.nil? returns boolean true.