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 Edit: I tried using a lambda and a proc, but it changed nothing.define_method
?
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.
- Calling
self.class.define_method
actually defines an instance method ofObject
. That meant that everything received that new method, which is the reason for the apparent context shift (Child.foo
was actually callingChild.foo
and notParent.foo
). Whoops. - In order to define a class method, you have to grab the actual class object, which, for some reason, isn't
self
, it'sclass << 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'
精彩评论