The letter A styled as Alchemists logo. lchemists
Published April 18, 2020 Updated October 14, 2025
Cover
Barewords Pattern

The Barewords Pattern allows you message plain bare word methods with no additional ceremony so you can use the same syntax throughout your object. Most importantly, this pattern keeps your objects consistent by improving searchability, debugability, and refactoring. Consider this simple example:

class Name
  def initialize first, middle, last
    @first = first
    @middle = middle
    @last = last
  end

  def to_s = "#{first} #{middle} #{last}"

  private

  attr_reader :first, :middle, :last
end

Notice, by using attr_reader, we have a clean way to assemble the full name (i.e. first, middle, and last) when casting to a string using bare words without littering your implementation with @ symbols for instance variables. Instead, everything is clean and consistent.

Now that you understand the basics, let’s dive deeper.

Guidelines

There are a few guidelines to keep in mind when using this pattern such as:

  • No surrounding quotes.

  • No special sigil as a prefix (i.e. $, @, @@, etc).

  • No preceding method call syntax.

  • No ending parenthesis.

  • No uppercase (i.e. EXAMPLE), snakecase (i.e. ExAmPle), or any character that would not be used in a typical Ruby method.

Usage

Returning to the Name object, shown earlier, let’s dissect how the implementation adheres to the Barewords Pattern:

  1. The @first, @middle, and @last instance variables are initialized via the constructor and never referenced again. Construction should be the only place instance variables are used.

  2. The public API of the Name object is locked down using the private attr_reader macro to ensure @first, @middle, and @last are bareword methods and are read-only to prevent mutability.

  3. The #to_s instance method is scoped to only using the bareword methods without having to know the specifics of using globals, instance variables, constants, etc.

For example, here is the proper use of a bareword:

example

On the flip side, the following should be avoided with caveats to be pointed out shortly:

$example   # Global variable
EXAMPLE    # Constant variable
@@example  # Class variable
@example   # Instance variable

In fact, global and class variables should be avoided in general, not when only using barewords. They are a code smell and lead to hard to maintain code due to their broad and far reaching scopes. For example, here is a global variable example that should be avoided:

$middle = "Xavier"

class Name
  def initialize first, last
    @first = first
    @last = last
  end

  def to_s = "#{first} #{$middle} #{last}"

  private

  attr_reader :first, :last
end

The above is a code smell due to the following reasons:

  • Introduces a Global Variable Antipattern.

  • Use of $middle means the global variable can be mutated and referenced anywhere in the entire program.

  • Having a hard coded reference to the $middle global variable means specifically searching for $middle instead of middle when refactoring.

  • Breaks encapsulation because $middle is not scoped to the name Name object.

Same goes for class variables:

class Name
  @@middle = "Xavier"

  def initialize first, last
    @first = first
    @last = last
  end

  def to_s = "#{first} #{@@middle} #{last}"

  private

  attr_reader :first, :last
end

Unlike the $middle global variable example, @@middle is scoped to Name. Unfortunately, if we subclassed Name and mutated @@middle, both Name (superclass) and the corresponding subclass would be updated to share the same value. Again, do yourself a favor and avoid the Class Variable Antipattern.

Constants, on the other hand, are acceptable as long as they are scoped to a module and/or class. Even then, though, constants should be injected into the object being initialized for maximum benefit. For example, avoid the following:

class Name
  MIDDLE = "Xavier"

  def initialize first, last
    @first = first
    @last = last
  end

  def to_s = "#{first} #{MIDDLE} #{last}"

  private

  attr_reader :first, :last
end

While the above limits the scope of MIDDLE to the Name object, use of the constant still breaks encapsulation as first shown with the $middle global variable example earlier. Here’s a better solution:

class Name
  MIDDLE = "Xavier"

  def initialize first, last, middle: MIDDLE
    @first = first
    @last = last
    @middle = middle
  end

  def to_s = "#{first} #{middle} #{last}"

  private

  attr_reader :first, :middle, :last
end

The above is good because it accomplishes the following:

  • Leverages MIDDLE as a clearly defined default constant.

  • Initializes Name with middle being a keyword argument defaulting to the MIDDLE constant as the value. This also allows the middle keyword argument to be customized should someone need to construct the object with default value other than MIDDLE.

  • Allows middle to be a bareword via attr_reader so the implementation remains locally scoped and properly encapsulated.

Finally, instance variables are the most common use case. What you want to avoid is the following:

class Name
  def initialize first, middle, last
    @first = first
    @middle = middle
    @last = last
  end

  def to_s = "#{@first} #{@middle} #{@last}"
end

With the above, the object’s API access to @first, @middle, and @last remains private. Unfortunately, we are not using barewords in the #to_s method. This brings us back to the screenshot at the start of this article. What we want is the following:

class Name
  def initialize first, middle, last
    @first = first
    @middle = middle
    @last = last
  end

  def to_s = "#{first} #{middle} #{last}"

  private

  attr_reader :first, :middle, :last
end

Granted, the above adds a few more lines of code, which some people might grumble about, but the payoff for flexible and maintainable code is worth the effort.

Advantages

There are several advantages to the Barewords Pattern, which are:

  • The ability to allow greater flexibility when enhancing or refactoring code by limiting scope and reducing complexity. For example, when discussing the earlier Name object, we talked about this object not having to concern itself with where the values come from, how they should be interpolated, an so forth. Name, instead, has only the sole responsibility of concatenating a string. By using a consistent syntax, we have a single way to send the messages we want.

  • An immediate exception when making a typo. Instead of using first or @first and you use frst instead, you’ll get an exception stating that no local variable exists: undefined local variable or method `frst'. Whereas, had you used @frst instead of @first, you’ll end up with a nil which is much harder to debug. Having an exception thrown when a typo is introduced provides immediate feedback for quickly fixing the problem before deploying code to production and having to track down where a silent nil was introduced into the code.

  • Takes less key strokes and finger traversal to type first instead of @first.

Disadvanges

A slight disadvantage is the extra lines of code — the private attr_reader — required to generate private methods for the instance variables.

Performance

In terms of performance, there is no notable difference. Use the following script to see for yourself:

#! /usr/bin/env ruby
# frozen_string_literal: true

# Save as `benchmark`, then `chmod 755 benchmark`, and run as `./benchmark`.

require "bundler/inline"

gemfile true do
  source "https://rubygems.org"
  gem "benchmark-ips"
  gem "debug"
end

class Default
  def initialize first, middle, last
    @first = first
    @middle = middle
    @last = last
  end

  def to_s = "#{@first} #{@middle} #{@last}"
end

class Bare
  def initialize first, middle, last
    @first = first
    @middle = middle
    @last = last
  end

  def to_s = "#{first} #{middle} #{last}"

  private

  attr_reader :first, :middle, :last
end

default = Default.new "Zoe", "Alleyne", "Washburne"
bare = Bare.new "Zoe", "Alleyne", "Washburne"

Benchmark.ips do |benchmark|
  benchmark.config time: 5, warmup: 2

  benchmark.report("With") { bare.to_s }
  benchmark.report("Without") { default.to_s }

  benchmark.compare!
end

__END__

ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin24.2.0]
Warming up --------------------------------------
                With     1.248M i/100ms
             Without     1.249M i/100ms
Calculating -------------------------------------
                With     13.436M (± 1.3%) i/s   (74.43 ns/i) -     67.385M in   5.016010s
             Without     13.424M (± 0.9%) i/s   (74.49 ns/i) -     67.429M in   5.023216s

Comparison:
                With: 13436271.6 i/s
             Without: 13424476.6 i/s - same-ish: difference falls within error

As you can see, the performance hit is negligible.

Sublime Text

Due to this pattern being is so useful, and assuming you use Sublime Text, you can use a Sublime Text Setup snippet to quickly craft new objects. For example, to execute the snippet, you could type initb, which is short for initialize body, to yield the following template:

def initialize $1
  $2
end

private

attr_reader :$3

The template’s $1, $2, and $3 tab stops allow you to quickly fill in the rest of your implementation while still adhering to the Barewords Pattern.

Initable

To reduce typing or avoid the Sublime Text snippet altogether, you can supercharge your Ruby code further by using the Initable gem. Initable is designed to adhere to the Barewords Pattern while allowing you to write a single line of code. Here’s a before and after for comparison:

Before

class Name
  def initialize first, middle, last
    @first = first
    @middle = middle
    @last = last
  end

  def to_s = "#{first} #{middle} #{last}"

  private

  attr_reader :first, :middle, :last
end

Name.new "Zoe", "Alleyne", "Washburne"
#<Name:0x0000000128760d80 @first="Zoe", @middle="Alleyne", @last="Washburne">

After

class Name
  include Initable[%i[req first], %i[req middle], %i[req last]]
end

Name.new "Zoe", "Alleyne", "Washburne"
#<Name:0x000000013273ec30 @first="Zoe", @middle="Alleyne", @last="Washburne">

Notice how, when using Initable, you can create a Name instance with the same values by only using one line of code! There’s a lot you can do with Initable, so check out the documentation to learn more.

Avoidances

There are a few solutions in this space that should be avoided:

  • Ivar: This gem performs static and dynamic checks for unused/misspelled instance variables which adds another dependency, a lot more syntax, and effort than if you were to stick with the default attribute readers/writers and keep your instance variables relegated to your initializer.

  • Strict Ivars: This is a pre-processor that will mutate your code upon boot by applying safety checks to your instance variables but this requires setup and constant maintenance that can lead to surprising debugging situations (especially when your code doesn’t match runtime behavior).

Conclusion

Now that you have a good understanding of the Barewords Pattern, you can refactor your code with confidence by making all of your objects consistent and a joy to read with improved maintainability.