Labs ICT
โญ Pro Login

Mixins in Action

Sharing behavior across classes.

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 ->

๐Ÿงช Quick Quiz

What does include do in a class?