Variabel #
Ruby adalah bahasa yang dynamically typed — kamu tidak perlu mendeklarasikan tipe data sebelum menggunakan variabel. Tapi Ruby bukan berarti tidak punya aturan tentang variabel. Justru sebaliknya: Ruby sangat opinionated tentang di mana sebuah variabel bisa diakses. Sistem scope variabel di Ruby ditandai secara visual melalui prefix nama variabel itu sendiri — sehingga hanya dengan melihat nama variabel, kamu sudah bisa tahu ruang lingkup aksesnya. Memahami sistem ini dengan baik adalah fondasi untuk menulis kode Ruby yang benar, terutama ketika mulai bekerja dengan kelas dan objek.
Sistem Scope dan Prefix Variabel #
Di Ruby, jenis variabel ditentukan oleh karakter pertama dari namanya. Ini bukan sekadar konvensi — interpreter Ruby membaca prefix ini untuk menentukan bagaimana variabel tersebut disimpan dan dari mana ia bisa diakses.
| Prefix | Jenis | Contoh | Scope |
|---|---|---|---|
Huruf kecil / _ | Variabel lokal | nama, _temp | Dalam method atau blok saat ini |
@ | Variabel instance | @nama | Dalam seluruh instance objek |
@@ | Variabel kelas | @@jumlah | Dalam kelas dan semua instance-nya |
$ | Variabel global | $mode | Di seluruh program |
| Huruf kapital | Konstanta | MAX, PI | Dalam kelas atau modul (dibahas di artikel Konstanta) |
flowchart TD
A[Nama variabel dimulai dengan apa?] --> B["Huruf kecil / _"]
A --> C["@"]
A --> D["@@"]
A --> E["$"]
A --> F["Huruf KAPITAL"]
B --> B1["Variabel Lokal\nHanya dalam method/blok ini"]
C --> C1["Variabel Instance\nSeluruh objek, per-instance"]
D --> D1["Variabel Kelas\nDibagi semua instance kelas"]
E --> E1["Variabel Global\nDi mana saja dalam program"]
F --> F1["Konstanta\nDibahas di artikel berikutnya"]Variabel Lokal #
Variabel lokal adalah jenis variabel yang paling sering kamu gunakan sehari-hari. Ia hanya bisa diakses dalam method, blok, atau proc tempat ia didefinisikan — tidak lebih, tidak kurang.
def hitung_cicilan(harga, dp_persen, tenor_bulan)
dp = harga * dp_persen # variabel lokal
sisa_bayar = harga - dp # variabel lokal
cicilan = sisa_bayar / tenor_bulan # variabel lokal
cicilan.round(2)
end
puts hitung_cicilan(120_000_000, 0.3, 36) # => 2333333.33
# Semua variabel lokal di atas tidak bisa diakses dari sini:
puts dp # => NameError: undefined local variable or method 'dp'
puts sisa_bayar # => NameError: undefined local variable or method 'sisa_bayar'
Scope Variabel Lokal di Dalam Blok #
Blok membuat scope baru untuk variabel lokal yang didefinisikan di dalamnya. Tapi variabel lokal yang sudah ada di luar blok tetap bisa diakses dari dalam blok — ini yang membedakannya dari scope di bahasa seperti Java atau C.
total = 0
produk = ["laptop", "mouse", "keyboard"]
harga = [12_000_000, 350_000, 450_000]
produk.each_with_index do |nama, i|
subtotal = harga[i] # variabel lokal baru di dalam blok
total += subtotal # mengakses 'total' dari scope luar — valid
puts "#{nama}: #{subtotal}"
end
puts "Total: #{total}" # => Total: 12800000
puts subtotal # => NameError: 'subtotal' hanya hidup di dalam blok
Block-local Variables #
Ruby menyediakan cara eksplisit untuk memastikan sebuah variabel di dalam blok tidak akan mengubah variabel dengan nama yang sama di scope luar — menggunakan tanda titik koma (;) dalam parameter blok.
nilai = 100
# ANTI-PATTERN: blok tanpa block-local variable
# variabel 'nilai' di dalam blok merujuk ke 'nilai' yang sama di luar
[1, 2, 3].each do |n|
nilai = n * 10 # ← ini mengubah 'nilai' di scope luar!
end
puts nilai # => 30 (bukan 100 seperti yang mungkin diharapkan)
# BENAR: deklarasikan sebagai block-local variable dengan ;
nilai = 100
[1, 2, 3].each do |n; nilai| # 'nilai' setelah ; adalah variabel lokal blok
nilai = n * 10 # ini TIDAK mengubah 'nilai' di luar
puts nilai
end
puts nilai # => 100 (tetap tidak berubah)
Multiple Assignment #
Ruby mendukung assignment beberapa variabel sekaligus dalam satu baris — fitur yang disebut parallel assignment atau destructuring.
# Assignment sederhana bersamaan
a, b, c = 1, 2, 3
puts "#{a}, #{b}, #{c}" # => 1, 2, 3
# Swap dua variabel — cara idiomatik Ruby, tanpa variabel sementara
x, y = 10, 20
x, y = y, x
puts "x=#{x}, y=#{y}" # => x=20, y=10
# Destructuring dari array
koordinat = [3.14, 2.71, 1.41]
pi, e, sqrt2 = koordinat
puts pi # => 3.14
puts e # => 2.71
# Splat operator untuk menangkap "sisa"
pertama, *sisanya = [1, 2, 3, 4, 5]
puts pertama.inspect # => 1
puts sisanya.inspect # => [2, 3, 4, 5]
*awal, terakhir = [1, 2, 3, 4, 5]
puts awal.inspect # => [1, 2, 3, 4]
puts terakhir # => 5
# Dari hash (Ruby 3.1+)
data = {nama: "Rina", kota: "Surabaya"}
nama, kota = data.values_at(:nama, :kota)
puts "#{nama} dari #{kota}" # => Rina dari Surabaya
Variabel Instance #
Variabel instance adalah variabel yang melekat pada objek tertentu. Ia hidup selama objek itu hidup, dan setiap objek dari kelas yang sama memiliki salinan variabel instance-nya masing-masing yang benar-benar terpisah.
class Rekening
def initialize(nomor, pemilik, saldo_awal = 0)
@nomor = nomor # variabel instance
@pemilik = pemilik # variabel instance
@saldo = saldo_awal # variabel instance
@riwayat = [] # variabel instance — array kosong per objek
end
def setor(jumlah)
@saldo += jumlah
@riwayat << {jenis: :setor, jumlah: jumlah, waktu: Time.now}
self # kembalikan objek untuk method chaining
end
def tarik(jumlah)
raise "Saldo tidak mencukupi" if jumlah > @saldo
@saldo -= jumlah
@riwayat << {jenis: :tarik, jumlah: jumlah, waktu: Time.now}
self
end
def info
"Rekening #{@nomor} | Pemilik: #{@pemilik} | Saldo: #{@saldo}"
end
end
rek1 = Rekening.new("001", "Andi", 500_000)
rek2 = Rekening.new("002", "Budi", 1_000_000)
rek1.setor(200_000).setor(100_000) # method chaining berkat return self
rek2.tarik(300_000)
puts rek1.info # => Rekening 001 | Pemilik: Andi | Saldo: 800000
puts rek2.info # => Rekening 002 | Pemilik: Budi | Saldo: 700000
# @saldo rek1 dan rek2 sepenuhnya terpisah
Nilai Default Variabel Instance #
Variabel instance yang belum diinisialisasi bernilai nil — bukan error. Ini bisa menjadi sumber bug yang halus jika tidak diantisipasi.
class Pengguna
def tampilkan_nama
# @nama belum tentu sudah diset — bisa nil
puts @nama
end
def set_nama(nama)
@nama = nama
end
end
p = Pengguna.new
p.tampilkan_nama # => nil (tidak error, tapi mungkin tidak diinginkan)
p.set_nama("Citra")
p.tampilkan_nama # => Citra
# BENAR: inisialisasi semua variabel instance di initialize
class Pengguna
def initialize(nama = nil)
@nama = nama
@email = nil
@aktif = false
@login_at = nil
end
def tampilkan_nama
@nama || "Pengguna Anonim" # fallback jika nil
end
end
Variabel Instance di Level Kelas #
Variabel instance tidak hanya bisa ada di level objek — kelas itu sendiri juga objek di Ruby, sehingga bisa punya variabel instance sendiri. Ini berbeda dari variabel kelas (@@).
class Konfigurasi
# @timeout adalah variabel instance milik KELAS (bukan instance)
@timeout = 30
@max_retry = 3
class << self
attr_accessor :timeout, :max_retry
end
end
puts Konfigurasi.timeout # => 30
Konfigurasi.timeout = 60
puts Konfigurasi.timeout # => 60
# Subkelas punya variabel instance-nya sendiri — tidak berbagi dengan induk
class KonfigurasiPremium < Konfigurasi
@timeout = 120 # terpisah dari Konfigurasi.timeout
end
puts Konfigurasi.timeout # => 60 (tidak berubah)
puts KonfigurasiPremium.timeout # => 120
Inilah keunggulan variabel instance di level kelas dibanding variabel kelas @@: ia tidak bocor ke subkelas.
Variabel Kelas #
Variabel kelas (@@) berbagi nilai di seluruh instance dari kelas yang sama, termasuk subkelasnya. Ini adalah karakteristik yang membuatnya berguna sekaligus berbahaya.
class Koneksi
@@jumlah_aktif = 0
@@batas_maks = 10
def initialize(host)
raise "Batas koneksi tercapai!" if @@jumlah_aktif >= @@batas_maks
@host = host
@@jumlah_aktif += 1
puts "Koneksi ke #{@host} dibuka. Aktif: #{@@jumlah_aktif}"
end
def tutup
@@jumlah_aktif -= 1
puts "Koneksi ke #{@host} ditutup. Aktif: #{@@jumlah_aktif}"
end
def self.jumlah_aktif
@@jumlah_aktif
end
def self.batas_maks
@@batas_maks
end
end
k1 = Koneksi.new("db-primary") # => Aktif: 1
k2 = Koneksi.new("db-replica") # => Aktif: 2
k3 = Koneksi.new("cache-server") # => Aktif: 3
puts Koneksi.jumlah_aktif # => 3
k1.tutup # => Aktif: 2
puts Koneksi.jumlah_aktif # => 2
Bahaya Variabel Kelas dengan Inheritance #
Perilaku variabel kelas yang paling sering menyebabkan bug adalah ia diwarisi — dan berbagi — dengan subkelas.
# ANTI-PATTERN: variabel kelas bocor ke subkelas
class Hewan
@@jumlah = 0
def initialize
@@jumlah += 1
end
def self.jumlah
@@jumlah
end
end
class Kucing < Hewan; end
class Anjing < Hewan; end
Kucing.new
Kucing.new
Anjing.new
puts Hewan.jumlah # => 3
puts Kucing.jumlah # => 3 (bukan 2 seperti mungkin diharapkan!)
puts Anjing.jumlah # => 3 (bukan 1 seperti mungkin diharapkan!)
# Semua subkelas berbagi @@jumlah yang sama dengan kelas induk
# BENAR: gunakan variabel instance di level kelas jika butuh state per-kelas
class Hewan
@jumlah = 0
def self.jumlah
@jumlah
end
def self.inherited(subkelas)
subkelas.instance_variable_set(:@jumlah, 0)
end
def initialize
self.class.instance_variable_set(
:@jumlah,
self.class.instance_variable_get(:@jumlah) + 1
)
end
end
class Kucing < Hewan; end
class Anjing < Hewan; end
Kucing.new
Kucing.new
Anjing.new
puts Hewan.jumlah # => 0 (hanya hewan yang dibuat langsung)
puts Kucing.jumlah # => 2
puts Anjing.jumlah # => 1
Variabel Global #
Variabel global diawali dengan $ dan bisa diakses dari mana saja dalam program — dalam method, kelas, modul, bahkan dari file yang berbeda setelah di-require.
$versi_aplikasi = "2.1.0"
$mode_debug = false
def info_sistem
puts "Versi: #{$versi_aplikasi}"
puts "Debug: #{$mode_debug}"
end
class Logger
def log(pesan)
puts "[#{$versi_aplikasi}] #{pesan}" if $mode_debug
end
end
info_sistem
Variabel Global Bawaan Ruby #
Ruby sendiri menggunakan variabel global untuk menyimpan informasi sistem yang penting. Ini adalah variabel global yang boleh digunakan karena memang dirancang untuk itu.
# $0 — nama file program yang sedang dijalankan
puts $0 # => variabel.rb (atau nama file yang dijalankan)
# $PROGRAM_NAME — alias yang lebih ekspresif untuk $0
puts $PROGRAM_NAME
# $LOAD_PATH ($:) — daftar direktori pencarian library
puts $LOAD_PATH.first(3).inspect
# $stdout, $stderr, $stdin — stream I/O standar
$stdout.puts "Ditulis ke stdout"
$stderr.puts "Ditulis ke stderr"
# $/ — separator baris (default: "\n")
# $\ — separator output (default: nil)
# $, — separator antar argumen puts (default: nil)
# $! — exception terakhir yang ditangkap
begin
raise "Contoh error"
rescue => e
puts $!.message # => Contoh error (sama dengan e.message)
end
# $~ — hasil match regex terakhir
"Ruby 3.3" =~ /(\d+\.\d+)/
puts $~[0] # => 3.3
puts $1 # => 3.3 ($1, $2, dst adalah capture groups)
Variabel global bawaan yang sering digunakan:
$0 / $PROGRAM_NAME → nama file yang dijalankan
$LOAD_PATH / $: → jalur pencarian library
$stdout / $stderr → stream output standar
$stdin → stream input standar
$! → exception yang sedang aktif
$@ → backtrace exception saat ini
$~ → MatchData hasil regex terakhir
$1 .. $9 → capture groups regex
$/ → input record separator
✗ Jangan buat variabel global sendiri kecuali benar-benar diperlukan
Variabel global buatan sendiri (bukan yang bawaan Ruby) hampir selalu bisa digantikan dengan solusi yang lebih baik: konstanta untuk nilai yang tidak berubah, variabel instance di level kelas untuk state per-kelas, atau parameter method untuk meneruskan nilai antar bagian kode. Variabel global membuat kode sulit diuji karena state-nya tersebar di seluruh program.
Shadowing Variabel #
Shadowing terjadi ketika variabel baru dengan nama yang sama dengan variabel di scope luar didefinisikan di scope yang lebih dalam, menutupi akses ke variabel aslinya.
nilai = 100
# Shadowing di dalam blok
[1, 2, 3].each do |nilai| # parameter 'nilai' meng-shadow variabel luar
puts nilai # mencetak 1, 2, 3 — bukan 100
end
puts nilai # => 100 (variabel luar tidak berubah)
Shadowing bisa disengaja atau tidak disengaja. Yang berbahaya adalah shadowing yang tidak disengaja — ketika kamu menggunakan nama yang sudah dipakai di scope luar tanpa sadar.
# ANTI-PATTERN: shadowing tidak disengaja
def proses_pesanan(pesanan)
total = 0
pesanan.each do |item|
harga = item[:harga]
jumlah = item[:jumlah]
total = harga * jumlah # ← ini MENIMPA total di scope luar!
# developer mungkin bermaksud: total += harga * jumlah
end
total # hanya berisi kalkulasi item terakhir
end
# BENAR: nama yang berbeda untuk scope yang berbeda
def proses_pesanan(pesanan)
total = 0
pesanan.each do |item|
harga = item[:harga]
jumlah = item[:jumlah]
subtotal = harga * jumlah # nama berbeda, tidak ada konflik
total += subtotal
end
total
end
Ruby akan mengeluarkan peringatan jika kamu mengaktifkan warning mode (ruby -w) dan ada shadowing yang terdeteksi:
ruby -w program.rb
# => warning: shadowing outer local variable - nilai
Aturan Penamaan dan Konvensi #
Ruby punya aturan penamaan variabel yang ketat (sintaksis) sekaligus konvensi yang kuat (komunitas).
Aturan Sintaksis #
# VALID — nama variabel yang diperbolehkan:
nama = "Rani"
nama_lengkap = "Rani Putri"
_temp = 42
__internal = true
nama2 = "alias"
# TIDAK VALID — akan menyebabkan SyntaxError:
# 2nama = "tidak boleh dimulai angka"
# nama-ku = "tanda hubung bukan underscore"
# nama depan = "tidak boleh ada spasi"
Konvensi Komunitas #
# snake_case untuk variabel dan method — WAJIB di Ruby
total_harga = 150_000
nama_pengguna = "Budi"
is_aktif = true # atau gunakan aktif? untuk boolean (di method)
# ANTI-PATTERN: gaya dari bahasa lain yang tidak idiomatik di Ruby
totalHarga = 150_000 # ✗ camelCase
NamaPengguna = "Budi" # ✗ PascalCase (dicadangkan untuk Kelas/Modul)
TOTAL_HARGA = 150_000 # ✗ SCREAMING_SNAKE_CASE (dicadangkan Konstanta)
# Prefix underscore untuk variabel yang sengaja tidak digunakan
hasil_array.each do |nilai, _index| # _index diabaikan, tapi eksplisit
puts nilai
end
# Atau underscore tunggal untuk "throw-away"
array.each_with_index do |elemen, _|
puts elemen
end
Penamaan yang Deskriptif #
# ANTI-PATTERN: nama terlalu singkat atau tidak bermakna
d = 0.1
p = 150_000
t = d * p
r = p - t
# BENAR: nama yang menjelaskan isi dan tujuan
diskon_persen = 0.1
harga_awal = 150_000
nilai_diskon = diskon_persen * harga_awal
harga_akhir = harga_awal - nilai_diskon
# ANTI-PATTERN: nama terlalu panjang dan redundan
nilai_dari_variabel_diskon_persen_yang_akan_dikalikan = 0.1
# BENAR: singkat tapi bermakna — cukup untuk memahami konteksnya
diskon = 0.1
Panduan panjang nama variabel:
✓ 1-2 kata untuk variabel iterasi singkat (i, n, key, val)
✓ 2-4 kata untuk variabel umum (total_harga, nama_pengguna)
✓ Lebih panjang jika konteks membutuhkan kejelasan
✗ Satu huruf kecuali dalam loop yang benar-benar trivial
✗ Singkatan yang ambigu (tmp, dat, cnt — gunakan temp, data, count)
✗ Nama yang menyesatkan (daftar yang sebenarnya Hash, dll)
Freeze — Variabel yang Tidak Bisa Diubah #
Di Ruby, kamu bisa “membekukan” nilai sebuah objek menggunakan method freeze. Setelah di-freeze, objek tidak bisa dimodifikasi.
kota = "Jakarta".freeze
kota.upcase # => "JAKARTA" — aman, membuat objek baru
kota << " Selatan" # => FrozenError: can't modify frozen String
# Berguna untuk string yang digunakan sebagai key atau identifier:
STATUS_AKTIF = "aktif".freeze
STATUS_NONAKTIF = "nonaktif".freeze
# Dengan magic comment frozen_string_literal:
# frozen_string_literal: true
# Semua string literal di file otomatis frozen
sequenceDiagram
participant K as Kode
participant O as Objek
participant M as Memori
K->>M: nama = "Andi"
M-->>O: Buat String "Andi" (mutable)
K->>O: nama << " Wijaya"
O-->>M: Modifikasi in-place → "Andi Wijaya"
K->>M: nama = "Andi".freeze
M-->>O: Buat String "Andi" (frozen)
K->>O: nama << " Wijaya"
O-->>K: FrozenError! Tidak bisa dimodifikasiRingkasan #
- Prefix menentukan scope — huruf kecil (lokal),
@(instance),@@(kelas),$(global). Cukup lihat nama variabelnya untuk tahu di mana ia bisa diakses.- Variabel lokal terbatas pada method/blok — ia tidak bisa diakses dari scope yang lebih luar, tapi ia bisa mengakses variabel dari scope yang lebih luar.
- Block-local variable dengan
;— gunakan|param; lokal|untuk mencegah variabel blok meng-shadow variabel luar secara tidak sengaja.- Multiple assignment dan splat — Ruby mendukung
a, b = 1, 2danpertama, *sisa = arrayuntuk destructuring yang ekspresif.- Variabel instance bernilai
niljika belum diinisialisasi — selalu inisialisasi semua variabel instance diinitializeuntuk menghindari bug halus.- Variabel kelas
@@bocor ke subkelas — gunakan variabel instance di level kelas (@vardalamclass << self) untuk state per-kelas yang terisolasi.- Hindari variabel global buatan sendiri — hampir selalu ada solusi yang lebih baik: konstanta, parameter, atau variabel instance kelas.
- snake_case adalah wajib — nama variabel lokal dan instance selalu snake_case. camelCase dan PascalCase dicadangkan untuk konteks lain.
- Nama yang deskriptif lebih penting dari yang singkat —
total_hargajauh lebih baik darit, kecuali konteksnya memang trivial.freezeuntuk nilai yang tidak boleh berubah — gunakan pada string konstanta dan aktifkan# frozen_string_literal: trueuntuk performa lebih baik.