More verbosely, I have a module Narf
, which provides essential features to a range of classes. Specifically, I want to affect all classes that inherit Enumerable
. So I include Narf
in Enumerable
.
Array
is a class that includes Enumerable
by default. Yet, it is not affected by the late inclusion of Narf
in the module.
Interestingly, classes defined after the inclusion get Narf
from Enumerable
.
# This module provides essential features
module Narf
def narf?
puts "(from #{self.class}) ZORT!"
end
end
# I want all Enumerables to be able to Narf
module Enumerable
include Narf
end
# Fjord is an Enumerable defined *after* including Narf in Enumerable
class Fjord
include Enumerable
end
p Enumerable.ancestors # Notice that Narf *is* there
p Fjord.ancestors # Notice that Narf *is* here too
p Array.ancestors # But, grr, not here
# => [Enumerable, Narf]
# => [Fjord, Enumerable, Narf, Object, Kernel]
# => [Array, Enumerable, Object, Kernel]
Fjord.new.narf? # And this will print fine
Array.new.narf? # And this one will raise
# => (from Fjord) ZORT!
# => NoMethodError: undefined method `narf?' for []:Array
Modules are a way of grouping together methods, classes, and constants. Modules give you two major benefits. Modules provide a namespace and prevent name clashes.
What is the difference between a class and a module? Modules are collections of methods and constants. They cannot generate instances. Classes may generate instances (objects), and have per-instance state (instance variables).
A Module is a collection of methods, constants, and class variables. Modules are defined as a class, but with the module keyword not with class keyword. Important Points about Modules: You cannot inherit modules or you can't create a subclass of a module. Objects cannot be created from a module.
include is the most used and the simplest way of importing module code. When calling it in a class definition, Ruby will insert the module into the ancestors chain of the class, just after its superclass.
To embed a module in a class, you use the include statement in the class − If a module is defined in a separate file, then it is required to include that file using require statement before embedding module in a class. Consider the following module written in support.rb file.
If a third program wants to use any defined module, it can simply load the module files using the Ruby require statement − Here, it is not required to give .rb extension along with a file name. Here we are using $LOAD_PATH << '.' to make Ruby aware that included files must be searched in the current directory.
As with class methods, you call a module method by preceding its name with the module's name and a period, and you reference a constant using the module name and two colons. #!/usr/bin/ruby # Module defined in trig.rb file module Trig PI = 3.141592654 def Trig.sin(x) # ..
The user can use the module inside the class by using include keyword. In this case, the module works like a namespace. puts "Welcome to GFG Portal!" puts "Ruby Tutorial!"
There are two fixes to your problem that come to mind. None of them are really pretty:
a) Go through all classes that include Enumerable and make them also include Narf. Something like this:
ObjectSpace.each(Module) do |m|
m.send(:include, Narf) if m < Enumerable
end
This is quite hackish though.
b) Add the functionality to Enumerable directly instead of its own module. This might actually be ok and it will work. This is the approach I would recommend, though it's also not perfect.
class Array has already been mixed-in with the Enumerable module which doesn't include your Narf Module yet. Thats the reason it throws a( basically its methods )n error.
if you include Enumerable in Array again, ie.
class Array
include Enumerable
end
A mix-in makes a reference from the class to the included module, which in that particular objectspace has all methods to be included. If you modify any of the existing methods of a module, all the classes that include the module will reflect the changes.
But if you add a new modules to the already existing module, you have to re-include the module so that the reference can be updated.
In writing my question, inevitably, I came across an answer. Here's what I came up with. Let me know if I missed an obvious, much simpler solution.
The problem seems to be that a module inclusion flattens the ancestors of the included module, and includes that. Thus, method lookup is not fully dynamic, the ancestor chain of included modules is never inspected.
In practice, Array
knows Enumerable
is an ancestor, but it doesn't care about what's currently included in Enumerable
.
The good thing is that you can include
modules again, and it'll recompute the module ancestor chain, and include the entire thing. So, after defining and including Narf
, you can reopen Array
and include Enumerable
again, and it'll get Narf
too.
class Array
include Enumerable
end
p Array.ancestors
# => [Array, Enumerable, Narf, Object, Kernel]
Now let's generalize that:
# Narf here again just to make this example self-contained
module Narf
def narf?
puts "(from #{self.class}) ZORT!"
end
end
# THIS IS THE IMPORTANT BIT
# Imbue provices the magic we need
class Module
def imbue m
include m
# now that self includes m, find classes that previously
# included self and include it again, so as to cause them
# to also include m
ObjectSpace.each_object(Class) do |k|
k.send :include, self if k.include? self
end
end
end
# imbue will force Narf down on every existing Enumerable
module Enumerable
imbue Narf
end
# Behold!
p Array.ancestors
Array.new.narf?
# => [Array, Enumerable, Narf, Object, Kernel]
# => (from Array) ZORT!
Now on GitHub and Gemcutter for extra fun.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With