Jalan Pintas: Mengenal Pemrograman Ruby

Bagian 1: Dasar-Dasar Pemrograman Ruby

Sekilas Ruby

Ruby merupakan bahasa pemrograman yang interpreted, dinamis, dan open source. Ruby berfokus pada kesederhanaan dan produktivitas, sehingga Ruby merupakan sahabat baik programmer. Ruby mensupport multiparadigma: berorientasi objek, imperatif, reflektif, dan fungsional.

Pada bagian ini, kamu dapat mencoba-coba kode di bawah dengan mengklik tombol "Jalankan".

puts "Halo Ruby!"

# Ini komentar

puts 3+6.0 # 9.0

=begin
  Ini komentar untuk banyak baris
=end

total_partisipasi = 30
puts total_partisipasi
puts "Total orang yang hadir adalah #{total_partisipasi}"

def print_halo # ini method tanpa argumen
  puts "halo"
end

print_halo # jalankan method print_halo

def kali_dua(angka) # ini method dengan argumen
  puts "#{angka+angka}"
end

kali_dua(5)
kali_dua 5 # boleh tidak pakai kurung

puts <<-EOL
Bisa print
banyak baris
lho!
EOL

Konstanta dan Variabel

JEDA_TAHUN = 3 # konstanta
# JEDA_TAHUN = 10 # error karena sudah diassign

$tahun_ini = 2013 # variabel global
puts "Sekarang tahun #{$tahun_ini}."

umur = 20 # variabel lokal
puts "Sekarang umur saya #{umur}. Ngga deng becanda."

class Mobil
  @@sim = "A" # variabel kelas

  def nama
    @nama # variabel object
  end

  def nama=(nama)
    @nama = nama
  end

  def self.sim # class method
    @@sim
  end
end

puts "Mobil biasanya pakai SIM #{Mobil.sim}."
mobilku = Mobil.new
mobilku.nama = "Mobil Balap"
puts "Mobilku #{mobilku.nama}. Jadi aku pake SIM #{Mobil.sim}."

Tipe dan Struktur Data

Semua tipe data merupakan Object, termasuk tipe data primitif seperti Integer, Float, dan Boolean. Bahkan, nil dan Class pun merupakan Object.

umur = 20 # Integer
puts umur
suhu = 2.98 # Float
puts suhu

masih_muda = true # Boolean
puts masih_muda
remaja = umur < 20 && umur > 12 # Boolean
puts remaja

# puts nama # error karena belum diassign
nama = nil # NilClass
puts nama

Symbol

Symbol kelihatan hampir seperti String, hanya saja karakter yang diperbolehkan adalah titik dua (:) dan sama seperti variabel. Bedanya, Symbol selalu ada di memory/tidak pernah dihapus dari memory setelah dibuat pertama kali.

nama_konfigurasi = :rute # :rute adalah Symbol
puts nama_konfigurasi

# kalau diprint saja tidak kelihatannya tidak ada bedanya dengan string
puts :rata_kanan
puts "rata_kanan"

puts :"ini_string".inspect
puts :RataKiri.inspect
puts "RataKiri".inspect
puts "nenek moyangku seorang pelaut".to_sym.inspect
# :suka%suka # error

Untuk mengetahui Symbol lebih lanjut, kunjungi dokumentasinya.

Dapat Diiterasi: Array, Hash, Set, dan Range

Array

tinggi_murid = [156, 160, 149]
tinggi_murid << 189 # tambahkan 189 ke akhir Array tinggi_murid
puts tinggi_murid.inspect

nama_singkat = %w{andi budi nani} # Array berisi String
nama_singkat = ["andi", "budi", "nani"] # sama dengan di atas

# Iterasi
nama_singkat.each do |nama|
  puts "Halo #{nama}!"
end; puts
nama_singkat.each { |nama| puts "Halo #{nama}!" }
puts
nama_singkat.each { |nama|
  puts "Halo #{nama}!"
}

puts
puts nama_singkat.map { |nama| "Halo #{nama}" }.inspect

# Iterasi dengan Index

nama_singkat.each_with_index do |nama, index|
  puts "Tinggi #{nama} adalah #{tinggi_murid[index]}"
end; puts

nama_singkat.each_with_index { |nama, index| puts "Tinggi #{nama} adalah #{tinggi_murid[index]}" }
puts
nama_singkat.each_with_index { |nama, index|
  puts "Tinggi #{nama} adalah #{tinggi_murid[index]}"
}

Untuk mengetahui Array lebih lanjut, kunjungi dokumentasinya.

Hash

Hash mirip seperti sebuah tabel key-value, dengan key tidak berduplikat.

h = { key: "value" }
puts h
puts key: "value"
puts "key" => "value", x: "y"
puts :hihi => 8
puts Hash["a", 100, "b", 200]
puts Hash[ [ ["a", 100], ["b", 200] ] ]
puts Hash["a" => 100, "b" => 200]

hash = { hari_ini: 19, "besok" => 10 }
puts hash.keys.inspect
puts hash.values.inspect
puts hash["kemarin"] # nil kalau diprint jadi string kosong
puts hash[:hari_ini]
puts hash["besok"]
# Iterasi
hash.each_pair do |key, value|
  puts "#{key.to_s} jajan #{value} ribu"
end

hash.merge!(kemarin: 30)
puts hash[:kemarin]

Untuk mengetahui Hash lebih lanjut, kunjungi dokumentasinya.

Set

Set merupakan koleksi dengan elemen tak berurut tanpa duplikat. Set mirip dengan Array, tetapi kecepatan pencarian elemen mirip dengan Hash.

s1 = Set.new [1, 2]
s2 = [1, 2].to_set
puts s1 == s2
s1.add("foo")
puts s1.inspect
s1.merge([2, 6])
puts s1.inspect
puts s1.subset? s2
puts s2.subset? s1

Untuk mengetahui Set lebih lanjut, kunjungi dokumentasinya.

Range

Range merupakan jajaran dari Object yang memiliki elemen awal dan akhir.

range = (1..3)  # Termasuk elemen terakhir
puts range.to_a.inspect
puts (1...3).to_a.inspect # Tidak termasuk elemen terakhir
puts (0..-1).to_a.inspect # -1 adalah element ke n-1
puts (-1..-5).to_a.inspect
puts (-5..-1).to_a.inspect
puts ('a'..'e').to_a.inspect
puts ('a'...'e').to_a.inspect

puts
puts "Berat Badanku..."
berat_badanku_tiap_hari = [50, 51, 50, 49, 52]
puts berat_badanku_tiap_hari[2..3].inspect
puts berat_badanku_tiap_hari[0..-1].inspect
puts berat_badanku_tiap_hari[0...-1].inspect
puts berat_badanku_tiap_hari[-5...-1].inspect
puts berat_badanku_tiap_hari[-1...-5].inspect

puts
nilai = 10
case nilai
when (9..10)
  puts "Nilaimu A"
when (8...9)
  puts "Nilaimu B"
when (7...8)
  puts "Nilaimu C"
else
  puts "Nilaimu D"
end

Untuk mengetahui Range lebih lanjut, kunjungi dokumentasinya.

Konversi Tipe Data

Hampir semua tipe data di atas dapat dikonversi ke tipe data lain. Berikut beberapa contoh cara mengkonversi tipe data.

puts 10.to_f              # Integer ke Float
puts 10.4279.to_s         # Float ke String
puts [3, 4, 5].to_s       # Array ke String
puts (1..8).to_a.inspect  # Range ke Array
puts :hewan.to_s          # Symbol ke String
puts "hewan".to_sym       # String ke Symbol
puts nil.to_i             # NilClass ke Integer
puts({ key: "value" }.to_a.inspect)  # Hash ke Array

Operasi Logika dan Aritmatika

Ini sebagai info aja. Tidak harus hafal semua kok.

+ tambah

- kurang

* kali

/ bagi

% modulus

** pangkat

& dan (untuk bilangan berbasis 2)

| atau (untuk bilangan berbasis 2)

^ XOR (untuk bilangan berbasis 2)

~ invert bit (untuk bilangan berbasis 2)

<< shift kiri (untuk bilangan berbasis 2)

>> shift kanan (untuk bilangan berbasis 2)

&& dan (untuk Boolean)

and dan (untuk Boolean), tetapi prioritas rendah

|| atau (untuk Boolean)

or atau (untuk Boolean), tetapi prioritas rendah

! bukan (untuk Boolean)

not bukan (untuk Boolean)

== sama dengan

> lebih dari

< kurang dari

>= lebih dari atau sama dengan

<= kurang dari atau sama dengan

puts 3+8
puts 8*9.1
puts 8/4+10
puts 2**3
puts 3 | 6 # 11 (basis 2) atau 100 (basis 2)
puts 3 ^ 2 # 11 (basis 2) XOR 10 (basis 2)
puts 7 & 2
puts 10 % 5 # habis dibagi 5
puts 10 % 6 # ada sisa

puts
x = 9
puts x < 1 || x >= 10
puts x == '9' or x == 9 # prioritas or lebih rendah daripada ||
puts "#{x == '9' or x == 9}"
puts x.is_a?(Integer) && x < 29
puts x.is_a?(Integer) and x < 29

puts ([1,2,3] - [1,3]).inspect
puts ([1,3,4] + [9,8]).inspect
puts ([1,2,3] * 5).inspect

puts; puts "Singkat assign variabel dan operasi aritmatika"
a = 10
a /= 5   # a = a / 5
puts a
a += 19  # a = a + 19
puts a
a -= 18  # a = a - 18
puts a
a *= 8   # a = a * 8
puts a
a %= 2   # a = a % 2
puts a
a **= 3  # a = a ** 3
puts a

puts; puts "Assign variabel secara paralel"
a, b, c = 1, 's', 9
puts "a=#{a} b=#{b} c=#{c}"
a, b = b, c
puts "a=#{a} b=#{b} c=#{c}"

Sumber-sumber:

Statement Controls

Conditionals

if, else, dan unless

# if dan else

puts "hore pake if" if true
puts "hore pake unless" unless false

# Dalam conditional, nil diperlakukan seperti false, dan bukan nil diperlakukan seperti true.
puts "hore kebalik pake if" if nil
puts "hore kebalik pake unless" unless 7

if true
  puts "suka kucing" # Ini diprint
else
  puts "suka harimau"
end

if false
  puts "suka anjing"
else
  puts "suka kucing" # Ini diprint
end

# Yang di atas bisa disingkat menggunakan ternary operation
true ? puts("hore") : puts("yah kok gitu")  # "hore"

# Bandingkan yang di atas dengan unless dan else
unless true # unless adalah if not
  puts "suka laba-laba"
else
  puts "suka kucing" # Ini diprint
end

elsif

# elsif
nilai = 10
if nilai >= 9
  puts "Nilaimu A"
elsif nilai >= 8 # elsif berarti else if
  puts "Nilaimu B"
elsif nilai >= 7
  puts "Nilaimu C"
else
  puts "Nilaimu D"
end

mood = :pengen_gerak
if mood == :pengen_belanja
  puts "Cari baju ah"
elsif mood == :pengen_gerak
  puts "Lari marathon biar seger"
elsif mood == :pengen_tidur
  puts "Hibernasi seharian di rumah"
else
  puts "Main sama kucing aja deh"
end

Ada juga case…when untuk mempersingkat if, elsif, dan else jika menggunakan ==

mood = :pengen_gerak
case mood
when :pengen_belanja
  puts "Cari baju ah"
when :pengen_gerak
  puts "Lari marathon biar seger"
when :pengen_tidur
  puts "Hibernasi seharian di rumah"
else
  puts "Main sama kucing aja deh"
end

Loop

tinggi_badan = [170, 150, 160]

puts "for"
for tinggi in tinggi_badan
  puts "Tinggi badanmu #{tinggi} cm."
end

puts; puts "each"
# yang di atas sama dengan .each pada Array dan Set
tinggi_badan.each { |tinggi| puts "Tinggi badanmu #{tinggi} cm." }

# while: jalankan hingga kondisi tidak terpenuhi
puts; puts "while"
n = 0
while n < tinggi_badan.size
  puts tinggi_badan[n]
  n += 1
end

# until: jalankan hingga kondisi terpenuhi
puts; puts "until"
n = 0
until n >= tinggi_badan.size
  puts tinggi_badan[n]
  n += 1
end

puts; puts "begin-end-while"
n = -1
begin
  n += 1
  puts tinggi_badan[n]
end while n < tinggi_badan.size

puts; puts "begin-end-until"
n = -1
begin
  n += 1
  puts tinggi_badan[n]
end until n >= tinggi_badan.size

puts; puts "loop"
n = 0
loop do 
  puts tinggi_badan[n]
  n += 1
  break if n >= tinggi_badan.size
end

# jalankan x kali
puts; puts "10 kali tepuk tangan"
people = []
10.times { print "plok " }

Methods

# tanpa argumen
def mood_main_sama_kucing
  mood = :main_sama_kucing
  puts "Aku lagi mood #{mood}"
end

mood_main_sama_kucing


# dengan satu argumen
def moodku(mood)
  mood = :main_sama_kucing unless mood
  puts "Aku lagi mood #{mood}"
end

moodku nil
moodku :pengen_gerak


# memberi nilai default pada argumen
def moodku(mood=:tidur)
  mood = :main_sama_kucing unless mood
  puts "Aku lagi mood #{mood}"
end

moodku # sama dengan suggest_ngapain(:tidur) karena defaultnya :tidur
moodku(:pengen_tidur)

Kamu juga bisa buat method dengan lebih dari satu parameter

# lebih dari satu parameter
def moodku(mood1=nil, mood2)
  for mood in [mood1, mood2]
    mood = :main_sama_kucing unless mood
    puts "Aku lagi mood #{mood}"
  end
end

moodku :pengen_gerak, nil
moodku :pengen_belanja
puts

# variabel argument, argument bisa terserah berapa aja
def moodku(*moods)
  for mood in moods
    mood = :main_sama_kucing unless mood
    puts "Aku lagi mood #{mood}"
  end
end

moodku nil, :pengen_belanja, :pengen_gerak
puts

Proc

Proc disebut juga closures dalam ilmu komputer. Menurut saya, tipe data ini sangat spesial karena sifatnya seperti method atau sebuah snippet, tetapi dapat diassign seperti variabel.

Sekilas Proc

Kamu sebenarnya sudah melihat Proc pada bab Array. Berikut ini sekilas mengenai Proc yang merupakan block dan lambda.

# Sudah pernah lihat?
tinggi_badan = [170, 160, 150]
tinggi_badan.each { |tinggi| puts "Tinggi badanku #{tinggi}cm." }

puts; puts "--- block"
blok = proc { |tinggi| puts "Tinggi badanku #{tinggi}cm." }
# blok = Proc.new { |tinggi| puts "Tinggi badanku #{tinggi}cm." } # sama dengan di atas
puts "block merupakan sebuah instance dari #{blok.class}."
puts "Apakah block merupakan lambda? #{blok.lambda? ? "Ya" : "Tidak"}"
tinggi_badan.each &blok

puts; puts "--- lambda"
ld = lambda { |tinggi| puts "Tinggi badanku #{tinggi}cm." }
# ld = ->(tinggi) { puts "Tinggi badanku #{tinggi}cm." } # sama dengan di atas
puts "lambda merupakan sebuah instance dari #{ld.class}."
puts ld.lambda?
tinggi_badan.each &ld
puts

# Membuat method dengan argumen sebuah Proc
def kelas_blok(&block)
  puts block.class
  puts block.lambda?
end

puts kelas_blok { |tinggi| puts "Tinggi badanku #{tinggi}cm." }
puts kelas_blok &(lambda { |tinggi| puts "Tinggi badanku #{tinggi}cm." })
puts kelas_blok &->(tinggi) { puts "Tinggi badanku #{tinggi}cm." }

Perbedaan block dan lambda

block dan lambda terlihat seperti saudara kembar, walaupun secara syntax memang sudah terlihat berbeda. Pada dasarnya, lambda lebih bersifat seperti method, dan block lebih bersifat seperti snippet. Kamu juga perlu tahu 2 perbedaan besar antara keduanya.

  1. lambda cek jumlah argumen (arity), proc tidak.
    def jual_baju(handler)
      harga, n = 99900, 10
      handler.call(harga, n)
    end
    
    jual_baju proc { |harga, n, nama_penjaga_kasir|
      puts "Saya beli #{n} baju dengan harga Rp #{harga} per baju. Yang jaga kasir: #{nama_penjaga_kasir.inspect}"
    }
    
    # error
    jual_baju ->(harga, n, nama_penjaga_kasir) {
      puts "Saya beli #{n} baju dengan harga Rp #{harga} per baju. Yang jaga kasir: #{nama_penjaga_kasir.inspect}"
    }
  2. Jika ada return di dalam lambda, maka return akan terjadi di dalam lambda itu. Jika return ada di block, maka return akan terjadi di dalam block itu dan juga method yang memanggilnya.
    def proc_return
      proc { return "proc"}.call
      return "proc_return selesai"
    end
    
    def lambda_return
      lambda { return "lambda" }.call
      return "lambda_return selesai"
    end
    
    puts proc_return
    puts lambda_return; puts
    
    def iterasi_array_dengan_proc
      [1, 17].each { |tanggal_libur|
        puts "Tanggal #{tanggal_libur} libur!!"
        return "Udah ah liburannya" if tanggal_libur == 1
      }
    end
    
    def iterasi_array_dengan_lambda
      [1, 17].each &->(tanggal_libur) {
        puts "Tanggal #{tanggal_libur} libur!!"
        return "Udah ah liburannya" if tanggal_libur == 1
      }
    end
    
    puts iterasi_array_dengan_lambda.inspect; puts
    puts iterasi_array_dengan_proc.inspect

Mengenal Kemiripan block dan lambda

Pada umumnya, yang bisa kamu lakukan pada block dapat kamu lakukan juga pada lambda. Berikut ini beberapa contoh di mana cara kerja lambda dan proc sama saja.

puts "Method yang memiliki argument sebuah Proc"
def punya_proc
  yield
end

punya_proc { puts "yield proc" }
punya_proc &->{ puts "yield lambda" }

def punya_proc(&ada_proc)
  ada_proc.call
end

punya_proc { puts "call &proc" }
punya_proc &->{ puts "call &lambda" }

def punya_proc(ada_proc)
  ada_proc.call
end

punya_proc proc { puts "call proc" }
punya_proc ->{ puts "call lambda" }

puts; puts "Method yang memiliki lebih dari satu Proc"

def punya_banyak_proc(proc1, proc2)
  proc1.call
  proc2.call
end

punya_banyak_proc proc { puts "call proc1" }, proc { puts "call proc2" }
punya_banyak_proc ->{ puts "call lambda1" }, ->{ puts "call lambda2" }

puts; puts "Default nilai parameter untuk argumen Proc"

def kasih_default
  yield
  yield("hihihi")
end
kasih_default { |ini="default lho"| puts ini }
kasih_default &->(ini="default lho"){ puts ini }

Mari Bermain Bersama Proc!

angka = [1, 2, 3, 4]
angka.collect! { |n| n ** 2 }
puts angka.inspect; puts

puts "Memanggil Proc dengan yield"
class Array # Patch Array
  def comot!
    self.each_with_index do |n, i|
      self[i] = yield(n)
    end
  end
end

angka.comot! { |n| n ** 2 }
puts angka.inspect; puts

puts "Memanggil Proc dengan .call"
class Array
  def comot!(&code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end

angka.comot! &->(n) { n ** 2 }
puts angka.inspect; puts

puts "Membuat method dengan callback"
def jual_baju(callback)
  callback[:mulai].call
  puts "Jualan Baju..."
  callback[:selesai].call
end

jual_baju(mulai: lambda { puts "Buka Toko Baju" },
          selesai: lambda { puts "Tutup Toko Baju" })

Sumber-sumber:

Class

Ruby biasanya cukup object oriented, walaupun functional dan yang lain juga bisa. Ruby punya garbage collector, jadi tidak perlu atur2 alokasi seperti pada C/C++.

Deklarasi, Setter dan Getter

class Baju
  BAHAN_DEFAULT = "Bulu" # konstanta kelas

  attr_accessor :merek # buat setter dan getter untuk instance variable @merek
  attr_reader :tahun_desain # buat getter untuk instance variable @tahun_desain
  attr_writer :ada_stock # buat setter untuk instance variable @ada_stock

  def initialize(merek=nil) # method spesial yang dipanggil pada saat class baju diinisialisasi
    @merek = merek
    puts "merek: #{merek}"
  end

  def ada_stock? # tanda tanya biasanya untuk method yang return Boolean
    @ada_stock
  end

  # membuat getter dan setter sendiri
  def desainer # getter
    @desainer ? @desainer : "Belum Tahu"
  end

  def desainer=(desainer) # setter
    @desainer = desainer
  end

  def self.nama_item # class method
    "Baju Bagus"
  end

  class << self
    def nomor_rak # class method juga
      48953
    end
  end

  class Kain # nested class
    def nama
      "Katun"
    end
  end
end

baju = Baju.new
baju = Baju.new("Batique")
puts baju.desainer
baju.desainer = "XYZ"
puts baju.desainer
puts Baju.nama_item
puts Baju.nomor_rak
puts baju.class.nama_item
puts Baju::BAHAN_DEFAULT

kain = Baju::Kain.new
puts kain.nama

Access Modifiers

Secara default, semua method public dan instance variable protected. Kamu bisa mengubah method menjadi private atau protected, tetapi kamu tidak bisa mengubah instance variable seperti itu.

class Baju
  attr_accessor :merek, :ukuran
  attr_reader :tahun_desain

  def desainer # getter
    @desainer ? @desainer : "Belum Tahu"
  end

  def desainer=(desainer) # setter
    @desainer = desainer
  end

  protected :desainer= # method desainer= menjadi protected
  private :merek, :tahun_desain # method merek dan tahun desain menjadi private

  protected # yang di bawah ini menjadi protected

  attr_accessor :barcode # getter dan setter rating menjadi protected

  def rating # method ini menjadi protected
    5
  end

  private # yang di bawah ini menjadi private

  def warna
    @warna
  end
end

baju = Baju.new
# baju.warna # error karena private
# baju.rating # error karena protected
# baju.merek # error karena private
puts baju.desainer; puts
# baju.desainer = "Orang Keren" # error karena protected

class KostumBadut < Baju
  def bagus?
    puts self.rating # subclass bisa akses yang protected
    rating >= 3
  end
  
  def warni
    @warna = "Pelangi"
    puts warna.inspect
    self.warna # tidak bisa karena private
  end
end

kostum_badut = KostumBadut.new
puts "Kostum badutnya bagus? #{kostum_badut.bagus?}"
puts kostum_badut.warni

Inheritance

class Baju
  def desainer # getter
    @desainer ? @desainer : "Belum Tahu"
  end

  def desainer=(desainer) # setter
    @desainer = desainer
  end

  protected # yang di bawah ini menjadi protected

  attr_accessor :barcode # getter dan setter rating menjadi protected

  def rating # method ini menjadi protected
    5
  end

  private # yang di bawah ini menjadi private

  def warna
    @warna
  end
end

class LongSleeve < Baju
  attr_accessor :panjang_lengan
end

baju = LongSleeve.new
baju.panjang_lengan = 10
puts baju.panjang_lengan

baju.desainer = "Siapa ya"
puts baju.desainer

# puts baju.rating # error
# puts baju.warna # error

Callback

# self.inherited
class Kendaraan
  def self.inherited(subclass)
    puts "Subclass baru: #{subclass}"
  end
end

class Mobil < Kendaraan
end

class Motor < Kendaraan
end

Patching

Kamu bisa ubah-ubah kelas yang sudah define. Teknik ini disebut juga monkey patching.

# alias
class Rubyist
  def say!
    puts 'hello'
  end

  def say_with_log!
    puts "Calling method..."
    puts "hello"
    puts "...Method called"
  end

  alias_method :say_without_log!, :say!
  alias_method :say!, :say_with_log!
end

Untuk mengenal Class lebih jauh, kunjungi dokumentasi Class dan Object.

Module

Module merupakan kumpulan methods dan konstanta. Module mirip class, hanya saja dia bisa di-include atau di-extend ke modul atau kelas lain.

module DapatDipanggil
  def panggilan
    case @jenis_kelamin
    when :pria
      "Mr"
    when :wanita
      "Ms"
    else
      ""
    end
  end
end

module PakaiSeragam
  def seragam
    puts "Seragam kotak-kotak"
  end
end

class Pegawai
  include DapatDipanggil # method di dlm modul DapatDipanggil menjadi instance methods Pegawai
  extend PakaiSeragam # method di dlm modul PakaiSergam menjadi class methods Pegawai

  attr_accessor :jenis_kelamin, :nama

  def nama_lengkap
    "#{panggilan}. #{nama}"
  end
end

Pegawai.seragam
pegawai = Pegawai.new
pegawai.jenis_kelamin = :wanita
pegawai.nama = "Jeni"
puts pegawai.nama_lengkap

Callback

# self.included dan self.extended
module PakaiSeragam
  def self.included(a)
    puts "#{self} included di #{a}"
  end
  def self.extended(a)
    puts "#{self} extended di #{a}"
  end
end

class Murid
  extend PakaiSeragam
  include PakaiSeragam
end

Sebagai info, trik di atas dipermudah jika menggunakan ActiveSupport::Concern. Lihat contoh pada dokumentasinya untuk mengetahui lebih lanjut.

# self.method_removed dan self.method_added
module PakaiSeragam
  def self.method_removed(method_name)
    puts "Hapus method #{method_name.inspect}"
  end

  def self.method_added(method_name)
    puts "Tambah method #{method_name.inspect}"
  end

  def self.some_class_method() end

  def some_instance_method() end
  class << self
    remove_method :some_class_method
  end
  remove_method :some_instance_method
end

Patching

Seperti Class, kamu bisa monkey patch module yang sudah define.

module PakaiSeragam
  def warna
    puts "Hijau"
  end
end

module PakaiSeragam
  def motif
    puts "Kotak-kotak"
  end
end

Untuk mengenal Module lebih jauh, kunjungi dokumentasinya.

Exceptions

class MyException < Exception
end

begin
  raise MyException.new
  raise Exception
rescue MyException => e1
  puts e1.inspect
rescue Exception
  puts "Dapat Exception"
else
  puts "Tidak dapat Exception"
ensure
  puts "Ensure"
end

Untuk mengenal Exception lebih jauh, kunjungi dokumentasinya.

Throw dan Catch

Throw dan Catch pada Ruby bukanlah untuk exceptions. Mereka adalah cara untuk memberhentikan eksekusi program terlebih dahulu ketika program tidak perlu berjalan lagi.

catch :stop do
  puts "Aku mau jalan"
  throw :stop
end

def udahan
  for i in (1..10)
    print "#{i} "
    throw :udahan if i == 8
  end
end

catch :udahan do
  udahan
end
puts

throw :halo

Sumber-sumber:

Mengenal Metaprogramming

Metaprogramming adalah membuat program yang dapat membuat code dalam runtime. Metaprogramming mudah dilakukan pada Ruby, sehingga menjadi saat umum dipakai.

Eval

Ada 3 macam method untuk meng-evaluate sebuah snippet pada Ruby secara dinamis, yakni Kernel#eval, Object#instance_eval, dan Module#class_eval.

Kernel#eval

Kamu bisa evaluate Ruby dengan menggunakan Kernel#eval walaupun kamu cuma kasih String. Fitur ini biasanya ada pada bahasa yang tidak perlu dicompile seperti PHP.

eval "puts 'Halo Ruby!'"
a = eval "8943 + 4278932"
puts a

Object#instance_eval

Dengan instance_eval, kamu bisa menambah method atau variabel instance sendiri. Ini sangat mirip dengan patching, tetapi instance_eval cukup umum digunakan untuk membuat Domain-Specific Language (DSL).

class Baju
  def nama(nama)
    @nama = nama
  end

  def deskripsi
    "Nama baju ini adalah #{@nama}"
  end
  
  def self.buat(&blok)
    baju = Baju.new
    baju.instance_eval &blok # Untuk membuat Domain-Specific Language (DSL)
    baju
  end
end

Baju.instance_eval do
  def nomor_rak
    puts "Baju adalah instance dari Class, karena itu di sini kita menambah method class"
    10
  end
end

puts Baju.nomor_rak

puts <<-EOL

Kamu bisa buat DSL sendiri hanya dengan instance_eval.
Cukup panggil instance_eval pada sebuah object yang sudah diinisialisasi, dan
kamu dapat akses ke method instance pada object itu.
===
EOL
baju = Baju.buat do
  puts "Kamu bisa mengakses method dan variabel instance di sini.\n---"
  nama "Kostum Badut"
end
puts baju.deskripsi

Module#class_eval

class_eval hampir sama dengan instance_eval, hanya saja class_eval hanya dapat digunakan pada class atau module saja. Dengan class_eval kamu bisa menambah method atau variabel instance. Ini juga sangat mirip dengan patching.

class Baju
  def nama(nama)
    @nama = nama
  end

  def deskripsi
    "Nama baju ini adalah #{@nama}"
  end
end

Baju.class_eval do
  attr_accessor :ukuran

  def gambaran
    puts "#{self.deskripsi}. Ukurannya #{ukuran}"
  end
end

baju = Baju.new
baju.nama "Kostum Badut"
baju.ukuran = "XL"
baju.gambaran; puts

baju.class_eval do
  def ups_gagal
    puts "Jadi error deh"
  end
end

Variabel Instance

Kamu dapat dengan mudah mengakses, mengubah, atau mendefine variabel instance secara dinamis.

class Baju
end

puts Baju.instance_variables.inspect
Baju.instance_variable_set(:@kode, "BJ")
puts Baju.instance_variable_get(:@kode)
puts Baju.instance_variables.inspect
puts "---"
baju = Baju.new
puts baju.instance_variables.inspect
baju.instance_variable_set(:@nama, "Kostum Badut")
puts baju.instance_variable_get(:@nama)
puts baju.instance_variables.inspect

Variabel Class

Kamu dapat dengan mudah mengakses, mengubah, atau mendefine variabel class secara dinamis.

class Baju
end

puts; puts "Ubah variabel class"
puts Baju.class_variables.inspect
Baju.class_variable_set(:@@kode, "BJ")
puts Baju.class_variable_get(:@@kode)
puts Baju.class_variables.inspect

baju = Baju.new
puts baju.class_variables # error

Konstanta

Kamu juga bisa dengan mudah mengakses, mengubah, atau mendefine konstanta secara dinamis.

class Baju
end

Baju.const_set(:WARNA_DEFAULT, "Hitam")
puts Baju.const_get(:WARNA_DEFAULT)
puts Baju.constants.inspect

puts Baju.new.constants.inspect # error

Method

Kamu juga dapat dengan mudah mengakses, mengubah, atau mendefine method secara dinamis.

class Baju
  def warna
    @warna || "Hitam"
  end
  
  define_method(:beli) do
    puts "Beli baju"
  end
  
  private

  def kain
    "Katun"
  end
end

Baju.class_eval do
  define_method(:jual) do |harga|
    puts "Jual baju dengan harga Rp #{harga}"
  end
  
  define_method(:warna=) do |warna="Hitam"|
    puts "Warna baju #{warna.inspect}"
    @warna = warna
  end
end

baju = Baju.new
# puts baju.kain # tidak bisa panggil method yang private
puts baju.send(:kain)
baju.warna = # default warna hitam
puts baju.warna.inspect
baju.warna = "Putih"
puts baju.warna.inspect
baju.jual 399900

Baju.class_eval do
  remove_method(:warna)
  undef_method(:beli)
end
# baju.warna # error
# puts baju.beli # error

class Baju
  def method_missing(*args)
    puts "Oalah methodnya ngga ada: #{args.inspect}"
  end
end

puts; puts "Panggil method yang ga ada"
puts baju.ngasal

Perbedaan dari undef_method dan remove_method sangat sedikit. undef_method menghapus method dari class yang bersangkutan. Apabila method yang dihapus dipanggil, exception NoMethodError akan muncul. remove_method juga menghapus method dari class yang bersangkutan. Hanya saja, jika method yang dihapus dipangx gil, Ruby akan mencari superclass yang mengimplementasi method itu dan memanggilnya.

Sumber-sumber:


>> Bab selanjutnya -- Bagian 2: Coba-Coba Sendiri

Daftar IsiCoba RubyInstall RubyKontak
Daftar IsiCobaInstall@

Daftar Isi

  1. Bagian 1: Dasar-Dasar Pemrograman Ruby
  2. Bagian 2: Coba-Coba Sendiri
  3. Appendix
puts "Halo Ruby!"
Jalankan

Output