Overview
Endless method definition provides a streamlined syntax for writing short, single-expression methods in Ruby. Instead of using the traditional def...end
block structure, you can define methods using the =
operator followed by the method body on the same line.
This feature addresses the common pattern of methods that simply return the result of a single expression, making the code more compact and readable for simple operations.
# Traditional syntax
def square(x)
x * x
end
# Endless method syntax
def square(x) = x * x
The endless method syntax is particularly useful for:
- Mathematical operations and calculations
- Simple data transformations
- Accessor methods with slight modifications
- Methods that return single values or expressions
Ruby treats endless methods identically to traditional methods in terms of visibility, inheritance, and method dispatch. They support all standard method features including parameters, keyword arguments, and block parameters.
class Calculator
def add(a, b) = a + b
def multiply(a, b) = a * b
def percentage(value, total) = (value.to_f / total * 100).round(2)
end
calc = Calculator.new
calc.add(5, 3) # => 8
calc.percentage(25, 80) # => 31.25
Basic Usage
Endless method definition uses the def method_name(parameters) = expression
syntax. The expression after the equals sign becomes the return value of the method.
class Person
def initialize(first, last)
@first_name = first
@last_name = last
end
def full_name = "#{@first_name} #{@last_name}"
def initials = "#{@first_name[0]}#{@last_name[0]}"
def name_length = full_name.length
end
person = Person.new("John", "Doe")
person.full_name # => "John Doe"
person.initials # => "JD"
person.name_length # => 8
Endless methods support all parameter types including optional parameters, keyword arguments, and splat operators:
class StringProcessor
def truncate(text, length = 50) = text.length > length ? text[0...length] + "..." : text
def format_name(first:, last:) = "#{last}, #{first}"
def join_words(*words, separator: " ") = words.join(separator)
end
processor = StringProcessor.new
processor.truncate("This is a long sentence that needs truncation", 20)
# => "This is a long sente..."
processor.format_name(first: "Jane", last: "Smith")
# => "Smith, Jane"
processor.join_words("Ruby", "is", "awesome", separator: "-")
# => "Ruby-is-awesome"
You can use endless methods for simple conditional logic and method chaining:
class User
attr_reader :age, :premium
def initialize(age, premium = false)
@age = age
@premium = premium
end
def adult? = age >= 18
def senior? = age >= 65
def discount_rate = premium ? 0.15 : (senior? ? 0.10 : 0.05)
def display_status = "#{adult? ? 'Adult' : 'Minor'} #{premium ? '(Premium)' : ''}"
end
user = User.new(25, true)
user.adult? # => true
user.discount_rate # => 0.15
user.display_status # => "Adult (Premium)"
Endless methods work with class methods and module methods:
module MathUtils
def self.factorial(n) = n <= 1 ? 1 : n * factorial(n - 1)
def self.fibonacci(n) = n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2)
end
class Temperature
def self.celsius_to_fahrenheit(celsius) = celsius * 9.0 / 5 + 32
def self.fahrenheit_to_celsius(fahrenheit) = (fahrenheit - 32) * 5.0 / 9
end
MathUtils.factorial(5) # => 120
Temperature.celsius_to_fahrenheit(25) # => 77.0
Advanced Usage
Endless methods can handle complex expressions and method chains, making them suitable for more sophisticated operations while maintaining readability:
class DataProcessor
def process_scores(scores) = scores.map(&:to_f).select { |s| s >= 0 }.sort.reverse
def average(numbers) = numbers.empty? ? 0 : numbers.sum.to_f / numbers.size
def normalize_text(text) = text.strip.downcase.gsub(/\s+/, ' ')
end
processor = DataProcessor.new
processor.process_scores(['85', '92', '78', '-5', '88'])
# => [92.0, 88.0, 85.0, 78.0]
processor.average([85, 92, 78, 88]) # => 85.75
processor.normalize_text(" Hello World ") # => "hello world"
Endless methods integrate with Ruby's functional programming features, including blocks and higher-order functions:
class ListProcessor
def map_with_index(array, &block) = array.each_with_index.map(&block)
def filter_map(array, &block) = array.filter_map(&block)
def partition_by_type(array, type) = array.partition { |item| item.is_a?(type) }
def group_by_length(strings) = strings.group_by(&:length)
end
processor = ListProcessor.new
words = ['cat', 'elephant', 'dog', 'butterfly']
processor.map_with_index(words) { |word, i| "#{i}: #{word}" }
# => ["0: cat", "1: elephant", "2: dog", "3: butterfly"]
processor.group_by_length(words)
# => {3=>["cat", "dog"], 8=>["elephant"], 9=>["butterfly"]}
You can use endless methods for creating fluent interfaces and method chaining patterns:
class QueryBuilder
def initialize(table) = @query = "SELECT * FROM #{table}"
def where(condition) = tap { @query += " WHERE #{condition}" }
def order_by(column) = tap { @query += " ORDER BY #{column}" }
def limit(count) = tap { @query += " LIMIT #{count}" }
def to_sql = @query
end
query = QueryBuilder.new('users')
.where('age > 18')
.order_by('name')
.limit(10)
.to_sql
# => "SELECT * FROM users WHERE age > 18 ORDER BY name LIMIT 10"
Endless methods work effectively with pattern matching and case expressions:
class ResponseHandler
def status_message(code) = case code
when 200..299 then "Success"
when 300..399 then "Redirect"
when 400..499 then "Client Error"
when 500..599 then "Server Error"
else "Unknown Status"
end
def parse_response(response) = response => { status:, body: }; process_body(body, status)
def process_body(body, status) = status < 400 ? JSON.parse(body) : { error: body }
end
handler = ResponseHandler.new
handler.status_message(404) # => "Client Error"
Common Pitfalls
The most frequent mistake with endless methods is attempting to use multiple statements or complex control flow. Endless methods can only contain a single expression:
# WRONG: Multiple statements
def process_data(data) = data.clean; data.validate; data.save
# WRONG: Complex control flow
def calculate_tax(income) =
if income < 20000
0
elsif income < 50000
income * 0.1
else
income * 0.2
end
# CORRECT: Single expression with ternary operator
def calculate_tax(income) = income < 20000 ? 0 : (income < 50000 ? income * 0.1 : income * 0.2)
# BETTER: Use traditional method for complex logic
def calculate_tax(income)
return 0 if income < 20000
return income * 0.1 if income < 50000
income * 0.2
end
Another common issue is misunderstanding operator precedence with endless methods:
# Potentially confusing precedence
def calculate(a, b) = a + b * 2 # This is a + (b * 2), not (a + b) * 2
# Clear with parentheses
def calculate_sum_doubled(a, b) = (a + b) * 2
def calculate_with_multiplier(a, b) = a + (b * 2)
# Testing precedence
def test_method(x) = x > 5 ? "big" : "small" # Works as expected
def confusing_method(x) = x > 5 && x < 10 ? "medium" : x > 10 ? "big" : "small" # Hard to read
Endless methods can create subtle issues with method visibility and scope:
class Example
private
def private_helper(x) = x * 2
public
def public_method(x) = private_helper(x) + 1 # This works fine
# But this might be confusing about visibility
def another_public(x) = self.private_helper(x) + 1 # NoMethodError!
end
example = Example.new
example.public_method(5) # => 11
example.another_public(5) # => NoMethodError: private method `private_helper'
Be careful with endless methods that modify state or have side effects, as they can hide important operations:
class Counter
def initialize = @count = 0
# This hides the side effect of incrementing
def increment = @count += 1 # Returns new value, but mutation isn't obvious
# More explicit about side effects
def increment_and_return
@count += 1
end
# Better for pure operations
def next_value = @count + 1 # No side effects
end
Endless methods with complex return types can be harder to understand:
# Hard to understand what this returns
def complex_operation(data) = data.group_by(&:type).transform_values { |v| v.map(&:process).compact }
# More readable with intermediate variables in traditional method
def complex_operation(data)
grouped = data.group_by(&:type)
grouped.transform_values { |items| items.map(&:process).compact }
end
Reference
Syntax Forms
Form | Example | Description |
---|---|---|
def method_name = expression |
def double = x * 2 |
Basic endless method |
def method_name(params) = expression |
def add(a, b) = a + b |
With parameters |
def method_name(params, **opts) = expression |
def format(text, **opts) = text.upcase |
With keyword arguments |
def method_name(*args) = expression |
def sum(*nums) = nums.sum |
With splat parameters |
def method_name(&block) = expression |
def apply(&block) = block.call |
With block parameter |
Supported Parameter Types
Parameter Type | Syntax | Example |
---|---|---|
Required | def method(param) |
def square(x) = x * x |
Optional | def method(param = default) |
def greet(name = "World") = "Hello #{name}" |
Keyword | def method(key:) |
def format(text:) = text.upcase |
Optional Keyword | def method(key: default) |
def pad(text, width: 10) = text.ljust(width) |
Splat | def method(*args) |
def join(*words) = words.join(' ') |
Double Splat | def method(**opts) |
def build(**attrs) = attrs.to_json |
Block | def method(&block) |
def apply(&block) = block.call if block |
Method Types
Type | Syntax | Example |
---|---|---|
Instance Method | def method = expression |
def name = @name |
Class Method | def self.method = expression |
def self.version = "1.0" |
Module Method | def self.method = expression |
def self.utility = "helper" |
Private Method | private; def method = expression |
private; def secret = @secret |
Protected Method | protected; def method = expression |
protected; def internal = @data |
Valid Expressions
Expression Type | Example | Notes |
---|---|---|
Arithmetic | a + b , x ** 2 |
All math operators supported |
String operations | "Hello #{name}" , text.upcase |
Interpolation and method calls |
Method calls | obj.method , self.helper |
Instance and class methods |
Conditionals | x > 0 ? "positive" : "negative" |
Ternary operator only |
Logical | a && b , !flag |
Boolean operations |
Array/Hash | [a, b, c] , {key: value} |
Literal collections |
Case expressions | case x; when 1 then "one" else "other" end |
Full case syntax |
Ruby Version Compatibility
Ruby Version | Support | Notes |
---|---|---|
3.0+ | Full support | Initial implementation |
2.7 | Not supported | Use traditional def...end |
2.6 and earlier | Not supported | Use traditional def...end |
Limitations
Limitation | Description | Alternative |
---|---|---|
Single expression only | Cannot use multiple statements | Use traditional method |
No explicit return |
Cannot use return keyword |
Expression becomes return value |
No local variables | Cannot assign local variables | Use instance variables or traditional method |
No rescue clauses | Cannot handle exceptions inline | Use traditional method with rescue |
No complex control flow | No if/elsif/else blocks |
Use ternary or case expressions |