What happens at the background when variables are declared in Ruby? - 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.

Related

Calling Custom Writers Within Instance Methods: Why is self not implicit? [duplicate]

This question already has answers here:
Why do Ruby setters need "self." qualification within the class?
(3 answers)
Closed 6 years ago.
I am familiar with the conventional way of using attr_accessors within the initialize method:
class Dog
attr_accessor :name
def initialize(name)
#name = name
end
end
dog = Dog.new("Denver")
p dog.name
=> "Denver"
I was playing around with custom writers and readers. I was suprised to realize the following:
Within instance methods: you can call upon a reader method without explicitly specifying self because self is implicit.
Within instance methods: you MUST explicitly call self to call upon a writer method. self IS NOT implicit for attr_writers
Example 1 to show that self is implicit when calling attr_reader methods within instance methods:
class Dog
def name
#name
end
def name=(val)
#name = val
end
def initialize(name)
#name = name
end
def call_name
# no need to say: p self.name
p name
end
end
dog = Dog.new("Denver")
dog.call_name
=> "Denver"
Example 2 to show that self IS NOT implicit when calling upon attr_writers within instance methods. In other words: the instance variable is not getting set because I did not prepend the writer method below with self:
class Dog
def name
#name
end
def name=(val)
#name = val
end
def initialize(name_val)
# works as expected with: self.name = name_val
name = name_val
end
def change_name
# works as expected with: self.name = "foo"
name = "foo"
end
end
dog = Dog.new("Denver")
p dog.name
dog.change_name
p dog.name
=> nil
=> nil
Question: why isn't self implicit within instance methods when calling attr_writers? Why do I have to explicitly specify self for attr_writers within instance methods?
Question: why isn't self implicit within instance methods for
attr_writers?
Because defining a local variable takes precedence.
So here:
name = "foo"
you do not write an attribute, but defining a local variable name.

Instance variable access in 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

Ruby inheritance nil class

I'm trying to make a game but I am having problems settinga default value for an attribute and having a different default value for each subclass.
Here is the problem:
class Player
attr_accessor :hp
#hp = 2
end
class Harper < Player
#hp = 5
end
bill = Harper.new.hp #=>nil
I'm expecting Harper.new.hp to be 5 but it's showing nil instead, and I don't understand why.
The problem with your initialization is that it exists at the class level. That is, you are creating a class instance variable (confusing?) not an object-instance variable as you expect.
In order to create an instance variable you need to do it in a method run at the instance level, like the initialize method which runs when you create an object with the "new" method.
Example:
class Hello
#world = "World!"
def initialize
#to_be_or_not_to_be = "be!"
end
end
=> :initialize
inst = Hello.new
inst.instance_variables
=> [:#to_be_or_not_to_be]
Hello.instance_variables
=> [:#world]
inst.class.instance_variables
=> [:#world]
You need to place your assignments on an initialize function:
class Player
attr_accessor :hp
def initialize
#hp = 2
end
end
class Harper < Player
def initialize
super ## May not be necessary for now.
#hp = 5
end
end
bill = Harper.new.hp
# => 5
new class method runs instance method initialize, so your code should look like:
class Harper < Player
def initialize
#hp = 5
end
end

Can't get object data from class instance in ruby

How can I get the data out of my class instance? I can get this:
instance = MyModule::MyClass.new(obj1, obj2)
puts instance
#=> #<MyModule::MyClass:0x0000010120de68>
puts instance.inspect
#=> #<MyModule::MyClass:0x000001019157b0 #obj1=#<MyModule::MyOtherClass:0x00000101915b20 #obj=["stuff", "more stuff", "things"]>, #obj2=#<MyModule::MyThirdClass:0x00000101915a80 #obj=["more things", "even more"]>>
I thought I could just do instance.obj1 and get the #obj array from this. This gives me "undefined method." What's wrong here?
You can't access instance variables from the outside by default:
class Foo
def initialize(obj)
#obj = obj
end
end
foo = Foo.new(123) #<Foo:0x007fdc312205f0 #obj=123>
foo.obj # undefined method `obj' ... (NoMethodError)
You have to create a getter to do so, e.g. via attr_reader:
class Foo
attr_reader :obj
def initialize(obj)
#obj = obj
end
end
foo = Foo.new(123)
foo.obj #=> 123
You'd have to post your class definition for us to be certain what the problem is, but I'm guessing that you didn't define attribute getters/setters for your objects. Because of the way the Ruby variable scoping works, instance variables are localized to the inner scope of the object instance (meaning that they are only accessible within method definitions within the class definition).
To access these variables from outside the scope of the class definition, you must define attribute getters and setters, with are methods that expose these variables to the outer scope:
class Foo
def initialize(name)
#name = name
end
# attribute getter for #name
def name
#name
end
# attribute setter for #name
def name=(n)
#name = n
end
end
This is such a common pattern that Ruby has provided helper methods for defining setters and getters for instance variables: attr_reader, attr_writer, and attr_accessor.
attr_reader takes one or more symbols as arguments (which correspond to the instance variables you wish to target) and creates getter methods for each.
attr_writer takes the same list of symbols as attr_reader but creates setter methods for each instance variable name passed as an argument
attr_accessor generates both setter and getter methods for the instance variables named.
class Bar
attr_reader :name # only allow reading of #name
attr_accessor :rank, :age # enable both reading and writing of #rank and #age
def initialize(name, rank, age)
#name = name
#rank = rank
#age = age
end
end
b = Bar.new('Jack BeLucky', 'Corporal', 32)
b.name
=> 'Jack BeLucky'
b.age
=> 32
b.age += 1
b.age
=> 33

What does ##variable mean in Ruby?

What are Ruby variables preceded with double at signs (##)? My understanding of a variable preceded with an at sign is that it is an instance variable, like this in PHP:
PHP version
class Person {
public $name;
public function setName($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
Ruby equivalent
class Person
def set_name(name)
#name = name
end
def get_name()
#name
end
end
What does the double at sign ## mean, and how does it differ from a single at sign?
A variable prefixed with # is an instance variable, while one prefixed with ## is a class variable. Check out the following example; its output is in the comments at the end of the puts lines:
class Test
##shared = 1
def value
##shared
end
def value=(value)
##shared = value
end
end
class AnotherTest < Test; end
t = Test.new
puts "t.value is #{t.value}" # 1
t.value = 2
puts "t.value is #{t.value}" # 2
x = Test.new
puts "x.value is #{x.value}" # 2
a = AnotherTest.new
puts "a.value is #{a.value}" # 2
a.value = 3
puts "a.value is #{a.value}" # 3
puts "t.value is #{t.value}" # 3
puts "x.value is #{x.value}" # 3
You can see that ##shared is shared between the classes; setting the value in an instance of one changes the value for all other instances of that class and even child classes, where a variable named #shared, with one #, would not be.
[Update]
As Phrogz mentions in the comments, it's a common idiom in Ruby to track class-level data with an instance variable on the class itself. This can be a tricky subject to wrap your mind around, and there is plenty of additional reading on the subject, but think about it as modifying the Class class, but only the instance of the Class class you're working with. An example:
class Polygon
class << self
attr_accessor :sides
end
end
class Triangle < Polygon
#sides = 3
end
class Rectangle < Polygon
#sides = 4
end
class Square < Rectangle
end
class Hexagon < Polygon
#sides = 6
end
puts "Triangle.sides: #{Triangle.sides.inspect}" # 3
puts "Rectangle.sides: #{Rectangle.sides.inspect}" # 4
puts "Square.sides: #{Square.sides.inspect}" # nil
puts "Hexagon.sides: #{Hexagon.sides.inspect}" # 6
I included the Square example (which outputs nil) to demonstrate that this may not behave 100% as you expect; the article I linked above has plenty of additional information on the subject.
Also keep in mind that, as with most data, you should be extremely careful with class variables in a multithreaded environment, as per dmarkow's comment.
# - Instance variable of a class
## - Class variable, also called as static variable in some cases
A class variable is a variable that is shared amongst all instances of a class. This means that only one variable value exists for all objects instantiated from this class. If one object instance changes the value of the variable, that new value will essentially change for all other object instances.
Another way of thinking of thinking of class variables is as global variables within the context of a single class.
Class variables are declared by prefixing the variable name with two # characters (##). Class variables must be initialized at creation time
## denotes a class variable, i.e. it can be inherited.
This means that if you create a subclass of that class, it will inherit the variable. So if you have a class Vehicle with the class variable ##number_of_wheels then if you create a class Car < Vehicle then it too will have the class variable ##number_of_wheels
The answers are partially correct because ## is actually a class variable which is per class hierarchy meaning it is shared by a class, its instances and its descendant classes and their instances.
class Person
##people = []
def initialize
##people << self
end
def self.people
##people
end
end
class Student < Person
end
class Graduate < Student
end
Person.new
Student.new
puts Graduate.people
This will output
#<Person:0x007fa70fa24870>
#<Student:0x007fa70fa24848>
So there is only one same ##variable for Person, Student and Graduate classes and all class and instance methods of these classes refer to the same variable.
There is another way of defining a class variable which is defined on a class object (Remember that each class is actually an instance of something which is actually the Class class but it is another story). You use # notation instead of ## but you can't access these variables from instance methods. You need to have class method wrappers.
class Person
def initialize
self.class.add_person self
end
def self.people
#people
end
def self.add_person instance
#people ||= []
#people << instance
end
end
class Student < Person
end
class Graduate < Student
end
Person.new
Person.new
Student.new
Student.new
Graduate.new
Graduate.new
puts Student.people.join(",")
puts Person.people.join(",")
puts Graduate.people.join(",")
Here, #people is single per class instead of class hierarchy because it is actually a variable stored on each class instance. This is the output:
#<Student:0x007f8e9d2267e8>,#<Student:0x007f8e9d21ff38>
#<Person:0x007f8e9d226158>,#<Person:0x007f8e9d226608>
#<Graduate:0x007f8e9d21fec0>,#<Graduate:0x007f8e9d21fdf8>
One important difference is that, you cannot access these class variables (or class instance variables you can say) directly from instance methods because #people in an instance method would refer to an instance variable of that specific instance of the Person or Student or Graduate classes.
So while other answers correctly state that #myvariable (with single # notation) is always an instance variable, it doesn't necessarily mean that it is not a single shared variable for all instances of that class.
# and ## in modules also work differently when a class extends or includes that module.
So given
module A
#a = 'module'
##a = 'module'
def get1
#a
end
def get2
##a
end
def set1(a)
#a = a
end
def set2(a)
##a = a
end
def self.set1(a)
#a = a
end
def self.set2(a)
##a = a
end
end
Then you get the outputs below shown as comments
class X
extend A
puts get1.inspect # nil
puts get2.inspect # "module"
#a = 'class'
##a = 'class'
puts get1.inspect # "class"
puts get2.inspect # "module"
set1('set')
set2('set')
puts get1.inspect # "set"
puts get2.inspect # "set"
A.set1('sset')
A.set2('sset')
puts get1.inspect # "set"
puts get2.inspect # "sset"
end
class Y
include A
def doit
puts get1.inspect # nil
puts get2.inspect # "module"
#a = 'class'
##a = 'class'
puts get1.inspect # "class"
puts get2.inspect # "class"
set1('set')
set2('set')
puts get1.inspect # "set"
puts get2.inspect # "set"
A.set1('sset')
A.set2('sset')
puts get1.inspect # "set"
puts get2.inspect # "sset"
end
end
Y.new.doit
So use ## in modules for variables you want common to all their uses, and use # in modules for variables you want separate for every use context.

Resources