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

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.

Related

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

Class variables not in the scope of class functions

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

Ruby: Why is an instance variable defined inside a class nil?

class Something
#b = [4432]
def screen
puts #b.class
end
end
s = Something.new
s.screen
outputs 'Nilclass'. Was wondering, why does an instance variable which is defined inside a class always part of NilClass?
Instance variables belong to an object (aka an instance), that's why they are called instance variables. Every instance has its own instance variables.
In your case, there are two objects: Something (which is an instance of Class) and s (which is an instance of Something). Each of those two objects has its own set of instance variables. Something has an instance variable called #b which points to [4432]. s has no instance variable named #b because you never assign to it, and uninitialized instance variables evaluate to nil.
You need to set it like this:
class Something
def initialize
#b = [4432]
end
def screen
puts #b.class
end
end
The way you did it, the variable belongs to Something class itself, not its instance. Observe:
class Something
#b = [4432]
end
s = Something.new
s.instance_variable_get(:#b) # => nil # !> instance variable #b not initialized
Something.instance_variable_get(:#b) # => [4432]
Generally the instance variable must be defined inside the constructor whereas in ruby the default constructor is initialize the syntax is
def initialize
end #these is the default constructor in ruby
so when we define the insatnce variable inside the constructor and when we create the instance of a class then that instance/object will contain the copy of instance variables
most important thing is that though the instance/object contains the instance variable the instance/object cannot access it why because by default the instance data is private so in order to access it we need to define the getters and setter for those instance variable
class Something
attr_accessor:b
def initialize
#b = [4432]
end
s=Something.new
puts"#{s.b}"
Because the variable #b does not exist!. For e.g. the following would produce the same results you see.
class Something
#b = [4432]
def screen
puts #a.class #=> note #a which is non-existent
end
end
s = Something.new
s.screen
Whereas
class Something
#b = [4432]
def screen
puts #a.class
end
def self.screen
puts #b.class
end
end
s = Something.new
s.screen #=> NilClass
Something.screen #=> Array
if you initialize #b outside the initializer, you want #b scope to be a class variable, so you have to call it ##b :
##b has the same value for all Instance of your Something Class
like :
class Somthing
##b = [4432]
def initialize
#[...]
end
def screen
puts ##b.class
end
end
#Jörg W Mittag answer is correct. I just wont to add, that defining an instance variable in class != defining instance variable in an instance of that class. To create an instance variable, in your case in s instance you need to add an initialize method witch gets triggered when new method is called on a class.
def initialize(b_value = default_value)
#b = b_value
end

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.

Ruby newbie, what is the difference between #var and ##var in a class

as the title says,
what is the difference between #var and ##var in a class definition?
Also, what is the difference between self.mymethod and mymethod in defining a method?
##var is a class variable, it is shared between class and all instances of this class. You can access this variable from class methods and from instance methods.
class C
##a = 1
def self.m1 # define class method (this is similar to def C.m1, because self will evaluate to C in this context)
##a
end
def m2 # define instance method
##a
end
end
C.m1 # => 1
C.new.m2 # => 1
#var is a class instance variable. Normally you can get access to this instance variable from the class methods.
class C
#a = 1
def self.m1
#a
end
def m2
# no direct access to #a because in this context #a will refer to regular instance variable, not instance variable of an object that represent class C
end
end
C.m1 # => 1
These variables might be confusing and you should always know the context where you define instance variable #... - it might be defined in the instance of an object that represent a class or might be an instance of regular object.
self always refers to the current object.Check the following Eg:-
class Test
def test
puts "At the instance level, self is #{self}"
end
def self.test
puts "At the class level, self is #{self}"
end
end
Test.test
#=> At the class level, self is Test
Test.new.test
#=> At the instance level, self is #<Test:0x28190>
object variables are so named because they have scope within, and are associated
to, the current object.an object variable, is then accessible from any other method inside that object.
Class variables are particularly useful for storing information relevant to all objects
of a certain class.
In intuitive terms, instance vars are used to keep track of the state of each object. On the other hand, class variables are used to keep track of the state of all instances of the class. E.g. you might use ##count to keep track of the number of this class' objects that have been instantiated, like so:
class User
##count = 0
attr_reader :name
def initialize(name)
##count += 1
#name = name
end
end
User.count gives you the number of users that have been instantiated so far.
user = User.new('Peter') increases User.count by one and user.name returns Peter.

Resources