Overview
Ruby style guides establish consistent formatting, naming, and structural conventions for Ruby codebases. The Ruby community maintains widely-adopted style standards that improve code readability, reduce cognitive overhead, and facilitate collaboration across development teams. These conventions cover syntax preferences, naming patterns, code organization, and structural decisions.
Ruby's flexible syntax allows multiple ways to express the same logic, making style consistency crucial for maintainability. Style guides address spacing, indentation, method definitions, conditional statements, and class organization. Tools like RuboCop enforce these conventions automatically, detecting style violations and suggesting corrections.
# Community-preferred style
def calculate_total(items)
items.sum(&:price)
end
# Avoid this style
def calculate_total( items )
items.sum{ |item| item.price }
end
The style guide distinguishes between syntax rules enforced by the Ruby parser and community conventions that enhance readability. While Ruby accepts various formatting approaches, consistent style reduces the mental overhead required to understand code structure and intent.
# Preferred: consistent spacing and alignment
user = User.new(
name: 'John Doe',
email: 'john@example.com',
role: 'admin'
)
# Avoid: inconsistent spacing
user=User.new(name:'John Doe',email: 'john@example.com', role:'admin')
Basic Usage
Indentation uses two spaces consistently throughout Ruby files. Tab characters create inconsistent visual alignment across different editors and should be avoided. Method definitions, class bodies, conditional statements, and blocks require proper indentation to clearly indicate code structure.
class UserService
def initialize(repository)
@repository = repository
end
def create_user(attributes)
if valid_attributes?(attributes)
@repository.save(User.new(attributes))
else
raise ArgumentError, 'Invalid user attributes'
end
end
private
def valid_attributes?(attributes)
attributes.key?(:name) && attributes.key?(:email)
end
end
Method names use snake_case for multi-word identifiers. Class names use PascalCase, while constants use SCREAMING_SNAKE_CASE. Variable names follow snake_case conventions, creating consistent naming patterns across codebases.
class PaymentProcessor
PROCESSING_FEE = 2.50
def process_credit_card(card_number, amount)
transaction_id = generate_transaction_id
charge_amount = amount + PROCESSING_FEE
# Processing logic
end
end
String literals prefer single quotes unless interpolation or escape sequences are required. This convention reduces visual noise and clearly indicates when string content includes dynamic elements. Double quotes signal that string interpolation or special characters are present.
# Preferred for static strings
error_message = 'User not found'
file_path = '/tmp/data.csv'
# Use double quotes for interpolation
greeting = "Hello, #{user.name}!"
formatted_output = "Total: $#{amount}\nTax: $#{tax}"
Hash syntax follows the modern colon notation when keys are symbols, falling back to hash rocket syntax for other key types. This creates visual consistency while maintaining compatibility with various key types.
# Modern symbol key syntax
config = {
host: 'localhost',
port: 3000,
timeout: 30
}
# Hash rocket for mixed key types
options = {
'Content-Type' => 'application/json',
:retries => 3,
42 => 'answer'
}
Advanced Usage
Complex conditional statements benefit from guard clauses that handle edge cases early, reducing nesting levels and improving readability. This pattern works particularly well for validation logic and early returns.
def process_order(order)
return handle_invalid_order if order.nil?
return handle_empty_order if order.items.empty?
return handle_insufficient_funds unless sufficient_funds?(order)
charge_payment(order)
fulfill_order(order)
send_confirmation(order)
end
private
def handle_invalid_order
{ status: 'error', message: 'Order cannot be nil' }
end
Module composition requires careful attention to method visibility and namespace organization. Private methods remain private within their defining module, while protected methods allow access from other instances of the same class hierarchy.
module Cacheable
extend ActiveSupport::Concern
included do
class_attribute :cache_options, default: {}
end
class_methods do
def cache_for(duration, key_prefix: nil)
self.cache_options = { duration: duration, key_prefix: key_prefix }
end
end
private
def cache_key(suffix = nil)
parts = [self.class.cache_options[:key_prefix], id, suffix].compact
parts.join(':')
end
end
Metaprogramming requires explicit documentation and clear naming conventions to maintain code comprehensibility. Dynamic method definitions should include comments explaining the generated methods and their expected behavior.
class DynamicAttributeAccessor
# Generates getter and setter methods for specified attributes
# Creates methods like: first_name, first_name=, last_name, last_name=
def self.attr_accessor_with_history(*attributes)
attributes.each do |attr|
define_method(attr) do
instance_variable_get("@#{attr}")
end
define_method("#{attr}=") do |value|
track_change(attr, instance_variable_get("@#{attr}"), value)
instance_variable_set("@#{attr}", value)
end
end
end
private
def track_change(attribute, old_value, new_value)
@attribute_history ||= {}
@attribute_history[attribute] ||= []
@attribute_history[attribute] << { from: old_value, to: new_value, at: Time.current }
end
end
Block syntax follows specific conventions based on block complexity and line count. Single-line blocks use curly braces with spaces, while multi-line blocks use do/end keywords. This visual distinction helps readers quickly identify block scope and complexity.
# Single-line blocks with curly braces
users.select { |user| user.active? }
names.map { |name| name.capitalize }
# Multi-line blocks with do/end
users.each do |user|
send_welcome_email(user)
create_initial_preferences(user)
log_user_creation(user)
end
# Complex single-line blocks may use do/end for clarity
complex_data.group_by do |item|
"#{item.category}:#{item.subcategory}:#{item.type}"
end
Common Pitfalls
Excessive method chaining creates debugging challenges and violates the Law of Demeter. Each method call introduces potential failure points, making error diagnosis difficult when chains become too long or complex.
# Problematic: excessive chaining
def user_location
current_user.profile.address.coordinates.latitude.to_f
end
# Breaks if any intermediate object is nil
# Preferred: defensive coding with intermediate variables
def user_location
profile = current_user&.profile
return nil unless profile
address = profile.address
return nil unless address&.coordinates
address.coordinates.latitude&.to_f
end
Monkey patching core classes appears convenient but creates hidden dependencies and potential conflicts with other code. These modifications affect global behavior and can break when Ruby versions change or other libraries modify the same methods.
# Avoid: monkey patching String
class String
def palindrome?
self == self.reverse
end
end
# Preferred: utility modules or methods
module StringUtils
def self.palindrome?(string)
string == string.reverse
end
end
# Or as a refinement for controlled scope
module StringPalindromeRefinement
refine String do
def palindrome?
self == self.reverse
end
end
end
Instance variable access patterns can create subtle bugs when variables are accessed before initialization. Ruby returns nil for uninitialized instance variables, which may hide programming errors or create unexpected behavior.
class User
# Problematic: direct instance variable access
def full_name
"#{@first_name} #{@last_name}"
end
# Returns " " if variables are uninitialized
# Preferred: accessor methods with validation
def full_name
"#{first_name} #{last_name}".strip
end
private
def first_name
@first_name || raise('First name not initialized')
end
def last_name
@last_name || raise('Last name not initialized')
end
end
Conditional assignment operators can mask initialization bugs and create debugging challenges. The ||=
operator only assigns when the variable is falsy, which includes nil and false, potentially causing unexpected behavior.
# Problematic: ||= with boolean false
def premium_user?
@premium ||= calculate_premium_status
# Bug: always recalculates if premium status is false
end
# Preferred: explicit nil checking
def premium_user?
return @premium unless @premium.nil?
@premium = calculate_premium_status
end
# Or use defined? for uninitialized variables
def expensive_calculation
@result = perform_calculation unless defined?(@result)
@result
end
Production Patterns
Team codebases benefit from automated style enforcement through tools like RuboCop, which provides consistent formatting across all contributors. Configuration files allow teams to customize rules while maintaining baseline consistency standards.
# .rubocop.yml configuration example
AllCops:
TargetRubyVersion: 3.1
Exclude:
- 'vendor/**/*'
- 'db/schema.rb'
Style/StringLiterals:
EnforcedStyle: single_quotes
Layout/LineLength:
Max: 120
AllowedPatterns: ['\A\s*#']
Metrics/MethodLength:
Max: 15
Exclude:
- 'spec/**/*'
Large applications require namespace organization strategies that prevent naming conflicts and improve code discoverability. Module nesting creates clear hierarchies while avoiding deeply nested structures that become unwieldy.
# Organized namespace structure
module PaymentSystem
class CreditCardProcessor
include PaymentSystem::Concerns::Retryable
include PaymentSystem::Concerns::Loggable
def process(transaction)
with_retries(max_attempts: 3) do
log_transaction_attempt(transaction)
perform_charge(transaction)
end
end
end
module Concerns
module Retryable
def with_retries(max_attempts:)
attempts = 0
begin
yield
rescue StandardError => e
attempts += 1
retry if attempts < max_attempts
raise e
end
end
end
end
end
Error handling strategies require consistent exception hierarchies and informative error messages. Custom exception classes provide specific error types while maintaining Ruby's exception inheritance structure.
module APIClient
class Error < StandardError; end
class ConnectionError < Error; end
class TimeoutError < Error; end
class AuthenticationError < Error; end
class Client
def fetch_user(id)
response = http_client.get("/users/#{id}")
case response.status
when 200
JSON.parse(response.body)
when 401
raise AuthenticationError, 'API key invalid or expired'
when 404
raise Error, "User #{id} not found"
when 500..599
raise ConnectionError, "Server error: #{response.status}"
else
raise Error, "Unexpected response: #{response.status}"
end
rescue Net::TimeoutError => e
raise TimeoutError, "Request timed out: #{e.message}"
end
end
end
Performance-sensitive code requires careful attention to object allocation and method call patterns. String concatenation, array operations, and hash lookups can become bottlenecks in high-throughput scenarios.
class LogProcessor
# Efficient string building for large datasets
def format_entries(entries)
output = String.new(capacity: entries.size * 100)
entries.each do |entry|
output << format_timestamp(entry.timestamp)
output << ' '
output << entry.level.upcase
output << ' '
output << entry.message
output << "\n"
end
output
end
private
# Memoized formatter for repeated use
def format_timestamp(timestamp)
@timestamp_cache ||= {}
key = timestamp.to_i
@timestamp_cache[key] ||= timestamp.strftime('%Y-%m-%d %H:%M:%S')
end
end
Reference
Naming Conventions
Element | Convention | Example |
---|---|---|
Classes and Modules | PascalCase | UserService , PaymentProcessor |
Methods and Variables | snake_case | calculate_total , user_name |
Constants | SCREAMING_SNAKE_CASE | MAX_RETRIES , DEFAULT_TIMEOUT |
Files | snake_case.rb | user_service.rb , payment_processor.rb |
Directories | snake_case | app/services , lib/payment_system |
Indentation and Spacing
Context | Rule | Example |
---|---|---|
Basic indentation | 2 spaces | def method\n code\nend |
Method parameters | Align or indent | method(param1,\n param2) |
Hash literals | Align values | { key1: value,\n key2: other } |
Array literals | Align elements | [item1,\n item2] |
Method chaining | Indent continued lines | object\n .method1\n .method2 |
String and Symbol Usage
Context | Preference | Example |
---|---|---|
Static strings | Single quotes | 'Hello world' |
Interpolated strings | Double quotes | "Hello #{name}" |
Hash keys (symbols) | Colon syntax | { name: 'John' } |
Hash keys (strings) | Hash rocket | { 'Content-Type' => 'json' } |
Multi-line strings | Heredoc | <<~TEXT\n content\nTEXT |
Block Syntax
Scenario | Syntax | Example |
---|---|---|
Single line, simple | Curly braces | array.map { |x| x * 2 } |
Multi-line | do/end | array.each do |item| process(item) end |
Method argument | Curly braces | expect { risky_code }.to raise_error |
Complex single line | do/end | items.group_by do |item| item.category end |
Method Definition Patterns
Pattern | Syntax | Use Case |
---|---|---|
Simple method | def name; end |
Basic methods |
With parameters | def name(param); end |
Required parameters |
Optional parameters | def name(param = default); end |
Default values |
Keyword arguments | def name(key:); end |
Named parameters |
Splat arguments | def name(*args); end |
Variable arguments |
Double splat | def name(**opts); end |
Keyword options |
Conditional Expressions
Style | Syntax | Use Case |
---|---|---|
Standard if | if condition\n action\nend |
Multi-line logic |
Inline if | action if condition |
Simple conditions |
Ternary | condition ? true_value : false_value |
Simple assignment |
Case statement | case value\nwhen pattern\nend |
Multiple conditions |
Guard clause | return unless condition |
Early exit |
Class and Module Structure
Element | Order | Visibility |
---|---|---|
Constants | First | Public |
Class variables | After constants | Usually private |
Class methods | After variables | Mixed |
Include/Extend | Before instance methods | N/A |
Public methods | Main body | Public |
Protected methods | After public | Protected |
Private methods | Last | Private |
Common Anti-patterns
Anti-pattern | Problem | Better Approach |
---|---|---|
if !condition |
Double negative | unless condition |
array.length == 0 |
Verbose | array.empty? |
string.length > 0 |
Verbose | !string.empty? |
Nested ternary | Unreadable | Multi-line if/else |
Long parameter lists | Coupling | Parameter object |
Deep nesting | Complexity | Guard clauses |