πŸ’Ž Ruby

Ruby untuk Pemula: Panduan Lengkap

Tutorial lengkap belajar Ruby dari nol β€” instalasi, variabel, tipe data, operator, kontrol alur, block, iterator, class, module, dan quiz interaktif dengan contoh kode praktis

1. Pengenalan Ruby

Ruby adalah bahasa pemrograman tingkat tinggi yang dibuat oleh Yukihiro Matsumoto (biasa dipanggil "Matz") dan pertama kali dirilis pada tahun 1995. Ruby dirancang dengan filosofi bahwa pemrograman harus menyenangkan dan produktif β€” Matz ingin menciptakan bahasa yang tidak hanya fungsional tetapi juga membahagiakan untuk digunakan.

Ruby dikenal dengan sintaksnya yang elegan dan intuitif, menjadikannya salah satu bahasa yang paling mudah dipelajari. Ruby digunakan secara luas untuk pengembangan web (terutama dengan framework Ruby on Rails), scripting, otomasi, dan berbagai aplikasi backend. Matz menginspirasi banyak bahasa modern seperti Python, JavaScript, dan Go.

Mengapa Memilih Ruby?

Keunggulan Penjelasan
Sintaks EleganRuby dirancang agar terbaca seperti bahasa Inggris, sangat natural dan menyenangkan
Object-Oriented SepenuhnyaSemua data di Ruby adalah objek, termasuk angka dan string
Block & IteratorFitur powerful untuk menulis kode yang ringkas dan ekspresif
Ruby on RailsFramework web terkenal yang memungkinkan pembuatan aplikasi web dengan cepat
Gems (Package)Ekosistem ribuan library yang siap digunakan melalui RubyGems
Open Source & GratisDilisensikan di bawah Ruby License, bebas digunakan untuk tujuan apapun

Ruby vs Bahasa Lain

Aspek Ruby Python JavaScript
TipeInterpretedInterpretedInterpreted (JIT)
ParadigmaOO SepenuhnyaMulti-paradigmaMulti-paradigma
Sintaks🟒 Elegan & Ekspresif🟒 Sederhana🟑 Sedang
Web FrameworkRails (sangat produktif)Django, FlaskExpress, Next.js
Cocok untukWeb Dev, Startup, ScriptingAI, Data ScienceFrontend & Fullstack
Diagram: Ekosistem Ruby
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   EKOSISTEM RUBY                      β”‚
β”‚                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  Web Dev  β”‚  β”‚ CLI Toolsβ”‚  β”‚  Testing &       β”‚    β”‚
β”‚  β”‚  Rails    β”‚  β”‚ Thor     β”‚  β”‚  Development     β”‚    β”‚
β”‚  β”‚  Sinatra  β”‚  β”‚ GLI      β”‚  β”‚  RSpec, Minitest β”‚    β”‚
β”‚  β”‚  Hanami   β”‚  β”‚ Rake     β”‚  β”‚  RuboCop         β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚ Data     β”‚  β”‚ DevOps   β”‚  β”‚  Gems &          β”‚    β”‚
β”‚  β”‚ Processingβ”‚ β”‚ Capistranoβ”‚  β”‚  Libraries       β”‚    β”‚
β”‚  β”‚ ActiveRecordβ”‚ Ansible  β”‚  β”‚  RubyGems.org    β”‚    β”‚
β”‚  β”‚ Sequel   β”‚  β”‚ Chef     β”‚  β”‚  Bundler         β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. Instalasi Ruby

Ruby bisa diinstal di semua sistem operasi utama. Cara yang direkomendasikan adalah menggunakan version manager seperti Rbenv atau rbenv untuk mengelola beberapa versi Ruby.

Instalasi di Windows

Windows
# 1. Kunjungi https://rubyinstaller.org/
# 2. Download Ruby+Devkit versi terbaru (Ruby 3.3+)
# 3. Jalankan installer
# 4. Setelah instalasi, jalankan ridk install untuk menginstal devkit

# Verifikasi instalasi di Command Prompt / PowerShell:
ruby --version
# Output: ruby 3.3.3 (2024-06-12 revision e12f4fa7f9) [x64-mingw-ucrt]

gem --version
# Output: 3.5.14

# Instal bundler untuk dependency management:
gem install bundler

Instalasi di macOS

macOS
# Menggunakan Homebrew:
brew install rbenv ruby-build

# Setup rbenv di shell (~/.zshrc atau ~/.bash_profile):
echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc
source ~/.zshrc

# Instal Ruby versi terbaru:
rbenv install 3.3.3
rbenv global 3.3.3

# Verifikasi:
ruby --version
# Output: ruby 3.3.3 (2024-06-12 revision e12f4fa7f9)

# Ruby sudah terpasang di macOS secara default (Ruby 2.x),
# tapi disarankan menggunakan rbenv untuk versi terbaru

Instalasi di Linux (Ubuntu/Debian)

Linux
# Install dependencies terlebih dahulu
sudo apt update && sudo apt install -y build-essential libssl-dev libreadline-dev zlib1g-dev

# Menggunakan rbenv (recommended):
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash
echo 'eval "$(rbenv init - bash)"' >> ~/.bashrc
source ~/.bashrc

# Instal Ruby:
rbenv install 3.3.3
rbenv global 3.3.3

# Atau gunakan apt (lebih cepat tapi versi mungkin lebih lama):
# sudo apt install ruby-full

# Verifikasi:
ruby --version
gem --version

Program Ruby Pertama

Ruby β€” hello.rb
# File: hello.rb
puts "Halo, dunia!"
puts "Selamat datang di Ruby!"
puts "Nama saya: BeebaneLabs"

# Menjalankan dari terminal:
# ruby hello.rb
#
# Output:
# Halo, dunia!
# Selamat datang di Ruby!
# Nama saya: BeebaneLabs

Ruby Interactive Console (IRB)

IRB
# Buka terminal, ketik "irb" atau "ruby" untuk masuk console interaktif
# irb(main):001:0> puts "Hello!"
# Hello!
# => nil
# irb(main):002:0> 2 + 3
# => 5
# irb(main):003:0> "Ruby" * 3
# => "RubyRubyRuby"
# irb(main):004:0> exit  # Keluar dari IRB
πŸ’‘ Tips

Gunakan rbenv atau rvm sebagai version manager untuk mengelola beberapa versi Ruby di satu komputer. Sangat berguna saat bekerja di proyek yang membutuhkan versi Ruby berbeda. Jangan lupa instal Bundler (gem install bundler) untuk mengelola dependency Ruby.

3. Variabel dan Tipe Data

Variabel di Ruby digunakan untuk menyimpan data. Ruby menggunakan dynamic typing, sehingga Anda tidak perlu mendeklarasikan tipe data secara eksplisit. Ruby memiliki konvensi penamaan yang unik berdasarkan scope variabel.

Jenis Scope Variabel di Ruby

Jenis Penanda Scope Contoh
Local VariableTanpa penandaMethod/block yang mendeklarasikannyanama = "Budi"
Instance Variable@Instance dari class@umur = 25
Class Variable@@Semua instance class@@jumlah = 0
Global Variable$Seluruh program$app_name = "MyApp"
ConstantHuruf besar awalKonstan, tidak boleh diubahPI = 3.14159

Mendeklarasikan Variabel

Ruby β€” Variabel
# Local variables β€” tanpa penanda apapun
nama = "Budi Santoso"       # string
umur = 25                    # integer
tinggi = 175.5               # float
is_active = true             # boolean

# Menampilkan nilai variabel
puts nama                     # Budi Santoso
puts "Umur: #{umur} tahun"   # Umur: 25 tahun (string interpolation)

# Ruby mendeteksi tipe secara otomatis
x = 10                # x adalah Integer
x = "sekarang string"  # x sekarang String β€” tidak masalah!

# Constant β€” dimulai dengan huruf besar
PI = 3.14159
MAX_SIZE = 100
puts PI    # 3.14159
# PI = 3.0  # Warning: PIε·²θ’«θ΅‹ε€Ό

# Aturan penamaan:
# βœ… nama_lengkap, umur_pengguna, _private_var, var_name2
# ❌ 2angka, nama-lengkap, my var (ada spasi)
# ⚠️ Nama class harus huruf besar: MyClass

Tipe Data Dasar

Tipe Contoh Penjelasan
Integer42, -10, 0Bilangan bulat
Float3.14, -0.5, 2.0Bilangan desimal
String"hello", 'world'String / teks
Symbol:nama, :statusSimbol unik, efisien untuk identifier
Booleantrue, falseNilai benar atau salah
nilnilTipe kosong (null)
Array[1, 2, 3]Kumpulan data terurut
Hash{a: 1, b: 2}Key-value pairs
Range1..10, 'a'..'z'Serangkaian nilai

Konversi Tipe Data

Ruby β€” Konversi Tipe
# Konversi tipe data
angka_str = "42"
angka_int = angka_str.to_i     # String β†’ Integer: 42
angka_float = angka_str.to_f   # String β†’ Float: 42.0

# Integer β†’ String
umur = 25
umur_str = umur.to_s           # "25"

# Float β†’ Integer
pi = 3.14159
pi_int = pi.to_i               # 3

# Ke Symbol
status = :active               # Symbol

# Cek tipe data dengan .class
puts 42.class          # Integer
puts "hello".class     # String
puts 3.14.class        # Float
puts true.class        # TrueClass
puts nil.class         # NilClass
puts :test.class       # Symbol
puts [1,2,3].class     # Array

# Cek dengan .is_a?
puts 42.is_a?(Integer)   # true
puts "hi".is_a?(String)  # true

# Input dari pengguna
print "Masukkan nama: "
nama = gets.chomp          # .chomp menghapus newline
print "Masukkan umur: "
umur = gets.chomp.to_i     # Konversi ke integer

4. Operator

Ruby menyediakan berbagai jenis operator untuk melakukan operasi terhadap data.

Operator Aritmatika

Ruby β€” Aritmatika
# Operator aritmatika dasar
a, b = 15, 4

puts a + b    # 19    (penjumlahan)
puts a - b    # 11    (pengurangan)
puts a * b    # 60    (perkalian)
puts a / b    # 3     (pembagian bulat jika kedua integer)
puts a.to_f / b  # 3.75  (pembagian desimal)
puts a % b    # 3     (modulus/sisa bagi)
puts a ** b   # 50625 (pangkat: 15^4)

# Shortcut assignment
x = 10
x += 5   # x = x + 5 β†’ 15
x -= 3   # x = x - 3 β†’ 12
x *= 2   # x = x * 2 β†’ 24
x /= 4   # x = x / 4 β†’ 6

# Integer division vs Float division
puts 7 / 2      # 3   (integer division)
puts 7.0 / 2    # 3.5 (float division)
puts 7.to_f / 2 # 3.5

Operator Perbandingan dan Logika

Ruby β€” Perbandingan
# Operator perbandingan β€” menghasilkan true/false
x, y = 10, 20

puts x == y   # false (sama dengan)
puts x != y   # true  (tidak sama)
puts x > y    # false (lebih besar)
puts x < y    # true  (lebih kecil)
puts x >= 10  # true  (lebih besar atau sama)
puts x <= 5   # false (lebih kecil atau sama)

# Operator spaceship (<=>) β€” unik di Ruby
# Mengembalikan -1, 0, atau 1
puts 5 <=> 3     # 1  (5 lebih besar)
puts 3 <=> 3     # 0  (sama)
puts 2 <=> 5     # -1 (2 lebih kecil)

# Operator logika
umur = 25
punya_sim = true

# AND β€” kedua kondisi harus true
bisa_mengemudi = umur >= 17 && punya_sim
puts bisa_mengemudi  # true

# OR β€” salah satu kondisi harus true
puts umur < 18 || punya_sim  # true

# NOT β€” membalik nilai boolean
puts !punya_sim  # false

# Operator keanggotaan
buah = ["apel", "mangga", "jeruk"]
puts buah.include?("apel")    # true
puts buah.include?("pisang")  # false

5. Kontrol Alur

Kontrol alur memungkinkan Anda mengatur bagaimana program dieksekusi berdasarkan kondisi tertentu.

If/Unless

Ruby β€” If/Unless
# if / elsif / else
umur = 20

if umur >= 21
  puts "Anda dewasa penuh"
elsif umur >= 17
  puts "Anda remaja hampir dewasa"
elsif umur >= 13
  puts "Anda remaja"
else
  puts "Anda masih anak-anak"
end
# Output: Anda remaja hampir dewasa

# One-line if (modifier)
puts "Halo!" if umur >= 18
# Output: Halo!

# unless (kebalikan dari if)
unless umur < 18
  puts "Anda boleh masuk"
end

# unless modifier
puts "Selamat datang" unless umur < 18

Case Statement

Ruby β€” Case
# case / when β€” seperti switch case yang powerful
hari = "Senin"

case hari
when "Senin"
  puts "Hari Senin β€” semangat!"
when "Selasa", "Rabu", "Kamis"
  puts "Hari kerja biasa"
when "Jumat"
  puts "Jumat β€” hari yang menyenangkan!"
when "Sabtu", "Minggu"
  puts "Weekend β€” waktunya libur!"
else
  puts "Hari tidak valid"
end

# case dengan range dan regex
suhu = 35
status = case suhu
         when 0..15    then "Dingin πŸ₯Ά"
         when 16..25   then "Sejuk 😊"
         when 26..35   then "Panas πŸ₯΅"
         else               "Ekstrem πŸ”₯"
         end
puts status  # Panas πŸ₯΅

# case tanpa argument (pattern matching style)
input = "abc123"
case input
when /^\d+$/   then "Angka saja"
when /^[a-z]+$/ then "Huruf saja"
when /^\w+$/   then "Alphanumeric"
else                "Campuran"
end

Looping

Ruby β€” Loop
# while loop
i = 1
while i <= 5
  puts "Iterasi ke-#{i}"
  i += 1
end

# until loop (kebalikan while)
i = 1
until i > 5
  puts "Iterasi ke-#{i}"
  i += 1
end

# for loop
for i in 1..5
  puts "Angka: #{i}"
end

# times loop
5.times do |i|
  puts "times ke-#{i}"
end

# upto / downto
1.upto(3) do |i|
  puts "Up: #{i}"
end

5.downto(1) do |i|
  puts "Down: #{i}"
end

# each pada Range
(1..5).each { |i| puts i }

# break dan next
3.times do |i|
  break if i == 2    # keluar dari loop
  puts "i=#{i}"
end

3.times do |i|
  next if i == 1     # skip iterasi ini
  puts "i=#{i}"
end

# loop dengan break (do-while style)
counter = 0
loop do
  counter += 1
  puts "Counter: #{counter}"
  break if counter >= 5
end

6. Method dan Fungsi

Method di Ruby didefinisikan dengan kata kunci def. Method bisa menerima parameter, mengembalikan nilai, dan bahkan menggunakan parameter default.

Method Dasar

Ruby β€” Method
# Method sederhana
def sapa
  puts "Halo, selamat datang!"
end

sapa  # Memanggil method
sapa()  # Juga valid

# Method dengan parameter
def sapa nama
  puts "Halo, #{nama}!"
end

sapa "Budi"      # Halo, Budi!
sapa("Rina")     # Halo, Rina!

# Method dengan multiple parameter
def tambah a, b
  return a + b  # return bisa ditulis atau tidak
end

hasil = tambah(5, 3)
puts hasil  # 8

# Nilai kembalian β€” Ruby otomatis mengembalikan ekspresi terakhir
def kali a, b
  a * b  # Tidak perlu kata kunci return!
end

puts kali(4, 3)  # 12

# Parameter default
def sapa_lengkap nama, greeting = "Halo"
  puts "#{greeting}, #{nama}!"
end

sapa_lengkap "Budi"            # Halo, Budi!
sapa_lengkap "Budi", "Selamat pagi"  # Selamat pagi, Budi!

# Keyword arguments
def buat_user(nama:, umur:, email:)
  puts "User: #{nama}, Umur: #{umur}, Email: #{email}"
end

buat_user(nama: "Budi", umur: 25, email: "budi@email.com")

# Variable-length arguments (* splat)
def hitung_total(*angka)
  angka.sum
end

puts hitung_total(1, 2, 3, 4, 5)  # 15

# Method dengan block
def proses_data(data)
  data.each do |item|
    yield(item)  # yield memanggil block yang diberikan
  end
end

proses_data([1, 2, 3]) do |item|
  puts "Memproses: #{item * 2}"
end

Accessor Methods

Ruby β€” Accessors
# Membuat getter dan setter manual
class Person
  def initialize(nama)
    @nama = nama
  end

  # Getter
  def nama
    @nama
  end

  # Setter
  def nama=(value)
    @nama = value
  end
end

p = Person.new("Budi")
puts p.nama      # Budi
p.nama = "Rina"  # Setter
puts p.nama      # Rina

# Menggunakan attr_accessor, attr_reader, attr_writer
class Student
  attr_accessor :nama, :umur       # getter + setter
  attr_reader :id                  # getter saja
  attr_writer :grade               # setter saja

  def initialize(nama, umur, id)
    @nama = nama
    @umur = umur
    @id = id
  end
end

s = Student.new("Andi", 20, 1001)
puts s.nama     # Andi
s.nama = "Rina" # setter tersedia
puts s.id       # 1001 (reader)
# s.id = 9999   # Error! id hanya reader

7. Array dan Hash

Array adalah kumpulan data terurut, sedangkan Hash adalah kumpulan data berupa pasangan key-value. Keduanya adalah struktur data paling penting di Ruby.

Array

Ruby β€” Array
# Membuat array
buah = ["apel", "mangga", "jeruk"]
angka = [1, 2, 3, 4, 5]
campuran = [1, "dua", 3.0, true, nil]

# Akses elemen
puts buah[0]        # apel
puts buah[-1]       # jeruk (elemen terakhir)
puts buah[1..2]     # ["mangga", "jeruk"] (slice)
puts buah.first     # apel
puts buah.last      # jeruk

# Menambah elemen
buah.push("pisang")         # Tambah di akhir
buah.unshift("anggur")      # Tambah di awal
buah.insert(2, "lemon")     # Tambah di posisi index 2
puts buah.inspect

# Menghapus elemen
buah.pop                    # Hapus elemen terakhir
buah.shift                  # Hapus elemen pertama
buah.delete("mangga")       # Hapus berdasarkan nilai
puts buah.inspect

# Informasi array
puts buah.length            # Jumlah elemen
puts buah.empty?            # true jika kosong
puts buah.include?("apel")  # true jika ada

# Iterasi
fruits = ["apel", "mangga", "jeruk"]

# .each β€” iterasi standar
fruits.each { |f| puts f }

# .each_with_index β€” dapat index juga
fruits.each_with_index do |f, i|
  puts "#{i}: #{f}"
end

# .map β€” transformasi dan kembalikan array baru
angka = [1, 2, 3, 4, 5]
kuadrat = angka.map { |n| n ** 2 }
puts kuadrat.inspect  # [1, 4, 9, 16, 25]

# .select β€” filter elemen
ganjil = angka.select { |n| n.odd? }
puts ganjil.inspect  # [1, 3, 5]

# .reject β€” kebalikan select
genap = angka.reject { |n| n.odd? }
puts genap.inspect   # [2, 4]

# .reduce / .inject β€” akumulasi
total = angka.reduce(0) { |sum, n| sum + n }
puts total  # 15
# Atau gunakan shortcut:
puts angka.sum   # 15

# .sort
numbers = [3, 1, 4, 1, 5, 9]
puts numbers.sort.inspect  # [1, 1, 3, 4, 5, 9]

# .flatten β€” array multi-dimensi jadi flat
multi = [[1, 2], [3, 4], [5]]
puts multi.flatten.inspect  # [1, 2, 3, 4, 5]

# .uniq β€” hapus duplikat
dups = [1, 2, 2, 3, 3, 3]
puts dups.uniq.inspect  # [1, 2, 3]

# .compact β€” hapus nil
dengan_nil = [1, nil, 2, nil, 3]
puts dengan_nil.compact.inspect  # [1, 2, 3]

Hash

Ruby β€” Hash
# Membuat hash
# Symbol keys (recommended)
user = { nama: "Budi", umur: 25, kota: "Jakarta" }

# String keys
data = { "nama" => "Budi", "umur" => 25 }

# Akses nilai
puts user[:nama]      # Budi
puts user[:umur]      # 25
puts user[:kota]      # Jakarta

# Mengubah / menambah nilai
user[:email] = "budi@email.com"
user[:umur] = 26
puts user.inspect

# Menghapus nilai
user.delete(:email)
puts user.inspect

# Iterasi hash
user.each do |key, value|
  puts "#{key}: #{value}"
end

# .keys dan .values
puts user.keys.inspect    # [:nama, :umur, :kota]
puts user.values.inspect  # ["Budi", 26, "Jakarta"]

# .has_key? dan .has_value?
puts user.has_key?(:nama)      # true
puts user.has_value?("Budi")   # true

# .fetch β€” dengan default value
puts user.fetch(:nama, "Tidak diketahui")  # Budi
puts user.fetch(:email, "Tidak diketahui") # Tidak diketahui

# .merge β€” menggabungkan hash
h1 = { a: 1, b: 2 }
h2 = { b: 3, c: 4 }
puts h1.merge(h2).inspect  # {:a=>1, :b=>3, :c=>4}

# Hash default value
counter = Hash.new(0)  # Default value = 0
"hello world".each_char do |c|
  counter[c] += 1
end
puts counter.inspect
# {"h"=>1, "e"=>1, "l"=>3, "o"=>2, " "=>1, "w"=>1, "r"=>1, "d"=>1}

# Ruby 3.1+ β€” .filter_map, .tally
words = ["apple", "banana", "cherry", "avocado"]
result = words.filter_map { |w| w if w.start_with?("a") }
puts result.inspect  # ["apple", "avocado"]

8. Block dan Iterator

Block adalah salah satu fitur paling powerful dan unik di Ruby. Block adalah potongan kode yang dapat diberikan ke method sebagai parameter. Block sangat umum digunakan untuk iterasi, callback, dan DSL (Domain Specific Language).

Jenis Block

Ruby β€” Block
# Block dengan do...end (untuk block multi-line)
[1, 2, 3].each do |n|
  puts "Angka: #{n}"
end

# Block dengan { } (untuk block single-line)
[1, 2, 3].each { |n| puts "Angka: #{n}" }

# Block dengan parameter
[1, 2, 3].each_with_index do |value, index|
  puts "Index #{index}: #{value}"
end

# Menggunakan yield β€” memanggil block dari dalam method
def say_hello
  puts "Sebelum yield"
  yield           # Memanggil block yang diberikan
  puts "Setelah yield"
end

say_hello { puts "Halo dari block!" }
# Output:
# Sebelum yield
# Halo dari block!
# Setelah yield

# yield dengan argumen
def proses(angka)
  result = yield(angka)
  puts "Hasil: #{result}"
end

proses(10) { |n| n * 2 }  # Hasil: 20

# Block_given? β€” cek apakah block diberikan
def greet(name = "Teman")
  if block_given?
    yield(name)
  else
    puts "Halo, #{name}!"
  end
end

greet("Budi") { |n| puts "Selamat datang, #{n}!" }
greet()  # Halo, Teman!

Proc, Lambda, dan Closure

Ruby β€” Proc, Lambda
# Proc β€” objek yang menyimpan block
square = Proc.new { |n| n ** 2 }
# Atau syntax shorthand:
square = proc { |n| n ** 2 }

puts square.call(5)   # 25
puts square.call(3)   # 9

# Lambda β€” seperti Proc tapi lebih ketat
double = lambda { |n| n * 2 }
# Atau syntax lambda:
double = ->(n) { n * 2 }

puts double.call(5)   # 10
puts double[3]        # 6 (juga bisa pakai [])

# Perbedaan Proc vs Lambda:
# 1. Lambda memeriksa jumlah argumen, Proc tidak
# 2. Lambda return dari lambda, Proc return dari method pemanggil

# Menggunakan Proc/Lambda sebagai parameter
def apply_to_all(array, operation)
  array.map(&operation)
end

angka = [1, 2, 3, 4, 5]
puts apply_to_all(angka, proc { |n| n * 2 }).inspect
# [2, 4, 6, 8, 10]

puts apply_to_all(angka, ->(n) { n ** 2 }).inspect
# [1, 4, 9, 16, 25]

# Variable-number of block parameters
def flexible_method
  yield("Budi", 25, "Jakarta")
end

flexible_method do |*args|
  puts args.inspect  # ["Budi", 25, "Jakarta"]
end

Iterator Populer

Ruby β€” Iterator
# .each β€” iterasi dasar
["a", "b", "c"].each { |x| puts x }

# .each_with_index β€” dengan index
["a", "b", "c"].each_with_index { |x, i| puts "#{i}: #{x}" }

# .map / .collect β€” transformasi
[1, 2, 3].map { |n| n * 10 }  # [10, 20, 30]

# .select / .filter β€” filter
(1..10).select { |n| n.even? }  # [2, 4, 6, 8, 10]

# .reject β€” kebalikan select
(1..10).reject { |n| n.even? }  # [1, 3, 5, 7, 9]

# .reduce / .inject β€” akumulasi
(1..5).reduce(:+)    # 15
(1..5).reduce(1) { |acc, n| acc * n }  # 120 (faktorial!)

# .any? / .all? / .none? β€” predicate iteration
[1, 2, 3].any? { |n| n > 2 }     # true
[1, 2, 3].all? { |n| n > 0 }     # true
[1, 2, 3].none? { |n| n > 10 }   # true

# .find / .detect β€” cari elemen pertama yang cocok
(1..100).find { |n| n % 7 == 0 && n % 3 == 0 }  # 21

# .flat_map β€” map + flatten
[[1, 2], [3, 4], [5]].flat_map { |a| a.map { |n| n * 2 } }
# [2, 4, 6, 8, 10]

# .group_by β€” kelompokkan
(1..10).group_by { |n| n.odd? ? "ganjil" : "genap" }
# {"ganjil"=>[1, 3, 5, 7, 9], "genap"=>[2, 4, 6, 8, 10]}

# .each_with_object
result = [1, 2, 3].each_with_object([]) do |n, arr|
  arr << n * 10
end
puts result.inspect  # [10, 20, 30]

9. Class dan Object

Ruby adalah bahasa Object-Oriented sepenuhnya. Semua data di Ruby adalah objek, termasuk angka, string, dan bahkan nil. Class mendefinisikan blueprint untuk membuat objek.

Class Dasar

Ruby β€” Class
# Class sederhana
class Hewan
  attr_accessor :nama, :jenis

  def initialize(nama, jenis)
    @nama = nama    # Instance variable
    @jenis = jenis
  end

  def info
    "#{nama} adalah seekor #{jenis}"
  end

  def suara
    "..."
  end
end

# Membuat objek
kucing = Hewan.new("Kitty", "kucing")
puts kucing.info   # Kitty adalah seekor kucing
puts kucing.suara  # ...

kucing.nama = "Mochi"  # Setter tersedia (attr_accessor)
puts kucing.info   # Mochi adalah seekor kucing

# Instance variable hanya bisa diakses di dalam class
# unless ada accessor method
puts kucing.instance_variables.inspect
# [:@nama, :@jenis]

Inheritance

Ruby β€” Inheritance
# Inheritance β€” class turunan
class Hewan
  attr_reader :nama

  def initialize(nama)
    @nama = nama
  end

  def info
    "Saya adalah #{nama}"
  end

  def suara
    raise NotImplementedError, "Harus di-override!"
  end
end

class Kucing < Hewan
  def suara
    "Meow! 🐱"
  end

  def bermain
    "#{nama} bermain dengan bola benang 🧢"
  end
end

class Anjing < Hewan
  def suara
    "Guk! πŸ•"
  end

  def bermain
    "#{nama} bermain lempar tangkap 🎾"
  end
end

kucing = Kucing.new("Kitty")
anjing = Anjing.new("Buddy")

puts kucing.info     # Saya adalah Kitty
puts kucing.suara    # Meow! 🐱
puts kucing.bermain  # Kitty bermain dengan bola benang 🧢

puts anjing.info     # Saya adalah Buddy
puts anjing.suara    # Guk! πŸ•

# Cek inheritance
puts Kucing.ancestors.inspect
# [Kucing, Hewan, Object, Kernel, BasicObject]

Method Visibility

Ruby β€” Visibility
# Tiga level visibility di Ruby:
# public    β€” bisa diakses dari mana saja
# protected β€” hanya bisa diakses dari class yang sama
# private   β€” hanya bisa diakses dari dalam class

class AkunBank
  attr_reader :pemilik

  def initialize(pemilik, saldo)
    @pemilik = pemilik
    @saldo = saldo
  end

  def info_publik
    "Akun milik #{@pemilik}"  # public
  end

  protected

  def saldo_lengkap
    "Saldo #{@pemilik}: Rp #{format('%.0f', @saldo)}"  # protected
  end

  private

  def validasi_pin(pin)  # private
    pin == 1234
  end
end

akun = AkunBank.new("Budi", 1_000_000)
puts akun.info_publik          # OK
# puts akun.saldo_lengkap      # Error! protected
# puts akun.validasi_pin(1234) # Error! private

Class Methods dan Self

Ruby β€” Class Method
# Class method β€” dipanggil pada class, bukan instance
class Calculator
  @@history = []    # Class variable

  def initialize(value)
    @value = value
  end

  def self.history
    @@history
  end

  def self.hapus_history
    @@history.clear
  end

  def tambah(lain)
    result = @value + lain
    Calculator.history << { aksi: :tambah, hasil: result }
    result
  end
end

# Menggunakan class method
calc = Calculator.new(10)
puts calc.tambah(5)   # 15
puts calc.tambah(3)   # 13

puts Calculator.history.inspect
# [{:aksi=>:tambah, :hasil=>15}, {:aksi=>:tambah, :hasil=>13}]

Calculator.hapus_history
puts Calculator.history.inspect  # []

# self β€” referensi ke objek saat ini
class User
  attr_reader :nama

  def initialize(nama)
    @nama = nama
  end

  def say_hello
    puts "Halo, saya #{self}!"  # self adalah objek User
  end

  def to_s
    "User(#{nama})"
  end
end

Comparable Module

Ruby β€” Comparable
# Include Comparable untuk mendapatkan metode perbandingan
class Mahasiswa
  include Comparable

  attr_reader :nama, :ipk

  def initialize(nama, ipk)
    @nama = nama
    @ipk = ipk
  end

  def <=>(other)
    ipk <=> other.ipk
  end

  def to_s
    "#{nama} (IPK: #{ipk})"
  end
end

mhs1 = Mahasiswa.new("Budi", 3.8)
mhs2 = Mahasiswa.new("Rina", 3.9)
mhs3 = Mahasiswa.new("Andi", 3.5)

# Comparable menyediakan: <, >, <=, >=, ==, between?
puts mhs1 < mhs2    # true
puts mhs1 > mhs3    # true

mahasiswa = [mhs1, mhs2, mhs3]
puts mahasiswa.sort.inspect
# [Andi (IPK: 3.5), Budi (IPK: 3.8), Rina (IPK: 3.9)]

10. Module dan Mixin

Module di Ruby adalah nama yang dapat dikelompokkan method dan constant. Module mirip dengan class tetapi tidak bisa diinstansiasi. Module digunakan untuk namespace dan mixin (membagikan method ke beberapa class).

Module Dasar

Ruby β€” Module
# Module sebagai Namespace
module Utilitas
  VERSION = "1.0.0"

  def self.info
    "Utilitas v#{VERSION}"
  end
end

puts Utilitas.info        # Utilitas v1.0.0
puts Utilitas::VERSION    # 1.0.0

# Module sebagai Mixin
module DapatBerjalan
  def berjalan
    "#{nama} sedang berjalan 🚢"
  end
end

module DapatBerlari
  def berlari
    "#{nama} sedang berlari πŸƒ"
  end
end

class Manusia
  include DapatBerjalan     # Instance methods
  include DapatBerlari

  attr_reader :nama

  def initialize(nama)
    @nama = nama
  end
end

budi = Manusia.new("Budi")
puts budi.berjalan  # Budi sedang berjalan 🚢
puts budi.berlari   # Budi sedang berlari πŸƒ

# extend β€” untuk class methods
module InfoKelas
  def info
    "Saya adalah class #{name}"
  end
end

class Produk
  extend InfoKelas
end

puts Produk.info  # Saya adalah class Produk

Include vs Extend vs Prepend

Ruby β€” Mixin Methods
# include β€” menambahkan method sebagai INSTANCE method
module Greeting
  def greet
    "Halo dari include!"
  end
end

class MyClass
  include Greeting
end

MyClass.new.greet  # OK

# extend β€” menambahkan method sebagai CLASS method
class AnotherClass
  extend Greeting
end

AnotherClass.greet  # OK
# AnotherClass.new.greet  # Error!

# prepend β€” method module dipanggil SEBELUM method class
module Logging
  def save
    puts "Logging: save dipanggil"
    super  # Panggil method save dari class
  end
end

class Document
  def save
    puts "Document disimpan!"
  end
end

class LoggableDocument < Document
  prepend Logging
end

LoggableDocument.new.save
# Logging: save dipanggil
# Document disimpan!

# Method Resolution Order (MRO)
puts LoggableDocument.ancestors.inspect
# [Logging, LoggableDocument, Document, Object, ...]

Module dengan Enumerable

Ruby β€” Enumerable
# Include Enumerable untuk mendapatkan semua metode iterasi
class DaftarBelanja
  include Enumerable

  def initialize
    @items = []
  end

  def <<(item)
    @items << item
  end

  # Enumerable membutuhkan method each
  def each(&block)
    @items.each(&block)
  end

  # Sekarang kita punya semua method Enumerable:
  # map, select, reject, any?, all?, find, reduce, sort, dll
end

belanja = DaftarBelanja.new
belanja << "Beras"
belanja << "Telur"
belanja << "Susu"
belanja << "Roti"

# Menggunakan Enumerable methods
puts belanja.sort.inspect
# ["Beras", "Roti", "Susu", "Telur"]

panjang = belanja.select { |item| item.length > 4 }
puts panjang.inspect  # ["Beras", "Susu", "Roti"]

puts belanja.any? { |item| item.start_with?("T") }  # true
puts belanja.count  # 4

# to_s override
def to_s
  @items.join(", ")
end

puts belanja  # Beras, Telur, Susu, Roti

11. Manipulasi String

Ruby memiliki fitur manipulasi string yang sangat kaya dan elegan.

Interpolasi dan Escape

Ruby β€” String
# String interpolation (hanya berfungsi dengan "")
nama = "Budi"
umur = 25
puts "Nama: #{nama}, Umur: #{umur}"   # Nama: Budi, Umur: 25
puts "2 + 2 = #{2 + 2}"              # 2 + 2 = 4

# Single quotes β€” tidak ada interpolasi
puts 'Nama: #{nama}'   # Nama: #{nama} (literal)

# Escape characters
puts "Baris 1\nBaris 2"       # Newline
puts "Tab\there"               # Tab
puts "Quote: \"Halo\""         # Quote
puts "Backslash: \\"           # Backslash

# Heredoc β€” string multi-baris
html = <<~HTML
  <html>
    <body>
      <h1>#{nama}</h1>
    </body>
  </html>
HTML

# Raw string (tanpa escape)
path = %q(C:\Users\Nama\Documents)  # Single
path = %Q(C:\Users\#{nama}\Documents)  # Double (ada interpolation)

Method String Populer

Ruby β€” String Methods
teks = "  Hello, Ruby World!  "

# Ukuran
puts teks.length          # 23
puts teks.empty?          # false

# Case
puts teks.downcase        # "  hello, ruby world!  "
puts teks.upcase          # "  HELLO, RUBY WORLD!  "
puts "hello".capitalize   # "Hello"
puts "hello".swapcase     # "HELLO"

# Trim
puts teks.strip           # "Hello, Ruby World!"
puts teks.lstrip          # "Hello, Ruby World!  "
puts teks.rstrip          # "  Hello, Ruby World!"

# Pencarian
puts teks.include?("Ruby")   # true
puts teks.index("Ruby")      # 9
puts teks.count("l")         # 3

# Ganti
puts teks.gsub("Ruby", "Python")  # "  Hello, Python World!  "
puts teks.strip.gsub(" ", "_")    # "Hello,_Ruby_World!"

# Split dan Join
kalimat = "apel mangga jeruk pisang"
buah = kalimat.split(" ")
puts buah.inspect  # ["apel", "mangga", "jeruk", "pisang"]
puts buah.join(", ")  # "apel, mangga, jeruk, pisang"

# Start dengan, end dengan
puts "hello".start_with?("he")  # true
puts "hello".end_with?("lo")    # true

# Repeat
puts "Ha" * 3    # "HaHaHa"

# Pad
puts "42".rjust(5, "0")  # "00042"
puts "halo".ljust(10, ".")  # "halo......."
puts "hi".center(10, "-")  # "----hi----"

# Encoding
puts "Ruby".encoding   # UTF-8
puts "Ruby".bytesize   # 4

12. Error Handling

Ruby menggunakan begin...rescue...ensure untuk menangani error. Ini mirip dengan try-catch di bahasa lain.

Ruby β€” Error Handling
# begin...rescue...ensure
begin
  result = 10 / 0
rescue ZeroDivisionError => e
  puts "Error: #{e.message}"
  puts "Class: #{e.class}"
ensure
  puts "Ini selalu dieksekusi (cleanup)"
end
# Output:
# Error: divided by 0
# Class: ZeroDivisionError
# Ini selalu dieksekusi (cleanup)

# Multiple rescue
begin
  # some code
  data = JSON.parse("invalid json")
rescue ZeroDivisionError
  puts "Pembagian dengan nol"
rescue JSON::ParserError
  puts "JSON tidak valid"
rescue => e  # Tangkap semua error lainnya
  puts "Error lain: #{e.class} - #{e.message}"
end

# Rescue inline (single line)
def bagi(a, b)
  a / b
rescue ZeroDivisionError
  puts "Tidak bisa bagi dengan nol!"
  0  # Return value saat error
end

# Raise β€” melempar error sendiri
def validasi_umur(umur)
  raise ArgumentError, "Umur harus positif" if umur < 0
  raise ArgumentError, "Umur tidak valid" unless umur.is_a?(Integer)
  umur
end

# Custom error class
class InsufficientFundsError < StandardError
  attr_reader :saldo, :dibutuhkan

  def initialize(saldo, dibutuhkan)
    @saldo = saldo
    @dibutuhkan = dibutuhkan
    super("Saldo tidak cukup. Saldo: Rp#{saldo}, Dibutuhkan: Rp#{dibutuhkan}")
  end
end

def tarik_tunai(saldo, jumlah)
  raise InsufficientFundsError.new(saldo, jumlah) if jumlah > saldo
  saldo - jumlah
end

begin
  sisa = tarik_tunai(100000, 150000)
rescue InsufficientFundsError => e
  puts e.message
  puts "Selisih: Rp#{e.dibutuhkan - e.saldo}"
end

13. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Ruby:

Pertanyaan 1: Apa output dari kode berikut? puts [1, 2, 3].map { |n| n * 2 }.inspect

a) [1, 2, 3]
b) [2, 4, 6]
c) [1, 4, 9]
d) 6

Pertanyaan 2: Apa perbedaan antara include dan extend di Ruby?

a) include untuk class methods, extend untuk instance methods
b) include untuk instance methods, extend untuk class methods
c) Keduanya sama saja
d) include untuk inheritance, extend untuk mixin

Pertanyaan 3: Apa yang dilakukan yield dalam sebuah method Ruby?

a) Mengembalikan nilai dari method
b) Memanggil block yang diberikan ke method
c) Menghentikan eksekusi method
d) Membuat method baru secara dinamis

Pertanyaan 4: Manakah yang merupakan cara yang benar untuk mendefinisikan hash di Ruby?

a) { "nama" = "Budi" }
b) { nama: "Budi" }
c) hash(nama => "Budi")
d) [nama: "Budi"]

Pertanyaan 5: Apa output dari puts 15 / 4 di Ruby?

a) 3.75
b) 3
c) 4
d) Error
πŸ” Zoom
100%
🎨 Tema