CrackedRuby logo

CrackedRuby

Percent Notation

Overview

Ruby provides percent notation as an alternative syntax for creating literals without traditional quote marks or brackets. Percent notation uses the % character followed by a type indicator and delimiters to define strings, arrays, regular expressions, and symbols.

The general syntax follows the pattern %[type][delimiter][content][delimiter], where the type determines the literal kind and delimiters can be most non-alphanumeric characters. Ruby supports eight primary percent notations: %q and %Q for strings, %w and %W for word arrays, %r for regular expressions, %s for symbols, and %i and %I for symbol arrays.

Percent notation becomes particularly valuable when literal content contains the same characters typically used as delimiters. Rather than escaping multiple quote marks or backslashes, developers can choose delimiters that don't appear in the content.

# Traditional string with escaping
message = "He said, \"Ruby's percent notation is useful.\""

# Percent notation avoids escaping
message = %Q{He said, "Ruby's percent notation is useful."}

# Word array without brackets and quotes
languages = %w[ruby python javascript go rust]
# => ["ruby", "python", "javascript", "go", "rust"]

Ruby processes percent literals at parse time, converting them into their equivalent traditional forms before execution. This means percent notation carries no runtime performance implications while providing cleaner syntax for complex literal definitions.

Basic Usage

Each percent notation type serves specific literal creation needs. The %q notation creates single-quoted strings that don't interpolate variables or process escape sequences, while %Q creates double-quoted strings with full interpolation and escape processing.

name = "Alice"

# %q behaves like single quotes
greeting = %q[Hello #{name}!]
puts greeting  # => "Hello #{name}!"

# %Q behaves like double quotes  
greeting = %Q[Hello #{name}!]
puts greeting  # => "Hello Alice!"

# %Q processes escape sequences
path = %Q[C:\Documents\#{name}\files]
puts path  # => "C:\Documents\Alice\files"

Word arrays use %w for string elements without interpolation and %W for elements with interpolation support. These notations split content on whitespace and return string arrays.

# %w creates array of strings
fruits = %w[apple banana cherry date elderberry]
# => ["apple", "banana", "cherry", "date", "elderberry"]

# %W supports interpolation
size = "large"
containers = %W[small medium #{size} extra-large]
# => ["small", "medium", "large", "extra-large"]

# Whitespace splits elements
mixed = %w[
  red
  green blue
  yellow
]
# => ["red", "green", "blue", "yellow"]

Regular expression literals use %r notation, accepting any delimiter pair. This proves especially useful for patterns containing forward slashes that would require escaping in traditional /pattern/ syntax.

# Traditional regex with escaping
path_pattern = /\/users\/\d+\/posts\/\d+/

# %r notation avoids escaping
path_pattern = %r{/users/\d+/posts/\d+}

# Different delimiters work equally
email_pattern = %r<[\w\.-]+@[\w\.-]+\.\w+>
phone_pattern = %r|\d{3}-\d{3}-\d{4}|

# Interpolation works in %r
domain = "example.com"
domain_pattern = %r{@#{domain}$}

Symbol creation uses %s for single symbols and %i for symbol arrays. Symbol arrays behave similarly to word arrays but return symbols instead of strings.

# Single symbol creation
status = %s[active]
# => :active

# Symbol arrays without interpolation
states = %i[pending active inactive archived]
# => [:pending, :active, :inactive, :archived]

# Symbol arrays with interpolation  
prefix = "user"
events = %I[#{prefix}_created #{prefix}_updated #{prefix}_deleted]
# => [:user_created, :user_updated, :user_deleted]

Advanced Usage

Delimiter selection in percent notation follows specific rules that enable creative solutions to escaping problems. Ruby accepts most non-alphanumeric characters as delimiters, including paired characters like parentheses, brackets, braces, and angle brackets.

# Paired delimiters automatically match
config = %Q{
  database: {
    host: "localhost",
    port: 5432,
    name: "myapp_#{Rails.env}"
  }
}

# Single character delimiters
sql = %Q|SELECT * FROM users WHERE name LIKE '%#{pattern}%'|
regex = %r!^https?://[\w\.-]+/\w+!
commands = %w@git status@git add .@git commit@

# Special characters as delimiters
javascript = %q$
  function calculate() {
    return Math.PI * radius ** 2;
  }
$

Nested percent notations enable complex literal construction, though Ruby requires careful delimiter management to avoid parsing conflicts. Each percent notation maintains its own delimiter scope.

# Nested percent notations with different delimiters
template = %Q{
  <div class="#{%w[container fluid].join(' ')}">
    <p>Pattern: #{%r{/api/v\d+/users/\d+}}</p>
    <ul>
#{%w[home about contact].map { |page| "      <li>#{page}</li>" }.join("\n")}
    </ul>
  </div>
}

# Complex data structure construction
configuration = {
  environments: %i[development test staging production],
  database_urls: %W[
    postgres://localhost/myapp_development
    postgres://localhost/myapp_test  
    postgres://#{ENV['DB_HOST']}/myapp_staging
    postgres://#{ENV['DB_HOST']}/myapp_production
  ],
  patterns: {
    email: %r{[\w\.-]+@[\w\.-]+\.\w+},
    phone: %r{\(\d{3}\)\s\d{3}-\d{4}},
    uuid: %r{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}
  }
}

Ruby allows percent notation in method calls and complex expressions, enabling readable inline literal construction. Method chaining works naturally with percent notation results since they produce standard Ruby objects.

# Method chaining with percent notation
processed_words = %w[Hello World Ruby Programming]
  .map(&:downcase)
  .select { |word| word.length > 4 }
  .map { |word| word.capitalize }
# => ["Hello", "World", "Programming"]

# Percent notation in method arguments
def process_config(env_vars, patterns, symbols)
  # Implementation
end

process_config(
  %W[PATH=#{Dir.pwd}/bin HOME=#{Dir.home} USER=#{ENV['USER']}],
  %r{config/.*\.ya?ml$},
  %i[database cache session logging]
)

# Complex regex construction with interpolation
def build_route_pattern(namespace, resource, action = nil)
  action_part = action ? "/#{action}" : ""
  %r{^/#{namespace}/#{resource}/\d+#{action_part}$}
end

pattern = build_route_pattern("api", "users", "posts")
# => /^\/api\/users\/\d+\/posts$/

Heredoc syntax combines with percent notation for sophisticated string construction, though this requires understanding Ruby's parsing precedence rules.

# Percent notation within heredocs
def generate_migration(table_name, columns)
  column_definitions = %w[id:primary_key created_at:datetime updated_at:datetime] + columns
  
  <<~MIGRATION
    class Create#{table_name.capitalize} < ActiveRecord::Migration[7.0]
      def change
        create_table :#{table_name} do |t|
#{column_definitions.map { |col| "          t.#{col.tr(':', ' :')}" }.join("\n")}
        end
      end
    end
  MIGRATION
end

migration = generate_migration("users", %w[name:string email:string age:integer])

Common Pitfalls

Delimiter conflicts represent the most frequent percent notation pitfall. When content contains the chosen delimiter character, Ruby parsing fails or produces unexpected results. Developers must select delimiters absent from literal content or switch to alternative notation forms.

# Problematic delimiter choice
broken_path = %q{/home/user/file.txt}  # Fails - closing delimiter missing
# SyntaxError: unterminated string

# Content contains delimiter
math_formula = %q|x = |value| + 3|  # Fails - premature termination
# Results in: "x = "

# Correct delimiter selection
file_path = %q[/home/user/file.txt]
math_formula = %q{x = |value| + 3}

Interpolation confusion occurs when developers mix %q/%w/%i (no interpolation) with %Q/%W/%I (interpolation enabled). This leads to literal #{} appearing in output instead of variable substitution.

user = "alice"

# Wrong notation - no interpolation
greeting = %q[Hello #{user}!]
puts greeting  # => "Hello #{user}!"

# Wrong notation - no interpolation in symbol arrays
actions = %i[create_#{user} update_#{user} delete_#{user}]
# => [:create_#{user}, :update_#{user}, :delete_#{user}]

# Correct notation with interpolation
greeting = %Q[Hello #{user}!]
actions = %I[create_#{user} update_#{user} delete_#{user}]
# => [:create_alice, :update_alice, :delete_alice]

Word array whitespace handling surprises developers expecting literal preservation. Ruby splits on any whitespace character, including newlines and tabs, potentially creating more array elements than expected.

# Unexpected splitting behavior
items = %w[
  first
  second third
  fourth	fifth
]
# => ["first", "second", "third", "fourth", "fifth"] (5 elements, not 3)

# Leading/trailing whitespace ignored
spaced = %w[  start   middle   end  ]
# => ["start", "middle", "end"]

# Empty elements from multiple spaces
problematic = %w[one  two   three]
# => ["one", "two", "three"] (Ruby collapses multiple spaces)

# Preserving literal spacing requires string notation
preserved = %Q[  start   middle   end  ]
# => "  start   middle   end  "

Regular expression delimiter selection becomes critical when patterns contain common regex delimiters. Choosing delimiters that appear in the pattern creates parsing errors or incorrect pattern boundaries.

# Problematic regex with slash delimiter
url_pattern = %r{/api/v\d+/users/\d+}  # Works fine
file_pattern = %r{/path/to/file\.txt}  # Works fine

# Problems arise with delimiter conflicts
# Don't use { } if pattern contains braces
json_pattern = %r{{.*}}  # Fails - delimiter conflict
# Use alternative delimiters
json_pattern = %r|\{.*\}|  # Correct

# Email pattern with @ delimiter problems  
email_bad = %r@[\w\.-]+@[\w\.-]+\.\w+@  # Fails - @ in pattern and delimiter
email_good = %r{[\w\.-]+@[\w\.-]+\.\w+}  # Correct

Escaping behavior differs between percent notation types, causing confusion when developers expect consistent rules. The %q family behaves like single quotes with minimal escaping, while %Q family processes all escape sequences.

# Different escaping behavior
single_quoted = %q[Line 1\nLine 2\tTabbed]
puts single_quoted  # => "Line 1\nLine 2\tTabbed" (literal \n and \t)

double_quoted = %Q[Line 1\nLine 2\tTabbed]  
puts double_quoted  
# => Line 1
# => Line 2    Tabbed (processed \n and \t)

# Backslash handling varies
path_q = %q[C:\Users\Name\Documents]    # => "C:\\Users\\Name\\Documents"
path_Q = %Q[C:\Users\Name\Documents]    # => "C:UsersNameDocuments" (invalid path)
path_correct = %Q[C:\\Users\\Name\\Documents]  # => "C:\\Users\\Name\\Documents"

Symbol array interpolation requires the %I notation, but the result differs from string interpolation behavior. Ruby converts interpolated expressions to strings before creating symbols, which can produce unexpected symbol names.

number = 42

# String interpolation preserves type information context
strings = %W[item_#{number} count_#{number}]
# => ["item_42", "count_42"]

# Symbol interpolation converts everything to string representation
symbols = %I[item_#{number} count_#{number}]  
# => [:item_42, :count_42]

# Complex expressions become string representations
today = Date.today
symbols = %I[event_#{today.year}_#{today.month}]
# => [:event_2024_8] (assuming current date)

# Method calls in interpolation
user = OpenStruct.new(name: "Alice", id: 123)
symbols = %I[user_#{user.name.downcase}_#{user.id}]
# => [:user_alice_123]

Reference

Percent Notation Types

Notation Equivalent Interpolation Escape Processing Description
%q Single quotes '...' No Minimal Single-quoted string
%Q Double quotes "..." Yes Full Double-quoted string
%w Array of single-quoted strings No Minimal Word array
%W Array of double-quoted strings Yes Full Word array with interpolation
%r Regular expression /.../ Yes Full Regular expression
%s Symbol :symbol No Minimal Single symbol
%i Array of symbols No Minimal Symbol array
%I Array of symbols Yes Full Symbol array with interpolation

Delimiter Options

Delimiter Type Examples Behavior
Paired Characters (), [], {}, <> Auto-matching pairs
Single Characters !, @, #, $, %, ^, &, *, +, =, ~, :, ;, ?, /, \, _, -, | Same character opens and closes
Alphanumeric Not allowed Reserved for other Ruby syntax

Escape Sequences by Type

Sequence %q/%w/%i/%s %Q/%W/%I/%r Description
\\ \ \ Literal backslash
\' ' ' Single quote
\" \" " Double quote
\n \n Newline Newline character
\t \t Tab Tab character
\r \r Carriage return Carriage return
\b \b Backspace Backspace character
\f \f Form feed Form feed character
\v \v Vertical tab Vertical tab
\a \a Bell Bell/alert character
\e \e Escape Escape character
\nnn \nnn Octal value Octal character code
\xhh \xhh Hex value Hexadecimal character code
\uhhhh \uhhhh Unicode Unicode character

Method Return Types

Notation Return Type Example Result
%q{text} String "text"
%Q{text} String "text"
%w{a b c} Array<String> ["a", "b", "c"]
%W{a b c} Array<String> ["a", "b", "c"]
%r{pattern} Regexp /pattern/
%s{symbol} Symbol :symbol
%i{a b c} Array<Symbol> [:a, :b, :c]
%I{a b c} Array<Symbol> [:a, :b, :c]

Performance Characteristics

Aspect Behavior
Parse Time Converted to equivalent forms at parse time
Runtime Performance Identical to equivalent traditional syntax
Memory Usage Same as traditional syntax
Interpolation Cost Same as string interpolation in traditional syntax
Regular Expression Caching %r patterns cached identically to /pattern/ syntax

Common Use Cases

Scenario Recommended Notation Example
String with many quotes %Q{} %Q{He said "Hello" and 'Goodbye'}
File paths with backslashes %q[] %q[C:\Program Files\App]
Arrays of simple words %w[] %w[red green blue yellow]
Command line arguments %W[] %W[--config #{config_file} --verbose]
Regex with slashes %r{} %r{/api/v\d+/users}
Symbol creation %s[] %s[active_status]
Configuration symbols %i[] %i[development test production]
Dynamic symbol arrays %I[] %I[#{prefix}_start #{prefix}_end]