Instance variable access in Ruby - ruby

I have this code with a class definition:
class Test
attr_accessor :state
#state = 4
def check_state
puts "state is #{#state}"
end
end
obj = Test.new
obj.check_state
There is no output of instance variable value.
But if I call obj.state = 4 before obj.check_state, I will get
"state is 4".
Why is that?
Thanks.

In your current code, you're defining an instance variable on the Test class, rather than on instances of Test. That is, you could access it with a class method:
class Test
#state = 4
def self.state
#state
end
end
# Test.state
# => 4
But that's not what you want here; you don't want that value to be present on your class, you want it to be present for each instance of your class. To initialize instance variables on instances of classes, you should provide a constructor:
class Test
attr_accessor :state
def initialize
#state = 4
end
def check_state
puts "state is #{#state}"
end
end
# Test.new.state
# => 4

self variable(current object) inside class definition is class object itself not the receiver object.
self is receiver(obj), only inside method definition.
https://www.youtube.com/watch?v=X2sgQ38UDVY
at 48 minute...
class Test
attr_accessor :state
puts self.inspect
#state = 4
def check_state
puts "state is #{#state}"
puts self.inspect
end
end
obj = Test.new
obj.state=4
obj.check_state

Related

What happens at the background when variables are declared in Ruby?

I would like to know what happens behind the scene when variables are declared in ruby. For example, What differentiates these variables from one another?
#normal variables
name = "John"
#instant variables
#name = "John"
#class variables
##name = "John"
#class instance variables
def self.namer
#name = "John"
end
#constants
NAME = "John"
Normal variables, like name, are local. They're only available in the scope in which they were declared.
class Dog
def set_a_variable
name = "Fido"
end
def show_a_variable
name
end
end
my_dog = Dog.new
my_dog.set_a_variable
my_dog.show_a_variable
=> NameError: undefined local variable or method `name'
Instance variables, like #name, belong to the instance of a class, so every instance method for an instance of a class has access to that variable. If not set, nil is assumed.
class Dog
def set_a_variable
#name = "Fido"
end
def show_a_variable
#name
end
end
my_dog = Dog.new
my_dog.set_a_variable
my_dog.show_a_variable
=> "Fido"
my_second_dog = Dog.new
my_second_dog.show_a_variable
=> nil # not shared between different instances
Class variables, like ##legs, are accessible by all instances of a class, so every every instance has access to that variable. They're also inherited by sub-classes.
class Animal
def set_a_variable
##legs = 4
end
end
class Dog < Animal
def show_a_variable
##legs
end
end
my_animal = Animal.new
my_animal.set_a_variable
my_dog = Dog.new
my_dog.show_a_variable
=> 4
my_second_dog = Dog.new
my_second_dog.show_a_variable
=> 4
Class instance variables (#name defined in a class method) belong to the specific class, so every instance method has access to that variable, but it's not inherited by child classes.
class Animal
def self.set_a_variable
#legs = 2
end
def self.show_a_variable
#legs
end
def show_a_variable
self.class.show_a_variable
end
end
class Dog < Animal
def self.set_a_variable
#legs = 4
end
end
my_dog = Dog.new
Dog.set_a_variable
my_animal = Animal.new
Animal.set_a_variable
my_dog.show_a_variable
=> 4
Constants are NOT global, but are accessible via scoping anywhere.
class Animal
LEGS = 4
end
class Dog
def show_a_variable
Animal::LEGS
end
end
my_dog = Dog.new
my_dog.show_a_variable
=> 4
Variables are never declared in Ruby. They just spring into existence when they are first assigned.
Their scope differentiates them.

Ruby attr_accessor multiple classes

Sorry for the newbie question but how do I pass/access attar_accessor to and from multiple classes? in my example below the Foo class never is able to see the updated values from the Level class.
class Level
attr_accessor :level, :speed
end
class Foo
attr_reader :a, :b
def initialize
#x = Level.new()
#a = #x.level
#b = #x.speed
end
def reload
#a = #x.level
#b = #x.speed
end
end
#testa = Foo.new()
#testb = Level.new()
#testb.level = 5
#testb.speed = 55
puts "Value for attr_accessor is:"
puts "#{#testb.level}"
puts "#{#testb.speed}"
puts "#{#testa.a}"
puts "#{#testa.b}"
#testa.reload
puts "#{#testa.a}"
puts "#{#testa.b}"
Instance of Level class you declaring in Foo's constructor and #testb are two different objects.
You might want to edit your Foo class this way:
class Foo
def initialize(level)
#x = level # very strange name for such a thing.
#a = #x.level
#b = #x.speed
end
# rest of the class body is the same as yours
# so it is omitted
end
And then do your tests:
level = Level.new() # BTW: no need of instance variables here.
foo = Foo.new(level) # good job. Your foo has "captured" the level.
# et cetera

Why are my instance variables not existing across methods of the same class in ruby?

I am doing the ruby koans and I am on the DiceSet project. I've made the DiceSet class but my instance variables don't seem to persist with the instance like I thought they would. My code is
class DiceSet
attr_reader :values
#values = []
puts #values.class
def roll(number_of_rolls)
(1..number_of_rolls).each do |roll|
puts #values.class
#values << (1..6).to_a.sample
end
return #values
end
end
The koan then uses my DiceSet class with
dice = DiceSet.new
dice.roll(5)
puts dice.values.class
assert dice.values.is?(Array)
I put the puts commands in there to follow whats happening with the #values instance variable and only the first puts #values.class says its an Array class. All the others are returning NilClass. Am I using instance variables incorrectly or is there something else I am missing? Do instance variables get deallocated after a method call?
EDIT: My class works correctly now that I have put #values = [] in the roll method as suggested below. My only question now, is why the roll method thinks that #values is a NilClass instead of an array when I put #values = [] in an initialize method
In Ruby everything are objects. The Ruby interpreter assumes that all instance variables belong to the current object self. This is also true in a class definition. The role of self belongs to the class itself, so the instance variable #values belongs to the class. Don’t get confused! Instance variables of the class are different from instance
variables of that class’s objects. Also you don't need specify return keyword explicitly Try this:
class DiceSet
attr_accessor :values
def roll(number_of_rolls)
#values = []
(1..number_of_rolls).each do |roll|
#values << (1..6).to_a.sample
end
#values
end
end
dice = DiceSet.new
dice.roll(5)
puts dice.values.class
assert dice.values.is_a?(Array)
Each DiceSet instance has its own #values, and furthermore, the class DiceSet also has its own #values. They are all different from one another. If you want the instances and the class to share the same variable, you should use a class variable ##values.
Just put the declaration of #values = [] in the initialized method and your code should work as expected.
class DiceSet
attr_reader :values
def initialize()
#values = []
end
def roll(number_of_rolls)
(1..number_of_rolls).each do |roll|
puts #values.class
#values << (1..6).to_a.sample
end
return #values
end
end
Try this:
class Cat
attr_accessor :age
def initialize
#age = 12
end
#age = 6
def meow
puts "I'm #{#age}"
end
def self.meow
puts "I'm #{#age}, going on #{#age+1}"
end
end
Cat.age = 4 # => NoMethodError: undefined method `age=' for Cat:Class
p Cat.age # =? NoMethodError: undefined method `age' for Cat:Class
Cat.meow # => I'm 6, going on 7
cat = Cat.new
p cat.age # => 12
cat.meow # => I'm 12
cat.age = 20 # => 20
cat.meow # => I'm 20
Were I to add
class << self
attr_accessor :age
end
the first three lines of output would become:
Cat.age = 4 # => 4
p Cat.age # => 4
Cat.meow # => I'm 4, going on 5

Ruby: Unexpected Results from class_exec When Defining Class Variable

In Ruby, when defining the contents of a class with class_exec, I am getting unexpected results. When I define a class variable in the block sent to class_exec, the class variable is being defined on Object instead of the class on which class_exec is being called:
class X; end
X.class_exec do
##inner_value = "123"
def inner_value
##inner_value
end
def inner_value=(arg)
##inner_value = arg
end
end
obj1 = X.new
puts obj1.inner_value
puts ##inner_value
puts Object.class_variables
Produces:
123
123
##inner_value
This does not happen when using class_eval:
X.class_eval(<<-RUBY)
##inner_value = "123"
def inner_value
##inner_value
end
def inner_value=(arg)
##inner_value = arg
end
RUBY
obj1 = X.new
puts obj1.inner_value
puts ##inner_value
puts Object.class_variables
Produces:
123
and an error:
uninitialized class variable ##inner_value in Object (NameError)
The results with class_eval are what I would expect to happen in both cases. I have tried this with both MRI 1.8.7 and MRI 1.9.3 and got the same results running on Windows XP.
Is this expected behavior? If so, why? If not, bug?
class variables are bound to the class in which they are declared at compile time. The block passed to class_exec is compiled before it is passed to class_exec, so the class variables are bound to Object.
I guess your class_exec is at the top level, which is in Object, so that's where they go. To demonstrate:
public
class Object
##x = "ribbit"
end
def foo
puts "test: #{##x}"
end
x = Object.new
x.foo
This is why when you use class vars in a module, all classes that include that module (through the included methods) will see the same class variables. The class variables are bound to the module. If you run this:
class WithClassVars
def self.classvars
#classvars ||= {}
end
def classvars
self.class.classvars
end
end
class A < WithClassVars;end
class B < WithClassVars;end
a = A.new
b = B.new
a.classvars[:a] = 1
b.classvars[:a] = 2
puts a.classvars
puts b.classvars
a and b will end up with the same data.
If you pass your code as a string to class_eval, the string is compiled in class_eval, so you can make sure they are in the right class then.
So if you want to store per-class data, you have to either go with class_eval, or use some mechanism to use a class's instance variables. Say:
class WithClassVars
def self.classvars
#classvars ||= {}
end
def classvars
self.class.classvars
end
end
class A < WithClassVars;end
class B < WithClassVars;end
a = A.new
b = B.new
a.classvars[:a] = 1
b.classvars[:a] = 2
puts a.classvars
puts b.classvars

why there are class variables in ruby?

If creating a class variable is often dangerous and unpredictable why do we need them?
If solution is just to use class instance variable with the class level accessors:
class Foo
#variable = :something
def self.getvariable
#variable
end
def self.setvariable(value)
#variable = value
end
end
Then why do we need class variables???
Class variables have their use on occasion, but I agree that using the eigenclass is frequently more useful:
class Foo
#bar = 'bar'
class << self
attr_accessor :bar
end
end
puts Foo.bar # bar
puts Foo.bar = 'baz' # baz
The above is safe with inheritance, because it sets a variable in the Foo constant, rather than a class variable.
Foo.new.instance_eval { puts ##bar } # error
This has several causes:
It's syntactic sugar. You can always get a class variable (whether you are in class or instance scope) using ##var. This won't work for instance variables of the class.
Class variables persist for the singleton classes of the instances of this class. Example:
class Test
#instance_var = 0
##class_var = 0
def self.instance_var
#instance_var
end
def self.class_var
##class_var
end
end
Test.instance_var #=> 0
Test.class_var #=> 0
Test.new.singleton_class.instance_var #=> nil
Test.new.singleton_class.class_var #=> 0
Here is an example (think ActiveRecord):
class Base
def connect(connection)
##connection = connection
end
def connection
##connection
end
end
class User < Base
end
class SuperUser < User
end
Base.new.connect("A connection")
puts User.new.connection #=> A connection
puts SuperUser.new.connection #=> A connection
The trick here is that class variable is accessible from an instance method and is inherited. Try this:
class Base
def self.connect(connection)
#connection = connection
end
def self.connection
#connection
end
def connection
self.class.connection
end
end
class User < Base
end
Base.connect("A connection")
puts User.new.connection #=> nil
You will get nil as self.connection tries to access it's own class instance variable (from User class), and it is not inherited.
Added: And yes, it can be dangerous if you misuse it:
##a = "A"
class A
def self.a
##a
end
def a
##a
end
end
puts A.a #=> A
puts A.new.a #=> A

Resources