Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: class C includes module M; including module N in M does not affect C. What gives?

Tags:

module

ruby

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.

Example:

# 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
like image 398
kch Avatar asked Oct 31 '09 20:10

kch


People also ask

What is the use of module in Ruby?

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 difference between module and class in Ruby?

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).

What is module function in Ruby on Rails?

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.

Can we include class in Ruby?

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.

How to embed a module in a class in Ruby?

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.

How to load the module files using Ruby require statement?

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.

How do you call a module method in Ruby?

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) # ..

How to use a module inside of a class?

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!"


3 Answers

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.

like image 172
sepp2k Avatar answered Sep 27 '22 17:09

sepp2k


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.

like image 32
Rishav Rastogi Avatar answered Sep 27 '22 16:09

Rishav Rastogi


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.

like image 22
kch Avatar answered Sep 27 '22 16:09

kch