Mixins in Action
Mixins are how Ruby shares behavior between classes. Instead of inheriting from one parent, a class can include modules to gain their methods. There are three ways to mix in a module: include, extend, and prepend. Each does something slightly different.
module Greeter
def greet
"Hello from #{self.class}!"
end
end
class Person
include Greeter
end
class Robot
include Greeter
end
puts Person.new.greet # => "Hello from Person!"
puts Robot.new.greet # => "Hello from Robot!"
Try it Yourself ->
include vs extend vs prepend
include adds module methods as instance methods. extend adds them as class methods. prepend inserts the module before the class in the method lookup chain, letting you override methods cleanly.
module InstanceMethods
def hello
"Hello, I'm an instance method"
end
end
module ClassMethods
def hello
"Hello, I'm a class method"
end
end
module Prepended
def hello
"Prepended: #{super}"
end
end
class Example
include InstanceMethods
extend ClassMethods
prepend Prepended
end
puts Example.new.hello # => "Prepended: Hello, I'm an instance method"
puts Example.hello # => "Hello, I'm a class method"
Try it Yourself ->
Real-World Example: Toggleable
Here's a practical mixin that adds on/off toggle behavior to any class. Once you define it as a module, you can mix it into any class that needs that behavior.
module Toggleable
def self.included(base)
base.instance_variable_set(:@enabled, false)
end
def toggle!
@enabled = !@enabled
self
end
def on?
@enabled
end
def off?
!@enabled
end
def enable
@enabled = true
self
end
def disable
@enabled = false
self
end
end
class Light
include Toggleable
def status
on? ? "Light is ON" : "Light is OFF"
end
end
class Notification
include Toggleable
def status
on? ? "Notifications enabled" : "Notifications disabled"
end
end
light = Light.new
puts light.status # => "Light is OFF"
light.toggle!
puts light.status # => "Light is ON"
notif = Notification.new
notif.enable
puts notif.status # => "Notifications enabled"
Try it Yourself ->
Module Ancestors Chain
When you mix in modules, Ruby builds an ancestors chain. This determines which method gets called when there's a name conflict. The last module included is checked first.
module A
def talk
"Talking from A"
end
end
module B
def talk
"Talking from B"
end
end
class MyClass
include A
include B
end
puts MyClass.new.talk # => "Talking from B"
puts MyClass.ancestors # => [MyClass, B, A, Object, Kernel, BasicObject]
Try it Yourself ->
Mixins vs Inheritance
Use inheritance when there's a true "is-a" relationship โ a Dog is an Animal. Use mixins when you want to share behavior across unrelated classes โ both a File and a String can be Enumerable. Mixins are for capabilities. Inheritance is for identity.
module Searchable
def search(query)
select { |item| item.to_s.include?(query) }
end
end
class WordList
include Searchable
def initialize(words)
@words = words
end
def each(&block)
@words.each(&block)
end
include Enumerable
end
words = WordList.new(["apple", "banana", "apricot", "cherry"])
puts words.search("ap") # => ["apple", "apricot"]
Try it Yourself ->