views:

252

answers:

2

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!

A: 

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"
ashrewdmint
Here's a Why the Lucky Stiff article that explains metaclasses in detail: http://dannytatom.github.com/metaid/
ashrewdmint
class << self; self; end => That is a singleton class, not required to add class-level methods. It depends what your goal is.
Trevoke
A: 

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.

Trevoke
Thanks for the link, Trevoke. I'll take a look at the post when I have some time.
ashrewdmint