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] |