Mixins are how Ruby shares behaviour across classes; Enumerable and Comparable are the most-used examples.
A module is a container of methods and constants that can't be instantiated. It has two jobs.
As a namespace, it groups related classes: module Billing; class Invoice; end; end is referenced as Billing::Invoice. Gems put everything under one top-level module to avoid clashes.
As a mixin, it adds behaviour to classes:
include Modadds the module's methods as instance methods. The module is inserted into the class's ancestor chain just above the class.extend Modadds them as methods on a single object, typically the class itself, so they become class methods.prepend Modinserts the module below the class, so its methods run first and can callsuperto reach the class's own method. This is the clean way to wrap existing methods.
Mixins work through contracts. Comparable needs <=> and gives you <, <=, ==, >, between? and clamp. Enumerable needs each and gives you map, select, sort, sum and the rest.
Klass.ancestors shows the lookup order, and obj.is_a?(Mod) is true for included modules. A module's self.included(base) hook runs when it is included, which is how libraries add class methods at the same time (Rails wraps this pattern in ActiveSupport::Concern).
module_function makes utility modules whose methods are called as Mod.method, like Math.sqrt.
Example
class Version
include Comparable
attr_reader :parts
def initialize(s) = @parts = s.split(".").map(&:to_i)
def <=>(other) = parts <=> other.parts
def to_s = parts.join(".")
end
p Version.new("1.10.0") > Version.new("1.9.3") # true
p [Version.new("2.0"), Version.new("1.2")].max.to_s # "2.0"
class Playlist
include Enumerable
def initialize(*songs) = @songs = songs
def each(&) = @songs.each(&)
end
p Playlist.new("b", "a", "c").sort # ["a", "b", "c"]
module Loud
def to_s = super.upcase + "!"
end
class Version
prepend Loud
end
puts Version.new("3.4") # 3.4!
p Version.ancestors.first(3) # [Loud, Version, Comparable]Remember
- Modules are namespaces and mixins; they can't be instantiated
- include adds instance methods, extend adds singleton methods, prepend wraps
- Comparable needs <=>; Enumerable needs each
Watch out for
- Including a module expecting its methods as class methods; that's extend (or the included hook).