Overview
Ruby method definitions use the def
keyword followed by the method name, optional parameters, method body, and closing end
keyword. Methods in Ruby are defined on objects and classes, with flexible parameter syntax including positional arguments, keyword arguments, blocks, and splat operators.
The def
keyword creates methods that become part of the object or class where they are defined. Ruby methods return the value of the last evaluated expression unless an explicit return
statement is used. Method names follow snake_case convention and can include question marks for predicate methods or exclamation marks for destructive methods.
def calculate_total(price, tax_rate = 0.08)
price * (1 + tax_rate)
end
def valid_email?(email)
email.include?('@') && email.include?('.')
end
def process_data!(data)
data.map!(&:upcase)
end
Ruby supports method definition at multiple levels including instance methods, class methods, singleton methods, and module methods. The visibility of methods can be controlled using private
, protected
, and public
keywords. Methods can accept blocks using yield
or explicit block parameters with the &
operator.
Method definitions can include complex parameter patterns combining required parameters, optional parameters with defaults, keyword arguments, splat operators for variable arguments, and double splat operators for keyword argument collection.
class DataProcessor
def process(data, format: :json, **options, &block)
formatted_data = convert_format(data, format)
filtered_data = apply_options(formatted_data, options)
block_given? ? yield(filtered_data) : filtered_data
end
private
def convert_format(data, format)
# conversion logic
end
end
Basic Usage
Method definition begins with the def
keyword, followed by the method name and optional parameter list. The method body contains the executable code, and the definition ends with the end
keyword. Ruby methods automatically return the value of the last evaluated expression.
def greet(name)
"Hello, #{name}!"
end
result = greet("Alice")
# => "Hello, Alice!"
Parameters can have default values, making them optional when the method is called. Default values are evaluated at method definition time for simple values and at call time for complex expressions.
def create_user(name, role = 'user', active = true)
{
name: name,
role: role,
active: active,
created_at: Time.now
}
end
user1 = create_user("Bob")
# => {:name=>"Bob", :role=>"user", :active=>true, :created_at=>...}
user2 = create_user("Carol", "admin", false)
# => {:name=>"Carol", :role=>"admin", :active=>false, :created_at=>...}
Keyword arguments provide explicit parameter naming and can be required or optional. Required keyword arguments raise an error if not provided during method invocation.
def configure_server(host:, port: 3000, ssl: false)
{
host: host,
port: port,
ssl: ssl
}
end
config = configure_server(host: "localhost", ssl: true)
# => {:host=>"localhost", :port=>3000, :ssl=>true}
Variable arguments can be captured using the splat operator (*), which collects remaining positional arguments into an array. The double splat operator (**) captures remaining keyword arguments into a hash.
def log_message(level, *messages, timestamp: Time.now, **metadata)
{
level: level,
messages: messages,
timestamp: timestamp,
metadata: metadata
}
end
result = log_message("INFO", "User login", "Session started",
user_id: 123, ip: "192.168.1.1")
# => {:level=>"INFO", :messages=>["User login", "Session started"],
# :timestamp=>..., :metadata=>{:user_id=>123, :ip=>"192.168.1.1"}}
Methods can accept blocks using yield
to execute the provided block or by using an explicit block parameter with the &
operator. The block_given?
method checks if a block was provided.
def process_items(items, &processor)
return items unless block_given?
items.map do |item|
yield(item)
end
end
numbers = [1, 2, 3, 4, 5]
squared = process_items(numbers) { |n| n ** 2 }
# => [1, 4, 9, 16, 25]
Advanced Usage
Method definition supports complex parameter combinations that mix positional arguments, keyword arguments, splat operators, and blocks. The parameter order must follow Ruby's specific sequence: required positional, optional positional, splat, required keywords, optional keywords, double splat, and block.
def complex_method(required_pos, optional_pos = "default", *splat_args,
required_kw:, optional_kw: "kw_default", **double_splat, &block)
{
required_pos: required_pos,
optional_pos: optional_pos,
splat_args: splat_args,
required_kw: required_kw,
optional_kw: optional_kw,
double_splat: double_splat,
has_block: block_given?
}
end
result = complex_method("req", "opt", "extra1", "extra2",
required_kw: "req_kw", additional: "value") { "block" }
Dynamic method definition uses define_method
to create methods programmatically. This approach enables metaprogramming patterns where method names and behavior are determined at runtime.
class APIClient
ENDPOINTS = %w[users posts comments tags].freeze
ENDPOINTS.each do |endpoint|
define_method "fetch_#{endpoint}" do |id = nil|
url = id ? "/#{endpoint}/#{id}" : "/#{endpoint}"
make_request(url)
end
define_method "create_#{endpoint.singularize}" do |data|
make_request("/#{endpoint}", method: :post, data: data)
end
end
private
def make_request(url, method: :get, data: nil)
# HTTP request implementation
"#{method.upcase} #{url} #{data}"
end
end
client = APIClient.new
client.fetch_users
# => "GET /users "
client.create_user(name: "Alice")
# => "POST /users {:name=>\"Alice\"}"
Singleton methods are defined on individual objects rather than classes, creating object-specific behavior. These methods exist only for the specific object instance where they are defined.
user = User.new("Bob")
def user.admin?
true
end
def user.permissions
%w[read write delete admin]
end
user.admin?
# => true
user.permissions
# => ["read", "write", "delete", "admin"]
other_user = User.new("Carol")
other_user.admin?
# => NoMethodError: undefined method `admin?'
Method aliasing creates alternative names for existing methods using alias
or alias_method
. This technique supports backward compatibility and provides alternative interfaces.
class Calculator
def add(a, b)
a + b
end
alias_method :plus, :add
alias_method :sum, :add
def calculate(operation, a, b)
case operation
when :add, :plus, :sum
add(a, b)
end
end
end
calc = Calculator.new
calc.add(5, 3) # => 8
calc.plus(5, 3) # => 8
calc.sum(5, 3) # => 8
Method visibility controls access to methods using private
, protected
, and public
. Private methods can only be called without an explicit receiver, protected methods can be called by instances of the same class, and public methods are accessible everywhere.
class BankAccount
def initialize(balance)
@balance = balance
end
def deposit(amount)
@balance += validate_amount(amount)
end
def transfer(amount, target_account)
withdraw(amount)
target_account.receive_transfer(amount)
end
protected
def receive_transfer(amount)
@balance += amount
end
private
def validate_amount(amount)
raise ArgumentError, "Amount must be positive" if amount <= 0
amount
end
def withdraw(amount)
validate_amount(amount)
raise "Insufficient funds" if @balance < amount
@balance -= amount
end
end
Common Pitfalls
Method parameter evaluation timing creates unexpected behavior when default values involve mutable objects or method calls. Default values are evaluated once at method definition time for simple values but at call time for complex expressions.
# Dangerous: mutable default
def add_item(item, list = [])
list << item
list
end
list1 = add_item("first") # => ["first"]
list2 = add_item("second") # => ["first", "second"] - unexpected!
# Correct approach
def add_item(item, list = nil)
list = [] if list.nil?
list << item
list
end
Parameter order restrictions cause syntax errors when method definitions violate Ruby's required parameter sequence. Parameters must be ordered as: required positional, optional positional, splat, required keyword, optional keyword, double splat, block.
# Incorrect order - syntax error
def broken_method(optional = "default", required, *args)
# SyntaxError: unexpected parameter
end
# Correct order
def working_method(required, optional = "default", *args)
[required, optional, args]
end
Keyword argument conflicts occur when positional arguments and keyword arguments share names or when required keyword arguments are missing. Ruby 3.0 introduced stricter separation between positional and keyword arguments.
def process_data(data, format: :json, encoding: 'utf-8')
# method implementation
end
# Ruby 3.0+ requires explicit keyword syntax
options = { format: :csv, encoding: 'ascii' }
process_data("data", **options) # Correct
# process_data("data", options) # Warning or error in Ruby 3.0+
Block parameter shadowing happens when block parameters have the same name as variables in the outer scope, potentially causing unexpected behavior and masking bugs.
name = "Alice"
users = ["Bob", "Carol", "Dave"]
# Dangerous: parameter shadows outer variable
result = users.map { |name| "Hello, #{name}" }
# name is now "Dave" - outer variable was modified!
# Safe: use different parameter name
result = users.map { |user_name| "Hello, #{user_name}" }
# name remains "Alice"
Method redefinition warnings appear when methods are defined multiple times in the same context. Ruby allows method redefinition but issues warnings that can indicate unintended behavior.
class Calculator
def add(a, b)
a + b
end
# Warning: method redefined; discarding old add
def add(a, b)
puts "Adding #{a} and #{b}"
a + b
end
end
Private method inheritance creates confusion about method visibility in subclasses. Private methods are inherited but remain private, while protected methods maintain their visibility level.
class Parent
private
def secret_method
"parent secret"
end
end
class Child < Parent
def call_secret
secret_method # Works - inherited private methods are accessible
end
def call_secret_explicitly
self.secret_method # NoMethodError - private methods cannot be called with explicit receiver
end
end
Return value assumptions cause errors when developers expect explicit return statements. Ruby methods return the last evaluated expression, which may not always be the intended return value.
def process_user(user)
if user.valid?
user.save
send_welcome_email(user)
puts "User processed successfully" # This becomes the return value!
else
false
end
end
# Correct approach with explicit return
def process_user(user)
if user.valid?
user.save
send_welcome_email(user)
puts "User processed successfully"
true # Explicit return value
else
false
end
end
Reference
Method Definition Syntax
Pattern | Syntax | Example |
---|---|---|
Basic method | def name; end |
def greet; "Hello"; end |
With parameters | def name(param); end |
def greet(name); "Hello #{name}"; end |
Default parameters | def name(param = value); end |
def greet(name = "World"); "Hello #{name}"; end |
Keyword arguments | def name(key: value); end |
def greet(name: "World"); "Hello #{name}"; end |
Required keywords | def name(key:); end |
def greet(name:); "Hello #{name}"; end |
Variable arguments | def name(*args); end |
def sum(*numbers); numbers.sum; end |
Variable keywords | def name(**kwargs); end |
def config(**opts); opts; end |
Block parameter | def name(&block); end |
def each(&block); yield if block; end |
Parameter Order Requirements
Position | Parameter Type | Required | Example |
---|---|---|---|
1 | Required positional | Yes | required |
2 | Optional positional | No | optional = "default" |
3 | Splat arguments | No | *args |
4 | Required keywords | No | req_kw: |
5 | Optional keywords | No | opt_kw: "default" |
6 | Double splat | No | **kwargs |
7 | Block | No | &block |
Method Visibility Levels
Visibility | Keyword | Access Rules | Example |
---|---|---|---|
Public | public |
Accessible everywhere | public :method_name |
Protected | protected |
Same class/subclass instances | protected :method_name |
Private | private |
No explicit receiver | private :method_name |
Dynamic Method Definition
Method | Purpose | Returns | Example |
---|---|---|---|
define_method(name, &block) |
Creates method from block | Symbol | define_method(:add) { |a,b| a + b } |
define_method(name, method) |
Creates method from Method object | Symbol | define_method(:add, method(:+)) |
alias_method(new, old) |
Creates method alias | Symbol | alias_method :plus, :add |
remove_method(name) |
Removes method from class | Symbol | remove_method :deprecated_method |
undef_method(name) |
Prevents method inheritance | Symbol | undef_method :inherited_method |
Method Inquiry Methods
Method | Returns | Description |
---|---|---|
method_defined?(name) |
Boolean | Check if instance method exists |
private_method_defined?(name) |
Boolean | Check if private method exists |
protected_method_defined?(name) |
Boolean | Check if protected method exists |
public_method_defined?(name) |
Boolean | Check if public method exists |
instance_methods |
Array | Get all public/protected instance method names |
private_instance_methods |
Array | Get all private instance method names |
Block Handling Methods
Method | Returns | Description |
---|---|---|
block_given? |
Boolean | Check if block was provided to method |
yield |
Object | Execute provided block |
yield(args) |
Object | Execute block with arguments |
&block.call |
Object | Execute block via explicit parameter |
proc.call |
Object | Execute proc object |
Method Object Operations
Method | Returns | Description |
---|---|---|
method(:name) |
Method | Get Method object for instance method |
singleton_method(:name) |
Method | Get Method object for singleton method |
method.call(*args) |
Object | Invoke Method object |
method.arity |
Integer | Number of parameters method accepts |
method.parameters |
Array | Parameter information as [type, name] pairs |
method.source_location |
Array | File and line number where method defined |