Hello,

I am having trouble understanding what's going on here, and I wonder if any
guru here could enlighten me. I am getting infinite recursion when I don't
expect it, and it's something to do with 'including' a module at the top
level.

$ ruby --version
ruby 1.8.2 (2004-07-29) [i386-freebsd5]

Here's a small bit of code which demonstrates the issue:

---- 8< ------------------------------------------------------------------
require 'ostruct'

module MyApp
  O = OpenStruct.new
  O.testitem = "hello"
  def test
    "test ok"
  end
  def wibble
    return O.wibble       # should return nil
  end
end

klass = class <<MyApp::O; self; end
p klass.ancestors
#==> [OpenStruct, Object, Kernel]

include MyApp

klass = class <<MyApp::O; self; end
p klass.ancestors
#==> [OpenStruct, Object, MyApp, Kernel]

p O.testitem    #==> "hello"
p test          #==> "test ok"
p wibble        #==> stack level too deep (SystemStackError)  !!!
---- 8< ------------------------------------------------------------------

The idea is that I create an object 'MyApp::O', which uses method_missing to
capture calls to unknown methods (that's how OpenStruct works internally).

Also in module MyApp is a method 'wibble' that I want to call. It uses the
value of 'MyApp::O.wibble' in its processing.

Finally, I do 'include MyApp' to make the method "wibble" available at the
top level, and then call it.

At that point, I get an infinite loop. As far as I can tell:
- I call wibble (= MyApp#wibble)
- it calls MyApp::O.wibble
- while searching for the methods inside this object, it finds MyApp#wibble
  and thus recurses, instead of falling through to method_missing.

So my questions are:

(1) Why is this method found? In other words, why does including MyApp at
the top level affect the methods which are visible within MyApp::O ?

(2) (probably related). After doing 'include MyApp', I see that the
singleton class of MyApp::O includes MyApp in its ancestors. Why does it
appear there? I thought that the 'include' operation would only affect the
'main' object.

NOW: I have discovered I can solve this problem simply by putting
"module_function :wibble" after the definition of method wibble. So my next
question is:

(3) Why does this fix things? I see that MyApp still appears in the
ancestors of the singleton class of MyApp::O, but clearly the calling
sequence must have changed somehow.

I've read what there is about module_function in PickAxe v2, but it doesn't
have much to say (p558, p355). As far as I can gather, this should duplicate
my existing methods, so that they can be called as
    MyApp.test
    MyApp.wibble
but it doesn't say anything about otherwise changing the behaviour of the
existing methods that I defined. I couldn't find anything on the RubyGarden
wiki either.

Incidentally, I can remove OpenStruct and method_missing from the problem
set; the resulting code is less practically useful, but still demonstrates
the issue.

---- 8< ------------------------------------------------------------------
module MyApp
  O = Hash.new
  O[:testitem] = "hello"
  def test
    "test ok"
  end
  def wibble
    return O.wibble       # should give 'method not found'
  end
  #module_function :wibble
end

include MyApp

p O[:testitem]  #==> "hello"
p test          #==> "test ok"
p wibble        #==> stack level too deep (SystemStackError)  !!!
---- 8< ------------------------------------------------------------------

With module_function :wibble commented out, I get
8:in `wibble': stack level too deep (SystemStackError)

With module_function :wibble uncommented, I get
8:in `wibble':private method `wibble' called for {:testitem=>"hello"}:Hash (NoMethodError)

and again, I don't really understand why I get either of those messages. I
thought that 'wibble' would not be visible at all to MyApp::O, rather than
visible as a private method?

And final related question:

(4) How/why are the following two definitions different?

  module MyApp
    def wibble; puts "ok"; end
    module_function :wibble
  end
  MyApp.wibble   #==> "ok"
  include MyApp
  wibble         #==> "ok"
-----
  module MyApp
    def MyApp.wibble; puts "ok"; end
  end
  MyApp.wibble   #==> "ok"
  include MyApp
  wibble         #==> undefined local variable or method `wibble' for main:Object (NameError)


Anyway, I'd much appreciate it if someone could explain, or could point me
at some documentation which has enough detail about the method search
mechanism and the semantics of 'include' and 'module_function' that I can
work it out for myself.

Many thanks...

Brian.