Overview
- In Ruby, an object is a collection of (zero or more) instance variables. It also has a class (see below) and possibly a lazily-created singleton class to hold object-specified methods.
- A module is an object containing a collection of (zero or more) constants, class variables, instance methods, and included modules. You can include a module in another module and you can extend most objects with a module. Since Ruby 2, you can also prepend a module to a module.
# Parts of a module CONSTANT = "I'm a constant" @@class_var = "I'm a class variable" @class_inst_var = "I'm a class instance variable" # in a class/module definition def self.method; "I'm a class method"; end class << self def another_method; "I'm a class method too"; end end def method @inst_var = "I'm an instance variable" # inside an instance method "I'm an instance method" end
- A class is sub-class of module.
- Each class has a parent class called a super-class. The child class is called a sub-class. The class inherits the behaviors of the super-class. New classes are sub-classes of the Object class unless you specify otherwise.
- Classes can typically be instantiated via the new method.
- Classes are not valid parameters for include or extend.
- A “def method” adds a method to the “currently open” class or module. A “def object.method” adds a method to the singleton class for the object.
- When you include a module (let’s call it M1) in another module (let’s call it M2), M1’s constants and instance methods become visible in M2 (as constants and instance methods), and M1 will appear in M2’s included_modules list. M1’s class methods are not added to M2 (but see Including Class Methods below).
- When you extend an object with a module, the module’s instance methods are added to the object via an automatically-generated anonymous super-class of the singleton class (one for each extending module). In the case where the extended object is a module, the added methods are class methods, not instance methods. The object is unaffected by the module’s constants or class methods.
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.