Ruby Methods
Methods are the basic units of code organization, used to encapsulate reusable code logic. Ruby methods are powerful and flexible, supporting multiple parameter forms and advanced features. This chapter will introduce in detail the definition, calling, and advanced usage of methods in Ruby.
🎯 Method Basics
Method Definition
ruby
# Basic method definition
def greet
puts "Hello, World!"
end
# Method with parameters
def greet_person(name)
puts "Hello, #{name}!"
end
# Method with default parameters
def greet_with_title(name, title = "Mr./Ms.")
puts "Hello, #{title} #{name}!"
end
# Call methods
greet # Hello, World!
greet_person("Zhang San") # Hello, Zhang San!
greet_with_title("Li Si") # Hello, Mr./Ms. Li Si!
greet_with_title("Wang Wu", "Manager") # Hello, Manager Wang Wu!Method Return Values
ruby
# Methods automatically return the last expression value
def add(a, b)
a + b # Auto return
end
# Explicit return
def multiply(a, b)
return a * b
end
# Multiple return values
def get_name_and_age
["Zhang San", 25]
end
# Use parallel assignment to receive multiple return values
name, age = get_name_and_age
puts "Name: #{name}, Age: #{age}" # Name: Zhang San, Age: 25
# Conditional return
def divide(a, b)
return nil if b == 0
a / b
end
puts divide(10, 2) # 5
puts divide(10, 0) # nil📦 Parameter Types
Positional Parameters
ruby
# Required positional parameters
def create_user(name, age, email)
{name: name, age: age, email: email}
end
user = create_user("Zhang San", 25, "zhangsan@example.com")
# Optional positional parameters (default values)
def greet_person(name, greeting = "Hello", punctuation = "!")
"#{greeting}, #{name}#{punctuation}"
end
puts greet_person("Zhang San") # Hello, Zhang San!
puts greet_person("Li Si", "Ni Hao") # Ni Hao, Li Si!
puts greet_person("Wang Wu", "Bonjour", ".") # Bonjour, Wang Wu.Keyword Parameters
ruby
# Keyword parameters (Ruby 2.0+)
def create_user(name:, age:, email: nil)
{name: name, age: age, email: email}
end
# Must use keywords when calling
user = create_user(name: "Zhang San", age: 25)
user = create_user(age: 30, name: "Li Si", email: "lisi@example.com")
# Keyword parameters with default values
def greet_person(name:, greeting: "Hello", punctuation: "!")
"#{greeting}, #{name}#{punctuation}"
end
puts greet_person(name: "Zhang San") # Hello, Zhang San!Variable Arguments
ruby
# Variable arguments (splat operator)
def sum(*numbers)
numbers.reduce(0) { |total, n| total + n }
end
puts sum(1, 2, 3) # 6
puts sum(1, 2, 3, 4, 5) # 15
puts sum() # 0
# Mix positional parameters and variable arguments
def greet_and_list(name, *items)
puts "Hello, #{name}!"
puts "Your items:"
items.each { |item| puts "- #{item}" }
end
greet_and_list("Zhang San", "Apple", "Banana", "Orange")
# Hello, Zhang San!
# Your items:
# - Apple
# - Banana
# - OrangeKeyword Variable Arguments
ruby
# Keyword variable arguments (double splat operator)
def configure(**options)
options.each { |key, value| puts "#{key}: #{value}" }
end
configure(host: "localhost", port: 3000, debug: true)
# host: localhost
# port: 3000
# debug: true
# Mix various parameter types
def complex_method(required, optional = "default", *args, **kwargs)
puts "Required parameter: #{required}"
puts "Optional parameter: #{optional}"
puts "Variable arguments: #{args}"
puts "Keyword arguments: #{kwargs}"
end
complex_method("Required", "Optional", "Extra1", "Extra2", key1: "value1", key2: "value2")
# Required parameter: Required
# Optional parameter: Optional
# Variable arguments: ["Extra1", "Extra2"]
# Keyword arguments: {:key1=>"value1", :key2=>"value2"}Parameter Destructuring
ruby
# Array parameter destructuring
def process_coordinates((x, y))
puts "X coordinate: #{x}, Y coordinate: #{y}"
end
point = [10, 20]
process_coordinates(point) # X coordinate: 10, Y coordinate: 20
# Hash parameter destructuring
def process_person(name:, age:)
puts "Name: #{name}, Age: #{age}"
end
person = {name: "Zhang San", age: 25}
process_person(**person) # Name: Zhang San, Age: 25🔧 Method Advanced Features
Method Aliasing
ruby
# Create alias for method
def original_method
"Original Method"
end
alias_method :aliased_method, :original_method
puts original_method # Original Method
puts aliased_method # Original Method
# Use alias in class
class Calculator
def add(a, b)
a + b
end
alias_method :plus, :add
end
calc = Calculator.new
puts calc.add(2, 3) # 5
puts calc.plus(2, 3) # 5Method Visibility
ruby
class MyClass
def public_method
"Public Method"
end
private
def private_method
"Private Method"
end
protected
def protected_method
"Protected Method"
end
public
def call_private
private_method # Can call private methods within class
end
end
obj = MyClass.new
puts obj.public_method # Public Method
puts obj.call_private # Private Method
# obj.private_method # Error: private method
# obj.protected_method # Error: protected methodMethod Missing Handling
ruby
class FlexibleObject
def initialize
@data = {}
end
# Handle calls to undefined methods
def method_missing(method_name, *args, &block)
if method_name.to_s.end_with?('=')
# setter method
key = method_name.to_s.chomp('=').to_sym
@data[key] = args.first
else
# getter method
@data[method_name]
end
end
# Tell Ruby which methods can respond
def respond_to_missing?(method_name, include_private = false)
true
end
end
obj = FlexibleObject.new
obj.name = "Zhang San"
obj.age = 25
puts obj.name # Zhang San
puts obj.age # 25🔄 Blocks and Proc
Methods Accepting Blocks
ruby
# Methods accept block parameters
def with_logging
puts "Start execution"
yield if block_given?
puts "Execution complete"
end
with_logging { puts "Execute specific operation" }
# Start execution
# Execute specific operation
# Execution complete
# Explicitly accept block parameter
def with_timer(&block)
start_time = Time.now
result = block.call
end_time = Time.now
puts "Execution time: #{end_time - start_time} seconds"
result
end
result = with_timer { sleep(1); "Complete" }
puts resultProc and Lambda
ruby
# Create Proc
my_proc = Proc.new { |x| x * 2 }
puts my_proc.call(5) # 10
# Create Lambda
my_lambda = lambda { |x| x * 2 }
puts my_lambda.call(5) # 10
# Shorthand Lambda syntax
my_lambda2 = ->(x) { x * 2 }
puts my_lambda2.call(5) # 10
# Difference between Proc and Lambda
def test_return
proc = Proc.new { return "Proc returns" }
lambda = -> { return "Lambda returns" }
proc.call # Returns from test_return method
lambda.call # Returns only from lambda
"Method ended"
end
# puts test_return # Proc returnsPassing Blocks to Methods
ruby
# Pass block to other methods
def process_array(array, &block)
array.map(&block)
end
numbers = [1, 2, 3, 4, 5]
squared = process_array(numbers) { |n| n * n }
puts squared.inspect # [1, 4, 9, 16, 25]
# Use symbol shorthand
words = ["hello", "world", "ruby"]
upcased = words.map(&:upcase)
puts upcased.inspect # ["HELLO", "WORLD", "RUBY"]🎯 Method Practice Examples
Configuration Builder
ruby
class ConfigBuilder
def initialize
@config = {}
end
def method_missing(method_name, *args, &block)
if method_name.to_s.end_with?('=')
key = method_name.to_s.chomp('=').to_sym
@config[key] = args.first
elsif block_given?
@config[method_name] = block
elsif args.length == 1
@config[method_name] = args.first
elsif args.length > 1
@config[method_name] = args
else
@config[method_name]
end
end
def respond_to_missing?(method_name, include_private = false)
true
end
def build
@config.dup
end
def configure(&block)
instance_eval(&block) if block_given?
self
end
end
# Use configuration builder
config = ConfigBuilder.new.configure do
database_url "postgresql://localhost/myapp"
port 3000
debug true
after_initialize { puts "Initialization complete" }
end.build
puts config
# {:database_url=>"postgresql://localhost/myapp", :port=>3000, :debug=>true, :after_initialize=>#<Proc:0x000000010d0a8f80>}Chained Method Calls
ruby
class QueryBuilder
def initialize
@query = {}
end
def select(*fields)
@query[:select] = fields
self # Return self for chaining
end
def from(table)
@query[:from] = table
self
end
def where(condition)
@query[:where] = condition
self
end
def limit(count)
@query[:limit] = count
self
end
def order_by(field, direction = :asc)
@query[:order] = {field => direction}
self
end
def build
query_string = "SELECT "
query_string += @query[:select] ? @query[:select].join(", ") : "*"
query_string += " FROM #{@query[:from]}" if @query[:from]
query_string += " WHERE #{@query[:where]}" if @query[:where]
query_string += " ORDER BY #{@query[:order].keys.first} #{@query[:order].values.first}" if @query[:order]
query_string += " LIMIT #{@query[:limit]}" if @query[:limit]
query_string
end
end
# Use chained calls
query = QueryBuilder.new
.select(:name, :age)
.from(:users)
.where("age > 18")
.order_by(:name, :desc)
.limit(10)
.build
puts query
# SELECT name, age FROM users WHERE age > 18 ORDER BY name desc LIMIT 10Decorator Pattern Methods
ruby
class TextProcessor
def initialize(text)
@text = text
end
def process(&block)
if block_given?
@text = block.call(@text)
end
self
end
def upcase
process { |text| text.upcase }
end
def reverse
process { |text| text.reverse }
end
def strip
process { |text| text.strip }
end
def replace(pattern, replacement)
process { |text| text.gsub(pattern, replacement) }
end
def result
@text
end
end
# Use text processor
result = TextProcessor.new(" hello world ")
.strip
.upcase
.replace(/O/, "0")
.reverse
.result
puts result # DL0W 0LLEH🛡️ Method Security and Validation
Parameter Validation
ruby
class SafeMethods
def self.divide(dividend, divisor)
# Parameter type validation
raise ArgumentError, "Dividend must be a number" unless dividend.is_a?(Numeric)
raise ArgumentError, "Divisor must be a number" unless divisor.is_a?(Numeric)
# Business logic validation
raise ArgumentError, "Divisor cannot be zero" if divisor == 0
dividend / divisor
end
def self.create_user(name, age, email = nil)
# Validate required parameters
raise ArgumentError, "Name cannot be empty" if name.nil? || name.empty?
raise ArgumentError, "Age must be positive" unless age.is_a?(Integer) && age > 0
# Validate optional parameters
if email && !email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
raise ArgumentError, "Invalid email format"
end
{name: name, age: age, email: email}
end
end
# Use safe methods
begin
result = SafeMethods.divide(10, 2)
puts result # 5
user = SafeMethods.create_user("Zhang San", 25, "zhangsan@example.com")
puts user
rescue ArgumentError => e
puts "Error: #{e.message}"
endMethod Caching
ruby
class CachedMethods
def initialize
@cache = {}
end
def expensive_calculation(n)
# Simulate expensive calculation
cache_key = "expensive_#{n}"
if @cache.key?(cache_key)
puts "Get result from cache"
return @cache[cache_key]
end
puts "Execute calculation"
result = (1..n).reduce(1) { |acc, i| acc * i } # Calculate factorial
@cache[cache_key] = result
result
end
def clear_cache
@cache.clear
end
end
# Use cached methods
calculator = CachedMethods.new
puts calculator.expensive_calculation(5) # Execute calculation, 120
puts calculator.expensive_calculation(5) # Get result from cache, 120🎯 Method Best Practices
1. Method Design Principles
ruby
# Good practice: Single responsibility principle
class UserManager
def create_user(name, email)
validate_user_data(name, email)
user = build_user(name, email)
save_user(user)
end
private
def validate_user_data(name, email)
raise ArgumentError, "Name cannot be empty" if name.nil? || name.empty?
raise ArgumentError, "Invalid email format" unless valid_email?(email)
end
def build_user(name, email)
{name: name, email: email, created_at: Time.now}
end
def save_user(user)
# Save user logic
puts "User saved: #{user[:name]}"
end
def valid_email?(email)
email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
end
end2. Parameter Handling Best Practices
ruby
# Use keyword parameters for better readability
def create_http_request(
method:,
url:,
headers: {},
body: nil,
timeout: 30,
retries: 3
)
{
method: method.upcase,
url: url,
headers: headers,
body: body,
timeout: timeout,
retries: retries
}
end
# Clear when calling
request = create_http_request(
method: "post",
url: "https://api.example.com/users",
headers: {"Content-Type" => "application/json"},
body: '{"name": "Zhang San"}',
timeout: 60
)
# Use options hash pattern
def configure_database(options = {})
config = {
host: options[:host] || "localhost",
port: options[:port] || 5432,
database: options[:database] || "myapp",
username: options[:username] || "user",
password: options[:password] || nil
}
# Configuration logic
config
end3. Error Handling
ruby
class ApiClient
def get_user(user_id)
# Return result or error
response = make_request("/users/#{user_id}")
case response.status
when 200
parse_user_data(response.body)
when 404
raise UserNotFoundError, "User not found: #{user_id}"
when 403
raise PermissionError, "Insufficient permissions"
when 500
raise ServerError, "Server error"
else
raise ApiError, "Unknown error: #{response.status}"
end
end
private
def make_request(endpoint)
# Simulate HTTP request
OpenStruct.new(status: 200, body: '{"id": 1, "name": "Zhang San"}')
end
def parse_user_data(json_data)
JSON.parse(json_data)
rescue JSON::ParserError => e
raise DataParsingError, "Data parsing failed: #{e.message}"
end
end
# Custom exception classes
class ApiError < StandardError; end
class UserNotFoundError < ApiError; end
class PermissionError < ApiError; end
class ServerError < ApiError; end
class DataParsingError < ApiError; end4. Documenting Methods
ruby
class MathUtils
# Calculate the greatest common divisor of two numbers
#
# Uses Euclidean algorithm to calculate GCD
#
# @param a [Integer] First number
# @param b [Integer] Second number
#
# @return [Integer] Greatest common divisor
#
# @example
# MathUtils.gcd(48, 18) # => 6
# MathUtils.gcd(17, 13) # => 1
#
# @raise [ArgumentError] Raised when parameters are not positive integers
def self.gcd(a, b)
# Parameter validation
raise ArgumentError, "Parameters must be positive integers" unless a.is_a?(Integer) && b.is_a?(Integer)
raise ArgumentError, "Parameters must be greater than 0" unless a > 0 && b > 0
# Euclidean algorithm
while b != 0
a, b = b, a % b
end
a
end
# Calculate the average of an array
#
# @param numbers [Array<Numeric>] Array of numbers
#
# @return [Float] Average value
#
# @example
# MathUtils.average([1, 2, 3, 4, 5]) # => 3.0
# MathUtils.average([10, 20, 30]) # => 20.0
#
# @raise [ArgumentError] Raised when array is empty or contains non-numbers
def self.average(numbers)
# Parameter validation
raise ArgumentError, "Parameter must be an array" unless numbers.is_a?(Array)
raise ArgumentError, "Array cannot be empty" if numbers.empty?
raise ArgumentError, "Array must contain numbers" unless numbers.all? { |n| n.is_a?(Numeric) }
numbers.sum.to_f / numbers.length
end
end📚 Next Steps
After mastering Ruby methods, it is recommended to continue learning:
- Ruby Blocks and Iterators - Deep dive into block usage
- Ruby Proc and Lambda - Master functional programming features
- Ruby Metaprogramming - Learn dynamic method definition and invocation
- Ruby Design Patterns - Learn common design pattern applications
Continue your Ruby learning journey!