How can I prevent require duplicate files


#1

In a big ruby project, how to prevent requiring a file multiple times?
Like the #ifdef in C.


#2

Zhao Yi wrote:

In a big ruby project, how to prevent requiring a file multiple times?
Like the #ifdef in C.

require() already prevents multiple includes. load() forces an
unquestioned input.

If two modules require the same module…

require ‘foo’

…and if both modules specify the same path to foo (or no path), then
the file
only imports once.

However, if you require some elaborate path, and if another module
requires a
different path to the same file, Ruby does not care if the file is the
same - it
will load() and execute the same source twice.


#3

Phlip wrote:
require() already prevents multiple includes. load() forces an

unquestioned input.

If two modules require the same module…

require ‘foo’

…and if both modules specify the same path to foo (or no path), then
the file
only imports once.

However, if you require some elaborate path, and if another module
requires a
different path to the same file, Ruby does not care if the file is the
same - it
will load() and execute the same source twice.

If there are some variables defined in a module which is included by
more than one files, there will be a warning indicating “warning:
already initialized”. If ruby only imports once, why such warning is
thrown?


#4

Zhao Yi wrote:

If there are some variables defined in a module which is included by
more than one files, there will be a warning indicating “warning:
already initialized”. If ruby only imports once, why such warning is
thrown?

I don’t know about all cases but I suspect the biggest culprit is
require ‘foo’
twice with different paths.

If you have a Rails project, it has a Magic Loader that automagically
loads the
file foo.rb if the interpreter sees a Foo reference and has not seen a
definition for Foo yet. This is a miracle and a pain in the butt,
because a
subsequent require ‘foo’ line might not have the same path, and you get
the
double-require issue.

If the problem persists, given a warning on Bar, write:

unless defined? Bar
Bar = 42 # or whatever
end

That’s a total hack, but it works well enough.


#5

Phlip wrote:

I don’t know about all cases but I suspect the biggest culprit is
require ‘foo’
twice with different paths.

If you have a Rails project, it has a Magic Loader that automagically
loads the
file foo.rb if the interpreter sees a Foo reference and has not seen a
definition for Foo yet. This is a miracle and a pain in the butt,
because a
subsequent require ‘foo’ line might not have the same path, and you get
the
double-require issue.

That’s a total hack, but it works well enough.

But there is only one file named foo.rb, how could require ‘foo’ happen?
Is there a way to avoid printing the warning message?


#6

There must be a mistake, below is the test I’ve made. No warning at
all, even with warning level set to 2

Could you send some example?

% cat test.rb
require ‘thedef’
require ‘test1’
require ‘test2’

% cat thedef.rb
THEDEF = 1

% cat test1.rb
require ‘thedef’

% cat test2.rb
require ‘thedef’

% ruby -W2 test.rb
%

blambeau


#7

Well, we know that ruby 1.8.x doesn’t ensure unique loading with that
kind of require …

There’s another hack below, that should be consolidated however,

module Kernel
alias_method :old_require, :require
def require(path)
old_require(File.expand_path(path))
end
end

Otherwise, you can use ‘require File.expand_path(‘oldpath’)’ each
time, but that’s even more ugly.

blambeau


#8

LAMBEAU Bernard wrote:

There must be a mistake, below is the test I’ve made. No warning at
all, even with warning level set to 2

Now try:

require ‘test1’
require ‘./test1’
require ‘/home/someuser/test1’


#9

Brian C. wrote:

However, since he didn’t post the exact warnings seen, or the code which
produces them, it’s hard to help him further.

Hi,

This is the case, I have three files, a.rb, b.rb and c.rb.

a.rb:

module A
TEST=‘test’
end

b.rb:

require ‘a’
require ‘c’
class B
include A
end

c.rb:

require File.dirname(FILE)+’/a.rb’
class C
include A
end

when run b.rb, I will get the warning: “./a.rb:2: warning: already
initialized constant TEST”


#10

LAMBEAU Bernard wrote:

Well, we know that ruby 1.8.x doesn’t ensure unique loading with that
kind of require …

There’s another hack below, that should be consolidated however,

module Kernel
alias_method :old_require, :require
def require(path)
old_require(File.expand_path(path))
end
end

Yuk, that will break any program which uses any sort of external
library.

irb(main):001:0> File.expand_path(“net/http”)
=> “/home/candlerb/net/http”
irb(main):002:0> require File.expand_path(“net/http”)
LoadError: no such file to load – /home/candlerb/net/http
from (irb):2:in `require’
from (irb):2
from :0
irb(main):003:0> require ‘net/http’
=> true

The original question said:

“In a big ruby project, how to prevent requiring a file multiple times?
Like the #ifdef in C”

Well, nothing stops you using the same trick in Ruby:

unless defined? _FOO
_FOO = 1
… rest
end

But in general, as long as you use require sensibly (i.e. consistently
require ‘foo’ and not mixed with require ‘/path/to/foo’) then this isn’t
a problem.

So it seems to me the OP is doing something out-of-the-ordinary which is
causing the warnings (not errors) that he sees.

However, since he didn’t post the exact warnings seen, or the code which
produces them, it’s hard to help him further.


#11

Zhao Yi wrote:

c.rb:

require File.dirname(FILE)+’/a.rb’

That’s the source of the problem.

The solution is to set up $LOAD_PATH appropriately at the start of your
program, so that you never have to do require with an absolute path.

For example, let’s say you have the following directory structure for
your project:

bin/foo
bin/bar
lib/a
lib/b
lib/c

Stick the following at the top of bin/foo and bin/bar:

#!/usr/bin/ruby -w
$LOAD_PATH.unshift File.dirname(FILE)+"/…/lib"

From this point onwards you can do require ‘a’, require ‘b’ etc without
any absolute paths. This also has the advantage that it doesn’t matter
what the current directory is when you start the program. e.g. you can
do

$ cd /tmp
$ /home/me/bin/foo

and it will work as expected.

Hints:

  • When running scripts from the command line, you can use -Ilib to get
    the ‘lib’ dir added to the load path.

  • If you’re playing with Rails, look at config/boot.rb


#12

Brian C. wrote:

#!/usr/bin/ruby -w
$LOAD_PATH.unshift File.dirname(FILE)+"/…/lib"

this works. thanks.