evianpring evianpring - 6 months ago 14
Ruby Question

What is self in class << self and why is this different from the Class instance that is defining this block of code?

The code below attempts to create a class MyClass which delegates a call to method self.add to the object returned by calling method self.newAdd.

class MyAdd
def add(a,b)
a + b
end
end


class MyClass
def self.newAdd
MyAdd.new
end

def self.delegate(*methods, options)
return unless options.has_key?(:to)

methods.each do |method|
define_method method, ->(*args, &prc) do
delegated_to = Object.const_get(options[:to]).call()
delegated_to.send(method, *args, &prc)
end
end
end

class << self
debugger;
delegate :add, to: :newAdd
end
end


The error that occurs in running this code is


NoMethodError: undefined method 'delegate' for #
<Class:MyClass>



If I navigate to the directory where the file is saved and open up the interpreter, execution stops at the debugger on the fourth to last line. I can then look at the available methods to self and to MyClass

require_relative './test.rb'
MyClass.methods - Object.methods #[:newAdd, :delegate]
self.methods - Object.methods #[:nesting]
self # #<Class:MyClass>
self.class # Class
MyClass.class # Class


Why is
self
not the same as
MyClass
inside of the
class << self
scope? More specifically, why is the method
delegate
not available to self inside of
class << self
?

Answer

Why is self not the same as MyClass inside of the class << self scope.

Because the class keyword always changes the scope:

class MyClass
  puts self  #=> MyClass

  class <<self
    puts self  #=>MyClass’s singleton class
  end
end

More specifically, why is the method delegate not available to self inside of class << self?

class MyClass

  def self.delegate
    puts "executing MyClass.dellegate()"
  end

  class <<self
    delegate 
  end

end

--output:--
1.rb:8:in `singleton class': undefined local variable or method `delegate' for #<Class:MyClass> (NameError)
  from 1.rb:7:in `<class:MyClass>'
  from 1.rb:1:in `<main>'

Note that the following constructs are equivalent:

class MyClass

  def self.delegate
    puts "executing MyClass.dellegate()"
  end

end

MyClass.delegate

--output:--
executing MyClass.dellegate()

and:

class MyClass

  class <<self
    def delegate
      puts "executing MyClass.dellegate()"
    end
  end

end

MyClass.delegate

--output:--
executing MyClass.dellegate()

Therefore, your code is equivalent to:

class MyClass

  class <<self
    def delegate
      puts "executing MyClass.dellegate()"
    end

    delegate
  end

end

If you ignore the outer MyClass for a moment, then you defined a class like this:

class <<self
  def delegate
    puts "executing MyClass.dellegate()"
  end

  delegate
end

That same structure can be replicated like this:

class Dog
  def bark
    puts “woof”
  end

  bark
end

which will produce the same type of error:

1.rb:7:in `<class:Dog>': undefined local variable or method `bark' for Dog:Class (NameError)
    from 1.rb:1:in `<main>'
  1. When you call a method and you don't specify a receiver, ruby uses whatever object is currently assigned to the self variable as the receiver.

  2. Inside a method, ruby assigns the object that called the method to the self variable. The object that called the method is not the same thing as the class (object) in which the method is defined.

  3. Inside a class, but outside of any method definitions, ruby assigns the class (object) to self.

I don't understand really what a class method on an eignclass is however.

Personally, I don't use the term eigenclass. In my opinion, ruby has made a decision that the term is singleton class. If you look through the docs for the Object class, there are no method names that have eigenclass in them, yet there are method names with singleton class in them.

All objects have a singleton class. A singleton class is an object. Therefore, every singleton class also has a singleton class--which means that the chain of singleton classes is infinite:

class Dog
end

s1 = Dog.singleton_class
puts s1  

s2 = s1.singleton_class
puts s2

s3 = s2.singleton_class
puts s3

--output:--
#<Class:Dog>
#<Class:#<Class:Dog>>
#<Class:#<Class:#<Class:Dog>>>

However, I've never seen anyone use a singleton class of a singleton class (s2) before in their code. I did it once to answer a question, and nobody had any idea what I was talking about.

There are some method lookup diagrams here, which might prove useful.