Class variables not in the scope of class functions - ruby

I have the following bit of code:
class Test {
#s = 'Bob'
def print_bob
p #s
end
end
When I try to call print_bob, it prints nil. Why is this? I understood class variables are visible from anywhere in a class?

#s is an class instance variable, not the instance variable of the instances of the Test class. print_bob is an instance method. As you are not defining those instance variables, while you are creating the instances of Test, thus when you will call the method print_bob on the instances of Test, you will get nil. Remember - instance and class variables, if you attempt to use them before defining, they will return nil.
Look the below code:
class Test
#s = 'Bob'
def print_bob
p #s
end
end
test = Test.new
# Now see, here no instance variables are listed, for instance test. Because you did
# not create any.
test.instance_variables # => []
# you can see, #s is listed in the method call, because has been defined when the
# class has been created.
Test.instance_variables # => [:#s]

#s is instance variable definition, not a class variable. So in the class body, defining #s defines class intance variable, but if you try to refer it in method body, you refer "regular" instance variable, which is unset.
Class variables are defined with ##, so this should work as you expect:
class Test {
##s = 'Bob'
def print_bob
p ##s
end
end

Related

How to read a class's instance variable scope and visibility

Why is it not possible to read a class's instance variable value if it was set within the class using the attr_accessor?
Is it because the the instance variable is "private?"
Setting the attribute this way works:
class Test
attr_accessor :magic
end
bob = Test.new
bob.magic = "cat" #set
print bob.magic #read
Setting the attribute this way fails:
class Test
attr_accessor :magic
#magic = "spell"
end
bob = Test.new
print bob.magic #this resolves to 'nil'
There is a significant difference between instance variables and class instance variables.
class Test
#magic = "spell" # class instance var, since the scope is class
def initialize
#magic = 42 # instance var, scope is instance
end
end
Those two live together, since they are defined on different objects:
Test.instance_variable_get(:#magic) #⇒ "spell"
Test.new.instance_variable_get(:#magic) #⇒ 42
That said, attr_accessor reads the variable from the scope, it was defined for. Yours was defined in class scope, therefore it reads the instance scoped variable.
To read the class instance variable, define attr_accessor on class’ singleton class level:
class Test
singleton_class.send :attr_accessor, :magic # reads class instance var ⇓
#magic = "spell" # class instance var, since the scope is class
attr_accessor :magic # reads instance var ⇓
def initialize
#magic = 42 # instance var, scope is instance
end
end
Test.magic
#⇒ "spell"
Test.new.magic
#⇒ 42
You can access Test's #magic by defining attr_accessor within the singleton class of Test:
class Test
#magic = "spell"
class << self
attr_accessor :magic
end
end
Test.magic #=> "spell"
You should move #magic declaration/assignment to the constructor, like this:
class Test
attr_accessor :magic
def initialize
#magic = "spell"
end
end
bob = Test.new
print bob.magic #spell
When you declare instance variable (#) outside any method (self), it becomes unreachable by the class instance.
Instance variables belong to objects (aka "instances"), that's why they are called instance variables.
In your first example, you are setting the instance variable #magic on bob to 'cat' and then you are reading the instance variable #magic on bob.
In your second example, you are setting the instance variable #magic on Test (which is a completely different object than bob) and then you are reading the instance variable #magic from bob, which hasn't been set yet, and uninitialized instance variables evaluate to nil.
Remember: classes are objects just like any other object. They can have instance variables just like any other object.

Accessing Instance Variable Directly in Ruby?

Why can't I access an instance variable directly in ruby without using an accessor method or instance_variable_get?
class Foo
#my_var
end
Why shouldn't we be able to use Foo.#my_var in this example?
The example provided by the OP is a class instance variable. These can only be accessed by class methods.
"regular" attribute accessors won't allow access from outside the class. Here are a couple of ways to create accessors that work:
class A
#class_instance_var = "foo"
class << self
attr_accessor :class_instance_var
end
end
puts A::class_instance_var # Output: foo
OR
class A
#class_instance_var = "foo"
def self.class_instance_var
#class_instance_var
end
end
puts A::class_instance_var # Output: foo
Class instance variables
Class instance variable names also begin with #. However, they are defined at class level, outside any methods. Class instance variables can only be accessed by class methods. They are shared amongst all instances of a class but not its subclasses. In other words, they are not inheritable. If the value of a class instance variable is changed in one instance of the class, all other instances are affected. Earlier we saw how all classes are instances of a built-in class called Class. That is what makes class instance variables possible.
class Vehicle
#count = 0 # This is a class instance variable
def initialize
self.class.increment_count
self.class.show_count
end
def self.increment_count # This is a class method
#count += 1
end
def self.show_count # This is a class method
puts #count
end
end
class Car < Vehicle
#count = 0
end
v1 = Vehicle.new # Output: 1
v2 = Vehicle.new # Output: 2
v3 = Vehicle.new # Output: 3
car1 = Car.new # Output: 1
car2 = Car.new # Output: 2
v3 = Vehicle.new # Output: 4
Let's review the example above. A class instance variable called #count is set in the Vehicle class, with an initial value of 0. Every time the Vehicle class is instantiated, the initialize method calls self.increment_count to increment the value of #count and self.show_count to return the new value. Then, we have the Car class, which is a subclass of Vehicle and inherits all of its methods. However, it does not inherit the #count class instance variable, as this type of variable is not inheritable. That's why the counter works within the Car class, but it has its own count.
Methods prefixed with self., such as self.increment_count and self.show_count, are class methods. That is the only kind of method capable of accessing class instance variables.
That's just not how the language is built. Maybe look at openstruct
require 'ostruct'
obj = OpenStruct.new(my_var: 1)
obj.my_var
# => 1
By the way, you're method of setting up an instance variable is not correct. You should only be setting instance variables inside instance methods or initialize, otherwise use class variables or constants.
An example with constants:
class Foo
MyVar = 1
end
Foo::MyVar
# => 1
You could also make Foo.new.#my_var work with method_missing:
class Foo
def method_missing(m, *args, &block)
self.instance_variable_get(m)
end
def initialize
#my_var = 1
end
end
Foo.new.#my_var
# => 1

How to Initialize Class Arrays in Ruby

I want to create an empty array as a class instance variable in Ruby. However, my current method does not seem to work.
Here is my code:
class Something
#something = []
def dosomething
s = 5
#something << s
end
end
When I call the function, it gives me an undefined method traceback.
However, if I do something similar with class variables, i.e.:
class Something
##something = []
def dosomething
s = 5
##something << s
end
end
This works perfectly.
I know I can use the initialize method to actually create an empty list for #something, but is there another way of doing this without using the initialize method? And why does this work for class variables?
EDIT: Fixed typo
You need to use initialize as a constructor as below code and is there any reason why not to use initialize/constructor. And please fix a typo error in class definition Class Something to class Something no camel case or first letter capitalize while in class
class Something
def initialize
#something = Array.new
end
def dosomething
s = 5
#something << s
end
end
class variable ## are available to the whole class scope. so they are working in the code and if you want to use instance variable # you need to initialize it as above. The instance variable is share with instance/objects of a class
for more details visit the link Ruby initialize method
At first you have a typo. Change Classto class. Next I suggest to use the initialize method. While creating a new object this is the perfect place to initialize instance variables.
class Something
##my_class_variable = [1]
def initialize
#something = []
end
def dosomething
s = 5
#something << s
end
def self.get_my_class_variable
##my_class_variable
end
end
Your script will be read and executed from top to bottom and after this,
you can access the class Something. While the parser reads your script/class/module you can define class variables (##), execute mixins and extend the class with other modules. This is why you can define a class variable, but you can not define an instance variable. Because actually you have no instance object from your class. You only have a class object. In ruby everything is an object. And your class object has a defined class variable now:
Something.get_my_class_variable
# => [1]
Now you can create an instance from your class. With Something.new the initialize method will be invoked and your instance variable will be defined.
something = Something.new
something.dosomething
# => [5]
Later, if you are familar with this you can define getter and setter methods with attr_reader, attr_writer and attr_accessor for instance objects or cattr_reader, cattr_writer and cattr_accessor for class objects. For example:
class Something
attr_reader :my_something
def initialize
#my_something = []
end
def dosomething
s = 5
#my_something << s
end
end
something = Something.new
something.my_something
# => []
something.dosomething
# => [5]
something.my_something
# => [5]
Your problem in trying to access #something in your instance method is that, in the scope of instance methods, # variables refer to instance variables, and your #something is a class instance variable.
# variables are instance variables of the instance that is self when they are created. When #something was created, self was the class Something, not an instance of Something, which would be the case inside an instance method.
How then to access a class instance variable in an instance method? Like regular instance variables, this must be done via a method, as in attr_accessor. One way to do this is to use class << self to tell the Ruby interpreter that the enclosed code should be evaluated with the class (and not the instance) as self:
class C
#foo = 'hello'
class << self
attr_accessor :foo # this will be a class method
end
def test_foo # this is, of course, an instance method
puts self.class.foo # or puts C.foo
end
end
We can show that this works in irb:
2.3.0 :005 > C.foo
=> "hello"
2.3.0 :006 > C.new.test_foo
hello
You have correctly created a class instance variable, #something, and initialized it to an empty array. There are two ways for instances to obtain or change the value of that variable. One is to use the methods Object#instance_variable_get and Object#instance_variable_set (invoked on the class):
class Something
#something = []
def dosomething
s = 5
self.class.instance_variable_get(:#something) << s
end
end
sthg = Something.new
sthg.dosomething
Something.instance_variable_get(:#something)
#=> 5
The other way is to create an accessor for the variable. There are several ways to do that. My preference is the following:
Something.singleton_class.send(:attr_accessor, :something)
Something.something #=> [5]
In your dosomething method you would write:
self.class.something << s

Rails and class variables

class MainController < ApplicationController
#my_var = 123
def index
var1 = #my_var
end
def index2
var2 = #my_var
end
end
Why is neither var1 no var2 equal to 123?
Variables with # are instance variables in ruby. If you're looking for class variables, they're prefixed with ##, so you should be using ##my_var = 123 instead.
And the reason you can't use instance variables that way, is because if you define instance variables outside methods, they don't live in the same scope as your methods, but only live while your class is interpreted.
var1 in your example is a local variable, which will only be visible inside the index method.
Examples:
class Foo
##class_variable = "I'm a class variable"
def initialize
#instance_variable = "I'm an instance variable in a Foo class"
local_variable = "I won't be visible outside this method"
end
def instance_method_returning_an_instance_variable
#instance_variable
end
def instance_method_returning_a_class_variable
##class_variable
end
def self.class_method_returning_an_instance_variable
#instance_variable
end
def self.class_method_returning_a_class_variable
##class_variable
end
end
Foo.new
=> #<Foo:0x007fc365f1d8c8 #instance_variable="I'm an instance variable in a Foo class">
Foo.new.instance_method_returning_an_instance_variable
=> "I'm an instance variable in a Foo class"
Foo.new.instance_method_returning_a_class_variable
=> "I'm a class variable"
Foo.class_method_returning_an_instance_variable
=> nil
Foo.class_method_returning_a_class_variable
=> "I'm a class variable"
#my_var, in your sample code, is an instance variable on the class MainController. That is, it's a class-level instance variable, and not an instance-level instance variable. It exists in a totally different scope to the instance variable associated with an instance of the class.
Within the body of your instance methods, index and index2, you are attempting to dereference an instance variable on an object that is an instance of class MainController, but you have not defined that instance variable anywhere, so you get back nil.
If you want to use #my_var as a class-level instance variable, you can get its value from within an instance of the class like this:
var1 = self.class.instance_variable_get(:#my_var)
Class variables are indicated with a ## prefix, and their use is not entirely encouraged. A couple of minutes with Google will tell you why.
Because code executes in different context. You can see here:
class MainController
puts self
def print_self
puts self
end
end
#=> MainController
MainController.new.print_self #=> <MainController:0x00000001761140>
As you can see in first print the self is MainController, in second print the self is the object which derived from MainController class.
In the assignment to #my_vay this variable belongs to MainController, and in the second cases, the #my_var belongs to object (not a class) and these varaibles are different.

Can I initialize a class variable using a global variable? (ruby)

Do I have create extra method for this kind of assignment? ##variable = #global_variable Why? I want to have some variables that hold values and definitions to be accessible all through my script and have only one place of definition.
#global_variable = 'test'
class Test
##variable = #global_variable
def self.display
puts ##variable
end
end
Test.display #gives nil
In Ruby, global variables are prefixed with a $, not a #.
$global = 123
class Foo
##var = $global
def self.display
puts ##var
end
end
Foo.display
correctly outputs 123.
What you've done is assign an instance variable to the Module or Object class (not sure which); that instance variable is not in scope of the class you've defined.

Resources