Ruby Naming Conventions
Ruby has very few naming rules enforced by the interpreter, but a very strong set of naming conventions enforced by the community — and following them isn’t optional if you want your code to be taken seriously or to work well with other Ruby code. Names are how Ruby developers communicate intent when there’s no static type system to lean on: a name tells you whether something is a variable or a class, whether a method returns a boolean, and whether calling it might change the object you called it on. This lesson covers exactly how Ruby parses names, what convention expects for every kind of identifier, and the mistakes that trip people up most often.
Overview: How Ruby Uses Names
Ruby doesn’t just tolerate consistent naming — it partially enforces it. The interpreter looks at the very first character of an identifier to decide what kind of thing it refers to: a lowercase letter or underscore signals a local variable or method call, a leading @ signals an instance variable, @@ a class variable, $ a global variable, and — critically — a leading uppercase letter is always parsed as a constant, which is exactly what class and module names are built from. Reassigning a constant even prints a warning (already initialized constant) because the parser treats it as a real rule, not just a suggestion.
Everything else you associate with “Ruby style” — snake_case for methods and variables, CamelCase for classes and modules, SCREAMING_SNAKE_CASE for constant values, a trailing ? on predicate methods, a trailing ! on “dangerous” mutating methods — is community convention, not something the parser checks. But it’s convention with teeth: the community-maintained Ruby Style Guide and the RuboCop linter enforce it by default across the ecosystem, and code that violates it is immediately recognizable as non-idiomatic.
Why does this matter so much in Ruby specifically? Because Ruby favors “programmer happiness” and readability over ceremony — there’s no type annotation announcing what a value is, so the name is often your only signal. A method ending in ? is a promise: it returns something meant to be used as a boolean and (by convention) never mutates its receiver. A method ending in ! is a warning: it may mutate the receiver in place, may raise where a same-named non-bang method would quietly return nil, or is otherwise the riskier twin of another method. Learn to read those signals and you can predict a lot about unfamiliar Ruby code before opening its source.
Syntax: The Naming Rules at a Glance
A quick reference for every kind of identifier you’ll name in Ruby:
| Identifier kind | Convention | Example |
|---|---|---|
| Local variables & method names | snake_case: lowercase, words separated by underscores |
user_name, calculate_total |
| Constants (fixed values) | SCREAMING_SNAKE_CASE; must start uppercase |
MAX_RETRIES |
| Classes & modules | CamelCase (a constant that’s also uppercase-first) |
ShoppingCart, PaymentProcessing |
| Predicate methods | end in ?; return a boolean-ish value, never mutate |
empty?, valid? |
| Mutating / “dangerous” methods | end in !; mutate the receiver or are the riskier version of a same-named method |
sort!, uniq! |
| Instance variables | @ + snake_case |
@user_name |
| Class variables | @@ + snake_case (rare — generally avoid) |
@@instance_count |
| Global variables | $ + snake_case (avoid outside special cases) |
$LOAD_PATH |
| File names | snake_case, matching the class/module the file defines | shopping_cart.rb defines ShoppingCart |
One hard rule worth calling out: only method names may end in ? or ! — a local variable, instance variable, or constant can never carry those suffixes. That’s a parser restriction, not a style choice.
Examples
Example 1: snake_case, SCREAMING_SNAKE_CASE, and a predicate method
MAX_RETRIES = 3
def calculate_area(width, height) = width * height
def empty_string?(str) = str.strip.empty?
user_name = "ada lovelace"
formatted_name = user_name.split.map(&:capitalize).join(" ")
puts "Max retries allowed: #{MAX_RETRIES}"
puts "Area: #{calculate_area(4, 5)}"
puts "Is blank? #{empty_string?(" ")}"
puts "Formatted name: #{formatted_name}"
Output:
Max retries allowed: 3
Area: 20
Is blank? true
Formatted name: Ada Lovelace
MAX_RETRIES is a true constant, so it’s SCREAMING_SNAKE_CASE. calculate_area and the local variable user_name are both snake_case. empty_string? ends in ? because it returns a boolean — notice it never mutates str, which is exactly what the ? promises.
Example 2: CamelCase classes and the bang-method contract
class ShoppingCart
attr_reader :items
def initialize
@items = []
end
def add_item(name, price:)
@items << { name: name, price: price }
self
end
def empty?
@items.empty?
end
def total_price
@items.sum { |item| item[:price] }
end
def apply_discount!(percentage)
@items.each { |item| item[:price] = (item[:price] * (1 - percentage / 100.0)).round(2) }
self
end
end
cart = ShoppingCart.new
cart.add_item("Keyboard", price: 50)
cart.add_item("Mouse", price: 20)
puts "Cart empty? #{cart.empty?}"
puts "Total before discount: #{cart.total_price}"
cart.apply_discount!(10)
puts "Total after discount: #{cart.total_price}"
Output:
Cart empty? false
Total before discount: 70
Total after discount: 63.0
ShoppingCart is a class, so it’s CamelCase. empty? is a predicate — it delegates straight to Array#empty?, another predicate. apply_discount! ends in ! because it mutates every item’s price in place; a reader sees the ! and knows not to call it carelessly on a cart they still need the original prices from.
Example 3: Namespacing with modules and symbols as identifiers
module PaymentProcessing
DEFAULT_CURRENCY = "USD"
class CreditCardCharge
def initialize(amount:, currency: DEFAULT_CURRENCY)
@amount = amount
@currency = currency
end
def summary
"#{@amount} #{@currency}"
end
end
end
charge = PaymentProcessing::CreditCardCharge.new(amount: 99.5)
puts charge.summary
config = { status: :pending, retries: 3 }
puts "Status: #{config[:status]}"
Output:
99.5 USD
Status: pending
PaymentProcessing is a module (CamelCase), used purely as a namespace for the class inside it. DEFAULT_CURRENCY is a constant living inside that namespace. The hash uses :status and :pending — symbols, not strings — because these are internal identifiers the program branches on, not text meant for a user to read.
How It Works Step by Step
When Ruby’s parser encounters a bare lowercase word like total, it has to decide whether that’s a local variable reference or a method call. It resolves this purely lexically, at parse time: if total has already appeared as the target of an assignment earlier in the same scope, every later bare total is read as that local variable. If it hasn’t, the parser compiles it as a method call on self — which is why calling an undefined bare word raises NameError: undefined local variable or method at runtime, a single error class covering both cases because the parser genuinely can’t always tell them apart from spelling alone.
Uppercase-first identifiers resolve differently. A reference to ShoppingCart or DEFAULT_CURRENCY triggers constant lookup, which first searches the current lexical nesting (the chain of module/class blocks the reference is textually written inside), and only then falls back to the ancestor chain of the innermost enclosing class. That’s why DEFAULT_CURRENCY is visible unqualified inside CreditCardCharge in Example 3 — it’s defined in the lexically enclosing PaymentProcessing module — but code outside that module must qualify it as PaymentProcessing::DEFAULT_CURRENCY.
@instance_variables and @@class_variables skip lexical lookup entirely: they resolve by object identity. Every object gets its own independent set of @-variables, created the moment they’re first assigned, while a @@-variable is shared across an entire class hierarchy — one of the reasons class variables are discouraged in idiomatic Ruby.
Common Mistakes
Mistake 1: Using camelCase instead of snake_case
This parses fine — Ruby doesn’t care about casing for methods or variables — but it instantly reads as non-idiomatic and clashes with every core and standard library method you’ll call alongside it.
class UserAccount
def getUserName
@userName
end
end
Corrected — snake_case throughout, and the getter drops the redundant get prefix Ruby doesn’t use:
class UserAccount
def user_name
@user_name
end
end
Mistake 2: Skipping the ? on a predicate method
valid reads like it might return the validated object, a message, or almost anything. A caller has no way to know it’s a boolean without reading the body.
def valid(user)
user.age >= 18
end
Corrected — the ? makes the boolean contract explicit at the call site (valid?(user) reads naturally in an if):
def valid?(user)
user.age >= 18
end
Mistake 3: Assuming a bang method always returns the mutated object
Several bang methods — including uniq!, compact!, and reject! — return nil specifically when they made no change, even though they still mutate the receiver when there is a change. Chaining directly off the return value is a classic footgun:
numbers = [1, 2, 3]
result = numbers.uniq!.reverse!
puts result.inspect
Output:
Raises NoMethodError: undefined method 'reverse!' for nil:NilClass
(because numbers had no duplicates, uniq! returned nil instead of the array)
Corrected — mutate the variable across separate statements instead of chaining off the bang method’s return value:
numbers = [1, 2, 3]
numbers.uniq!
numbers.reverse!
puts numbers.inspect
Output:
[3, 2, 1]
Best Practices
- Use
snake_casefor every local variable, method name, and parameter — no exceptions. - Reserve
SCREAMING_SNAKE_CASEfor true constants (fixed values), not for classes or modules, which getCamelCaseeven though both are technically constants to the parser. - Name one class or module per file, and name the file itself in snake_case matching the constant it defines (
shopping_cart.rbdefinesShoppingCart) — many autoloaders depend on this. - Suffix boolean-returning methods with
?, and drop theis_/has_prefixes common in other languages — writeactive?, notis_active?. - Only suffix a method with
!when a same-named, safer non-bang version genuinely exists or the method is meaningfully more dangerous than its name alone suggests — don’t add!just because a method mutates something. - Prefer symbols (
:status) over strings for internal identifiers and hash keys; use real strings only for text a human will actually read. - Avoid class variables (
@@) and global variables ($); prefer instance-level state or well-scoped constants. - Don’t rely on a leading underscore to signal “private” the way some other languages do — Ruby has real
privateandprotectedkeywords for that. - Remember Ruby’s truthiness when naming and using predicates: only
nilandfalseare falsy, so a predicate returning0,"", or[]is still truthy — make sure your?methods actually returntrue/falseor document clearly if they don’t. - Run RuboCop (or a similar linter) locally so naming convention violations get caught before code review.
Practice Exercises
Exercise 1: Write a class named Temperature with an instance variable holding a Celsius value, a predicate method freezing? that returns true when the temperature is 0°C or below, and a method to_fahrenheit that converts and returns the Fahrenheit value. Name everything following the conventions in this lesson.
Exercise 2: This method violates two naming conventions — find and fix both: def GetTotalPrice(itemList) itemList.sum end.
Exercise 3: Predict what happens when you run arr = [1, 2].freeze; result = arr.compact!.length, then explain why in terms of what compact! returns when nothing was removed.
Summary
- Ruby’s parser enforces identifier kind by sigil and case: lowercase-first for locals/methods, uppercase-first for constants,
@/@@/$for instance/class/global variables — but casing style within those categories (snake_case vs. CamelCase) is community convention, not a parser rule. - Methods and variables use
snake_case; classes and modules useCamelCase; true constants useSCREAMING_SNAKE_CASE. - Only method names may end in
?(predicate, returns boolean-ish, no mutation) or!(mutates and/or is the riskier twin of a same-named method). - Several bang methods return
nilwhen no change was made — never chain directly off their return value. - Prefer symbols for internal identifiers and hash keys; reserve strings for user-facing text.
- Constant lookup is lexical first, then ancestor-chain — unlike method lookup, which is purely ancestor-chain based.
- Idiomatic naming isn’t cosmetic in Ruby — it’s how readers infer behavior (mutation, booleans, visibility) that the language itself doesn’t declare statically.
