Ruby doesn’t have modules!
More specifically, Ruby doesn’t have real mixins (or interfaces or typeclasses if you like). But then what does a module mixin do? It modifies a class’s inheritance chain at runtime
class A; end class B < A; end B.ancestors # => [B, A, Object, Kernel] module M; end class B; include M end B.ancestors # => [B, M, A, Object, Kernel]
So to set things straight in your head, just remember that a module is a class can only be instantiated as a superclass of a class that includes the module. include and extend are methods that modify the class inheritance chain at runtime.
problems with the current module hack
So the methods are not included, they are inherited. This may not be the desired behavior. From the pickaxe:
a Ruby include does not simply copy the module’s instance methods into the class. Instead, it makes a reference from the class to the included module. If multiple classes include that module, they’ll all point to the same thing. If you change the definition of a method within a module, even while your program is running, all classes that include that module will exhibit the new behavior.[Of course, we’re speaking only of methods here. Instance variables are always per-object, for example.]
I would call this a bug more than a feature. The other problem with modules is that importing their methods is all or nothing. Normally we only want some of the methods from a module, but we pollute the namespace and risk clobbering other methods by importing them all.
a solution
So a real module system should isolate an include module from outside changes and allow including at a per method basis. Here is a stripped down implementation without error checking:
def import(mod, *meths) mod_dup = mod.dup unless meths.empty? mod_dup.module_eval do (mod.instance_methods.map {|m| m.to_sym} - meths).each do |meth| remove_method meth end end end end include mod_dup end
first the module is duplicated. This solves the problems of the imported module being changed in the future.
class E; import M end E.ancestors # => [E, #<Module:0xb7c6d358>, Object, Kernel]
If the original module is changed, the duplicate module will not be effected. The second thing that the code does is remove methods from the module that we have not specified.
module Foo def foo; 'foo' end def bar; 'bar' end end class Importer import Foo, :bar end Importer.new.bar # => 'bar' Importer.new.foo # => NoMethodError
remaining problems
One problem that I don’t know how to solve is that some module methods may depend on others. Like some of the other shortcomings in Ruby, this one will have to be solved by testing.
Update!
import imports all module private methods by default unless the option :import_private => false is passed.
I think this is a good compromise. Modules can be written so that public methods only depend on private methods, and users can choose which public methods to import. Private methods can be given verbose names to avoid namespace confilcts.
git it
The real deal includes error checking and lots of tests
gem install module-import
Browse the source or Git the source:
git clone git://github.com/gregwebs/module-import.git
documentation
- pretty README
- rdoc included in the gem.