Dynamically creating class method

前端 未结 1 554
迷失自我
迷失自我 2021-01-06 17:15

I am writing a class method to create another class method. There seems to be some strangeness around how class_eval and instance_eval operate with

相关标签:
1条回答
  • 2021-01-06 17:50

    The main difference between instance_eval and class_eval is that instance_eval works within the context of an instance, while class_eval works within the context of a class. I am not sure how familiar you are with Rails, but let's look at this for an example:

    class Test3 < ActiveRecord::Base
    
    end
    
    t = Test3.first
    t.class_eval { belongs_to :test_25 } #=> Defines a relationship to test_25 for this instance
    t.test_25 #=> Method is defined (but fails because of how belongs_to works)
    
    t2 = Test3.find(2)
    t2.test_25 #=> NoMethodError
    
    t.class.class_eval { belongs_to :another_test }
    t.another_test #=> returns an instance of another_test (assuming relationship exists)
    t2.another_test #=> same as t.another_test
    
    t.class_eval { id } #=> NameError
    t.instance_eval { id } #=> returns the id of the instance
    t.instance_eval { belongs_to :your_mom } #=> NoMethodError
    

    This happens because belongs_to is actually a method call that happens within the context of the class body, which you cannot call from an instance. When you try to call id with class_eval, it fails because id is a method defined on an instance, not in a class.

    Defining methods with both class_eval and instance_eval work essentially the same when called against an instance. They will define a method only on the instance of the object it is called against.

    t.class_eval do 
      def some_method
        puts "Hi!"
      end
    end
    
    t.instance_eval do
      def another_method
        puts "Hello!"
      end
    end
    
    t.some_method #=> "Hi!"
    t.another_method #=> "Hello!"
    
    t2.some_method #=> NoMethodError
    t2.another_method #=> NoMethodError
    

    They differ, however, when dealing with the class.

    t.class.class_eval do
      def meow
        puts "meow!"
      end
    end
    
    t.class.instance_eval do
      def bark
        puts "woof!"
      end
    end
    
    t.meow #=> meow!
    t2.meow #=> meow!
    
    t.bark #=> NoMethodError
    t2.bark #=> NoMethodError
    

    So where did bark go? It got defined on the instance of the class' singleton class. I'll explain more below. But for now:

    t.class.bark #=> woof!
    Test3.bark #=> woof!
    

    So to answer your question about what self is referring to within the class body, you can construct a little test:

    a = class Test4
      def bar
        puts "Now, I'm a #{self.inspect}"
      end
    
      def self.baz
        puts "I'm a #{self.inspect}"
      end
    
      class << self
        def foo
          puts "I'm a #{self.inspect}"
        end
    
        def self.huh?
          puts "Hmmm? indeed"
        end
    
        instance_eval do
          define_method :razors do
            puts "Sounds painful"
          end
        end
    
        "But check this out, I'm a #{self.inspect}"
      end
    end
    
    puts Test4.foo #=> "I'm a Test4"
    puts Test4.baz #=> "I'm a Test4"
    puts Test4.new.bar #=> Now I'm a #<Test4:0x007fa473358cd8>
    puts a #=> But check this out, I'm a #<Class:Test4>
    

    So what is happening here is that in the first puts statement above, we see that inspect is telling us that self within the context of a class method body is referring to the class Test4. In the second puts, we see the same thing, it's just defined differently (using the self.method_name notation for defining class methods). In the third, we see that self refers to an instance of Test4. The last one is a bit interesting because what we see is that self is referring to an instance of Class called Test4. That is because when you define a class, you're creating an object. Everything in Ruby is an object. This instance of object is called the metaclass or the eigenclass or singleton class.

    You can access the eigenclass with the class << self idiom. While you're in there, you actually have access to the internals of the eigenclass. You can define instance methods inside of the eigenclass, which is congruent with calling self.method_name. But since you are within the context of the eigenclass, you can attach methods to the eigenclass' eigenclass.

    Test4.huh? #=> NoMethodError
    Test4.singleton_class.huh? #=> Hmmm? indeed
    

    When you call instance_eval in the context of a method, you're really calling instance_eval on the class itself, which means that you are creating instance methods on Test4. What about where I called instance_eval inside of the eigenclass? It creates a method on the instance of Test4's eigenclass:

    Test4.razors #=> Sounds painful
    

    Hopefully, this clears up a few of your questions. I know that I have learned a few things typing out this answer!

    0 讨论(0)
提交回复
热议问题