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 |