CrackedRuby logo

CrackedRuby

Module Functions

Overview

Module Functions in Ruby provide a mechanism to define methods that exist simultaneously as both instance methods and module-level class methods. Ruby's module_function method transforms instance methods into module functions, creating a dual-access pattern where the same method can be invoked directly on the module or mixed into classes via inclusion.

When module_function is applied to methods, Ruby creates private instance method versions and public module method versions. This pattern enables modules to function as both mixins and namespaces, supporting different usage patterns without code duplication.

module MathUtils
  def square(n)
    n * n
  end
  
  module_function :square
end

# Module-level access
MathUtils.square(5)  # => 25

# Instance access via inclusion
class Calculator
  include MathUtils
  
  def calculate
    square(4)  # Private instance method
  end
end

Calculator.new.calculate  # => 16

Module Functions appear throughout Ruby's standard library in modules like Math, FileUtils, and Kernel. The pattern separates utility functionality from object state while maintaining Ruby's object-oriented principles.

Basic Usage

The module_function method accepts method names as symbols or strings, converting existing instance methods into module functions. Call module_function after defining the methods you want to transform.

module StringUtils
  def reverse_words(text)
    text.split.reverse.join(' ')
  end
  
  def truncate(text, length)
    text.length > length ? text[0, length] + '...' : text
  end
  
  module_function :reverse_words, :truncate
end

# Direct module calls
StringUtils.reverse_words("hello world")  # => "world hello"
StringUtils.truncate("long text here", 8)  # => "long tex..."

Calling module_function without arguments transforms all subsequently defined methods into module functions automatically:

module DateUtils
  module_function
  
  def days_ago(count)
    Date.today - count
  end
  
  def format_date(date)
    date.strftime('%B %d, %Y')
  end
end

DateUtils.days_ago(7)  # Works immediately
DateUtils.format_date(Date.today)  # Also works

When including modules with module functions, the instance methods become private to prevent accidental public exposure:

class Reporter
  include StringUtils
  
  def generate_summary(content)
    # Private access to module functions
    truncated = truncate(content, 100)
    reverse_words(truncated)
  end
end

reporter = Reporter.new
reporter.generate_summary("This is a long piece of content...")
# reporter.truncate("text", 10)  # NoMethodError - private method

Module functions maintain their original method bodies and behavior. Ruby creates method aliases rather than duplicating code, ensuring consistent behavior across access patterns.

Advanced Usage

Module functions support complex method signatures including blocks, keyword arguments, and variable argument lists. The transformation preserves all method characteristics:

module CollectionUtils
  def group_by_condition(collection, **options, &block)
    default_proc = options[:default] || proc { |item| item.nil? }
    predicate = block || default_proc
    
    collection.group_by(&predicate)
  end
  
  def batch_process(*collections, size: 10)
    collections.flat_map { |coll| coll.each_slice(size).to_a }
  end
  
  module_function :group_by_condition, :batch_process
end

# Complex usage with blocks and keywords
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = CollectionUtils.group_by_condition(numbers) { |n| n.even? }
# => {false=>[1, 3, 5, 7, 9], true=>[2, 4, 6, 8, 10]}

batches = CollectionUtils.batch_process(numbers, (11..15).to_a, size: 3)
# => [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10], [11, 12, 13], [14, 15]]

Module functions can access module constants and call other module functions, creating complex internal dependencies:

module CryptoUtils
  ALGORITHMS = ['sha256', 'md5', 'sha1'].freeze
  DEFAULT_ALGORITHM = 'sha256'.freeze
  
  def available_algorithms
    ALGORITHMS.dup
  end
  
  def hash_text(text, algorithm = DEFAULT_ALGORITHM)
    validate_algorithm!(algorithm)
    Digest.const_get(algorithm.upcase).hexdigest(text)
  end
  
  def validate_algorithm!(algorithm)
    unless available_algorithms.include?(algorithm)
      raise ArgumentError, "Unsupported algorithm: #{algorithm}"
    end
  end
  
  module_function :available_algorithms, :hash_text, :validate_algorithm!
end

Modules can selectively expose certain methods as module functions while keeping others as instance-only methods:

module ConfigManager
  attr_accessor :environment
  
  def load_config(file_path)
    # Instance method only - maintains state
    @config = YAML.load_file(file_path)
    @environment = detect_environment(@config)
  end
  
  def parse_yaml(content)
    # Stateless utility - make it a module function
    YAML.safe_load(content)
  end
  
  def merge_configs(*configs)
    # Stateless utility
    configs.reduce({}) { |merged, config| merged.merge(config) }
  end
  
  private
  
  def detect_environment(config)
    config.fetch('environment', 'development')
  end
  
  module_function :parse_yaml, :merge_configs
end

Common Pitfalls

Module functions create private instance methods, which can cause confusion when accessing them from included modules. The privacy applies to direct calls but not to calls within the same class:

module Helpers
  def format_currency(amount)
    "$%.2f" % amount
  end
  
  module_function :format_currency
end

class Order
  include Helpers
  
  def total_display
    format_currency(99.99)  # Works - internal call
  end
  
  def external_format(amount)
    self.format_currency(amount)  # NoMethodError - explicit receiver
  end
end

order = Order.new
order.total_display  # => "$99.99"
# order.external_format(50.0)  # Raises NoMethodError

The timing of module_function calls matters. Methods must be defined before being converted to module functions:

module BadExample
  module_function :undefined_method  # NoMethodError
  
  def undefined_method
    "This won't work"
  end
end

module GoodExample
  def working_method
    "This works"
  end
  
  module_function :working_method
end

Module functions don't automatically delegate to included modules. Dependencies must be explicitly managed:

module BaseUtils
  def utility_method
    "base utility"
  end
  
  module_function :utility_method
end

module ExtendedUtils
  include BaseUtils
  
  def extended_method
    utility_method + " extended"  # Works as instance method
  end
  
  module_function :extended_method
end

# ExtendedUtils.utility_method  # NoMethodError - not automatically available
ExtendedUtils.extended_method    # Works, but calls private instance method

Module functions with the same name in different modules can create namespace conflicts when included:

module ModuleA
  def shared_method
    "from A"
  end
  
  module_function :shared_method
end

module ModuleB  
  def shared_method
    "from B"
  end
  
  module_function :shared_method
end

class Conflicted
  include ModuleA
  include ModuleB
  
  def test
    shared_method  # => "from B" - last included wins
  end
end

Reference

Core Methods

Method Parameters Returns Description
module_function(*method_names) *method_names (Symbol/String) self Converts specified methods to module functions
module_function None self Makes all subsequently defined methods into module functions

Method Visibility

Context Instance Method Module Method Access Level
Direct module call N/A Public Module.method
Included class internal Private N/A method (no receiver)
Included class external N/A N/A Not accessible
Explicit receiver N/A N/A NoMethodError

Standard Library Examples

Module Module Functions Usage Pattern
Math sin, cos, log, sqrt Mathematical utilities
FileUtils cp, mv, mkdir_p File system operations
Kernel puts, print, require Core Ruby functionality
JSON parse, generate JSON processing
Base64 encode64, decode64 Base64 encoding/decoding

Method Resolution

# Method lookup order for module functions
class Example
  include ModuleWithFunctions
  
  def call_sequence
    method_name  # 1. Private instance method (from module_function)
    # 2. Other private methods in class
    # 3. Other private methods in included modules
    # 4. Protected/public methods follow normal lookup
  end
end

Performance Characteristics

Operation Performance Memory Impact
Module function creation O(1) per method Minimal - creates aliases
Module-level calls Same as instance calls No additional overhead
Instance calls (included) Same as normal methods No additional overhead
Method lookup Standard Ruby lookup Standard memory usage

Common Error Types

Error Cause Solution
NoMethodError Calling undefined module function Define method before module_function
NoMethodError Explicit receiver on private method Use implicit receiver in included context
ArgumentError Wrong number of arguments Check method signature matches usage
NameError Undefined constant reference Ensure constants defined before method calls