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?
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 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