bill
bill

Reputation: 368

What is the usage of .self in this ruby example

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

Answers (2)

Daniel Viglione
Daniel Viglione

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

davidhu
davidhu

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

Related Questions