iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…