Ruby Naming Conventions

Published by Programming Line in Ruby. Last updated .

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_case for every local variable, method name, and parameter — no exceptions.
  • Reserve SCREAMING_SNAKE_CASE for true constants (fixed values), not for classes or modules, which get CamelCase even 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.rb defines ShoppingCart) — many autoloaders depend on this.
  • Suffix boolean-returning methods with ?, and drop the is_/has_ prefixes common in other languages — write active?, not is_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 private and protected keywords for that.
  • Remember Ruby’s truthiness when naming and using predicates: only nil and false are falsy, so a predicate returning 0, "", or [] is still truthy — make sure your ? methods actually return true/false or 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 use CamelCase; true constants use SCREAMING_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 nil when 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.