CrackedRuby logo

CrackedRuby

Ruby Style Guide

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