开发者

Ruby: context switches when calling dynamically defined methods

Here's some test code that explains the issue I'm having. The Child class calls methods 开发者_运维百科on the Parent class. One of the Parent's methods defines a new method called foo on the Parent. After foo is defined, attempting to call it from Child class works, but the context is completely different (I can only access Child's instance variables, not Parent's).

My guess here that this has something to do with Ruby's closures. Should I use something besides a block when I call define_method? Edit: I tried using a lambda and a proc, but it changed nothing.

class Parent
  @foo = 'foo'

  def self.existing_method
    puts "Calling existing_method, @foo is #{@foo}"
  end

  def self.define_new_method
    self.class.send :define_method, :foo do
      context = methods.include?('bar') ? 'child' : 'parent'

      puts "Context is #{context}, @foo is #{@foo.inspect}"
    end
  end
end

class Child
  @foo = 'childfoo'

  def self.method_missing(*args, &block)
    Parent.send args.shift, *args, &block
  end

  def self.bar
  end
end

Child.existing_method    # Calling existing_method, @foo is foo
Child.define_new_method
Child.foo                # Context is child, @foo is "childfoo"
                         # (in Ruby 1.9, the context is parent, but
                         # @foo is still "childfoo")

Parent.foo               # Context is parent, @foo is "foo"

This result is not what I want. Child.foo's response should be the same as Parent.foo's.

Thanks in advance!


After a lot of digging, here's what I figured out.

  1. Calling self.class.define_method actually defines an instance method of Object. That meant that everything received that new method, which is the reason for the apparent context shift (Child.foo was actually calling Child.foo and not Parent.foo). Whoops.
  2. In order to define a class method, you have to grab the actual class object, which, for some reason, isn't self, it's class << self; self; end (yeah, I didn't get that either). The code is briefly mentioned here: http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html

Ruby makes my brain hurt sometimes. This code should return the expected results.

class Parent
  @foo = 'foo'

  def self.existing_method
    puts "Calling existing_method, @foo is #{@foo}"
  end

  def self.define_new_method
    inst = class << self; self; end # Do not understand this...
    inst.send :define_method, :foo do
      context = methods.include?(:bar) ? 'child' : 'parent'
      puts "Context is #{context}, @foo is #{@foo.inspect}"
    end
  end
end

class Child
  @foo = 'childfoo'

  def self.method_missing(*args, &block)
    return unless args.length > 0
    Parent.send args.shift, *args, &block
  end

  def self.bar
  end
end

Child.existing_method     # Calling existing_method, @foo is foo
Child.define_new_method
Child.foo                 # Context is parent, @foo is "foo"
Parent.foo                # Context is parent, @foo is "foo"


That's funny; I think Yehuda Katz's post from today explains exactly what you want: http://yehudakatz.com/2010/02/15/abstractqueryfactoryfactories-and-alias_method_chain-the-ruby-way/


EDIT: Okay - it's not "what you're asking for", on second thought, since it's not metaprogramming, but it's most likely closer to what you want to do. As far as your specific answer, I'll take a look at the code.


In Ruby 1.9x,

'methods' method return Symbol(method name) Array.

Therefore use respond_to method:

context = respond_to?(:bar) ? 'child' : 'parent'
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜