String Magic
Beyond upcase and split, Ruby’s string library is one of the richest in any language — format strings, encodings, regex, frozen strings, multi-line literals, heredocs, and the powerful tr/squeeze/scan family of methods.
Format, encoding, regex, heredocs, gsub
EXAMPLE
# 1) Format strings — printf-style
format('Hello, %s. You have %d new messages.', 'Mara', 3)
# 'Hello, Mara. You have 3 new messages.'
'%05d' % 42 # '00042'
'%-10s|' % 'hi' # 'hi |'
'%.2f' % 3.14159 # '3.14'
'%s = %d' % ['age', 30] # named-ish via array
# Named
'%{name} is %{age}' % { name: 'Mara', age: 30 }
format('%<name>s is %<age>d', name: 'Mara', age: 30)
# 2) String interpolation (double-quoted)
name = 'Mara'
"Hello, #{name}" # 'Hello, Mara'
"1 + 1 = #{1 + 1}" # '1 + 1 = 2'
# Single-quoted strings DON'T interpolate:
'No interpolation: #{name}' # 'No interpolation: #{name}'
# 3) Heredocs
message = <<~TEXT
Hi #{name},
Thanks for joining.
— The team
TEXT
# <<~ removes the leading whitespace based on the least-indented line.
# <<- removes only trailing tab/space; <<' indicates no interpolation.
sql = <<~SQL
SELECT id, name FROM users
WHERE active = true AND created_at > '2024-01-01'
ORDER BY id LIMIT 100
SQL
# 4) Encoding
s = 'café'
s.encoding # #<Encoding:UTF-8>
s.bytes.size # 5 (é is 2 bytes)
s.length # 4 (4 characters)
s.force_encoding('ASCII-8BIT') # reinterpret bytes
# Convert between encodings
'café'.encode('ISO-8859-1')
'café'.encode('US-ASCII', invalid: :replace, undef: :replace, replace: '?')
# 'caf?'
# Detect bad sequences
begin
bad.encode('UTF-8', 'ASCII-8BIT')
rescue Encoding::InvalidByteSequenceError => e
# ...
end
# 5) Regex — extract, match, replace
"order-42".match(/order-(\d+)/) { |m| m[1] } # '42'
"order-42".scan(/\d+/) # ['42']
"a1b2c3".scan(/([a-z])(\d)/) # [['a','1'],['b','2'],['c','3']]
"order-42" =~ /order-(\d+)/ # 0 (match position) or nil
$1 # '42' (last match group, avoid in lib code)
# 6) gsub — replace with patterns and blocks
"hello world".gsub('o', '0') # 'hell0 w0rld'
"hello world".gsub(/o/, '0') # same
"hello".gsub(/[aeiou]/) { |v| v.upcase } # 'hEllO'
"date: 2024-01-15".gsub(/(\d{4})-(\d{2})-(\d{2})/) { '\\3/\\2/\\1' } # '15/01/2024'
# Named captures
"date: 2024-01-15".gsub(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/, '\\<d>/\\<m>/\\<y>')
# 7) Iteration
"hello".chars # ['h','e','l','l','o']
"hello".each_char { |c| puts c }
"hello".bytes # [104, 101, 108, 108, 111]
"a\nb\nc".each_line { |l| puts l.chomp }
# 8) Squeeze, tr, count
"aaabbbcccc".squeeze # 'abc'
"aaabbbcccc".squeeze('a') # 'abbbcccc'
"hello".tr('el', 'ip') # 'hippo'
"hello".tr_s('el', 'ip') # 'hipo' (squeeze duplicates)
"hello".count('l') # 2
"hello".count('aeiou') # 2
# 9) Strip, chomp, chop
" hi \n".strip # 'hi'
"hi\n".chomp # 'hi' (removes one trailing newline)
"hi".chop # 'h' (removes last char)
# 10) Slicing
s = 'hello world'
s[0] # 'h'
s[0, 5] # 'hello'
s[0..4] # 'hello'
s[-5..-1] # 'world'
s[/wor.d/] # 'world'
# 11) Modification — bang methods
s = 'hello'
s.upcase!
puts s # 'HELLO'
# Bang methods mutate; non-bang return a new string.
# Strings can be mutable OR frozen (frozen-string-literal: true magic comment).
# 12) Frozen string literals
# At top of file:
# frozen_string_literal: true
#
# All string literals are frozen; +'mut' to get a mutable copy.
# Better performance; standard for new gems.
# 13) Multi-line concat
long = 'line 1 ' \
'line 2 ' \
'line 3'
# 'line 1 line 2 line 3'
# 14) Conversion + casting
'42'.to_i # 42
'42abc'.to_i # 42 (lax)
Integer('42abc') # ArgumentError
'3.14'.to_f # 3.14
42.to_s # '42'
42.to_s(2) # '101010' (binary)
255.to_s(16) # 'ff'
# 15) Inspect + display
puts "hi" # hi
p "hi" # "hi" (calls .inspect)
"hi".dump # '"hi"' with all escapes
# 16) Common bugs
# • Single quotes when interpolation needed — use double quotes
# • Mutating a frozen string — RuntimeError: can't modify frozen String
# • .length vs .bytesize — characters vs bytes
# • Encoding mismatch (Encoding::CompatibilityError) — force_encoding or .encode
# • gsub block returning nil — emits 'NoMethodError: undefined method '<' for nil'
# • Heredoc terminator with leading spaces (<<-FOO) — keep whitespace; use <<~ for indented
# • String#tr expects equal-length ranges in some cases — use gsub for arbitrary mapping
# • Use of $1 (global match var) in libraries — fragile; use Regexp::MatchData explicitly
Why it matters
Reach for gsub with a regex and block when you need rich replacement, heredocs (<<~) for multi-line literals, named captures for self-documenting regex, and frozen_string_literal: true at the top of every new file. Encoding bugs almost always come from forgetting that UTF-8 byte count ≠ character count.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
s = 'hello'
puts s.upcase, s.reverse, s.chars
puts 'a,b,c'.split(',').inspect
Try it Yourself »
Discussion
Loading…