Complete guide to Ruby functions covering definition, parameters, blocks, scope, and advanced techniques
last modified April 2, 2025
Functions (methods) are fundamental building blocks in Ruby programming. This guide covers everything from basic method definition to advanced techniques like blocks, procs, and lambdas. Learn to create flexible, reusable code with proper parameter handling and scope management. Mastering Ruby functions is essential for writing clean, maintainable code.
Ruby methods are defined with the def keyword followed by the method name. Parentheses for parameters are optional in definition and calls. Methods return the value of their last expression unless an explicit return is used. This example shows basic method syntax and calling conventions. Understanding these fundamentals is crucial for Ruby development.
basic_methods.rb
def greet “Hello, world!” end
def add_numbers num1, num2 num1 + num2 end
def max a, b return a if a > b b end
puts greet # => “Hello, world!” puts add_numbers(3, 4) # => 7 puts add_numbers 5, 6 # => 11 (parentheses optional) puts max(10, 20) # => 20
def no_return “This string is returned” end puts no_return # => “This string is returned”
Ruby methods are flexible in their definition and invocation syntax. The greet method shows a parameter-less definition, while add_numbers demonstrates parameter handling. The max method uses an explicit return statement to exit early.
Method calls can use parentheses or omit them when unambiguous. The last expression’s value is automatically returned, as shown in the no_return example. This implicit return behavior makes Ruby code concise while remaining clear.
Ruby offers several parameter types: required, optional, default-valued, and variable-length. Proper parameter handling makes methods more flexible and robust. This example demonstrates different parameter patterns in Ruby methods. Understanding these options helps create adaptable method interfaces.
parameters.rb
def full_name(first, last) “#{first} #{last}” end
def greet(name, greeting = “Hello”) “#{greeting}, #{name}!” end
def sum(*numbers) numbers.sum end
def create_person(name:, age:, email: nil) { name: name, age: age, email: email } end
puts full_name(“John”, “Doe”) # => “John Doe” puts greet(“Alice”) # => “Hello, Alice!” puts greet(“Bob”, “Hi”) # => “Hi, Bob!” puts sum(1, 2, 3) # => 6 puts sum(4, 5, 6, 7) # => 22
person = create_person(name: “Eve”, age: 30) puts person.inspect # => {:name=>“Eve”, :age=>30, :email=>nil}
def mixed(a, b = 2, *c, d:, e: 5) { a: a, b: b, c: c, d: d, e: e } end puts mixed(1, 3, 4, 5, d: 6).inspect # => {:a=>1, :b=>3, :c=>[4, 5], :d=>6, :e=>5}
Required parameters like first and last must always be provided. Default parameters (greeting = “Hello”) make arguments optional. The splat operator (*) collects variable arguments into an array, as shown in sum.
Keyword arguments (Ruby 2.0+) provide named parameters with optional defaults (email: nil). The mixed example combines all types: required, default, variable-length, and keyword parameters. This flexibility allows for highly customizable method signatures.
Ruby methods can return multiple values, which are automatically converted to an array. The return keyword is optional but useful for early exits. This example demonstrates various return value patterns. Understanding return behavior helps write more expressive methods.
returns.rb
def square(x) x * x end
def min_max(numbers) [numbers.min, numbers.max] end
def safe_divide(a, b) return “Cannot divide by zero” if b == 0 a / b end
def implicit_return “This is returned” end
def explicit_return return “This is returned” “This is not” end
puts square(5) # => 25 puts min_max([3, 1, 4, 2]).inspect # => [1, 4] puts safe_divide(10, 2) # => 5 puts safe_divide(10, 0) # => “Cannot divide by zero” puts implicit_return # => “This is returned” puts explicit_return # => “This is returned”
min, max = min_max([8, 3, 5, 2]) puts “Min: #{min}, Max: #{max}” # => “Min: 2, Max: 8”
The square method shows simple single-value return. min_max returns multiple values as an array, which can be destructured by the caller. safe_divide demonstrates early return for error handling.
Ruby always returns the last evaluated expression, making return optional. However, explicit returns improve clarity in complex methods. The example shows how multiple return values can be conveniently assigned to separate variables.
Blocks are anonymous code chunks passed to methods, enabling powerful customization. The yield keyword executes a passed block. This example demonstrates block usage and control flow. Mastering blocks is key to leveraging Ruby’s expressive power.
blocks.rb
def with_logging puts “Starting execution” yield puts “Completed execution” end
with_logging { puts “Inside the block” }
def repeat(times) times.times { yield } end
repeat(3) { puts “Hello!” }
def transform(number) yield(number) end
result = transform(5) { |x| x * 2 } puts “Transformed result: #{result}” # => 10
def optional_block if block_given? yield else puts “No block provided” end end
optional_block # => “No block provided” optional_block { puts “Block!” } # => “Block!”
def benchmark start = Time.now yield finish = Time.now puts “Execution time: #{finish - start} seconds” end
benchmark { sleep(1) } # => “Execution time: ~1.0 seconds”
The with_logging method wraps block execution with messages. repeat demonstrates parameter passing to blocks. transform shows how blocks can return values to the calling method.
block_given? checks if a block was provided, enabling optional blocks. The benchmark example measures block execution time, showing how blocks can contain multiple statements. Blocks are fundamental to Ruby’s iterators and DSL capabilities.
Procs and lambdas are objectified blocks that can be stored and passed around. They differ in argument handling and return behavior. This example compares these callable objects. Understanding them unlocks advanced Ruby patterns.
procs_lambdas.rb
square = Proc.new { |x| x * x } puts square.call(5) # => 25
cube = lambda { |x| x ** 3 } puts cube.call(3) # => 27
double = ->(x) { x * 2 } puts double.call(4) # => 8
def proc_return my_proc = Proc.new { return “Exiting proc” } my_proc.call “This won’t be reached” end
def lambda_return my_lambda = lambda { return “Exiting lambda” } my_lambda.call “This will be reached” end
puts proc_return # => “Exiting proc” puts lambda_return # => “This will be reached”
def apply_operation(x, operation) operation.call(x) end
puts apply_operation(10, square) # => 100 puts apply_operation(10, cube) # => 1000 puts apply_operation(10, double) # => 20
Procs are created with Proc.new or the proc method. Lambdas use lambda or the stabby arrow syntax (->). Both are called with call or [].
Key differences: lambdas check argument count while procs don’t, and return in a proc exits the enclosing method, not just the proc. The example shows how both can be passed as arguments to other methods.
Ruby methods can be converted to Method objects using method, allowing them to be passed around like procs. This example demonstrates method objects and their usage. This technique enables higher-order function patterns.
method_objects.rb class Calculator def add(a, b) a + b end
def multiply(a, b) a * b end end
calc = Calculator.new add_method = calc.method(:add) multiply_method = calc.method(:multiply)
puts add_method.call(3, 4) # => 7 puts multiply_method.call(3, 4) # => 12
add_proc = add_method.to_proc puts [1, 2, 3].map(&add_proc.curry(2).call(10)) # => [11, 12, 13]
unbound_add = Calculator.instance_method(:add) new_calc = Calculator.new bound_add = unbound_add.bind(new_calc) puts bound_add.call(5, 6) # => 11
Method objects preserve their receiver and can be called later with call. The example shows extracting methods from a Calculator instance and invoking them later.
Methods can be converted to procs with to_proc, enabling use with methods like map. Unbound methods (without a receiver) can be bound to new instances. This flexibility supports advanced metaprogramming patterns.
Ruby methods have different visibility levels: public, protected, and private. Instance variables have object scope, while local variables are method-scoped. This example demonstrates scope rules and visibility modifiers.
scope.rb class BankAccount def initialize(balance) @balance = balance end
def deposit(amount) @balance += amount log_transaction(“Deposit: #{amount}”) end
def withdraw(amount) if amount <= @balance @balance -= amount log_transaction(“Withdrawal: #{amount}”) else puts “Insufficient funds” end end
protected def log_transaction(message) puts “[LOG] #{message}” end
private def secret_processing puts “Secret bank processing” end end
account = BankAccount.new(100) account.deposit(50) # Works (public)
def scope_demo local_var = “I’m local” puts local_var end
scope_demo # => “I’m local”
Public methods are accessible anywhere, protected methods can only be called by the class or its subclasses, and private methods can only be called within the class context. The example shows proper encapsulation with transaction logging.
Instance variables (@balance) persist across method calls within an object. Local variables (local_var) exist only within their method. Understanding these scope rules prevents variable leakage and naming collisions.
Ruby supports functional programming patterns like higher-order functions, currying, and composition. This example demonstrates functional approaches to problem solving. These techniques lead to more declarative, reusable code.
functional.rb
def apply_math(x, y, operation) operation.call(x, y) end
add = ->(a, b) { a + b } multiply = ->(a, b) { a * b }
puts apply_math(3, 4, add) # => 7 puts apply_math(3, 4, multiply) # => 12
power = ->(x, y) { x ** y }.curry square = power.call(2) cube = power.call(3)
puts square.call(5) # => 25 puts cube.call(3) # => 27
def double(x) = x * 2 def increment(x) = x + 1
composed = method(:double).to_proc >> method(:increment).to_proc puts composed.call(5) # => 11 (double then increment)
def factorial(n) return 1 if n <= 1 n * factorial(n - 1) end
puts factorial(5) # => 120
Higher-order functions accept or return other functions, as shown in apply_math. Currying (partial application) creates specialized functions from general ones, like square from power.
Method composition chains operations together (>> for right-to- left). Recursion is demonstrated with factorial, though Ruby lacks tail call optimization by default. These patterns provide powerful abstraction tools.
Ruby’s method_missing hook and define_method enable dynamic method handling and creation. This example shows metaprogramming techniques for flexible APIs. These advanced features power many Ruby DSLs.
dynamic.rb class DynamicGreeter
def method_missing(name, *args) if name.to_s.start_with?(‘greet_’) language = name.to_s.split(’_’).last “#{language.capitalize} greeting: Hello!” else super end end
def respond_to_missing?(name, include_private = false) name.to_s.start_with?(‘greet_’) || super end
[‘morning’, ‘afternoon’, ’evening’].each do |time| define_method(“greet_#{time}”) do “Good #{time}!” end end end
greeter = DynamicGreeter.new puts greeter.greet_spanish # => “Spanish greeting: Hello!” puts greeter.greet_morning # => “Good morning!” puts greeter.greet_evening # => “Good evening!”
puts greeter.send(:greet_afternoon) # => “Good afternoon!”
method_missing catches calls to undefined methods, enabling dynamic responses like language-specific greetings. respond_to_missing? ensures proper method introspection.
define_method creates methods programmatically, as shown with time- based greetings. send dynamically invokes methods by name. These techniques enable flexible, adaptable APIs but should be used judiciously.
Use meaningful method names that indicate their purpose and side effects. Prefer small, single-responsibility methods over large complex ones. Use keyword arguments for methods with more than two parameters. Document methods with comments describing their purpose, parameters, and return values. Consider visibility levels (public/protected/private) to enforce proper encapsulation.
Learn more from these resources: Ruby Method Documentation, Proc Documentation, and Method Syntax.
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.