tags:

views:

219

answers:

1

Is there a version of require in ruby that either loads the whole file, or nothing at all?

The problem is that require starts loading from the top, and if it faces problems you end up with uncompleted definitions, for example, the following would still load a class A even if module C is not defined:

class B
  include C
end

In my particular case, I have a large set of inter-dependent files, and a loader that loads those files. To exemplify, I will simply the set of files to 4 files (a.rb, b.rb, c.rb and w.rb). The following is a listing of those files:

# In file a.rb
class A
  @foo = []
  @foo.push("in A")

  def self.inherited(subclass)
    foo = @foo.dup
    subclass.instance_eval do
      @foo = foo
    end
  end

  def self.get_foo
    @foo
  end
end

# In file b.rb
class B < A
  include C # if C is not already defined, the following line will not get executed although B will be defined.
  @foo.push("in B")
end

# In file c.rb
module C
end

# In file w.rb
class W < B
  @foo.push("in W")
end

The loader works by getting a list of current files, trying to require them one by one. If any file fails, it remains in the list and is tried again later. The code is something like this: (removed a lot of details for simplicity)

# In file loader.rb
files = Dir["*.rb"].reject { |f| f =~ /loader/ }
files.sort! # just for the purpose of the example, to make them load in an order that causes the problem
files.reject! { |f| require(f) rescue nil } while files.size > 0

I would ultimately want it load A, then find that B can't be loaded alone (so skip it), then load C, then find W can't yet be loaded (so skip it), then go back to B then W.

In that case, the output of p W.get_foo would be ["in A", "in B", "in W"], which is what I want.

What actually happens is that it loads A, then partially loads B, then C, then when it comes to W, it believes it can load it (since B is already defined). This triggers the self.inherited code at an incorrect time, and copies a non-ready-yet value of @foo, giving the output of p W.get_foo incorrectly to be ["in A", "in W"].

Having an all-or-nothing require would solve it.

Any ideas?

+4  A: 

If one file depends on another, that file should require the dependency itself. For example, b.rb should look like this:

require 'a'
require 'c'

class B < A
  include C # if C is not already defined, the following line will not get executed although B will be defined.
  @foo.push("in B")
end

and w.rb should look like this:

require 'b'

class W < B
  @foo.push("in W")
end

Afterwards, the outer load order no longer matters, nor does an "all-or-nothing" require approach. When b is loaded, it will first see the require for a and realize it's already been loaded, then it will require c because it realizes it hasn't yet loaded it. When c is required again, it will skip it from the outer loop.

Note: Be careful about your $LOAD_PATH and the paths passed to require. Ruby only recognizes duplicate requires when the paths are the same. It is best to use relative paths (relative to a path in the $LOAD_PATH) instead of absolute paths; otherwise, a file might get loaded twice.

Ryan McGeary
Thanks, I was aware that I could use `require` in each file, but i wanted it to be _automatic_. I came up with a complex solution (running a second ruby instance as a sandbox to test requires), but it seems this is the _best practice_, so I'll go for it :)
Sinan Taifour