Strings

Strings #

String adalah tipe data yang paling sering kamu temui di hampir setiap program Ruby. Dari memproses input pengguna, membangun respons API, hingga membaca file teks — semuanya melibatkan String. Ruby menyediakan Standard Library String yang sangat kaya: puluhan method bawaan yang mencakup pencarian, transformasi, pemformatan, encoding, hingga konversi tipe. Memahami method-method ini secara mendalam — bukan sekadar hafal sintaksnya, tapi juga tahu kapan dan mengapa memilih satu method dibanding yang lain — adalah fondasi untuk menulis kode Ruby yang idiomatis dan efisien.

Membuat dan Mendefinisikan String #

Sebelum masuk ke manipulasi, penting untuk memahami berbagai cara mendefinisikan String di Ruby karena masing-masing punya karakteristik berbeda yang memengaruhi perilaku program.

Ruby menyediakan beberapa sintaks literal untuk String:

# String dengan single quote — tidak menginterpretasi escape sequence
nama = 'Umar ibn Khattab'
path = 'C:\Users\dokumen'       # backslash tidak diproses

# String dengan double quote — menginterpretasi interpolasi dan escape
salam = "Assalamu'alaikum, #{nama}"
baris_baru = "Baris pertama\nBaris kedua"

# Heredoc — untuk string multi-baris
teks = <<~HEREDOC
  Ini adalah teks
  yang mencakup beberapa baris
  dengan indentasi yang dibersihkan otomatis.
HEREDOC

# String beku (frozen) — immutable, lebih efisien memori
# frozen_string_literal: true
konstanta = "nilai yang tidak berubah".freeze

Perbedaan antara single quote dan double quote bukan sekadar kosmetik. Single quote lebih cepat karena Ruby tidak perlu memindai karakter interpolasi, sedangkan double quote wajib digunakan saat kamu butuh #{} atau escape sequences seperti \n, \t.

flowchart TD
    A["Definisi String"] --> B{"Butuh interpolasi<br/>atau escape?"}

    B -- Ya --> C["Double quote<br/>&quot;Halo #123;nama&#125;&quot;"]
    B -- Tidak --> D["Single quote<br/>'Halo dunia'"]

    C --> E{"Butuh multi-baris?"}
    D --> E

    E -- Ya --> F["Heredoc<br/>&lt;&lt;~TEKS<br/>...<br/>TEKS"]
    E -- Tidak --> G["Gunakan literal biasa"]

    G --> H{"Nilai tidak boleh<br/>berubah?"}

    H -- Ya --> I[".freeze"]
    H -- Tidak --> J["String biasa"]

Panjang dan Pengecekan String #

Mengetahui ukuran dan kondisi sebuah String adalah operasi dasar yang sering dilakukan untuk validasi input atau logika kondisional.

teks = "Bismillah"

# Panjang string (dalam karakter)
teks.length    # => 9
teks.size      # => 9  (alias dari length)

# Panjang dalam bytes (berbeda jika ada karakter multi-byte)
teks.bytesize  # => 9

# Pengecekan kondisi
teks.empty?           # => false
"".empty?             # => true
"  ".empty?           # => false  (spasi bukan empty!)

# Pengecekan nil atau empty sekaligus (butuh ActiveSupport atau implementasi sendiri)
# Cara idiomatis Ruby murni:
teks.nil? || teks.empty?   # => false

# Pengecekan konten
teks.include?("llah")      # => true
teks.start_with?("Bis")    # => true
teks.end_with?("lah")      # => true
teks.start_with?("Bis", "Al")  # => true  (bisa cek beberapa prefix sekaligus)

empty? hanya mengecek apakah string punya panjang nol. String yang berisi hanya spasi — " " — tidak dianggap empty. Jika kamu ingin mengecek string yang “tidak berarti” termasuk yang berisi spasi saja, kombinasikan dengan strip:

"   ".strip.empty?   # => true

Akses dan Pengirisan String #

Ruby memungkinkan akses ke bagian tertentu dari String dengan sintaks yang sangat ekspresif menggunakan indeks atau range.

kalimat = "Alhamdulillah"

# Akses karakter tunggal (indeks dimulai dari 0)
kalimat[0]        # => "A"
kalimat[-1]       # => "h"  (indeks negatif dari belakang)
kalimat[-3]       # => "l"

# Pengirisan dengan range
kalimat[0..4]     # => "Alham"   (inklusif di kedua ujung)
kalimat[0...4]    # => "Alha"    (eksklusif di ujung kanan)
kalimat[2, 5]     # => "hamdu"   (mulai indeks 2, ambil 5 karakter)

# Pencarian dan pengambilan substring
kalimat["hamdu"]  # => "hamdu"   (nil jika tidak ditemukan)
kalimat["xyz"]    # => nil

# Pengambilan dengan regex
"harga: 15000"[/\d+/]   # => "15000"

Pengirisan dengan [start, length] sangat berguna untuk protokol biner atau parsing format teks terstruktur. Perhatikan bahwa indeks yang melebihi panjang string mengembalikan nil, bukan error — ini perilaku yang perlu kamu antisipasi saat parsing data yang tidak terprediksi.

# ANTI-PATTERN: asumsi indeks selalu valid
def ambil_kode(str)
  str[0..2].upcase   # crash jika str nil atau terlalu pendek
end

# BENAR: validasi sebelum akses
def ambil_kode(str)
  return nil if str.nil? || str.length < 3
  str[0..2].upcase
end

Transformasi Kapitalisasi #

Ruby menyediakan method yang lengkap untuk mengubah kapitalisasi String, dari yang sederhana hingga yang sadar Unicode.

teks = "halo dunia dari ruby"

teks.upcase       # => "HALO DUNIA DARI RUBY"
teks.downcase     # => "halo dunia dari ruby"
teks.capitalize   # => "Halo dunia dari ruby"   (hanya huruf pertama)
teks.swapcase     # => "HALO DUNIA DARI RUBY"   (balik semua case)

# Untuk judul (setiap kata kapital) — tidak ada method bawaan,
# tapi bisa dibangun dengan split + map
teks.split.map(&:capitalize).join(" ")
# => "Halo Dunia Dari Ruby"

Semua method di atas menghasilkan String baru. Untuk memodifikasi string asli (in-place), gunakan versi dengan tanda seru:

nama = "abu hurairah"
nama.capitalize!
# nama sekarang => "Abu hurairah"

# Versi ! mengembalikan nil jika tidak ada perubahan
"SUDAH KAPITAL".upcase!   # => nil (tidak ada perubahan)
Method upcase dan downcase bawaan Ruby tidak selalu benar untuk karakter non-ASCII di Ruby versi lama. Sejak Ruby 2.4, operasi ini sudah sadar Unicode secara default. Pastikan kamu tidak terjebak di perilaku lama jika bekerja dengan text multibahasa.

Pencarian dan Pemeriksaan Pola #

Mencari substring atau pola di dalam String adalah operasi yang sangat umum. Ruby menyediakan beberapa pendekatan dengan trade-off berbeda.

teks = "Ruby adalah bahasa yang elegan dan ekspresif"

# Pencarian posisi
teks.index("bahasa")        # => 14   (posisi pertama, nil jika tidak ada)
teks.rindex("a")            # => 40   (posisi terakhir dari belakang)
teks.index("tidak ada")     # => nil

# Pengecekan keberadaan
teks.include?("elegan")     # => true

# Pencocokan dengan regex
teks.match?(/\belegan\b/)   # => true  (hanya true/false, lebih cepat)
teks.match(/(\w+)\s+dan/)   # => MatchData  (mengandung capture group)

# Scan — ambil semua kecocokan
"aku suka ruby dan ruby suka aku".scan(/ruby/)
# => ["ruby", "ruby"]

"harga: 100, jumlah: 5".scan(/\d+/)
# => ["100", "5"]

Perhatikan perbedaan antara match? dan match:

MethodReturn ValueGunakan Saat
include?true/falseCek substring sederhana
match?true/falseCek pola regex, butuh kecepatan
matchMatchData / nilButuh capture group dari regex
scanArrayAmbil semua kemunculan pola
indexInteger / nilButuh posisi karakter
# Contoh penggunaan match untuk ekstrak data
email = "[email protected]"
hasil = email.match(/\A([\w+\-.]+)@([a-z\d\-.]+)\.([a-z]+)\z/i)

if hasil
  puts "Username: #{hasil[1]}"   # => "kontak"
  puts "Domain: #{hasil[2]}"     # => "example"
  puts "TLD: #{hasil[3]}"        # => "com"
end

Penggantian dan Substitusi #

Setelah menemukan pola, langkah berikutnya sering kali adalah menggantinya. Ruby membedakan penggantian untuk kemunculan pertama (sub) dan semua kemunculan (gsub).

kalimat = "kucing itu lucu, kucing itu menggemaskan"

# sub — ganti kemunculan PERTAMA saja
kalimat.sub("kucing", "anjing")
# => "anjing itu lucu, kucing itu menggemaskan"

# gsub — ganti SEMUA kemunculan
kalimat.gsub("kucing", "kelinci")
# => "kelinci itu lucu, kelinci itu menggemaskan"

# Dengan regex
"harga: Rp 15.000".gsub(/[^0-9]/, "")
# => "15000"

# Dengan block — transformasi dinamis
"hello world".gsub(/\b\w/) { |m| m.upcase }
# => "Hello World"

# Dengan hash — penggantian multipel sekaligus
"aeiou".gsub(/[aeiou]/, "a" => "@", "e" => "3", "o" => "0")
# => "@3i0u"
flowchart LR
    A[String Input] --> B{Ganti berapa\nkemunculan?}
    B -- "Pertama saja" --> C[sub / sub!]
    B -- "Semua" --> D[gsub / gsub!]
    C --> E{Penggantian\nsederhana?}
    D --> E
    E -- "Ya, string tetap" --> F["gsub('lama', 'baru')"]
    E -- "Dinamis / transformasi" --> G["gsub(/pola/) { |m| ... }"]
    E -- "Banyak sekaligus" --> H["gsub(/pola/, hash)"]
# ANTI-PATTERN: gsub berulang untuk banyak penggantian
teks = input
teks = teks.gsub("&", "&amp;")
teks = teks.gsub("<", "&lt;")
teks = teks.gsub(">", "&gt;")

# BENAR: satu gsub dengan hash
HTML_ESCAPES = { "&" => "&amp;", "<" => "&lt;", ">" => "&gt;" }
teks = input.gsub(/[&<>]/, HTML_ESCAPES)

Pembersihan dan Pemangkasan #

Data dunia nyata sering datang dengan whitespace yang tidak diinginkan, karakter tersembunyi, atau padding yang perlu dibersihkan sebelum diproses.

kotor = "  \t Halo Dunia \n  "

# strip — hapus whitespace di kedua ujung
kotor.strip    # => "Halo Dunia"

# lstrip — hapus whitespace di kiri saja
kotor.lstrip   # => "Halo Dunia \n  "

# rstrip — hapus whitespace di kanan saja
kotor.rstrip   # => "  \t Halo Dunia"

# chomp — hapus newline di akhir (sangat umum untuk output gets)
"baris teks\n".chomp    # => "baris teks"
"baris teks\r\n".chomp  # => "baris teks"  (handles Windows line ending)

# chop — hapus karakter TERAKHIR apapun (jarang dipakai)
"hello!".chop   # => "hello"

# delete — hapus karakter tertentu di mana saja
"h-e-l-l-o".delete("-")     # => "hello"
"abc123".delete("0-9")       # => "abc"  (range karakter)

# squeeze — kurangi karakter berulang menjadi satu
"hellooooo".squeeze          # => "helo"
"  banyak   spasi  ".squeeze(" ")  # => " banyak spasi "

# tr — transliterasi karakter (seperti sed y///)
"hello".tr("aeiou", "*")     # => "h*ll*"
"hello".tr("a-y", "b-z")     # => "ifmmp"  (geser satu huruf)

Kombinasi strip dan operasi lain sangat umum dalam pipeline pemrosesan input:

def bersihkan_input(raw)
  raw
    .strip
    .squeeze(" ")           # kurangi multiple spaces
    .gsub(/[^\w\s\-]/, "")  # hapus karakter khusus kecuali dash
    .downcase
end

bersihkan_input("  Hello   World!! \n")
# => "hello world"

Pemisahan dan Penggabungan #

Konversi antara String dan Array adalah operasi yang sangat sering dilakukan, terutama saat memproses CSV, token, atau teks terstruktur.

# split — String ke Array
"satu,dua,tiga".split(",")       # => ["satu", "dua", "tiga"]
"satu  dua   tiga".split         # => ["satu", "dua", "tiga"]  (default: whitespace)
"abcde".split("")                # => ["a", "b", "c", "d", "e"]  (per karakter)
"a,b,,c".split(",")              # => ["a", "b", "", "c"]
"a,b,,c".split(",", -1)          # => ["a", "b", "", "c"]  (-1: pertahankan trailing empty)
"satu,dua,tiga".split(",", 2)    # => ["satu", "dua,tiga"]  (limit jumlah potongan)

# join — Array ke String (method Array, bukan String)
["satu", "dua", "tiga"].join(", ")   # => "satu, dua, tiga"
["a", "b", "c"].join                 # => "abc"  (tanpa separator)

# lines — split berdasarkan baris
"baris1\nbaris2\nbaris3".lines
# => ["baris1\n", "baris2\n", "baris3"]

# chars — split per karakter
"hello".chars   # => ["h", "e", "l", "l", "o"]

# bytes — split per byte
"AB".bytes      # => [65, 66]
# Pola idiomatis: split → map → join (pipeline transformasi)
"nama:email:telepon"
  .split(":")
  .map { |field| field.capitalize }
  .join(" | ")
# => "Nama | Email | Telepon"

# ANTI-PATTERN: konkatenasi string dalam loop
hasil = ""
["a", "b", "c"].each { |s| hasil += s }  # membuat object baru setiap iterasi

# BENAR: gunakan join
hasil = ["a", "b", "c"].join

Pemformatan dan Padding #

Menampilkan data dalam format yang rapi — tabel CLI, log terstruktur, atau output yang mudah dibaca — membutuhkan kemampuan memformat String dengan presisi.

# center, ljust, rjust — padding dengan karakter pengisi
"ruby".center(10)        # => "   ruby   "
"ruby".center(10, "-")   # => "---ruby---"
"ruby".ljust(10)         # => "ruby      "
"ruby".ljust(10, ".")    # => "ruby......"
"ruby".rjust(10)         # => "      ruby"
"ruby".rjust(10, "0")    # => "000000ruby"

# Format printf-style dengan %
"Halo, %s! Kamu berusia %d tahun." % ["Umar", 30]
# => "Halo, Umar! Kamu berusia 30 tahun."

"Harga: Rp %.2f" % 15000.5
# => "Harga: Rp 15000.50"

# format / sprintf — lebih eksplisit
format("%-10s %5d", "apel", 100)
# => "apel        100"

# Contoh output tabel sederhana
header = format("%-15s %-10s %10s", "Nama", "Jabatan", "Gaji")
baris  = format("%-15s %-10s %10s", "Abu Bakar", "Manager", "Rp 15jt")
puts header
puts "-" * 38
puts baris
SpesifierKeteranganContoh
%sString"%s" % "halo""halo"
%dInteger"%d" % 42"42"
%fFloat"%.2f" % 3.14159"3.14"
%05dInteger dengan zero-padding"%05d" % 7"00007"
%-10sString rata kiri, lebar 10"%-10s" % "hi""hi "
%eScientific notation"%e" % 1234"1.234000e+03"
%xHexadecimal"%x" % 255"ff"

Konversi Tipe #

String sering perlu dikonversi ke tipe lain saat memproses input pengguna atau data dari sumber eksternal. Ruby menyediakan method konversi yang eksplisit dan idiomatis.

# String ke numerik
"42".to_i          # => 42
"3.14".to_f        # => 3.14
"0xFF".to_i(16)    # => 255  (parse hexadecimal)
"0b1010".to_i(2)   # => 10   (parse binary)
"100".to_r         # => (100/1)  (Rational)
"3.14".to_c        # => (3.14+0i)  (Complex)

# Perilaku saat string tidak valid
"abc".to_i         # => 0   (tidak error, tapi hasilnya 0!)
"3.14abc".to_f     # => 3.14  (berhenti di karakter non-numerik)
"abc".to_f         # => 0.0

# Integer() dan Float() — lebih ketat, raise exception jika tidak valid
Integer("42")      # => 42
Integer("abc")     # => ArgumentError: invalid value for Integer
Float("3.14")      # => 3.14
Float("abc")       # => ArgumentError: invalid value for Float

# String ke symbol dan sebaliknya
"nama_method".to_sym    # => :nama_method
:nama_method.to_s       # => "nama_method"

# String ke array karakter
"hello".chars           # => ["h", "e", "l", "l", "o"]

# Cek apakah string adalah angka valid (Ruby tidak punya is_numeric bawaan)
def angka_valid?(str)
  Integer(str) rescue false
end
# ANTI-PATTERN: menggunakan to_i untuk validasi numerik
def proses_usia(input)
  usia = input.to_i
  if usia > 0  # SALAH: "abc".to_i == 0, bukan error
    "Usia valid: #{usia}"
  else
    "Input tidak valid"
  end
end
# proses_usia("0") salah dianggap tidak valid
# proses_usia("-5") lolos tanpa validasi range

# BENAR: gunakan Integer() dengan rescue
def proses_usia(input)
  usia = Integer(input)
  raise ArgumentError, "Usia harus positif" unless usia > 0
  "Usia valid: #{usia}"
rescue ArgumentError => e
  "Input tidak valid: #{e.message}"
end

Encoding dan Unicode #

Di era modern, program Ruby hampir pasti berurusan dengan teks multibahasa. Memahami encoding adalah kunci untuk menghindari bug yang sulit dilacak.

# Cek encoding string saat ini
"hello".encoding        # => #<Encoding:UTF-8>

# Force encoding — tandai ulang tanpa konversi data
str = "\xFF\xFE"
str.force_encoding("UTF-16LE")

# Encode — konversi sebenarnya (dengan konversi data)
"Héllo".encode("ISO-8859-1")   # => String dalam ISO-8859-1
"Héllo".encode("ASCII", invalid: :replace, undef: :replace, replace: "?")
# => "H?llo"  (karakter tak terdefinisi diganti ?)

# Valid encoding?
"hello".valid_encoding?   # => true

# Panjang karakter vs bytes (penting untuk multibyte)
arab = "مرحبا"
arab.length      # => 5  (5 karakter)
arab.bytesize    # => 10  (10 bytes dalam UTF-8, setiap karakter Arab 2 byte)

# Iterasi per karakter (benar untuk Unicode)
"こんにちは".each_char { |c| print c + " " }
# => こ ん に ち は
flowchart TD
    A[String Masuk] --> B{Apakah encoding\nsudah diketahui?}
    B -- Ya --> C{Encoding sesuai\nkebutuhan?}
    B -- Tidak --> D["Cek .encoding\natau .valid_encoding?"]
    D --> C
    C -- Ya --> E[Gunakan langsung]
    C -- Tidak --> F{Perlu konversi\ndata?}
    F -- Ya --> G[".encode('target_encoding')"]
    F -- "Hanya re-label" --> H[".force_encoding('label')"]
    G --> I{Ada karakter\ntidak valid?}
    I -- Ya --> J["Gunakan opsi\ninvalid: :replace"]
    I -- Tidak --> E
Jangan pernah menggunakan force_encoding untuk “memperbaiki” string dengan karakter rusak — method ini hanya mengubah label encoding tanpa mengonversi data. Jika kamu perlu konversi sebenarnya, gunakan encode. Salah kaprah ini adalah sumber bug encoding yang sangat umum.

Method Iterasi pada String #

Ruby memungkinkan iterasi langsung pada String tanpa perlu konversi ke Array terlebih dahulu, yang lebih efisien memori untuk string besar.

teks = "Baris pertama\nBaris kedua\nBaris ketiga"

# each_line — iterasi per baris
teks.each_line do |baris|
  puts baris.chomp.upcase
end
# BARIS PERTAMA
# BARIS KEDUA
# BARIS KETIGA

# each_char — iterasi per karakter
hitungan = Hash.new(0)
"mississippi".each_char { |c| hitungan[c] += 1 }
# => {"m"=>1, "i"=>4, "s"=>4, "p"=>2}

# each_byte — iterasi per byte (untuk protokol biner)
"ABC".each_byte { |b| print "#{b} " }
# => 65 66 67

# upto — iterasi string secara urutan leksikografis
"a".upto("e") { |c| print c + " " }
# => a b c d e

"aa".upto("ac") { |s| print s + " " }
# => aa ab ac

Method Utilitas Lainnya #

Ada beberapa method String yang tidak masuk kategori di atas tapi sangat berguna dalam praktik sehari-hari.

# reverse — balik string
"hello".reverse      # => "olleh"

# count — hitung kemunculan karakter
"mississippi".count("s")    # => 4
"hello world".count("aeiou")  # => 3

# sum — jumlahkan nilai ASCII
"ABC".sum      # => 198  (65 + 66 + 67)

# hex, oct — parse string sebagai angka hex/oktal
"ff".hex       # => 255
"77".oct       # => 63

# succ / next — increment string
"a".succ       # => "b"
"z".succ       # => "aa"
"az".succ      # => "ba"
"zz9".succ     # => "aaa0"

# Berguna untuk generate ID sederhana atau sequence
kode = "A0"
5.times { puts kode = kode.succ }
# A1, A2, A3, A4, A5

# replace — ganti konten in-place (ubah string yang sama)
str = "hello"
str.replace("world")
# str sekarang => "world"  (object yang sama, bukan yang baru)

# dup dan clone — salin string
original = "hello".freeze
salinan = original.dup    # bisa dimutasi
salinan << " world"       # => "hello world"

Kapan Beralih ke Pendekatan Lain #

Meski Standard Library String Ruby sangat lengkap, ada situasi di mana kamu perlu alat yang berbeda:

Tetap gunakan String bawaan jika:
  ✓ Manipulasi teks sederhana hingga menengah
  ✓ Pencarian dan substitusi dengan substring atau regex sederhana
  ✓ Pemformatan output untuk CLI atau log
  ✓ Parsing format teks yang tidak terlalu kompleks
  ✓ Performa cukup untuk volume data normal

Pertimbangkan pendekatan alternatif jika:
  ✗ Parse HTML/XML — gunakan Nokogiri (tidak parse dengan regex!)
  ✗ Parse CSV kompleks — gunakan library csv dari stdlib
  ✗ Parse JSON — gunakan JSON.parse bawaan
  ✗ Template teks kompleks — gunakan ERB atau Mustache
  ✗ Manipulasi teks volume sangat besar — pertimbangkan StringIO untuk stream
  ✗ Pattern matching sangat kompleks — pecah menjadi parser terstruktur

Ringkasan #

  • Pilih sintaks literal yang tepat — single quote untuk string statis, double quote untuk interpolasi dan escape sequences, heredoc untuk multi-baris.
  • strip vs chompstrip membersihkan semua whitespace di kedua ujung, chomp hanya menghapus newline di akhir; keduanya punya kasus penggunaan yang berbeda.
  • sub vs gsubsub ganti kemunculan pertama, gsub ganti semua; gunakan block atau hash untuk transformasi dinamis dan penggantian multipel.
  • to_i tidak aman untuk validasi — gunakan Integer() dan Float() yang melempar exception jika input tidak valid.
  • match? lebih cepat dari match — gunakan match? jika hanya butuh true/false, simpan match untuk saat kamu butuh capture group.
  • Panjang karakter vs bytes — untuk string Unicode/multibyte, length memberi jumlah karakter, bytesize memberi jumlah bytes; keduanya berbeda untuk teks non-ASCII.
  • force_encoding bukan untuk perbaikan — hanya re-label encoding tanpa mengonversi data; gunakan encode untuk konversi sebenarnya.
  • Pipeline split → map → join adalah pola idiomatis Ruby untuk transformasi teks terstruktur yang mudah dibaca dan dipelihara.
  • Hindari konkatenasi string dalam loop — gunakan join atau << (shovel operator) daripada += yang membuat object baru setiap iterasi.

← Sebelumnya: Artikel   Berikutnya: IO →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact