CrackedRuby logo

CrackedRuby

Naming Conventions

Overview

Ruby naming conventions establish systematic patterns for identifiers across variables, methods, classes, modules, constants, and files. The Ruby parser enforces specific naming rules through syntax requirements, while the community maintains additional style conventions for readability and consistency.

Variable names use lowercase letters with underscores (user_name), while method names follow identical patterns but can include question marks for predicates (valid?) and exclamation marks for destructive operations (save!). Class and module names use PascalCase (UserAccount), and constants use uppercase letters with underscores (MAX_RETRY_COUNT).

# Variable naming
user_name = "Alice"
total_count = 42

# Method naming
def calculate_total(items)
  items.sum(&:price)
end

def valid?
  errors.empty?
end

# Class naming
class UserAccount
  # Constant naming
  MAX_LOGIN_ATTEMPTS = 3
end

Ruby's naming system integrates with reflection APIs, enabling dynamic method calls and introspection. The naming patterns communicate intent: predicate methods ending in ? return boolean values, destructive methods ending in ! modify objects in place, and capitalized names indicate constants or classes.

File naming conventions align with class and module names through transformation rules. The file user_account.rb contains the UserAccount class, following Rails autoloading patterns that convert between snake_case filenames and PascalCase class names.

Basic Usage

Variables and Local Scope

Local variables and method parameters use lowercase letters with underscores separating words. Ruby requires the first character to be a lowercase letter or underscore, distinguishing variables from constants and classes.

# Standard variable patterns
first_name = "John"
last_name = "Doe"
user_age = 25
is_admin = false

# Method parameters
def create_user(first_name, last_name, email_address)
  User.new(
    first_name: first_name,
    last_name: last_name,
    email: email_address
  )
end

# Block parameters
users.each do |current_user|
  puts current_user.full_name
end

Instance and Class Variables

Instance variables begin with @ and follow the same underscore convention. Class variables use @@ prefix but are generally discouraged in favor of class instance variables.

class BankAccount
  # Class instance variable (preferred)
  @default_currency = "USD"

  def initialize(initial_balance)
    @current_balance = initial_balance
    @transaction_history = []
  end

  def self.default_currency
    @default_currency
  end

  private

  # Class variable (discouraged)
  @@total_accounts = 0
end

Method Naming Patterns

Method names use the same snake_case pattern as variables. Predicate methods end with ? to indicate boolean returns, while destructive methods end with ! to signal in-place modifications.

class Document
  def initialize(content)
    @content = content
    @published = false
  end

  # Predicate method
  def published?
    @published
  end

  # Regular method
  def word_count
    @content.split.length
  end

  # Destructive method
  def publish!
    @published = true
    @publication_date = Time.now
  end

  # Setter method
  def title=(new_title)
    @title = new_title.strip
  end
end

Class and Module Naming

Classes and modules use PascalCase with each word capitalized. Nested classes follow the same pattern, with the namespace indicated by the :: operator.

# Basic class naming
class EmailValidator
  def self.valid?(email)
    email.include?("@")
  end
end

# Module naming
module Authentication
  class TokenGenerator
    SECRET_KEY = ENV["TOKEN_SECRET"]

    def generate(user_id)
      # Token generation logic
    end
  end

  module Providers
    class GoogleAuth
      # OAuth implementation
    end
  end
end

Advanced Usage

Metaprogramming Naming Conventions

Dynamic method creation and metaprogramming follow enhanced naming patterns that maintain readability while enabling flexible APIs. Method generation often combines base names with suffixes or prefixes to create families of related methods.

class ConfigurationManager
  # Generate getter and setter methods dynamically
  %w[database_url api_key secret_token].each do |config_name|
    # Create getter method
    define_method(config_name) do
      instance_variable_get("@#{config_name}")
    end

    # Create setter method
    define_method("#{config_name}=") do |value|
      instance_variable_set("@#{config_name}", value)
    end

    # Create predicate method
    define_method("#{config_name}_present?") do
      !instance_variable_get("@#{config_name}").nil?
    end
  end

  # Generate environment-specific methods
  %w[development test production].each do |env|
    define_method("#{env}_config") do
      @configurations[env.to_sym]
    end

    define_method("#{env}?") do
      current_environment == env
    end
  end
end

DSL and Builder Pattern Naming

Domain-specific languages benefit from carefully crafted naming that reads naturally while maintaining Ruby conventions. Builder patterns often use method chaining with descriptive names.

class QueryBuilder
  def initialize(model_class)
    @model_class = model_class
    @conditions = {}
    @joins = []
    @ordering = []
  end

  # Chain-friendly method names
  def where_field_equals(field, value)
    @conditions[field] = value
    self
  end

  def where_field_in(field, values)
    @conditions["#{field}_in"] = values
    self
  end

  def join_table(table_name, on_condition = nil)
    @joins << { table: table_name, condition: on_condition }
    self
  end

  def order_by_field(field, direction = :asc)
    @ordering << { field: field, direction: direction }
    self
  end

  def execute_query
    # Build and execute the actual query
    @model_class.where(@conditions)
                .joins(build_join_clause)
                .order(build_order_clause)
  end

  private

  def build_join_clause
    @joins.map { |join| "#{join[:table]} ON #{join[:condition]}" }.join(" ")
  end

  def build_order_clause
    @ordering.map { |order| "#{order[:field]} #{order[:direction]}" }.join(", ")
  end
end

# Usage with natural DSL naming
results = QueryBuilder.new(User)
  .where_field_equals(:active, true)
  .where_field_in(:role, ['admin', 'moderator'])
  .join_table(:profiles, 'users.id = profiles.user_id')
  .order_by_field(:created_at, :desc)
  .execute_query

Module Extension and Hook Methods

Ruby's module system enables sophisticated naming patterns for hooks, callbacks, and extensions. Convention dictates specific patterns for methods that modify class behavior.

module Trackable
  def self.included(base_class)
    base_class.extend(ClassMethods)
    base_class.class_eval do
      # Add instance methods to the including class
      after_initialize :setup_tracking
      before_save :update_modification_timestamp
    end
  end

  module ClassMethods
    def track_field_changes(*field_names)
      field_names.each do |field_name|
        # Create change tracking methods
        define_method("#{field_name}_changed?") do
          instance_variable_get("@#{field_name}_original") !=
          instance_variable_get("@#{field_name}")
        end

        define_method("#{field_name}_was") do
          instance_variable_get("@#{field_name}_original")
        end

        define_method("reset_#{field_name}_tracking") do
          instance_variable_set("@#{field_name}_original",
                               instance_variable_get("@#{field_name}"))
        end
      end
    end
  end

  private

  def setup_tracking
    # Initialize tracking for all instance variables
    instance_variables.each do |var_name|
      original_var_name = "#{var_name}_original"
      instance_variable_set(original_var_name,
                           instance_variable_get(var_name))
    end
  end

  def update_modification_timestamp
    @last_modified = Time.now
  end
end

Common Pitfalls

Case Sensitivity and Scope Confusion

Ruby's naming rules create subtle scope boundaries that developers frequently misunderstand. Variables beginning with uppercase letters become constants, changing their behavior significantly.

# Pitfall: Accidental constant creation
class PaymentProcessor
  def process_payment(amount)
    # This creates a constant, not a variable!
    Total = amount * 1.1  # Ruby warning: already initialized constant

    # Correct approach
    total = amount * 1.1

    # Another pitfall: constant reassignment
    MAX_AMOUNT = 1000
    MAX_AMOUNT = 2000  # Warning: already initialized constant
  end
end

# Pitfall: Mistaking local variables for method calls
class UserValidator
  def initialize
    @valid = true
  end

  def check_validity
    # This creates a local variable, doesn't call the method!
    valid = false

    # The instance variable remains unchanged
    puts @valid  # => true
    puts valid   # => false

    # Correct method call requires explicit receiver
    self.valid = false
  end

  private

  def valid=(value)
    @valid = value
  end
end

Method Name Collision and Shadowing

Ruby's flexible naming can lead to unintended method shadowing, particularly with global methods and standard library conflicts.

class FileProcessor
  def initialize(filename)
    @filename = filename
  end

  # Pitfall: Shadowing Object#p method
  def p(content)
    # This shadows the global p method
    puts "Processing: #{content}"
  end

  # Pitfall: Shadowing Kernel#system
  def system
    @operating_system ||= detect_os
  end

  # Better naming to avoid conflicts
  def process_content(content)
    puts "Processing: #{content}"
  end

  def operating_system
    @operating_system ||= detect_os
  end

  private

  def detect_os
    # OS detection logic
  end
end

# Pitfall: Local variable shadowing method parameters
def calculate_discount(price, discount_rate)
  if price > 100
    # This creates a local variable, not using the parameter
    discount_rate = 0.2
  end

  price * discount_rate  # Uses local variable, not parameter
end

# Corrected version with clear naming
def calculate_discount(original_price, base_discount_rate)
  effective_discount_rate = if original_price > 100
    0.2  # Premium discount
  else
    base_discount_rate
  end

  original_price * effective_discount_rate
end

Dynamic Method Generation Pitfalls

Metaprogramming amplifies naming issues, particularly when generating methods that conflict with existing ones or violate naming conventions.

class APIClient
  # Pitfall: Generating methods that conflict with Object methods
  %w[send class type].each do |endpoint|
    # This overwrites important Object methods!
    define_method(endpoint) do |data|
      make_request("/#{endpoint}", data)
    end
  end

  # Better approach with namespaced method names
  %w[send class type].each do |endpoint|
    define_method("api_#{endpoint}") do |data|
      make_request("/#{endpoint}", data)
    end

    define_method("#{endpoint}_endpoint") do |data|
      make_request("/#{endpoint}", data)
    end
  end

  # Pitfall: Inconsistent predicate method generation
  STATES = %w[active pending cancelled].freeze

  STATES.each do |state|
    # Inconsistent naming - some end with ?, some don't
    if state.end_with?('ed')
      define_method("#{state}") do
        @state == state
      end
    else
      define_method("#{state}?") do
        @state == state
      end
    end
  end

  # Corrected version with consistent predicate naming
  STATES.each do |state|
    define_method("#{state}?") do
      @state == state
    end
  end
end

Production Patterns

Framework Integration Naming

Production Ruby applications often integrate with frameworks that impose additional naming conventions beyond core Ruby standards. Rails applications particularly benefit from consistent naming patterns that align with framework expectations.

# Rails-style naming conventions
class OrderProcessingService
  # Service object naming pattern
  def self.call(order_params)
    new(order_params).call
  end

  def initialize(order_params)
    @order_params = order_params
    @processing_errors = []
  end

  def call
    return failure_result unless valid_params?

    process_payment
    update_inventory
    send_confirmation_email

    success_result
  end

  private

  attr_reader :order_params, :processing_errors

  # Predicate method for validation
  def valid_params?
    required_fields.all? { |field| order_params[field].present? }
  end

  # Action methods with descriptive names
  def process_payment
    payment_result = PaymentGateway.charge(
      amount: order_params[:total],
      token: order_params[:payment_token]
    )

    processing_errors << "Payment failed" unless payment_result.success?
  end

  def update_inventory
    order_params[:items].each do |item|
      inventory_item = InventoryItem.find(item[:id])
      inventory_item.reduce_quantity(item[:quantity])
    end
  rescue InventoryError => e
    processing_errors << "Inventory error: #{e.message}"
  end

  def send_confirmation_email
    OrderMailer.confirmation_email(order_params[:user_email]).deliver_later
  end

  def required_fields
    %i[user_email total payment_token items]
  end

  def success_result
    OpenStruct.new(success?: true, errors: [])
  end

  def failure_result
    OpenStruct.new(success?: false, errors: processing_errors)
  end
end

Configuration and Environment Naming

Production applications require robust configuration management with naming patterns that clearly indicate environment-specific behavior and deployment contexts.

class ApplicationConfiguration
  # Environment-specific configuration accessors
  def self.database_url_for_environment(env = current_environment)
    case env.to_s
    when 'production'
      production_database_url
    when 'staging'
      staging_database_url
    when 'test'
      test_database_url
    else
      development_database_url
    end
  end

  # Feature flag naming with environment awareness
  def self.feature_enabled?(feature_name, environment = current_environment)
    feature_key = "#{feature_name}_enabled_in_#{environment}"
    ENV.fetch(feature_key, 'false').downcase == 'true'
  end

  # Service endpoint naming for different environments
  def self.api_endpoint_url(service_name, environment = current_environment)
    base_url = case environment.to_s
    when 'production'
      'https://api.production.company.com'
    when 'staging'
      'https://api.staging.company.com'
    else
      'http://localhost:3001'
    end

    "#{base_url}/#{service_name}/v1"
  end

  # Monitoring and logging configuration
  def self.log_level_for_environment(environment = current_environment)
    case environment.to_s
    when 'production'
      Logger::WARN
    when 'staging'
      Logger::INFO
    else
      Logger::DEBUG
    end
  end

  private

  def self.current_environment
    ENV.fetch('RAILS_ENV', 'development')
  end

  def self.production_database_url
    ENV.fetch('DATABASE_URL')
  end

  def self.staging_database_url
    ENV.fetch('STAGING_DATABASE_URL')
  end

  def self.test_database_url
    ENV.fetch('TEST_DATABASE_URL', 'sqlite3::memory:')
  end

  def self.development_database_url
    ENV.fetch('DEV_DATABASE_URL', 'sqlite3:db/development.sqlite3')
  end
end

Monitoring and Observability Naming

Production systems require consistent naming for monitoring, logging, and observability components. Method names should clearly indicate their monitoring purpose and data collection intent.

module ApplicationMonitoring
  class MetricsCollector
    # Metric naming with clear business intent
    def record_user_login_attempt(user_id, successful:, login_method:)
      increment_counter(
        'user_login_attempts_total',
        tags: {
          success: successful.to_s,
          method: login_method,
          user_type: determine_user_type(user_id)
        }
      )

      if successful
        record_successful_login_metrics(user_id, login_method)
      else
        record_failed_login_metrics(user_id, login_method)
      end
    end

    def record_api_request_duration(endpoint, duration_ms, status_code)
      histogram(
        'api_request_duration_milliseconds',
        duration_ms,
        tags: {
          endpoint: sanitize_endpoint_name(endpoint),
          status_class: "#{status_code}"[0] + 'xx',
          status_code: status_code.to_s
        }
      )
    end

    def record_background_job_execution(job_class, duration, success:)
      job_name = normalize_job_class_name(job_class)

      increment_counter(
        'background_jobs_executed_total',
        tags: {
          job_name: job_name,
          success: success.to_s
        }
      )

      histogram(
        'background_job_duration_seconds',
        duration,
        tags: { job_name: job_name }
      )
    end

    private

    def determine_user_type(user_id)
      # Logic to categorize user type for metrics
      user = User.find(user_id)
      user.admin? ? 'admin' : 'regular'
    rescue
      'unknown'
    end

    def sanitize_endpoint_name(endpoint)
      # Remove IDs and sensitive data from endpoint names
      endpoint.gsub(/\/\d+/, '/:id')
              .gsub(/\/[a-f0-9-]{36}/, '/:uuid')
    end

    def normalize_job_class_name(job_class)
      job_class.name.underscore.gsub('_job', '')
    end

    def increment_counter(metric_name, tags: {})
      # Metric collection implementation
    end

    def histogram(metric_name, value, tags: {})
      # Histogram metric implementation
    end

    def record_successful_login_metrics(user_id, login_method)
      # Additional success-specific metrics
    end

    def record_failed_login_metrics(user_id, login_method)
      # Additional failure-specific metrics
    end
  end
end

Testing Strategies

Test Method Naming Conventions

Test methods require descriptive names that clearly communicate the scenario being tested, expected behavior, and any specific conditions or edge cases involved.

RSpec.describe UserAccountManager do
  # Context-specific describe blocks with clear naming
  describe '#create_user_account' do
    context 'when provided with valid user parameters' do
      it 'creates a new user account with correct attributes' do
        user_params = {
          email: 'test@example.com',
          first_name: 'John',
          last_name: 'Doe'
        }

        result = described_class.create_user_account(user_params)

        expect(result).to be_success
        expect(result.user.email).to eq('test@example.com')
        expect(result.user.full_name).to eq('John Doe')
      end

      it 'sends welcome email to the new user' do
        user_params = { email: 'test@example.com', first_name: 'John' }

        expect(WelcomeMailer).to receive(:deliver_welcome_email)
          .with(email: 'test@example.com')

        described_class.create_user_account(user_params)
      end
    end

    context 'when provided with invalid email format' do
      it 'returns failure result with validation errors' do
        invalid_params = { email: 'invalid-email', first_name: 'John' }

        result = described_class.create_user_account(invalid_params)

        expect(result).to be_failure
        expect(result.errors).to include('Invalid email format')
      end

      it 'does not create user record in database' do
        invalid_params = { email: 'invalid-email', first_name: 'John' }

        expect {
          described_class.create_user_account(invalid_params)
        }.not_to change(User, :count)
      end
    end

    context 'when email address already exists' do
      before do
        create(:user, email: 'existing@example.com')
      end

      it 'returns failure result indicating duplicate email' do
        duplicate_params = { email: 'existing@example.com', first_name: 'Jane' }

        result = described_class.create_user_account(duplicate_params)

        expect(result).to be_failure
        expect(result.errors).to include('Email already registered')
      end
    end
  end
end

Dynamic Method Testing Patterns

Testing dynamically generated methods requires specialized naming and setup patterns to ensure comprehensive coverage of both the generation mechanism and the resulting method behavior.

RSpec.describe ConfigurationManager do
  # Test the dynamic method generation itself
  describe 'dynamic configuration method generation' do
    let(:config_fields) { %w[api_key database_url secret_token] }

    it 'generates getter methods for each configuration field' do
      config_fields.each do |field_name|
        expect(subject).to respond_to(field_name)
      end
    end

    it 'generates setter methods for each configuration field' do
      config_fields.each do |field_name|
        expect(subject).to respond_to("#{field_name}=")
      end
    end

    it 'generates predicate methods for each configuration field' do
      config_fields.each do |field_name|
        expect(subject).to respond_to("#{field_name}_present?")
      end
    end
  end

  # Test the behavior of dynamically generated methods
  describe 'dynamically generated method behavior' do
    describe '#api_key and #api_key=' do
      it 'stores and retrieves api_key value correctly' do
        test_key = 'test-api-key-123'

        subject.api_key = test_key
        expect(subject.api_key).to eq(test_key)
      end
    end

    describe '#api_key_present?' do
      context 'when api_key is set' do
        before { subject.api_key = 'some-key' }

        it 'returns true' do
          expect(subject.api_key_present?).to be true
        end
      end

      context 'when api_key is nil' do
        before { subject.api_key = nil }

        it 'returns false' do
          expect(subject.api_key_present?).to be false
        end
      end
    end
  end

  # Test edge cases in dynamic method generation
  describe 'edge cases in dynamic method generation' do
    it 'handles method names with special characters correctly' do
      # Test that method generation works with various field names
      special_fields = %w[oauth_2_token api_v3_endpoint ssl_cert_path]

      config_manager = Class.new do
        include ConfigurationManager

        special_fields.each do |field_name|
          define_configuration_methods(field_name)
        end
      end.new

      special_fields.each do |field_name|
        expect(config_manager).to respond_to(field_name)
        expect(config_manager).to respond_to("#{field_name}=")
        expect(config_manager).to respond_to("#{field_name}_present?")
      end
    end
  end
end

Mock and Stub Naming for Clarity

Test doubles and mocks benefit from descriptive naming that clearly indicates their purpose and the real objects they represent, particularly in complex integration scenarios.

RSpec.describe PaymentProcessingService do
  describe '#process_order_payment' do
    let(:successful_payment_gateway_double) do
      instance_double(
        PaymentGateway,
        charge: payment_success_result,
        refund: refund_success_result
      )
    end

    let(:failing_payment_gateway_double) do
      instance_double(
        PaymentGateway,
        charge: payment_failure_result
      )
    end

    let(:payment_success_result) do
      instance_double(
        PaymentResult,
        success?: true,
        transaction_id: 'txn_123456',
        amount_charged: 99.99
      )
    end

    let(:payment_failure_result) do
      instance_double(
        PaymentResult,
        success?: false,
        error_message: 'Insufficient funds',
        error_code: 'insufficient_funds'
      )
    end

    let(:order_with_valid_payment_info) do
      build(
        :order,
        total_amount: 99.99,
        payment_token: 'valid_token_123',
        customer_email: 'customer@example.com'
      )
    end

    context 'when payment gateway processes payment successfully' do
      before do
        allow(PaymentGateway).to receive(:new)
          .and_return(successful_payment_gateway_double)
      end

      it 'returns success result with transaction details' do
        result = described_class.process_order_payment(order_with_valid_payment_info)

        expect(result).to be_success
        expect(result.transaction_id).to eq('txn_123456')
        expect(result.amount_charged).to eq(99.99)
      end

      it 'sends payment confirmation email to customer' do
        confirmation_mailer_double = instance_double(ActionMailer::MessageDelivery)

        expect(PaymentConfirmationMailer)
          .to receive(:payment_successful)
          .with(
            customer_email: 'customer@example.com',
            transaction_id: 'txn_123456',
            amount: 99.99
          )
          .and_return(confirmation_mailer_double)

        expect(confirmation_mailer_double).to receive(:deliver_later)

        described_class.process_order_payment(order_with_valid_payment_info)
      end
    end

    context 'when payment gateway fails to process payment' do
      before do
        allow(PaymentGateway).to receive(:new)
          .and_return(failing_payment_gateway_double)
      end

      it 'returns failure result with error details' do
        result = described_class.process_order_payment(order_with_valid_payment_info)

        expect(result).to be_failure
        expect(result.error_message).to eq('Insufficient funds')
        expect(result.error_code).to eq('insufficient_funds')
      end
    end
  end
end

Reference

Variable and Method Naming Rules

Pattern Usage Example Notes
snake_case Variables, methods, file names user_name, calculate_total Required by Ruby parser
method? Predicate methods valid?, empty? Returns boolean value
method! Destructive methods save!, sort! Modifies object in place
method= Setter methods name=, value= Assignment method syntax
@instance_var Instance variables @user_name, @total_count Single @ prefix
@@class_var Class variables @@connection_pool Double @@ prefix (discouraged)
$global_var Global variables $DEBUG, $LOAD_PATH Single $ prefix (discouraged)

Class and Module Naming Rules

Pattern Usage Example Notes
PascalCase Classes, modules UserAccount, EmailValidator First letter capitalized
CONSTANT Constants MAX_RETRIES, DEFAULT_TIMEOUT All uppercase with underscores
Module::Class Namespaced classes API::Client, Auth::TokenValidator Double colon separator

File and Directory Naming

Pattern Usage Example Notes
snake_case.rb Ruby files user_account.rb, email_validator.rb Matches class name pattern
snake_case/ Directories user_management/, payment_processing/ Logical grouping
snake_case_spec.rb Test files user_account_spec.rb RSpec convention
snake_case_test.rb Test files user_account_test.rb Minitest convention

Metaprogramming Naming Patterns

Pattern Usage Example Description
define_method(name) Dynamic methods define_method("user_#{role}") Runtime method creation
attr_accessor Property methods attr_accessor :name, :email Getter/setter generation
method_missing Dynamic dispatch def method_missing(name, *args) Catch undefined methods
const_missing Dynamic constants def self.const_missing(name) Catch undefined constants

Special Method Names

Method Purpose Return Type Example Usage
initialize Object construction nil def initialize(name)
to_s String representation String def to_s; @name; end
to_str String conversion String For implicit string conversion
to_a Array conversion Array def to_a; [@name, @age]; end
to_h Hash conversion Hash def to_h; {name: @name}; end
call Function object Any def call(*args); end
[] Element access Any def [](key); @data[key]; end
[]= Element assignment Any def []=(key, value)

Naming Convention Violations and Warnings

Violation Ruby Behavior Example Solution
Constant reassignment Warning issued MAX = 100; MAX = 200 Use configuration object
Variable shadows method Local variable created name = 'John' when name method exists Use self.name or different variable name
Method overrides built-in Silent override def send(msg) Use descriptive prefix: send_message
Reserved word usage Syntax error def class; end Use alternative: def klass

Environment and Framework Conventions

Framework Class Naming File Naming Special Patterns
Rails UserController, User user_controller.rb, user.rb Autoloading conventions
RSpec UserSpec user_spec.rb Test class naming
Sinatra MyApp my_app.rb Application class
Gem MyGem::API my_gem/api.rb Module namespacing