Here's what I eventually came up with. In the same file that initially declares Baz
:
module Foo
def self.included(base)
constants.each { |c| base.const_set(c, const_get("#{self}::#{c}")) }
end
end
module Baz
include Foo
#... etc.
end
When Baz
includes Foo
, it will set a corresponding constant Baz::Whatever
for every Foo::Whatever
, with Foo::Whatever
as the value.
If you're worried Foo
may already define self.included
, you can use alias_method
to adjust for that:
module Foo
alias_method :old_included, :included if self.method_defined? :included
def self.included(base)
old_included(base) if method_defined? :old_included
constants.each { |c| base.const_set(c, const_get("#{self}::#{c}")) }
end
end
This approach has two limitations --
- All the constants (including classes) in
Foo
that we care about must be defined at the time include Foo
is evaluated -- extensions added to Foo
later will not be captured.
- The file that defines
Foo.included
here must be required before any file in Baz
that uses any of those constants -- simple enough if clients are just using require 'baz'
to pull in a baz.rb
that in turn uses Dir.glob
or similar to load all the other Baz
files, but it's important not to require those files directly.
Roko's answer gets around problem (1) above using const_missing
, but it still has an analogous problem to (2), in that one has to ensure add_const_missing_to_classes
is called after all classes in Baz
are defined. It's a shame there's no const_added
hook.
I suspect the const_missing
approach also suffers performance-wise by depending on const_missing
and const_get
for every constant reference. This might be mitigated by a hybrid that caches the results, i.e. by calling const_set
in const_missing
, but I haven't explored that since trying to figure out scoping inside define_singleton_method
always gives me a headache.