iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Modules & Mixins

A Ruby module is a namespace + a bag of methods. Two main uses: include to mix instance methods into a class; extend to add class methods. Modules are how Ruby does multiple inheritance.

include, extend, namespace, comparable

EXAMPLE
# 1) Module as a namespace
module Geometry
    PI = 3.14159

    def self.area_of_circle(r)
        PI * r * r
    end

    class Point
        attr_reader :x, :y
        def initialize(x, y) = (@x, @y = x, y)
    end
end

Geometry::PI                                       # 3.14159
Geometry.area_of_circle(5)
Geometry::Point.new(1, 2)

# 2) Module as a mixin (include) — adds instance methods
module Greetable
    def greet
        "Hi, I'm #{name}"
    end
end

class User
    attr_reader :name
    include Greetable
    def initialize(name) = @name = name
end

User.new('Ada').greet                              # "Hi, I'm Ada"

# 3) extend — adds CLASS methods
module Findable
    def find(id) = all.find { |x| x.id == id }
end

class User
    extend Findable
    def self.all = @all ||= []
end

User.find(1)

# 4) include vs extend (the one rule)
#   include : instance methods (User#greet)
#   extend  : class methods    (User.find)

# Self-extending pattern — add both at once
module MyMixin
    def instance_method = 'instance'
    module ClassMethods
        def class_method = 'class'
    end
    def self.included(base)
        base.extend(ClassMethods)
    end
end

class Foo
    include MyMixin
end

Foo.new.instance_method                            # 'instance'
Foo.class_method                                   # 'class'

# 5) Comparable — implement <=> and get >, <, ==, between?
class Version
    include Comparable
    attr_reader :parts

    def initialize(s)
        @parts = s.split('.').map(&:to_i)
    end

    def <=>(other) = parts <=> other.parts
end

v1 = Version.new('1.2.3')
v2 = Version.new('1.10.0')
v1 < v2                                            # true
[v1, v2, Version.new('0.9')].sort

# 6) Enumerable — implement each and get map, select, reduce, find, etc.
class Range2
    include Enumerable
    def initialize(min, max) = (@min, @max = min, max)
    def each
        (@min..@max).each { |n| yield n }
    end
end

r = Range2.new(1, 5)
r.to_a                                             # [1, 2, 3, 4, 5]
r.map { |n| n * n }                                # [1, 4, 9, 16, 25]
r.select(&:even?)                                  # [2, 4]
r.reduce(:+)                                       # 15
r.find { |n| n > 3 }                               # 4

# 7) prepend — wrap methods (decorator pattern)
module Logged
    def call(*args)
        puts "calling with #{args}"
        result = super
        puts "returned #{result}"
        result
    end
end

class Service
    prepend Logged
    def call(name) = "hello #{name}"
end

Service.new.call('Ada')
# calling with ["Ada"]
# returned hello Ada

Service.ancestors                                  # [Logged, Service, Object, ...]

# include puts the module AFTER the class in the lookup chain.
# prepend puts it BEFORE — `super` in the module calls the class's method.

# 8) Module functions — can be called as methods OR on the module itself
module MathUtils
    module_function

    def square(x) = x * x
    def cube(x)   = x * x * x
end

MathUtils.square(4)                                 # 16  (class-style)
include MathUtils
square(4)                                           # 16  (instance-style)

# 9) Constants in modules
module Config
    DEFAULT_TIMEOUT = 30
    DEFAULT_RETRIES = 3
end

Config::DEFAULT_TIMEOUT

# 10) Common standard-library modules
# Enumerable    — implement `each`, get a fluent collection API
# Comparable    — implement `<=>`, get comparison operators
# Math          — math.sin, Math.sqrt, etc.
# Kernel        — methods available everywhere (puts, raise, lambda)
# ObjectSpace   — introspection (rarely used in app code)
# Singleton     — restrict a class to one instance

# 11) Namespacing nested modules / classes
module MyApp
    module Models
        class User; end
        class Post; end
    end
    module Services
        class Authenticator; end
    end
end

MyApp::Models::User
MyApp::Services::Authenticator

# Rails-style: file paths mirror module names (autoload-friendly)
# app/models/my_app/models/user.rb
# app/services/my_app/services/authenticator.rb

# 12) refinements — scoped monkey-patching (Ruby 2.0+)
module StringPlus
    refine String do
        def shout = upcase + '!'
    end
end

class Foo
    using StringPlus
    def yell(s) = s.shout
end

Foo.new.yell('hi')                                  # 'HI!'
# 'hi'.shout                                        # NoMethodError — refinement only inside Foo

# Use refinements when you want to add behaviour without polluting global namespace.

# 13) Singleton pattern
require 'singleton'

class Logger
    include Singleton
    def log(msg) = puts "[log] #{msg}"
end

Logger.instance.log('hi')
# Logger.new                                        # NoMethodError — Singleton blocks .new

# 14) Common patterns

# a) Stateless utility module
module TextUtils
    module_function
    def slugify(s) = s.downcase.gsub(/[^a-z0-9]+/, '-').sub(/^-|-$/, '')
    def truncate(s, max) = s.length <= max ? s : s[0...max] + '...'
end

# b) Behavioural mixin
module Auditable
    def audit(action, payload = {})
        Audit.log(class: self.class.name, action: action, payload: payload)
    end
end

class Order
    include Auditable
end

# c) Concerns (Rails)
module Sluggable
    extend ActiveSupport::Concern

    included do
        before_save :generate_slug
    end

    def generate_slug = self.slug = title.parameterize

    class_methods do
        def find_by_slug(s) = find_by(slug: s)
    end
end

class Post < ApplicationRecord
    include Sluggable
end

# 15) Best practices
#   • Use modules to share behaviour across unrelated classes
#   • Prefer composition (delegation, modules) over inheritance
#   • Keep modules focused — one responsibility each (Auditable, Cacheable, Loggable)
#   • Use Comparable + Enumerable for value objects
#   • Don't nest deeply — 2 levels is usually enough
#   • Module functions for stateless helpers (slugify, parse_x)

# 16) Common bugs
#   • Forgetting that include adds instance methods only (extend for class methods)
#   • Using prepend without `super` → infinite recursion
#   • Refinements not being `using`-imported in the right scope
#   • Confusing constant lookup — Module nesting matters
#   • Mutable shared state in module-level @@variables (avoid)

Why it matters

Modules are Ruby’s answer to multiple inheritance: small, focused mixins (Comparable, Enumerable, Auditable) composed into classes via include/extend/prepend. Reach for them before adding another inheritance level.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
module Greetable
    def hello = "hi, #{name}"
end
class User
    include Greetable
    attr_reader :name
    def initialize(name) = @name = name
end
Try it Yourself »

Exercise

Mix a module into a class.

class User Greetable end

Discussion

Loading…