Overview
Confirming The Effects Of include And extend In Modules
The following program can be used to see the affect of using include and extend in modules (and classes):
module Inner
INNER = "Inner constant"
def self.inner_cm; "Inner class method"; end
def inner_im; "Inner instance method"; end
end
module Outer
include Inner;
OUTER = "Outer constant"
def self.outer_cm; "Outer class method"; end
def outer_im; "Outer instance method"; end
end
module Extension
EXT = "Extension constant"
def self.ext_cm; "Extension class method"; end
def ext_im; "Extension instance method"; end
end
class MyClass; include Outer; extend Extension; end
puts "Constants: " +
(MyClass.constants(true) - Object.constants(true)).inspect
puts "Class methods: " + (MyClass.methods - Object.methods).inspect
puts "Instance methods: " +
(MyClass.instance_methods - Object.instance_methods).inspect
The output is as follows:
Constants: [:OUTER, :INNER]
Class methods: [:ext_im]
Instance methods: [:outer_im, :inner_im]
Method Resolution Order
The following program can be used to show the class/module hierarchy and order of method resolution for sub-classing (inheritance), include, and extend:
module Mod1; def m; puts "Mod 1"; super; end; end
module Mod2; def m; puts "Mod 2"; super; end; end
module Mod3; def m; puts "Mod 3"; super; end; end
module Mod4; def m; puts "Mod 4"; super; end; end
module Mod5; def m; puts "Mod 5"; super; end; end
module Mod6; def m; puts "Mod 6"; super; end; end
class Base; def m; puts "Base"; end; end
class Sub < Base
include Mod1, Mod2; include Mod3
def m; puts "Sub"; super; end
end
o = Sub.new.extend(Mod4, Mod5).extend Mod6
puts "Sub ancestors: " + o.class.ancestors.inspect
o.m
Regrettably, the include and extend methods process their parameters from last to first, so you need to know that method resolution order is not simply last-to-first encountered when called with multiple modules. The output is as follows:
Sub ancestors: [Sub, Mod3, Mod1, Mod2, Base, Object, Kernel, BasicObject]
Mod 6
Mod 4
Mod 5
Sub
Mod 3
Mod 1
Mod 2
Base
Pictorially, it looks like this (with the number in parentheses indicating the search order):
Including Class Methods
It is also possible to add class methods as part of an include or to add instance methods as part of an extend using the included or extended callbacks, respectively:
module Inc_Me
def inst_m; end
module ClassMethods; def class_m1; end; end
def self.included (base)
base.class_exec do
extend ClassMethods # method 1 - extend with named sub-module
Module.new do # method 2 - extend with anonymous module
def class_m2; end
end.tap { |mod| extend mod }
def self.class_m3; end # method 3 - add directly to the class
end
end
end
module Ext_Me
def class_m; end # instance method here, class there
module InstanceMethods; def inst_m1; end; end
def self.extended (base)
base.class_exec do
include InstanceMethods # method 1
Module.new do # method 2
def inst_m2; end
end.tap { |mod| include mod }
def inst_m3; end # method 3
end
end
end
module M1; include Inc_Me; end
puts "M1 class methods: " + (M1.methods - Object.methods).inspect
puts "M1 instance methods: " +
(M1.instance_methods - Object.instance_methods).inspect
puts "M1 included modules: " + M1.included_modules.inspect, ''
module M2; extend Ext_Me; end
puts "M2 class methods: " + (M2.methods - Object.methods).inspect
puts "M2 instance methods: " +
(M2.instance_methods - Object.instance_methods).inspect
puts "M2 included modules: " + M2.included_modules.inspect
which produces:
M1 class methods: [:class_m3, :class_m2, :class_m1]
M1 instance methods: [:inst_m]
M1 included modules: [Inc_Me]
M2 class methods: [:class_m]
M2 instance methods: [:inst_m3, :inst_m2, :inst_m1]
M2 included modules: [#<Module:0x00000000cbd108>, Ext_Me::InstanceMethods]
It is better to use the include-with-extend method (as in module Inc_Me) than the extend-with-include method (as in module Ext_Me), as the primary module name gets included in the included_modules list.
It is also better to extend a sub-class (methods 1 or 2) rather than adding the class methods directly (method 3), since the extended modules are each added to a separate, invisible super-class instead of to the including module itself. The benefit here is that the behaviors can be chained using super if desired, as shown by this code:
module Inc1
module ClassMethods; def m1; puts "Inc1 m1"; super rescue nil; end; end
def self.included (base)
base.class_exec do
extend ClassMethods
Module.new do
def m2; puts "Inc1 m2"; super rescue nil; end
end.tap { |mod| extend mod }
def self.m3; puts "Inc1 m3"; super rescue nil; end
end
end
end
module Inc2
module ClassMethods; def m1; puts "Inc2 m1"; super rescue nil; end; end
def self.included (base)
base.class_exec do
extend ClassMethods
Module.new do
def m2; puts "Inc2 m2"; super rescue nil; end
end.tap { |mod| extend mod }
def self.m3; puts "Inc2 m3"; super rescue nil; end
end
end
end
module M; include Inc2, Inc1; end
M.m1; M.m2; M.m3
which produces:
Inc2 m1
Inc1 m1
Inc2 m2
Inc1 m2
Inc2 m3
The included Callback And Nested Includes
If your module includes other modules, the included callbacks for the other modules (if present) will be called when they are included in your module, but not when your module is included elsewhere. This code shows the problem:
module M1
CONST1 = 'M1 constant'
module ClassMethods; def cm1; 'M1 class method'; end; end
def im1; 'M1 instance method'; end
def self.included (base)
puts "#{self} included in #{base}"
base.class_exec { extend ClassMethods }
end
end
module M2
include M1
def self.included (base); puts "#{self} included in #{base}"; end
end
module M3; include M2; end
puts "M2 class methods: " + (M2.methods - Object.methods).inspect
puts M3::CONST1
puts "M3 class methods: " + (M3.methods - Object.methods).inspect
puts "M3 instance methods: " +
(M3.instance_methods - Object.instance_methods).inspect
which produces:
M1 included in M2
M2 included in M3
M2 class methods: [:included, :cm1]
M1 constant
M3 class methods: []
M3 instance methods: [:im1]
The including module’s included callback should therefore call the included callback for any included modules if none of the base object’s ancestors have previously included the other modules:
def M2.included (base)
puts "#{self} included in #{base}"
M1.included base if M1.respond_to?(:included) &&
(!base.respond_to?(:superclass) || !base.superclass.include?(M1))
end
which, after the change, produces:
M1 included in M2
M2 included in M3
M1 included in M3
M2 class methods: [:included, :cm1]
M1 constant
M3 class methods: [:cm1]
M3 instance methods: [:im1]
Download It
A Ruby gem (called extended_include) based on this posting is available at rubygems.org.