Extending classes with modules
Yesterday we looked at how Ruby's Module allows us to share behaviour between classes. In that example, we used include to mix in instance methods from the module. But what if we want to share class methods instead?
Ruby provides the extend method for this purpose:
module Stockable
def stock
@stock ||= {}
end
def restock(item, quantity)
stock[item] = quantity
end
def low_stock
stock.select { |_, qty| qty < 5 }.keys
end
end
class Tea
extend Stockable
end
class Vinyl
extend Stockable
end
Tea.restock("Earl Grey", 12)
Tea.restock("Chai Latte", 3)
Tea.low_stock # => ["Chai Latte"]
Vinyl.restock("Abbey Road", 8)
Vinyl.restock("Second Toughest in the Infants", 2)
Vinyl.low_stock # => ["Second Toughest in the Infants"]
Note: each class keeps its own @stock hash. The methods came from the same module, but the data lives on whichever class is calling them. So Tea's stock and Vinyl's stock never get tangled up.
Technically, extend adds the module to the class's singleton ancestor chain. That is to say, it is functionally equivalent to Tea.singleton_class.include(Stockable).
History
extend has been part of Ruby since its inception.
The machinery that makes extend work, the singleton class (aka "metaclass" or "eigenclass"), came from Smalltalk. While Smalltalk uses metaclasses only on classes, Ruby went a step further and exposed singleton classes for any object, and because classes are objects, they get the same treatment. Such is the beautiful consistency of Ruby's object model.