This modules thing somehow really does not work for me, please help

Hey,

thank you in advance for reading this post. I struggle with
modularization / packaging / name spacing (however you want to call
it) via ruby modules. I have the feeling that somehow rails is
responsible for the problems I am facing. I hope, you can help me.

Here’s an example to demonstrate more exactly what I mean:

module Storage
module Administration

def self.do_something()
  # .. omitted
end

end
end

This does sometimes work under the rails console (development mode),
most of the time I get:

ruby-1.9.2-p290 :004 > Storage::Administration.do_something()
NoMethodError: undefined method `do_something’ for
Storage::Administration:Module

This one here works more often (but not always):

module Storage::Administration

def self.do_something()
  # .. omitted
end

end

What’s about the constant resolution operator (::)? As far as I know
it is only allowed to reference constants (like module or class
names), but not directly in definitions (module X::Y::Z).

As I know it from pure ruby the first example should do it. You just
nest the modules as you need and simply reopen a module if you want to
extend it.

So, the contents of my ruby files used for this example:

“storage.rb” file (in a directory called “storage”):

module Storage
end

And my “administration.rb” (in a subdirectory of “storage” called
“administration”):

module Storage
module Administration

… omitted

end
end

OR (as I also tried)

module Storage::Administration

# .. omitted

end

What am I doing wrong? Why can’t rails (console, runner, etc.) access
my static method all the time?

Thank you very much for any suggestions!
ms

On Sat, Dec 10, 2011 at 11:51 PM, ms [email protected] wrote:

module Administration

end

module Storage

… omitted

end

What am I doing wrong? Why can’t rails (console, runner, etc.) access
my static method all the time?

Thank you very much for any suggestions!

Under which directory exactly are you saving these files ?
Which version of rails?

If you use files under /lib, this may help (since recent versions of
Rails,
files under lib are not by default auto-loaded).

# Custom directories with classes and modules you want to be

autoloadable.
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir[“#{config.root}/lib/**/”]

to understand the issue, I suggest you try step by step

Storage
Storage::Administration
Storage::Administration.function
to see how far it works and where exactly it breaks.

This code below works reliably in Rails 3.1.x

$ cat lib/storage/administration.rb
module Storage
module Administration
def self.foo
“foo”
end
end
end

$ rails c
Loading development environment (Rails 3.1.3)
001:0> Storage
=> Storage
002:0> Storage::Administration
=> Storage::Administration
003:0> Storage::Administration.foo
=> “foo”
004:0> quit

$ rgrep -i autoload config/application.rb
# Custom directories with classes and modules you want to be
autoloadable.
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir[“#{config.root}/lib/**/”]

I would expect this code to be reliable on your side as well.

HTH,

Peter

On 10 Dec 2011, at 22:51, ms [email protected] wrote:

Hey,

thank you in advance for reading this post. I struggle with
modularization / packaging / name spacing (however you want to call
it) via ruby modules. I have the feeling that somehow rails is
responsible for the problems I am facing. I hope, you can help me.

It sounds like the issue is with rails’ autoloading system

What’s about the constant resolution operator (::)? As far as I know
it is only allowed to reference constants (like module or class
names), but not directly in definitions (module X::Y::Z).

module X::Y::Z is fine, but requires that X::Y already exists, whereas

module X
module Y
module Z
end
end
end

Doesn’t. This also means that in the first case rails will look (via
const_missing) for X::Y (in x/y.rb) whereas in the second case it won’t.
If in x/y.rb you added some methods to Y and x/y.rb had never been
loaded but z.rb had been then X::Y would still exist because of z.rb,
but those methods from y.rb wouldn’t.

As I know it from pure ruby the first example should do it. You just
nest the modules as you need and simply reopen a module if you want to
extend it.

So, the contents of my ruby files used for this example:

“storage.rb” file (in a directory called “storage”):

module Storage
end

Actually, rails would expect to find Storage in storage.rb at the top
level, ie not in the storage folder

end

OR (as I also tried)

module Storage::Administration

… omitted

end

Ditto, rails expects to find Storage::Administration in
storage/administration.rb

What am I doing wrong? Why can’t rails (console, runner, etc.) access
my static method all the time?

Probably a combination of not putting things quite in the right place
and using the
module X
module Y
end
end
style, which doesn’t force rails to load x.rb, leading to different
behaviour depending on whether you reference X before X::Y or the other
way around.

Fred

On Sun, Dec 11, 2011 at 3:04 PM, Frederick C. <
[email protected]> wrote:

module Y

Thanks for the very interesting explanation.

If I understand your reasoning correctly, the logic would be that
in the cases with separate module definitions:

  1. module X::Y::Z is loaded from the file …/lib/x/y/z.rb
  2. this implicitely also defines the constant X::Y
    (based solely on information in file …/lib/x/y/z.rb
    and not trying to read …/lib/x/y.rb)
  3. when X::Y is used later on, it is never really loaded from the
    file …/lib/x/y.rb , so what is defined there can be
    mysteriously missing (depending on the order modules where loaded).

However, I could not produce this behavior as I understood it.

As a test, I made 2 implementations of the module in ./lib/x/y/z.rb

In both cases I have in …/lib/x/y.rb

…/lib/x$ cat y.rb
puts “loading module X / module Y in file …/lib/x/y.rb”

module X
module Y
end
end

Test 1: separate module definitions

…/lib/x/y$ cat z.rb
module X
module Y
module Z
def self.foo
“Z.foo”
end
end
end
end

Results:

$ rails c
Loading development environment (Rails 3.1.3)
001:0> ActiveSupport::Dependencies.logger = Rails.logger #=> <…>
002:0> ActiveSupport::Dependencies.log_activity=true #=> true
003:0> X::Y::Z
loading module X / module Y in file …/lib/x/y.rb
=> X::Y::Z

in logfile:
Dependencies: called load_missing_constant(Object, :X)
Dependencies: called load_missing_constant(X, :Y)
Dependencies: called require_or_load("…/lib/x/y.rb", nil)
Dependencies: loading …/lib/x/y
Dependencies: called load_file("…/lib/x/y.rb", [“X::Y”, “Y”])
Dependencies: called new_constants_in(“X”, :Object)
Dependencies: New constants: X::Y
Dependencies: loading …/lib/x/y.rb defined X::Y
Dependencies: called load_missing_constant(X::Y, :Z)
Dependencies: called require_or_load("…/lib/x/y/z.rb", nil)
Dependencies: loading …/lib/x/y/z
Dependencies: called load_file("…/lib/x/y/z.rb", [“X::Y::Z”, “Y::Z”,
“Z”])
Dependencies: called new_constants_in(“X::Y”, “Y”, :Object)
Dependencies: New constants: X::Y::Z
Dependencies: loading …/lib/x/y/z.rb defined X::Y::Z

Test 2: (in 1 direct line module X::Y::Z)

…/lib/x/y$ cat z.rb
module X::Y::Z
def self.foo
“Z.foo”
end
end

Results:

$ rails c
Loading development environment (Rails 3.1.3)
001:0> ActiveSupport::Dependencies.logger = Rails.logger #=> <…>
002:0> ActiveSupport::Dependencies.log_activity=true #=> true
003:0> X::Y::Z
loading module X / module Y in file …/lib/x/y.rb
=> X::Y::Z

In logifle:

Dependencies: called load_missing_constant(Object, :X)
Dependencies: called load_missing_constant(X, :Y)
Dependencies: called require_or_load("…/lib/x/y.rb", nil)
Dependencies: loading …/lib/x/y
Dependencies: called load_file("…/lib/x/y.rb", [“X::Y”, “Y”])
Dependencies: called new_constants_in(“X”, :Object)
Dependencies: New constants: X::Y
Dependencies: loading …/lib/x/y.rb defined X::Y
Dependencies: called load_missing_constant(X::Y, :Z)
Dependencies: called require_or_load("…/lib/x/y/z.rb", nil)
Dependencies: loading …/lib/x/y/z
Dependencies: called load_file("…/lib/x/y/z.rb", [“X::Y::Z”, “Y::Z”,
“Z”])
Dependencies: called new_constants_in(“X::Y”, “Y”, :Object)
Dependencies: New constants: X::Y::Z
Dependencies: loading …/lib/x/y/z.rb defined X::Y::Z

So, my experiments seem to suggest that in both cases
that I tried, there is an explicit order where all intermediate
modules are loaded in turn (with load_missing_constants),
even if only the deepest module would be strictly required to
evaluate X::Y::Z

Could you show an example of where the problem you described
occurs. Or maybe this behavior has changed recently?

Thanks,

Peter

On Dec 11, 6:10pm, Peter V. [email protected] wrote:

So, my experiments seem to suggest that in both cases
that I tried, there is an explicit order where all intermediate
modules are loaded in turn (with load_missing_constants),
even if only the deepest module would be strictly required to
evaluate X::Y::Z

Could you show an example of where the problem you described
occurs. Or maybe this behavior has changed recently?

Yeah I mixed things up a bit there as well as slightly mis-
remembering. if you do things as rails expects, and rely entirely on
the const_missing hooks then either way works. but if you deviate from
that then things are different.

For example if z.rb is

module X
module Y
module Z
end
end
end

and you do require_dependency ‘x/y/z’ then x/y.rb won’t be loaded

but if you do

module X::Y::Z
end

then it will.

I guess the point I was trying to make was that if ruby ends up
reading z.rb then using module X::Y::Z guarantees that x.rb and x/y.rb
will be read, but the other form doesn’t, although the usual scenarios
would mean that x.rb and x/y.rb would have been read earlier on
anyway.

Fred

On Sun, Dec 11, 2011 at 8:30 PM, Frederick C. <
[email protected]> wrote:

end
then it will.

I guess the point I was trying to make was that if ruby ends up
reading z.rb then using module X::Y::Z guarantees that x.rb and x/y.rb
will be read, but the other form doesn’t, although the usual scenarios
would mean that x.rb and x/y.rb would have been read earlier on
anyway.

Thanks for the additional info. I was now able to reproduce your point.

…/lib/x/y$ cat z.rb
module X
module Y
module Z
def self.foo
“Z.foo”
end
end
end
end

and using ‘require_dependency’ the result the functionality in the
file x/y.rb is not loaded when loading module Z:

$ rails c
Loading development environment (Rails 3.1.3)
001:0> ActiveSupport::Dependencies.logger = Rails.logger
002:0> ActiveSupport::Dependencies.log_activity=true
003:0> require_dependency ‘x/y/z’
=> true

In log this gives:

Dependencies: called require_or_load("…/lib/x/y/z.rb", nil)
Dependencies: loading …/lib/x/y/z
Dependencies: called load_file("…/lib/x/y/z.rb", [“X::Y::Z”, “Y::Z”,
“Z”])
Dependencies: called new_constants_in(“X::Y”, “Y”, :Object)
Dependencies: New constants: X::Y::Z, X
Dependencies: loading …/lib/x/y/z.rb defined X::Y::Z, X

X::Y::Z is defined as expected

X is also defined (that is an ancestor)

X::Y and Y are not defined (while these are ancestors too)

004:0> X::Y.foo
NoMethodError: undefined method foo' for X::Y:Module from (irb):4 from .../gems/railties-3.1.3/lib/rails/commands/console.rb:45:instart’
from …/gems/railties-3.1.3/lib/rails/commands/console.rb:8:in start' from .../gems/railties-3.1.3/lib/rails/commands.rb:40:in<top
(required)>’
from script/rails:6:in require' from script/rails:6:in
005:0> require_dependency ‘x/y’
loading module X / module Y in file …/lib/x/y.rb
=> true

This shows in the log:

Dependencies: called require_or_load("…/lib/x/y.rb", nil)
Dependencies: loading …/lib/x/y
Dependencies: called load_file("…/lib/x/y.rb", [“X::Y”, “Y”])
Dependencies: called new_constants_in(“X”, :Object)
Dependencies: New constants:

006:0> X::Y.foo
=> “Y.foo”

With the version:

/lib/x/y$ cat z.rb
module X::Y::Z
def self.foo
“Z.foo”
end
end

The result is:

$ rails c
Loading development environment (Rails 3.1.3)
001:0> ActiveSupport::Dependencies.logger = Rails.logger
002:0> ActiveSupport::Dependencies.log_activity=true
003:0> require_dependency ‘x/y/z’
loading module X / module Y in file …/lib/x/y.rb
=> true

log now shows a recursive resolution

Dependencies: called require_or_load("…/lib/x/y/z.rb", nil)
Dependencies: loading …/lib/x/y/z
Dependencies: called load_file("…/lib/x/y/z.rb", [“X::Y::Z”, “Y::Z”,
“Z”])
Dependencies: called new_constants_in(“X::Y”, “Y”, :Object)
Dependencies: called load_missing_constant(Object, :X)
Dependencies: called load_missing_constant(X, :Y)
Dependencies: called require_or_load("…/lib/x/y.rb", nil)
Dependencies: loading …/lib/x/y
Dependencies: called load_file("…/lib/x/y.rb", [“X::Y”, “Y”])
Dependencies: called new_constants_in(“X”, :Object)
Dependencies: New constants: X::Y
Dependencies: loading …/lib/x/y.rb defined X::Y
Dependencies: New constants: X::Y::Z, X
Dependencies: loading …/lib/x/y/z.rb defined X::Y::Z, X

004:0> X::Y.foo
=> “Y.foo”

Thanks again,

Peter