Reputation: 368
I've been reading my textbook, and we have come to classes and the keyword self
came up. I've been reading some tutorials on tutorialpoint and have read a bunch of SO questions, but for some reason it just isn't clicking in my head Use of ruby Self, so I decided I would tinker around with some examples
Consider
class Box
# Initialize our class variables
@@count = 0
def initialize(w,h)
# assign instance avriables
@width, @height = w, h
@@count += 1
end
def self.printCount()
puts "Box count is : #@@count"
end
end
# create two object
box1 = Box.new(10, 20)
box2 = Box.new(30, 100)
# call class method to print box count
Box.printCount()
Why will we get an error if we remove self.
from our printCount()
method? I know that self
is important to distinguish between class variables and instance variables like in my example @width,@height
and @@count
.
So what I think is that since I am trying to modify the class variable @@count
, I need to use the .self
keyword since I am trying to modify a class variable. Thus whenever we want to change a class variable we must use the form def self.methodName
.
Is my thought process correct?
Upvotes: 1
Views: 433
Reputation: 9407
I would like to provide a more concrete definition which clarifies the lookup algorithm.
First, let's define self
. self in Ruby is a special variable that always references the current object. The current object (self) is the default receiver on method calls. Second, self is where instance variables are found.
class MyClass
def method_one
@var = 'var'
method_two
end
def method_two
puts "@var is #{@var}"
end
end
obj = MyClass.new
obj.method_one
Above, when we call method_one, self will refer to the object instantiated, since we invoked method_one on an explicit receiver (the object instance). so self.method_one in the method definition in the Class will refer to the object instance not the Class object itself. @var will be stored in self. Note that when method_two is called, since there is no default receiver, the receiver is self. So when method_two is called, we remain in the same object instance. That is why @var in method_two will refer to the same @var in method_one. It is the same object.
Ruby supports inheritance. So if we call a method on self and it is not defined in its class, then Ruby will search for the instance method in the superclass. This happens until Ruby gets to BasicObject. If it cannot find the method in any of the superclasses, it raises a NoMethodError.
Now there is another important piece to the inheritance chain. There is an anonymous class called the singleton class that Ruby will inject into this inheritance chain. Where in the inheritance chain? It inserts it right before the original class of the object. This way, when ruby searches for the method, it will hit the singleton class before it hits the original class of the object.
> msg = 'Hello World'
=> "Hello World"
> def msg.hello_downcase
> puts 'Hello World'.downcase
> end
=> :hello_downcase
> msg.downcase
=> "hello world"
> msg2 = 'Goodbye'
=> "Goodbye"
> msg2.hellow_downcase
NoMethodError: undefined method `hellow_downcase' for "Goodbye":String
The lookup algorithm:
msg -> Anonymous Singleton Class (hello_downcase method is in here) -> String -> Object
msg2 -> String -> Object
In the above example, msg and msg2 are both object instances of the String class object. But we only opened up the singleton class of msg and not msg2. the hello_downcase
method was inserted into the singleton class of msg. It's important to note that when we add another singleton method, it will reopen the same singleton class again; it will not open another anonymous singleton class. There will only be one anonymous singleton class per instance.
Notice above I said the String class object, and not just the String class. That's because a class itself is an object. A class name is simply a constant which points to an object:
class HelloWorld
def say_hi
puts 'Hello World'
end
end
More precisely, in the above example, HelloWorld is a constant which points to an object, whose class is Class. Because of this, the lookup chain will be different for HelloWorld and its instances. An instance's class is HelloWorld. And when we invoke a method with the instance as the receiver, inside HelloWorld method definitions, self will refer to that instance. Now HelloWorld's class is Class (Since Class.new is what created HelloWorld). And because of this, its inheritance chain looks different:
#<HelloWorld:0x007fa37103df38> -> HelloWorld -> Object
HelloWorld -> Class -> Module -> Object
Now since HelloWorld is also an object, just as with instances, we can open up its Singleton Class.
class HelloWorld
def self.say_hi_from_singleton_class
puts 'Hello World from the Singleton Class'
end
end
HelloWorld.say_hi_from_singleton_class
The lookup algorithm:
HelloWorld -> Anonymous Singleton Class -> Class (this is where the new method is defined) -> Module -> Object
Why does this work? As mentioned, a method call with an explicit receiver changes the value of self to point to that object. The second thing that changes the value of self is a class definition. self inside of the class definition refers to the class object, referred to by constant HelloWorld. This is only the case while inside of the class definition. Once we leave the class definition, self will no longer refer to the constant HelloWorld.
> puts self
main
> class HelloWorld
> puts self
> end
HelloWorld
=> nil
> puts self
main
Ultimately, there are two ways in which the special variable self will change: 1) when you call a method with an explicit receiver, 2) inside of a class definition.
Upvotes: 0
Reputation: 10432
There are two types of methods you are using here: instance methods and class methods. As you know, Ruby is an object oriented programming language, so everything is an object. Each object has its own methods that it can call. Let's look at your code
class Box
# Initialize our class variables
@@count = 0
def initialize(w,h)
# assign instance avriables
@width, @height = w, h
@@count += 1
end
def self.printCount()
puts "Box count is : #@@count"
end
end
When you create a method with self.method_name
, you are creating the method for the class itself. So the object of Box
has a method called printCount()
. That is why you can directly call the method.
Box.printCount()
However, if you declare a new instance of the class Box
, calling printCount() would result in an error.
box1 = Box.new(1,1)
box1.printCount() #=> undefined method `printCount'
This is because box1
is an instance of the class Box
, and the printCount
method is only accessible to the class Box
.
If you remove the self
before the method printCount
, it will become an instance method, and then box1
will have access to that method, but then the class Box
will not.
And a few semantics, Ruby uses snake_case for method names, so printCount
should be print_count
. This is just standard practice, doesn't really affect how the code runs.
Also, you need to be careful with class variables, ie @@count
. They don't behave as you would expect in Ruby. It does not just belong in the class it is declared in, it is also part of any of its descendants.
For example, let's say I define a new class call SmallBox
and inherit from Box
.
box1 = Box.new(1,1)
box1 = Box.new(1,1)
Now, the count should be 2 for Box
. However, if you try to access the @@count
from my new class,
class SmallBox < Box
p @@count
end
This would print 2
as well.
Any changes to the class variable from the descendants will change its value.
For example, I declare an instance of SmallBox
, which would add 1 to @@count
. You can see if you check the count in Box
, it also added 1.
small1 = SmallBox.new(1,1)
class SmallBox
p @@count #=> 3
end
class Box
p @@count #=> 3
end
Upvotes: 7