Ruby Mixins
Sharing functionality across multiple classes
🔀 What are Ruby Mixins?
Mixins let you share methods across multiple classes without inheritance. They solve Ruby's single inheritance limitation by allowing classes to include multiple modules for added functionality.
# Define a mixin module
module Printable
def print_info
puts "Printing information..."
end
end
class Document
include Printable
end
doc = Document.new
doc.print_info
Output:
Printing information...
Key Mixin Concepts
Composition
Combine multiple behaviors
include Module1
include Module2
Reusability
Share code across classes
module Shared
# methods
end
Multiple Mixins
Include many modules
class A
include B, C
end
Method Lookup
Ruby searches included modules
ancestors
🔹 Creating Mixins
Mixins are modules that add functionality to classes. Create a module with methods, then include it in any class that needs those features:
module Walkable
def walk
puts "#{self.class} is walking"
end
end
module Talkable
def talk
puts "#{self.class} is talking"
end
end
class Human
include Walkable
include Talkable
end
class Robot
include Walkable
include Talkable
end
human = Human.new
human.walk
human.talk
robot = Robot.new
robot.walk
robot.talk
Output:
Human is walking
Human is talking
Robot is walking
Robot is talking
🔹 Mixins vs Inheritance
Inheritance creates "is-a" relationships, while mixins add "can-do" abilities. Use mixins when classes need shared behavior but aren't related by type:
module Chargeable
def charge
puts "Charging battery..."
end
end
class Phone
include Chargeable
def call
puts "Making a call"
end
end
class Laptop
include Chargeable
def compute
puts "Computing..."
end
end
phone = Phone.new
phone.charge
phone.call
laptop = Laptop.new
laptop.charge
laptop.compute
Output:
Charging battery...
Making a call
Charging battery...
Computing...
🔹 Mixin with Instance Variables
Mixins can access and modify instance variables of the class they're included in. This allows mixins to work with the object's state:
module Nameable
def full_name
"#{@first_name} #{@last_name}"
end
def initials
"#{@first_name[0]}.#{@last_name[0]}."
end
end
class Person
include Nameable
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
end
class Author
include Nameable
def initialize(first_name, last_name, books)
@first_name = first_name
@last_name = last_name
@books = books
end
end
person = Person.new("John", "Doe")
puts person.full_name
puts person.initials
author = Author.new("Jane", "Smith", 5)
puts author.full_name
puts author.initials
Output:
John Doe
J.D.
Jane Smith
J.S.
🔹 Method Lookup Chain
When you call a method, Ruby searches in a specific order: the class, then included modules (last included first), then parent classes. Use ancestors to see the lookup chain:
module A
def greet
puts "Hello from A"
end
end
module B
def greet
puts "Hello from B"
end
end
class MyClass
include A
include B
end
obj = MyClass.new
obj.greet # Which greet will be called?
puts "\nMethod lookup order:"
puts MyClass.ancestors
Output:
Hello from B
Method lookup order:
MyClass
B
A
Object
Kernel
BasicObject
🔹 Practical Mixin Example
Here's a real-world example showing how mixins add specific capabilities to different classes without forcing them into an inheritance hierarchy:
module Timestampable
def created_at
@created_at ||= Time.now
end
def age
Time.now - created_at
end
end
module Taggable
def add_tag(tag)
@tags ||= []
@tags << tag
end
def tags
@tags || []
end
end
class BlogPost
include Timestampable
include Taggable
attr_accessor :title
def initialize(title)
@title = title
end
end
post = BlogPost.new("Ruby Mixins")
post.add_tag("ruby")
post.add_tag("programming")
puts "Post: #{post.title}"
puts "Tags: #{post.tags.join(', ')}"
puts "Created: #{post.created_at}"
Output:
Post: Ruby Mixins
Tags: ruby, programming
Created: 2025-01-07 10:30:45 +0000