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 |