3

別のクラスメソッドを作成するためのクラスメソッドを作成しています。クラスメソッドのコンテキスト内でどのようclass_evalに動作するかについては、奇妙なことがあるようです。instance_eval説明する:

class Test1
  def self.add_foo
    self.class_eval do # does what it says on the tin
      define_method :foo do
        puts "bar"
      end
    end
  end
end

Test1.add_foo # creates new instance method, like I'd expect
Test1.new.foo # => "bar"


class Test2
  def self.add_foo
    self.instance_eval do # seems to do the same as "class_eval"
      define_method :foo do
        puts "bar"
      end
    end
  end
end

Test2.add_foo # what is happening here?!
Test2.foo # => NoMethodError
Test2.new.foo # => "bar"


class Test3
  def self.add_foo
    (class << self; self; end).instance_eval do # call explicitly on metaclass
      define_method :foo do
        puts "bar"
      end
    end
  end
end

Test3.add_foo # => creates new class method, as I'd expect
Test3.foo # => "bar"

私の理解では、クラスメソッドは、問題のクラス(Test2この場合)のメタクラスで定義されたインスタンスメソッドです。add_fooそのロジックに基づいて、クラスメソッド呼び出しのレシーバーはメタクラスであると予想されます。

  • メソッド内で何をself指しているのですか?Test2.add_foo
  • instance_evalこのレシーバーオブジェクトを呼び出すと、インスタンスメソッドが作成されるのはなぜですか?
4

1 に答える 1

5

instance_evalとの主な違いclass_evalinstance_eval、インスタンスのコンテキストclass_eval内で機能するのに対し、クラスのコンテキスト内で機能することです。Railsにどれだけ慣れているかはわかりませんが、例としてこれを見てみましょう。

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

これbelongs_toは、実際にはクラス本体のコンテキスト内で発生するメソッド呼び出しであり、インスタンスから呼び出すことはできないために発生します。で呼び出そうとするとid、はクラスではなくインスタンスで定義されたメソッドであるclass_evalため、失敗します。id

class_eval両方でメソッドを定義instance_evalし、インスタンスに対して呼び出された場合、基本的に同じように機能します。それらは、呼び出されるオブジェクトのインスタンスでのみメソッドを定義します。

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

ただし、クラスを扱う場合は異なります。

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

それで、樹皮はどこに行きましたか?クラスのシングルトンクラスのインスタンスで定義されました。以下で詳しく説明します。しかし、今のところ:

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

selfしたがって、クラス本体内で何を参照しているかについての質問に答えるために、簡単なテストを作成できます。

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>

したがって、ここで起こっていることは、上記の最初のputsステートメントで、inspectがself、クラスメソッド本体のコンテキスト内でクラスTest4を参照していることを示していることがわかります。2番目putsの例では、同じことがわかります。定義が異なるだけです(self.method_nameクラスメソッドを定義するための表記法を使用)。self3番目では、これがTest4のインスタンスを参照していることがわかります。最後の1つは少し興味深いものです。これは、呼び出さselfれたのインスタンスを参照していることがわかるためです。これは、クラスを定義するときに、オブジェクトを作成しているためです。Rubyのすべてがオブジェクトです。このオブジェクトのインスタンスは、メタクラスまたは固有クラスまたはシングルトンクラスと呼ばれます。ClassTest4

イディオムを使用して固有クラスにアクセスできますclass << self。そこにいる間、実際には固有クラスの内部にアクセスできます。を呼び出すことと一致する固有クラス内でインスタンスメソッドを定義できますself.method_name。ただし、eigenclassのコンテキスト内にいるため、eigenclassのeigenclassにメソッドをアタッチできます。

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

instance_evalメソッドのコンテキストで呼び出す場合、実際instance_evalにはクラス自体を呼び出しています。つまり、Test4でインスタンスメソッドを作成していることになります。固有クラス内でinstance_evalを呼び出した場所はどうですか?Test4の固有クラスのインスタンスにメソッドを作成します。

Test4.razors #=> Sounds painful

うまくいけば、これはあなたの質問のいくつかをクリアします。私はこの答えをタイプすることをいくつか学んだことを知っています!

于 2012-12-13T18:34:58.453 に答える