From: Shadowfirebird Date: 2008-07-24T17:02:43+09:00 Subject: Re: circular 'require' I've had my think. I think I have some conclusions which might be useful for other people in the same boat, and since I've not seen them anywhere else, I hope you'll allow me to bore you with them. No doubt cooler heads than mine will see things differently, but there you go. And, maybe I'm missing the obvious, anyway. 1) require is mildly broken (a). It doesn't set $" until after the given file is loaded. This can lead to errors when you have a circular call of requires. If it set $" first, circular references would just be ignored. Of course, you should never actually *need* to have circular 'require' referefences, but: see my next point. 2) Require is mildly broken (b). Secondly -- and I suppose that this is less of a bug and more of a bugbear -- there is no way to distinguish between require-because-it-wont-load-otherwise and require-because-some-methods-wont-run-otherwise. This second sort isn't entirely necessary, but it's aesthetically pleasing and useful for unit testing. 3) You can get around (1) by manually pushing the program name into $" at the start of each file. This done, you can use a new idiom -- END { require } -- to specify require-because-some-methods-wont-run-otherwise. Not the prettiest thing, but neither the ugliest, I think: "in the end, this is required." Now I suppose I'd better show my working if I'm going to say anything as contentious as all that. Apologies for the length of this, but then, you can stop reading now. OTOH, I'd really like to hear other points of view. A new set of example programs: # runner.rb require 'root' Root.create_data() Root.customers.each{|c| puts c.oid, c.name} # root.rb require 'customer' class Root @@list = [] def self.create_data() Customer.new("Dolly") Customer.new("Reymond") end def self.customers() return @@list.find_all{|x| x.kind_of?(Customer)} end attr_accessor :oid def initialize() @oid = @@list.size @@list << self end end # customer.rb require 'root' class Customer < Root attr_accessor :name def initialize(name) super() @name = name end end Broadly speaking this follows the same approach as my actual code. As listed, of course, it doesn't work: run runner.rb and it loads root.rb, which starts by loading customer.rb, which starts by loading root.rb, which... I needed to make a distinction (#2 above) between code that referred to another object when it was loaded; and code that referred to another object when it was run. root.rb doesn't need a 'require 'customer'; runner.rb does. But this is rather unhelpful when it comes to unit testing: when writing the test class for Root I shouldn't have to remember to require customer. Ideally I should be able to put the require in root.rb somehow. For experimental purposes lets try putting the "require 'customer'" at the *end* of root.rb. This has practical implications -- no programmer is going to look for it there -- but it works; running runner.rb produces no errors. Oddly enough, putting "END {require 'customer'}" at the start of the file does not work. But, wait. What is more interesting, is that if you run root.rb or customer.rb you get warnings about redefined methods. You shouldn't see this if require works as described -- a file should never be called twice. What appears to be happening is that $" is set after the file is loaded -- that is, it's not being set until the chain of requires has finished. We can defeat that behaviour by adding $" << "" to the start of root.rb and customer.rb. The warnings now disappear. (And that seems like a bug to me, or at least an undocumented feature.) This is really odd: if we now try END{ require 'customer'} at the start of root.rb -- well, after the $" thing -- it *works*. Really odd, but nice. So, root.rb now looks like this: # root.rb $" << "root.rb" END { require 'customer' } class Root @@list = [] def self.create_data() Customer.new("Dolly") Customer.new("Reymond") end def self.customers() return @@list.find_all{|x| x.kind_of?(Customer)} end attr_accessor :oid def initialize() @oid = @@list.size @@list << self end end And I can live with that. But I'm open to suggestions -- any suggestions, except rude ones...