On Thursday, June 16, 2011 11:50:57 AM Piotr S. wrote:
Hm, I do prefer using require_relative over require + $LOAD_PATH
changes; it looks cleaner to me and doesn’t touch shared
globals, but I guess that’s more of a personal preference.
So, while writing the rest of this (long) post, I think I actually dug
up a
solution for you, in the form of the ‘autoloader’ gem I wrote awhile
back:
https://github.com/masover/autoloader
If everything in your ‘lib’ hierarchy is normally loaded via autoload,
and
follows a decent naming convention, I think this solves your problem. In
your
example, somewhere in ‘lib/foo.rb’, you could do this:
require ‘autoloader’
AutoLoader << File.dirname(FILE)
module Foo
include AutoLoader
end
Ok, there’s some FILE ugliness, but in exchange, you automagically
get
‘autoload’ statements generated for everything, and you don’t have to
modify
your load path.
Caveat: I wrote this awhile ago, when Merb was still a thing. It seems
to
still work, but you might still want to read it to understand what it
does.
It’s not that long:
https://github.com/masover/autoloader/blob/master/lib/autoloader.rb
What I was trying to ask is whether there couldn’t be
a Kernel#autoload_relative call or a ‘relative: true’
optional parameter to Kernel#autoload to make it symmetric
with the require/require_relative combination…
There is one thing I would much rather have than this: Enough hooks
into
either the language or the autoload mechanism so that things like this
are
possible to do entirely inside Ruby.
That is: Currently, we have const_missing, but it isn’t called when
someone
tries to define a constant. For example, if I have Rails-style
const_missing
hacks, and I want to re-open a previously-defined module, I need to do
this:
Foo::Bar.module_eval do
…
end
instead of:
module Foo
module Bar
…
end
end
The latter will never hit const_missing, and would thus never load
foo.rb or
whatever. And sure, if I’m re-opening Bar, I’m probably better off
defining a
separate module and forcing Bar to include it. But what if this is
inside
foo/bar.rb? Now if I say
require ‘foo/bar’
I’ll get Foo::Bar, but I won’t ever load foo.rb – whereas, say,
Foo.module_eval do
module Bar
…
end
end
…ew.
Anyway, what I really want is something like:
autoload :Foo do
# block to ensure Foo is defined
end
In this case, autoload is the wrong word, but this would let me write
the
ultimate autoload-based replacement for the old Rails const_missing
hack. What
I have now is in the ‘autoloader’ gem:
AutoLoader << ‘/path/to/lib’
It will crawl ‘lib’ one level deep, take every ‘.rb’ file, and create an
‘autoload’ tag for it. But in order to map Foo::Bar to ‘foo/bar.rb’, I
still
need the user to have a foo.rb file which does some extra work:
module Foo
include AutoLoader
end
I can’t think of any way to make this work other than having some
callback
fire when the file actually gets loaded. Last I checked, Kernel#autoload
would
call ‘require’ from C when fired, so overloading ‘require’ doesn’t help.
And
const_missing isn’t a substitute, nor is there any way to duplicate
Kernel#autoload’s functionality from Ruby.
I actually think autoloading via require_relative is a bad idea, versus
just
adding something to the load path. Some people think autoload itself is
a bad
idea. There are probably workarounds enough for both of us. But it does
frustrate me that Kernel#autoload is so inflexible, and it really
doesn’t seem
like it needs to be.