Re: mod_ruby and include statements (mixins)

Brian Candler <[email protected]> Sun, 4 Sep 2005 14:35:29 +0100
Newsgroups gmane.comp.apache.mod-ruby
Message-ID <[email protected]>
On Wed, Aug 24, 2005 at 07:35:19PM -0600, Matthew Thill wrote:
> Perhaps someone can explain this behaviour to me.
> 
> This is on an OpenBSD system. Ruby 1.8.2 and mod_ruby 1.2.4 installed 
> successfully. Apache runs in a chroot environment. To avoid starting 
> Apache with the -u flag (to turn chroot off) I copied the ruby 
> installation into the chroot environment (/var/www) so things would 
> work. Things do work as expected, except for this bit of strange 
> behaviour. Any insight anyone can offer would be appreciated.
> 
> I have replicated the problem with a few small files.
> 
> The first file, call it ruby1.rb, is created and I run it from the 
> shell, using the command "ruby ruby1.rb".
> 
> *********** Start ruby1.rb *******************
> module M
>   def some_method
>   end
> end
> 
> class T
>   include M
> 
>   def T.main
>     puts methods.include?('some_method').to_s
>   end
>   def main
>     puts methods.include?('some_method').to_s
>   end
> end
> T.main
> t = T.new
> t.main
> include M
> puts methods.include?('some_method').to_s
> *********** End ruby1.rb *******************
> 
> The output is as expected:
> false
> true
> true
> 
> If I put this file somewhere Apache can see it and run it through 
> mod_ruby, I get an internal server error. In Apache's error log I see this:
> `append_features': cyclic include detected (ArgumentError)
> 
> So, mod_ruby is doing something weird with include statements (read: I 
> don't understand what it's doing).
> 
> To work around this, I put the module M definition into a file (call it 
> m.rb).
> 
> ************* Start m.rb *******************
> module M
>   def some_method
>   end
> end
> ************ End m.rb **********************
> 
> The code put in m.rb is removed from ruby1.rb (the first 4 lines), and 
> is replaced with "load {path/to/m.rb}".
> 
> I then run ruby1.rb through Apache and mod_ruby again, and receive this 
> output (I put the linefeeds in to make it easier to read):
> false
> true
> false
> 
> The first two are as expected, but the last "false" is not expected. 
> Apparently, with mod_ruby, only a class can include (or mix-in) a 
> module. When I try to include a module in a script and run it through 
> mod_ruby, the methods defined in the module are not available. I can 
> only access the methods from the module if I have a class include the 
> module.
> 
> A posibly related issue is the cyclic include error. I would appear as 
> though defining a module in the same file as a class results in the 
> module being "automatically" included. To test this, I put ruby1.rb in 
> its original form back and removed the "include M" statement. This did 
> fix the cyclic include error, but the module methods were still not 
> available and the output was:
> false
> true
> false
> 
> Can anyone provide me with an explanation for this behaviour? Is this 
> just the way mod_ruby works? Should it work this way? I would think a 
> script that runs in the shell environment should work exactly the same 
> in the mod_ruby environment.

In the mod_ruby environment, each file to be executed is loaded inside an
anonymous module, kind-of as if wrapped by:

    module FOOBARrandom123456
    ... code goes here
    end

This is to minimise the interactions between unrelated code for different
applications which are all run within the mod_ruby interpreter.

You can get the same effect using "load(filename,true)" - see ri Kernel#load

Now this lets us replicate your problem without using mod_ruby:

    $ cat ruby2.rb
    load 'ruby1.rb', true
    $ ruby ruby2.rb
    false
    true
    ./ruby1.rb:19:in `append_features': cyclic include detected (ArgumentError)
            from ./ruby1.rb:19:in `include'
            from ./ruby1.rb:19
            from ruby2.rb:1:in `load'
            from ruby2.rb:1

I hope this gives you a starting point for tracking down the problem
further. You can minimise your test case to:

    $ cat ruby3.rb
    load 'ruby4.rb', true
    $ cat ruby4.rb
    module M
      def some_method
      end
    end

    include M
    $ ruby ruby3.rb
    ./ruby4.rb:6:in `append_features': cyclic include detected (ArgumentError)
            from ./ruby4.rb:6:in `include'
            from ./ruby4.rb:6
            from ruby3.rb:1:in `load'
            from ruby3.rb:1

and you can always post this onto the ruby-talk mailing list for further
explanation.

Regards,

Brian.