Seleksi Kondisi #
Setiap program yang berguna membuat keputusan — menjalankan kode ini jika kondisi itu terpenuhi, melewati blok itu jika tidak. Ruby menyediakan beragam cara untuk mengekspresikan keputusan ini: dari if-elsif-else yang klasik, unless yang membaca seperti kalimat bahasa Inggris, hingga case/when yang jauh lebih powerful dari switch di bahasa lain karena bisa mencocokkan berdasarkan kelas, regex, range, dan bahkan kondisi kustom. Yang membuat seleksi kondisi di Ruby menarik adalah semuanya adalah ekspresi — artinya if, unless, dan case semuanya mengembalikan nilai dan bisa digunakan langsung dalam assignment. Memahami nuansa ini membuat kode Ruby terasa lebih ringkas dan ekspresif.
if — Bentuk Dasar #
if adalah fondasi seleksi kondisi. Blok kode di dalamnya hanya dieksekusi jika kondisi bernilai truthy.
stok = 5
if stok > 0
puts "Produk tersedia"
end
Karena if adalah ekspresi, ia mengembalikan nilai dari baris terakhir yang dieksekusi — atau nil jika kondisi tidak terpenuhi:
nilai = 85
pesan = if nilai >= 60
"Lulus"
end
puts pesan.inspect # => "Lulus"
# Jika kondisi tidak terpenuhi, if mengembalikan nil
pesan2 = if nilai < 60
"Tidak lulus"
end
puts pesan2.inspect # => nil
Karena if mengembalikan nilai, kamu bisa menggunakannya langsung dalam assignment — sebuah idiom yang sering terlihat di kode Ruby yang bersih:
# ANTI-PATTERN: assignment di setiap cabang — repetitif
grade = nil
if nilai >= 90
grade = "A"
elsif nilai >= 80
grade = "B"
elsif nilai >= 70
grade = "C"
else
grade = "D"
end
# BENAR: if sebagai ekspresi — satu assignment, bersih
grade = if nilai >= 90 then "A"
elsif nilai >= 80 then "B"
elsif nilai >= 70 then "C"
else "D"
end
if-elsif-else — Percabangan Bertingkat #
Gunakan elsif (perhatikan: bukan elseif atau else if) untuk menambahkan cabang kondisi tambahan:
def kategori_usia(umur)
if umur < 0
raise ArgumentError, "Umur tidak boleh negatif"
elsif umur < 13
"anak-anak"
elsif umur < 18
"remaja"
elsif umur < 60
"dewasa"
else
"lansia"
end
end
puts kategori_usia(8) # => "anak-anak"
puts kategori_usia(15) # => "remaja"
puts kategori_usia(35) # => "dewasa"
puts kategori_usia(65) # => "lansia"
Ruby mengevaluasi setiap kondisi dari atas ke bawah dan berhenti di cabang pertama yang terpenuhi — cabang berikutnya tidak diperiksa lagi. Ini berarti urutan elsif penting:
# ANTI-PATTERN: urutan kondisi yang salah — kondisi lebih luas di atas
def level_diskon(total)
if total > 100_000 # ini selalu true jika total > 500_000 juga true
"Bronze - 5%"
elsif total > 500_000 # tidak akan pernah tercapai untuk total > 500_000!
"Gold - 15%"
elsif total > 1_000_000
"Platinum - 20%"
end
end
# BENAR: kondisi paling spesifik (paling ketat) di atas
def level_diskon(total)
if total > 1_000_000
"Platinum - 20%"
elsif total > 500_000
"Gold - 15%"
elsif total > 100_000
"Bronze - 5%"
else
"Tanpa diskon"
end
end
unless — Kebalikan if #
unless mengeksekusi blok kode ketika kondisinya tidak terpenuhi — ia adalah if not yang lebih mudah dibaca dalam banyak situasi.
stok_habis = false
unless stok_habis
puts "Silakan tambahkan ke keranjang"
end
# Ekuivalen dengan:
if !stok_habis
puts "Silakan tambahkan ke keranjang"
end
unless paling kuat ketika kondisi yang diperiksa lebih mudah dirumuskan secara negatif:
# unless membaca lebih natural dari if !
unless user.terblokir?
izinkan_login(user)
end
unless saldo < minimum_saldo
proses_penarikan(jumlah)
end
unless ENV["RAILS_ENV"] == "production"
puts "[DEBUG] Mode development aktif"
end
Jangan gunakanunlessdengan kondisi yang mengandung negasi atau operator||dan&&— hasilnya sulit dibaca dan rawan salah baca. Aturan sederhana: jika kamu butuhunlessdengan||atau&&, gunakanifsaja dengan kondisi yang dirumuskan ulang secara positif.
# ANTI-PATTERN: unless dengan negasi — dua negasi dalam satu ekspresi
unless !user.aktif? # artinya: "jika user aktif" — pakai if saja!
proses(user)
end
# ANTI-PATTERN: unless dengan ||
unless a.nil? || b.nil? # membingungkan — sulit diparsing di kepala
gabungkan(a, b)
end
# BENAR: tulis ulang dengan if
if user.aktif?
proses(user)
end
if a && b
gabungkan(a, b)
end
Modifier Postfix — if dan unless Satu Baris #
Ruby memungkinkan penulisan if dan unless di akhir baris — setelah kode yang ingin dikondisikan. Ini disebut modifier postfix dan merupakan salah satu idiom Ruby yang paling ekspresif.
# Modifier if
puts "Akses diizinkan" if user.admin?
kirim_email(user) if user.email_terverifikasi?
log.warn("Saldo rendah") if saldo < batas_minimum
# Modifier unless
redirect_to(root_path) unless user.login?
raise ArgumentError, "Nama tidak boleh kosong" unless nama.present?
Modifier postfix paling tepat digunakan untuk:
Kapan gunakan modifier postfix:
✓ Guard clause — menolak kondisi tidak valid di awal method
✓ Ekspresi satu baris yang tidak perlu else
✓ Kode yang membaca natural dari kiri ke kanan
✓ Logging dan debugging yang opsional
✗ Kondisi yang butuh blok multi-baris
✗ Kondisi kompleks yang menyulitkan pembacaan kiri ke kanan
✗ Ketika ada else — gunakan if-else biasa
# ANTI-PATTERN: logika kompleks dalam modifier postfix
simpan_data(proses(transformasi(input))) if valid?(input) && !locked? && user.punya_izin?(:tulis)
# BENAR: pecah jika kondisi kompleks
return unless valid?(input)
return if locked?
return unless user.punya_izin?(:tulis)
simpan_data(proses(transformasi(input)))
Guard Clause dan Early Return #
Guard clause adalah pola penggunaan if/unless modifier di awal method untuk menangani kasus-kasus tepi (edge case) sesegera mungkin, sebelum masuk ke logika utama. Ini adalah salah satu pola yang paling dianjurkan di Ruby karena mengurangi nesting dan membuat logika utama tetap bersih.
# ANTI-PATTERN: nesting dalam yang membuat logika utama terkubur
def proses_pembayaran(user, order, kartu)
if user
if user.aktif?
if order
if order.belum_dibayar?
if kartu && kartu.valid?
# logika utama di sini — 5 level dalam!
charge(kartu, order.total)
order.tandai_lunas
kirim_notifikasi(user, order)
else
"Kartu tidak valid"
end
else
"Order sudah dibayar"
end
else
"Order tidak ditemukan"
end
else
"User tidak aktif"
end
else
"User tidak ditemukan"
end
end
# BENAR: guard clause — tangani kasus tepi di atas, logika utama di bawah
def proses_pembayaran(user, order, kartu)
return "User tidak ditemukan" unless user
return "User tidak aktif" unless user.aktif?
return "Order tidak ditemukan" unless order
return "Order sudah dibayar" unless order.belum_dibayar?
return "Kartu tidak valid" unless kartu&.valid?
# logika utama — tidak ada nesting, mudah dibaca
charge(kartu, order.total)
order.tandai_lunas
kirim_notifikasi(user, order)
"Pembayaran berhasil"
end
flowchart TD
A[Masuk ke method] --> B{user ada?}
B -- Tidak --> R1["return: User tidak ditemukan"]
B -- Ya --> C{user.aktif?}
C -- Tidak --> R2["return: User tidak aktif"]
C -- Ya --> D{order ada?}
D -- Tidak --> R3["return: Order tidak ditemukan"]
D -- Ya --> E{order belum dibayar?}
E -- Tidak --> R4["return: Order sudah dibayar"]
E -- Ya --> F{kartu valid?}
F -- Tidak --> R5["return: Kartu tidak valid"]
F -- Ya --> G[Logika utama\ncharge, tandai lunas, notifikasi]
G --> H["return: Pembayaran berhasil"]case/when — Pattern Matching yang Powerful #
case/when di Ruby jauh lebih powerful dari switch/case di kebanyakan bahasa lain. when menggunakan operator === (triple equals) untuk pencocokan — dan setiap kelas bisa mendefinisikan ===-nya sendiri. Artinya when bisa mencocokkan berdasarkan nilai, kelas, range, regex, Proc, dan lebih banyak lagi.
Pencocokan Nilai Dasar #
hari = "Rabu"
pesan = case hari
when "Senin", "Selasa", "Rabu", "Kamis", "Jumat"
"Hari kerja"
when "Sabtu", "Minggu"
"Hari libur"
else
"Hari tidak dikenal"
end
puts pesan # => "Hari kerja"
Pencocokan Berdasarkan Range #
nilai = 78
grade = case nilai
when 90..100 then "A — Sangat Baik"
when 80..89 then "B — Baik"
when 70..79 then "C — Cukup"
when 60..69 then "D — Kurang"
else "E — Tidak Lulus"
end
puts grade # => "C — Cukup"
Pencocokan Berdasarkan Kelas #
Ini adalah fitur case/when yang paling membedakan Ruby dari bahasa lain — when bisa mencocokkan berdasarkan tipe objek:
def deskripsikan(nilai)
case nilai
when Integer then "Integer: #{nilai}"
when Float then "Float: #{nilai}"
when String then "String: \"#{nilai}\""
when Array then "Array dengan #{nilai.length} elemen"
when Hash then "Hash dengan #{nilai.size} kunci"
when Symbol then "Symbol: :#{nilai}"
when NilClass then "Nilai kosong (nil)"
when TrueClass, FalseClass then "Boolean: #{nilai}"
else "Tipe tidak dikenal: #{nilai.class}"
end
end
puts deskripsikan(42) # => Integer: 42
puts deskripsikan(3.14) # => Float: 3.14
puts deskripsikan("halo") # => String: "halo"
puts deskripsikan([1, 2, 3]) # => Array dengan 3 elemen
puts deskripsikan(nil) # => Nilai kosong (nil)
puts deskripsikan(true) # => Boolean: true
Pencocokan Berdasarkan Regex #
def klasifikasikan_input(teks)
case teks
when /\A\d+\z/
"Angka bulat: #{teks.to_i}"
when /\A\d+\.\d+\z/
"Angka desimal: #{teks.to_f}"
when /\A[\w.+-]+@[\w-]+\.[a-z]{2,}\z/i
"Alamat email: #{teks}"
when /\Ahttps?:\/\//
"URL: #{teks}"
when /\A(\+62|0)\d{9,12}\z/
"Nomor telepon Indonesia: #{teks}"
else
"Teks biasa: #{teks}"
end
end
puts klasifikasikan_input("42") # => Angka bulat: 42
puts klasifikasikan_input("[email protected]") # => Alamat email: ...
puts klasifikasikan_input("https://ruby-lang.org") # => URL: ...
puts klasifikasikan_input("08123456789") # => Nomor telepon Indonesia: ...
case tanpa Ekspresi — Pengganti if-elsif #
case bisa digunakan tanpa ekspresi setelahnya, sehingga berperan sebagai pengganti if-elsif yang lebih bersih:
def periksa_kondisi(suhu, kelembaban, angin)
case
when suhu > 38 && kelembaban > 80
"Bahaya: panas ekstrem dan lembab"
when suhu > 35
"Peringatan: suhu sangat tinggi"
when angin > 80
"Peringatan: angin kencang"
when suhu < 10
"Peringatan: suhu sangat rendah"
else
"Kondisi normal"
end
end
Capture dengan then dan Variabel Lokal #
Sejak Ruby 3.x, case/in (pattern matching) memungkinkan ekstraksi nilai dari struktur data secara langsung:
# Pattern matching dengan case/in (Ruby 3.0+)
respons = { status: 200, data: { user: { nama: "Rani", role: :admin } } }
case respons
in { status: 200, data: { user: { nama: String => nama, role: :admin } } }
puts "Admin login: #{nama}"
in { status: 200, data: { user: { nama: String => nama } } }
puts "User login: #{nama}"
in { status: 401 }
puts "Tidak terotorisasi"
in { status: 404 }
puts "Tidak ditemukan"
in { status: (500..) }
puts "Server error"
end
# => "Admin login: Rani"
# Pattern matching untuk Array
koordinat = [10, 20, 30]
case koordinat
in [x, y]
puts "2D: (#{x}, #{y})"
in [x, y, z]
puts "3D: (#{x}, #{y}, #{z})"
end
# => "3D: (10, 20, 30)"
Ternary Operator #
Operator ternary (? :) adalah bentuk ringkas if-else untuk ekspresi satu baris yang mengembalikan nilai:
umur = 20
status = umur >= 18 ? "dewasa" : "anak-anak"
puts status # => "dewasa"
# Penggunaan idiomatik
label = saldo >= 0 ? "Kredit" : "Debit"
warna = error? ? :merah : :hijau
prefix = jumlah == 1 ? "item" : "items"
# ANTI-PATTERN: ternary untuk efek samping
kondisi ? puts("ya") : puts("tidak")
# ANTI-PATTERN: ternary bertumpuk
hasil = a > 0 ? (b > 0 ? "++ positif" : "+- campuran") : "negatif"
# BENAR: ternary hanya untuk ekspresi yang mengembalikan nilai
label = aktif? ? "Aktif" : "Nonaktif"
# BENAR: if-elsif untuk kondisi bertingkat
hasil = if a > 0 && b > 0
"keduanya positif"
elsif a > 0
"hanya a positif"
else
"a tidak positif"
end
then — Penulisan Kompak #
Keyword then memungkinkan penulisan if dan when dalam satu baris tanpa harus menggunakan newline sebagai separator:
# Dengan then di if
if x > 0 then puts "positif" end
# Lebih sering digunakan di case/when untuk satu baris:
case status
when :aktif then "Pengguna aktif"
when :pending then "Menunggu verifikasi"
when :suspend then "Akun ditangguhkan"
else "Status tidak dikenal"
end
Seleksi Kondisi sebagai Ekspresi dalam Konteks Lain #
Karena if, unless, dan case adalah ekspresi, mereka bisa digunakan di mana saja yang menerima nilai — termasuk sebagai argumen method, nilai hash, dan elemen array:
# Sebagai argumen method langsung
log.info(if debug? then "Mode debug aktif" else "Mode produksi" end)
# Dalam hash
config = {
timeout: if production? then 30 else 5 end,
log_level: (debug? ? :debug : :info)
}
# Return value method — Ruby otomatis mengembalikan ekspresi terakhir
def status_label(kode)
case kode
when 200 then "OK"
when 201 then "Created"
when 400 then "Bad Request"
when 401 then "Unauthorized"
when 404 then "Not Found"
when 500 then "Internal Server Error"
else "Unknown (#{kode})"
end
# tidak perlu 'return' — case mengembalikan nilai terakhirnya
end
puts status_label(404) # => "Not Found"
puts status_label(999) # => "Unknown (999)"
Memilih Konstruk yang Tepat #
flowchart TD
A[Perlu seleksi kondisi] --> B{Berapa banyak cabang?}
B --> C["Satu cabang\n(hanya jika true)"]
B --> D["Dua cabang\n(if dan else)"]
B --> E["Banyak cabang"]
C --> F{Kondisi positif\natau negatif?}
F --> G["Positif → if modifier\nputs x if kondisi"]
F --> H["Negatif → unless modifier\nputs x unless kondisi"]
D --> I{Satu baris\natau multi-baris?}
I --> J["Satu baris → ternary\nkondisi ? a : b"]
I --> K["Multi-baris → if-else"]
E --> L{Cocokkan nilai,\nkelas, atau range?}
L --> M["Ya → case/when"]
L --> N["Tidak → if-elsif-else"]
E --> O["Guard clauses di awal\nmethod → early return"]Panduan cepat memilih konstruk:
if modifier postfix → satu cabang, kondisi sederhana, satu baris
unless modifier postfix → satu cabang, kondisi negatif, satu baris
if-else → dua cabang, ekspresi atau multi-baris
unless-else → dua cabang, ketika kondisi negatif lebih natural
ternary ? : → dua cabang, satu baris, mengembalikan nilai
if-elsif-else → banyak cabang, kondisi bebas
case/when nilai → banyak cabang, cocokkan nilai, kelas, range, regex
Guard clause + return → validasi awal sebelum logika utama
Ringkasan #
if,unless, dancaseadalah ekspresi — semuanya mengembalikan nilai dan bisa digunakan langsung dalam assignment, bukan hanya sebagai statement.- Urutan
elsifmenentukan hasilnya — letakkan kondisi paling spesifik (paling ketat) di atas untuk menghindari kondisi yang lebih luas “memakan” kasus yang seharusnya jatuh ke kondisi di bawahnya.unlessuntuk kondisi negatif yang natural — tapi hindariunlessdengan||,&&, atau negasi di dalamnya karena membingungkan.- Modifier postfix untuk satu baris tanpa
else—puts x if kondisidanreturn unless valid?adalah idiom Ruby yang sangat umum dan terbaca natural.- Guard clause + early return mengurangi nesting — tangani semua kasus tepi di awal method dengan
return unless, biarkan logika utama tidak bersarang.case/whenmenggunakan===— bukan==, sehingga bisa mencocokkan berdasarkan kelas (Integer), range (1..10), regex (/\d+/), dan nilai sekaligus.casetanpa ekspresi — bisa digunakan sebagai penggantiif-elsifyang lebih bersih untuk kondisi yang tidak cocok dengan satu variabel.- Pattern matching
case/in— fitur Ruby 3.x yang memungkinkan ekstraksi (destructuring) data dari Hash dan Array langsung dalamwhen.- Ternary hanya untuk dua cabang satu baris — jangan tumpuk ternary, jangan gunakan untuk efek samping tanpa nilai kembalian.