Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby metaclass madness

I'm stuck. I'm trying to dynamically define a class method and I can't wrap my head around the ruby metaclass model. Consider the following class:

class Example

  def self.meta; (class << self; self; end); end

  def self.class_instance; self; end

end

Example.class_instance.class # => Class
Example.meta.class           # => Class

Example.class_instance  == Example      # => true
Example.class_instance  == Example.meta # => false

Obviously both methods return an instance of Class. But these two instances are not the same. They also have different ancestors:

Example.meta.ancestors            # => [Class, Module, Object, Kernel]
Example.class_instance.ancestors  # => [Example, Object, Kernel]

What's the point in making a difference between the metaclass and the class instance?

I figured out, that I can send :define_method to the metaclass to dynamically define a method, but if I try to send it to the class instance it won't work. At least I could solve my problem, but I still want to understand why it is working this way.

Update Mar 15, 2010 13:40

Are the following assumptions correct.

  • If I have an instance method which calls self.instance_eval and defines a method, it will only affect the particular instance of that class.
  • If I have an instance method which calls self.class.instance_eval (which would be the same as calling class_eval) and defines a method it will affect all instances of that particular class resulting in a new instance method.
  • If I have a class method which calls instance_eval and defines a method it will result in a new instance method for all instances.
  • If I have a class method which calls instance_eval on the meta/eigen class and defines a method it will result in a class method.

I think it starts to make sense to me. It would certainly limit your possibilities if self inside an class method would point to the eigen class. If so it would not be possible to define an instance method from inside a class method. Is that correct?

like image 713
t6d Avatar asked Mar 15 '10 10:03

t6d


1 Answers

Defining a singleton method dynamically is simple when you use instance_eval:

Example.instance_eval{ def square(n); n*n; end }
Example.square(2) #=> 4
# you can pass instance_eval a string as well.
Example.instance_eval "def multiply(x,y); x*y; end" 
Example.multiply(3,9) #=> 27

As for the difference above, you are confusing 2 things:

The meta class defined by you, is what called in Ruby community as singelton class or eigen class. That singleton class is the class that you can add class(singleton) methods to.

As for the class instance you are trying to define using the class_instance method, is nothing but the class itself, to prove it, just try adding an instance method to the class Example and check if the class_instance method defined by you returns the class Example itself by checking the existence of that method:

class Example
  def self.meta; (class << self; self; end); end
  def self.class_instance; self; end
  def hey; puts hey; end
end

Example.class_instance.instance_methods(false) #=> ['hey']

Anyway to sum it for you, when you want to add class methods, just add them to that meta class. As for the class_instance method is useless, just remove it.

Anyway I suggest you read this post to grasp some concepts of Ruby reflection system.

UPDATE

I suggest you read this nice post: Fun with Ruby's instance_eval and class_eval, Unfortunately class_eval and instance_eval are confusing because they somehow work against their naming!

Use ClassName.instance_eval to define class methods.

Use ClassName.class_eval to define instance methods.

Now answering your assumptions:

If I have an instance method which calls self.instance_eval and defines a method, it will only affect the particular instance of that class.

yes:

class Foo
  def assumption1()
    self.instance_eval("def test_assumption_1; puts 'works'; end")
  end
end

f1 = Foo.new
f1.assumption1
f1.methods(false) #=> ["test_assumption_1"]
f2 = Foo.new.methods(false) #=> []

If I have an instance method which calls self.class.instance_eval (which would be the same as calling class_eval) and defines a method it will affect all instances of that particular class resulting in a new instance method.

no instance_eval in that context will define singleton methods(not instance ones) on the class itself:

class Foo
  def assumption2()
    self.class.instance_eval("def test_assumption_2; puts 'works'; end")
  end
end

f3 = Foo.new
f3.assumption2
f3.methods(false) #=> []
Foo.singleton_methods(false) #=> ["test_assumption_2"]

For that to work replace instance_eval with class_eval above.

If I have a class method which calls instance_eval and defines a method it will result in a new instance method for all instances.

Nope:

class Foo
  instance_eval do
    def assumption3()
      puts 'works'
    end
  end
end

Foo.instance_methods(false) #=> []

Foo.singleton_methods(false) #=> ["assumption_3"]

That will make singleton methods, not instance methods. For that to work replace instance_eval with class_eval above.

If I have a class method which calls instance_eval on the meta/eigen class and defines a method it will result in a class method.

well no, that will make so sophisticated stuff, as it will add singleton method to the singleton class, I don't think that will have any practical use.

like image 141
Anon Mouse Avatar answered Oct 20 '22 06:10

Anon Mouse