Ruby Modules & Mixins Mastery
Master Ruby modules for namespacing, mixins with include, extend, and prepend for code reuse and clean architecture. Learn how to avoid repetition and organize your code elegantly.
Understanding Modules
Module: A collection of methods, constants, and classes that provides two main benefits:
1. Namespacing: Group related functionality together
2. Mixins: Share code between multiple classes without inheritance
Key Differences: Modules vs Classes
Cannot be instantiated: No Module.new
Cannot inherit: No superclass/subclass relationship
Can be mixed in: include, extend, prepend
Perfect for shared behavior: DRY principle
Creating Modules
Modules are defined similar to classes but serve different purposes.
# Basic module definition
module Greetings
def say_hello
puts "Hello from the Greetings module!"
end
def say_goodbye
puts "Goodbye from the Greetings module!"
end
end
# Module with constants
module MathConstants
PI = 3.14159
E = 2.71828
GOLDEN_RATIO = 1.618
def self .circle_area (radius)
PI * radius ** 2
end
end
Include: Adding Instance Methods
include adds the module's methods as instance methods to the class.
# Module with useful methods
module Printable
def print_info
puts "Class: #{self.class}"
puts "Object ID: #{self.object_id}"
puts "Methods: #{self.methods.grep(/print/).join(', ')}"
end
def print_border (text)
border = "=" * text.length
puts border
puts text
puts border
end
end
# Including the module in classes
class Document
include Printable # Module methods become instance methods
def initialize (title, content)
@title = title
@content = content
end
def display
print_border(@title) # Method from Printable module
puts @content
end
end
class Report
include Printable # Same module, different class
def initialize (data)
@data = data
end
end
# Using included methods
doc = Document .new("My Document" , "This is the content" )
report = Report .new([1 , 2 , 3 ])
doc.display # Uses print_border from module
doc.print_info # Direct access to module method
report.print_info # Same module method, different object
report.print_border("REPORT DATA" )
Extend: Adding Class Methods
extend adds the module's methods as class methods (singleton methods) to the class.
# Module with utility methods
module StringUtils
def titleize (text)
text.split.map(&:capitalize).join(" " )
end
def slugify (text)
text.downcase.gsub(/[^a-z0-9]/ , "-" ).gsub(/-+/ , "-" ).gsub(/^-|-$/ , "" )
end
def word_count (text)
text.split.length
end
end
# Extending a class with module methods
class Article
extend StringUtils # Module methods become class methods
def initialize (title, content)
@title = title
@content = content
end
def formatted_title
self .class .titleize(@title) # Call class method
end
def url_slug
self .class .slugify(@title)
end
end
# Using extended methods
# Call methods directly on the class
puts Article .titleize("hello world" ) # "Hello World"
puts Article .slugify("My Great Article!" ) # "my-great-article"
puts Article .word_count("This is a test" ) # 4
# Instance can use class methods
article = Article .new("my great article" , "Content here" )
puts article.formatted_title # "My Great Article"
puts article.url_slug # "my-great-article"
Prepend: Method Chain Control
prepend inserts the module earlier in the method lookup chain, allowing modules to override class methods.
# Module with enhanced behavior
module Loggable
def save
puts "[LOG] Starting save operation..."
result = super # Call the original method
puts "[LOG] Save operation completed"
result
end
end
# Class with original method
class User
prepend Loggable # Module method runs BEFORE class method
def initialize (name)
@name = name
end
def save
puts "Saving user: #{@name}"
true # Simulate successful save
end
end
# Compare with include
class Product
include Loggable # Class method runs BEFORE module method
def save
puts "Saving product"
super # This would call module method, but it won't work
end
end
# Testing prepend vs include
user = User .new("Alice" )
user.save
# Output:
# [LOG] Starting save operation...
# Saving user: Alice
# [LOG] Save operation completed
# Check method lookup order
puts User .ancestors
# [User, Loggable, Object, Kernel, BasicObject] - Loggable comes first!
Method Lookup Order
prepend: Module → Class → Parent Classes
include: Class → Module → Parent Classes
extend: Adds methods as class methods only
Namespacing with Modules
Modules provide namespacing to organize code and avoid naming conflicts.
# Namespace modules
module Banking
class Account
def initialize (balance)
@balance = balance
end
def withdraw (amount)
@balance -= amount if amount <= @balance
end
end
class Transaction
def initialize (amount, type)
@amount = amount
@type = type
end
end
end
module Gaming
class Account # Different Account class!
def initialize (username)
@username = username
@score = 0
end
def level_up
@score += 100
end
end
end
# Using namespaced classes
bank_account = Banking ::Account .new(1000 )
game_account = Gaming ::Account .new("player1" )
bank_account.withdraw(100 )
game_account.level_up
# Access constants in namespaces
module API
VERSION = "2.1.0"
BASE_URL = "https://api.example.com"
end
puts API ::VERSION # "2.1.0"
puts API ::BASE_URL
🚀 Interactive Practice
Interactive Code Runner
Ruby Code Editor
Run Code
Output will appear here when you run the code...
🎯 Key Takeaways
include: Adds module methods as instance methods
extend: Adds module methods as class methods
prepend: Like include but puts module first in method lookup chain
Namespacing: Use :: to access nested classes and constants
DRY principle: Modules help avoid code duplication
Method lookup: Understanding ancestors chain is crucial
Multiple inclusion: Classes can include multiple modules