3

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 = self.send(options[:to])
        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?

evianpring
  • 3,316
  • 1
  • 25
  • 54

3 Answers3

4

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.delegate()"
  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.delegate()"
  end

end

MyClass.delegate

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

and:

class MyClass

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

end

MyClass.delegate

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

Therefore, your code is equivalent to:

class MyClass

  class <<self
    def delegate
      puts "executing MyClass.delegate()"
    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.delegate()"
  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.

Note that it is the instances of the Dog class that can call the def's inside the Dog class, e.g. bark(). Similarly, it is the instances of the singleton class that can call the def's inside the singleton class, e.g. delegate()--the singleton class itself cannot call the def's inside the singleton class. The whole reason they are called singleton classes is because singleton classes have only one instance--in your case the one instance of the singleton class is MyClass. As a result, MyClass can call delegate(), but the singleton class cannot call delegate().

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>>>

Which means you can do stuff like this:

class Dog
  class <<self  #s1
    class <<self #s2
      def greet  #Instances of s2 can call greet, and the only instance of s2 is s1.
        puts "hello"
      end
    end
  end
end


class Dog
  class <<self
    #Inside here self = Dog's singleton class = s1
    greet  #equivalent to Dogs_singleton_class.greet
  end
end

--output:--
hello

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 a long, long time ago, and nobody had any idea what I was talking about.

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

Community
  • 1
  • 1
7stud
  • 46,922
  • 14
  • 101
  • 127
1

Here's a reworked section of your code that works without errors:

class MyClass
  def self.newAdd
    MyAdd.new
  end

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

    methods.each do |method|
      target = self

      define_method method, ->(*args, &prc) do
        delegated_to = target.send(options[:to])
        delegated_to.send(method, *args, &prc)
      end
    end
  end

  delegate :add, to: :newAdd
end

As a note, try to make a point of naming methods like new_add and avoid using capital letters in them.

You can't use const_get to retrieve a method. What you're looking for is method but that's not always the best plan. send is often sufficient.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • Your code above delegates an instance method :add to the class method :newAdd, correct? My intention was to delegate the method :add called on the class MyClass to the class method :newAdd. Actually, my intention was more general: if I call the delegate method inside my singleton class, then define_method (inside of delegate) will define an instance method inside of the singleton class, thus making the delegation between class methods. If I call the delegate method as you did, I should get delegation between instance methods. – evianpring Jun 02 '16 at 17:58
  • Which brings me to the original question (independently of the error you pointed out about using const_get inside of delegate): why don't I have access to the delegate method inside of class << self? – evianpring Jun 02 '16 at 17:59
  • At this point I realize that I still have confusion about what exactly the singleton class is and the difference from the usual class. After all, when I call define_method inside a class statement I get an instance method, and when I call define_method inside of a class << self statement I get a class method. So they must be different. – evianpring Jun 02 '16 at 18:02
  • By the way, if I'm not mistaken, my intention is to get functionality similar to how Rails delegate method works. I saw something similar to my example used in ActiveRecord when a user types in query commands to a model: these commands (where, on, select, etc) are delegated to another method. This delegation occurs in a class << self statement. I saw this in this Railscast: http://railscasts.com/episodes/239-activerecord-relation-walkthrough?autoplay=true – evianpring Jun 02 '16 at 18:05
  • 1
    Calling `class << self` in the context of a class ends up switching the context in a way that can confuse which methods are available. I prefer to avoid this, the nuances are hard to explain and often easily overlooked, leading to problems like what you have. What you're stumbling over here is the [Ruby eigenclass layer](http://stackoverflow.com/questions/1630815/why-isnt-the-eigenclass-equivalent-to-self-class-when-it-looks-so-similar). – tadman Jun 02 '16 at 18:06
  • You're probably better off using mixin-methods or just using ActiveSupport for the `delegate` feature than trying to write your own. – tadman Jun 02 '16 at 18:07
  • Yes, of course, though what I am currently working on is writing my own (workable but much smaller) version of the Rails framework just for the learning experience, so using ActiveSupport defeats the purpose of the exercise. – evianpring Jun 02 '16 at 18:17
  • @evianpring Sounds like you're going to have to read up on Ruby eigenclasses, then. Best of luck! – tadman Jun 02 '16 at 18:31
0

A version of the code I posted that actually works is the following:

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

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

    # methods are the methods that should be delegated to 
    # the object returned by calling the method passed in options[:to]
    methods.each do |method|
      define_method method, ->(*args, &prc) do
        delegated_to = self.send(options[:to])
        delegated_to.send(method, *args, &prc)
      end
    end
  end
end

class MyClass
  extend Delegate

  def self.newAdd
    MyAdd.new
  end

  class << self
    extend Delegate
    delegate :add, to: :newAdd
  end
end

Now if we call delegate :add, to: :newAdd from within class << self, we will delegate a call to the class method add to the class method newAdd. The reason for this is that inside of class << self, where self is the class MyClass, we are in a scope where self is the singleton class of MyClass. By extending module Delegate here, we are defining a class method on this singleton class. The method is called delegate, and within it define_method is called, which creates instance methods on the singleton class (e.g., add), which are therefore class methods on the original class (MyClass).

If we call delegate from within the class MyClass statement, we will delegate a call to the instance method add to the instance method newAdd, if it existed. The reason for this is that inside the scope created by class MyClass, self is MyClass, and by extending module Delegate here, we are creating a class method delegate (an instance method delegate on MyClass's singleton class). This class method makes calls to define_method, which creates instance methods on MyClass (e.g., add).

We end up with two methods called delegate, one is a class method of MyClass, and one is a class method of the singleton class of MyClass.

evianpring
  • 3,316
  • 1
  • 25
  • 54
  • That Delegate module code might be clever but it's *really* unreadable! Good code is easy to understand so a lot of refactoring needs to be done here – Yorkshireman Jun 02 '16 at 22:45