πŸš‚ Ruby on Rails

Ruby on Rails: Web Development

Tutorial Ruby on Rails untuk web development β€” MVC pattern, routing, Active Record, views, migrations, dan quiz interaktif dengan contoh kode praktis

1. Pengenalan Ruby on Rails

Ruby on Rails (atau biasa disebut Rails) adalah framework web open-source yang dibangun dengan bahasa Ruby oleh David Heinemeier Hansson pada tahun 2004. Rails mengikuti filosofi Convention over Configuration (CoC) dan Don't Repeat Yourself (DY), sehingga developer bisa fokus pada fitur alih-alih konfigurasi.

Rails digunakan oleh perusahaan besar seperti GitHub, Shopify, Airbnb, Basecamp, Twitter (awalnya), dan ribuan startup lainnya. Rails memungkinkan pengembangan web yang sangat cepat β€” Anda bisa membuat prototipe aplikasi web dalam hitungan jam.

Mengapa Menggunakan Rails?

Keunggulan Penjelasan
Convention over ConfigurationTidak perlu menulis banyak konfigurasi β€” Rails sudah mengatur semuanya
Don't Repeat YourselfSetiap logika hanya ditulis sekali β€” mengurangi duplikasi
Generator yang Powerfulrails generate membuat model, controller, views, migration otomatis
Active Record (ORM)Basis data yang elegan tanpa menulis SQL
ScaffoldingCRUD lengkap dibuat hanya dengan satu command
Ekosistem GemsRibuan library untuk autentikasi, API, background job, dll

Rails vs Framework Lain

Aspek Rails Django Laravel
BahasaRubyPythonPHP
ORMActive RecordDjango ORMEloquent
TemplatingERB, Haml, SlimJinja2Blade
CLIrails generatedjango-adminartisan
FilosofiCoC + DRYBattery IncludedElegant Syntax
Scaffolding🟒 Built-in🟑 Perlu extension🟒 Built-in
Diagram: Ekosistem Ruby on Rails
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚               RUBY ON RAILS ECOSYSTEM                 β”‚
β”‚                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚   Core    β”‚  β”‚  Tools   β”‚  β”‚   Database       β”‚    β”‚
β”‚  β”‚  Rails    β”‚  β”‚ Rake     β”‚  β”‚  PostgreSQL      β”‚    β”‚
β”‚  β”‚  Rack     β”‚  β”‚ Bundler  β”‚  β”‚  MySQL/SQLite    β”‚    β”‚
β”‚  β”‚  Sprocketsβ”‚  β”‚ Foreman  β”‚  β”‚  Active Record   β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚ Testing  β”‚  β”‚  Deploy  β”‚  β”‚   Testing        β”‚    β”‚
β”‚  β”‚  RSpec   β”‚  β”‚  Heroku  β”‚  β”‚  RSpec           β”‚    β”‚
β”‚  β”‚ Capybara β”‚  β”‚ Docker   β”‚  β”‚  Capybara        β”‚    β”‚
β”‚  β”‚ FactoryBotβ”‚  β”‚ Fly.io  β”‚  β”‚  FactoryBot      β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. Instalasi dan Setup

Untuk menggunakan Rails, Anda memerlukan Ruby, RubyGems, dan Node.js (untuk Asset Pipeline).

Prasyarat

Terminal
# 1. Pastikan Ruby sudah terinstal (minimal 3.0+)
ruby --version
# ruby 3.3.3 (2024-06-12 revision e12f4fa7f9)

# 2. Pastikan Node.js terinstal (untuk Asset Pipeline)
node --version
# v20.10.0

# 3. Pastikan Yarn atau npm tersedia
yarn --version
# 1.22.19

# 4. Pastikan database tersedia (PostgreSQL atau SQLite)
psql --version  # PostgreSQL
sqlite3 --version  # SQLite

Instalasi Rails

Terminal
# Instal Rails gem terbaru
gem install rails

# Verifikasi instalasi
rails --version
# Rails 7.1.3

# Jika menggunakan rbenv, rehash setelah instalasi
rbenv rehash

# Instal bundler jika belum terinstal
gem install bundler

# Cek semua gem Rails
gem list | grep rails

Konfigurasi Database PostgreSQL

PostgreSQL
# Di Ubuntu/Debian:
sudo apt install postgresql postgresql-contrib libpq-dev

# Buat user PostgreSQL
sudo -u postgres createuser --superuser $USER

# Set password untuk user
sudo -u postgres psql
\password your_username
\q

# Di macOS (Homebrew):
brew install postgresql@16
brew services start postgresql@16

# Verifikasi koneksi
psql postgres
# Anda sekarang di PostgreSQL console
\q  # Keluar
πŸ’‘ Tips

Untuk pengembangan lokal, Anda bisa menggunakan SQLite (default Rails) tanpa instalasi database server tambahan. Namun untuk production, sangat disarankan menggunakan PostgreSQL yang lebih robust dan mendukung fitur-fitur lanjutan seperti JSON columns, full-text search, dan banyak lagi.

3. Arsitektur MVC

Rails menggunakan pola arsitektur MVC (Model-View-Controller) yang memisahkan logika aplikasi menjadi tiga komponen utama. Pemisahan ini membuat kode lebih terorganisir, mudah di-maintain, dan scalable.

Komponen MVC

Komponen Lokasi Folder Tanggung Jawab Contoh
Modelapp/models/Data, business logic, validasi, relasi databaseuser.rb, post.rb
Viewapp/views/Tampilan HTML yang dilihat userusers/index.html.erb
Controllerapp/controllers/Menerima request, memproses, mengirim ke viewusers_controller.rb
Diagram: Alur Request MVC di Rails
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    Request     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          │───────────────>β”‚              β”‚
β”‚  Browser β”‚   GET /users   β”‚   Router     β”‚
β”‚  (Client)β”‚                β”‚  (routes.rb) β”‚
β”‚          β”‚                β”‚              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
      β–²                            β”‚
      β”‚                            β”‚ Match route
      β”‚                            β–Ό
      β”‚                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      β”‚                     β”‚              β”‚
      β”‚   HTML Response     β”‚  Controller  β”‚
      β”‚<───────────────────│ UsersControllerβ”‚
      β”‚                     β”‚  #index      β”‚
      β”‚                     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚                            β”‚
      β”‚                            β”‚ Query data
      β”‚                            β–Ό
      β”‚                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      β”‚                     β”‚              β”‚
      β”‚                     β”‚    Model     β”‚
      β”‚                     β”‚    User.all  β”‚
      β”‚                     β”‚              β”‚
      β”‚                     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚                            β”‚
      β”‚                            β”‚ Return data
      β”‚                            β–Ό
      β”‚                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      β”‚                     β”‚              β”‚
      β”‚   Rendered HTML     β”‚    View      β”‚
      β”‚<───────────────────│ users/index  β”‚
      β”‚                     β”‚  .html.erb   β”‚
      └─────────────────────│              β”‚
                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Alur Request Lengkap

Alur MVC
# 1. Browser mengirim request: GET /users
# 2. Router (routes.rb) mencocokkan URL dengan controller action
#    get '/users', to: 'users#index'
# 3. Controller menerima request dan memanggil Model
#    class UsersController < ApplicationController
#      def index
#        @users = User.all  # Query ke database via Model
#      end
#    end
# 4. Model mengambil data dari database
#    SELECT * FROM users;
# 5. Controller mengirim data ke View
#    @users tersedia di view
# 6. View merender HTML dengan data yang diterima
#    <% @users.each do |user| %>
#      <p><%= user.name %></p>
#    <% end %>
# 7. HTML dikembalikan ke browser

4. Membuat Proyek Rails Baru

Rails menyediakan generator yang sangat powerful untuk membuat proyek baru, model, controller, views, dan banyak lagi.

Membuat Aplikasi Baru

Terminal
# Buat aplikasi Rails baru dengan PostgreSQL
rails new blog_app -d postgresql

# Atau dengan SQLite (untuk pengembangan lokal)
rails new blog_app

# Masuk ke folder proyek
cd blog_app

# Lihat struktur folder yang dibuat
ls -la
# app/          - Kode utama (MVC)
# config/       - Konfigurasi aplikasi
# db/           - Database migrations & seeds
# public/       - File statis (CSS, JS, gambar)
# test/ atau spec/ - Unit test
# Gemfile       - Dependency Ruby
# config.ru     - Rack configuration
# Rakefile      - Rake tasks

# Konfigurasi database di config/database.yml
# Jalankan setup database
rails db:create
rails db:migrate

# Jalankan server Rails
rails server
# => Booting Puma
# => Rails 7.1.3 application starting in development
# => Listening on http://127.0.0.1:3000

# Buka browser: http://localhost:3000
# Anda akan melihat halaman selamat datang Rails!

Folder Structure Rails

Folder Isi Penjelasan
app/models/user.rbModel untuk interaksi database
app/views/users/Template HTML untuk setiap resource
app/controllers/users_controller.rbLogika penanganan request
config/routes.rbRoutingURL mapping ke controller#action
db/migrate/20240101_create_users.rbSkema database (migrations)
config/database.ymlKonfigurasiPengaturan koneksi database
GemfileDependenciesDaftar gem yang dibutuhkan proyek

Generator Commands

Rails Generator
# Generator sangat penting di Rails
# Menunjukkan semua generator yang tersedia
rails generate --help

# Generate model
rails generate model User name:string email:string age:integer
# Menghasilkan:
# - app/models/user.rb
# - db/migrate/20240101000000_create_users.rb
# - test/models/user_test.rb

# Generate controller
rails generate controller Users index show new edit
# Menghasilkan:
# - app/controllers/users_controller.rb
# - app/views/users/index.html.erb
# - app/views/users/show.html.erb
# - test/controllers/users_controller_test.rb

# Generate scaffold (semua sekaligus: model + controller + views + tests)
rails generate scaffold Post title:string body:text user:references
# Ini menghasilkan CRUD lengkap!

# Generate migration tunggal
rails generate migration AddRoleToUsers role:string

# Generate resource (model + controller + routes)
rails generate resource Article title:string content:text

# Jalankan migration setelah generate
rails db:migrate

# Rollback migration jika salah
rails db:rollback

# Undo generate
rails destroy model User
rails destroy controller Users

5. Routing

Routing di Rails didefinisikan di config/routes.rb. Routing menentukan URL mana yang diarahkan ke controller action mana.

Basic Routing

config/routes.rb
# config/routes.rb
Rails.application.routes.draw do

  # Root route (homepage)
  root "pages#home"

  # Basic routes
  get  '/about',   to: 'pages#about'
  get  '/contact', to: 'pages#contact'
  post '/contact', to: 'pages#submit_contact'

  # RESTful routes dengan resource
  resources :users
  # Menghasilkan 7 route:
  # GET    /users          => users#index
  # GET    /users/new      => users#new
  # POST   /users          => users#create
  # GET    /users/:id      => users#show
  # GET    /users/:id/edit => users#edit
  # PATCH  /users/:id      => users#update
  # DELETE /users/:id      => users#destroy

  # Resources dengan pilihan tertentu
  resources :posts, only: [:index, :show, :new, :create]
  # Hanya membuat 4 route yang dipilih

  resources :articles, except: [:destroy]
  # Semua route kecuali destroy

  # Nested resources
  resources :users do
    resources :posts
    # Menghasilkan:
    # GET /users/:user_id/posts
    # GET /users/:user_id/posts/new
    # GET /users/:user_id/posts/:id
    # dll...
  end

  # Member routes (memerlukan ID)
  resources :posts do
    member do
      post :publish
      # POST /posts/:id/publish
      post :archive
      # POST /posts/:id/archive
    end
  end

  # Collection routes (tidak memerlukan ID)
  resources :posts do
    collection do
      get :search
      # GET /posts/search
      get :drafts
      # GET /posts/drafts
    end
  end

  # Namespace untuk API
  namespace :api do
    namespace :v1 do
      resources :posts
      # /api/v1/posts
    end
  end

end

Named Routes dan Path Helpers

Named Routes
# Ketika Anda mendefinisikan routes, Rails otomatis membuat
# helper methods:

# resources :users menghasilkan:
users_path          # "/users"
user_path(@user)    # "/users/1"
new_user_path       # "/users/new"
edit_user_path(@user) # "/users/1/edit"

# Di controller:
redirect_to users_path
redirect_to user_path(@user)
redirect_to new_user_path

# Di views (ERB):
# <%= link_to "Semua User", users_path %>
# <%= link_to "Lihat User", user_path(@user) %>
# <%= link_to "Edit", edit_user_path(@user) %>
# <%= link_to "Hapus", user_path(@user), method: :delete %>

# Custom named route
get '/dashboard', to: 'pages#dashboard', as: :dashboard
# Menghasilkan: dashboard_path = "/dashboard"

# Cek semua routes yang tersedia:
# rails routes
# Atau di Rails 7+:
# rails routes --controller users

6. Controller dan Action

Controller menangani request HTTP dan mengirim response. Setiap controller action biasanya mengambil data dari model dan mengirimkannya ke view.

Controller Lengkap

app/controllers/users_controller.rb
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  def index
    @users = User.all.order(created_at: :desc)
    @user_count = @users.count
  end

  # GET /users/:id
  def show
    # @user sudah diset oleh set_user (before_action)
  end

  # GET /users/new
  def new
    @user = User.new
  end

  # POST /users
  def create
    @user = User.new(user_params)

    if @user.save
      redirect_to @user, notice: "User berhasil dibuat!"
    else
      render :new, status: :unprocessable_entity
    end
  end

  # GET /users/:id/edit
  def edit
    # @user sudah diset oleh set_user
  end

  # PATCH /users/:id
  def update
    if @user.update(user_params)
      redirect_to @user, notice: "User berhasil diupdate!"
    else
      render :edit, status: :unprocessable_entity
    end
  end

  # DELETE /users/:id
  def destroy
    @user.destroy
    redirect_to users_path, notice: "User berhasil dihapus!"
  end

  private

  def set_user
    @user = User.find(params[:id])
  end

  # Strong Parameters β€” whitelist parameter yang diizinkan
  def user_params
    params.require(:user).permit(:name, :email, :age, :role)
  end
end

Application Controller (Base Controller)

app/controllers/application_controller.rb
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_locale

  # Helper method untuk semua controllers & views
  helper_method :current_user, :logged_in?

  private

  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end

  def logged_in?
    current_user.present?
  end

  def require_login
    unless logged_in?
      redirect_to login_path, alert: "Anda harus login terlebih dahulu!"
    end
  end

  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
  end
end

7. Views dan ERB Template

Views di Rails menggunakan template engine ERB (Embedded Ruby) yang memungkinkan Anda menulis Ruby code di dalam HTML. Ada juga alternatif seperti Haml dan Slim.

ERB Template

app/views/users/index.html.erb
<%# app/views/users/index.html.erb %>

<h1>Daftar User (<%= @user_count %>)</h1>

<%# Flash messages %>
<% if notice %>
  <div class="alert alert-success">
    <%= notice %>
  </div>
<% end %>

<% if @users.any? %>
  <table class="table">
    <thead>
      <tr>
        <th>Nama</th>
        <th>Email</th>
        <th>Umur</th>
        <th>Aksi</th>
      </tr>
    </thead>
    <tbody>
      <% @users.each do |user| %>
        <tr>
          <td><%= user.name %></td>
          <td><%= user.email %></td>
          <td><%= user.age %></td>
          <td>
            <%= link_to "Lihat", user_path(user), class: "btn btn-info" %>
            <%= link_to "Edit", edit_user_path(user), class: "btn btn-warning" %>
            <%= link_to "Hapus", user_path(user),
                data: { turbo_method: :delete, turbo_confirm: "Yakin?" },
                class: "btn btn-danger" %>
          </td>
        </tr>
      <% end %>
    </tbody>
  </table>
<% else %>
  <p>Belum ada user.</p>
<% end %>

<%= link_to "Buat User Baru", new_user_path, class: "btn btn-primary" %>

Partials (Template Terpisah)

Partials
# Partial diawali dengan underscore: _user.html.erb
# app/views/users/_user.html.erb

<div class="user-card">
  <h3><%= user.name %></h3>
  <p>Email: <%= user.email %></p>
  <p>Umur: <%= user.age %> tahun</p>
  <%= link_to "Lihat Detail", user_path(user) %>
</div>

# Menggunakan partial di view utama:
# app/views/users/index.html.erb

<% @users.each do |user| %>
  <%= render partial: "user", locals: { user: user } %>
  <%# Atau shorthand: %>
  <%= render user %>
<% end %>

# Partial dengan collection
<%= render partial: "user", collection: @users %>
# Setiap item otomatis tersedia sebagai 'user' di partial

# Layout partial (shared/_header.html.erb)
<%= render "shared/header" %>

# Yield untuk content_for di layout
<% content_for :title, "Daftar User" %>

# app/views/layouts/application.html.erb
# <title><%= content_for?(:title) ? yield(:title) : "BeebaneLabs" %></title>

Helper Methods

app/helpers/users_helper.rb
# app/helpers/users_helper.rb
module UsersHelper
  def user_avatar(user, size: 50)
    if user.avatar.attached?
      image_tag user.avatar, size: "#{size}x#{size}", class: "avatar"
    else
      image_tag "default_avatar.png", size: "#{size}x#{size}", class: "avatar"
    end
  end

  def format_umur(umur)
    case umur
    when 0..12  then "#{umur} tahun (Anak-anak)"
    when 13..17 then "#{umur} tahun (Remaja)"
    when 18..64 then "#{umur} tahun (Dewasa)"
    else             "#{umur} tahun (Lansia)"
    end
  end

  def status_badge(user)
    if user.active?
      content_tag :span, "Aktif", class: "badge badge-success"
    else
      content_tag :span, "Nonaktif", class: "badge badge-danger"
    end
  end
end

# Menggunakan di view:
# <%= user_avatar(@user, size: 100) %>
# <%= format_umur(@user.age) %>
# <%= status_badge(@user) %>

8. Active Record

Active Record adalah ORM (Object-Relational Mapping) bawaan Rails yang memungkinkan Anda berinteraksi dengan database menggunakan Ruby objects. Anda bisa melakukan operasi CRUD tanpa menulis SQL!

Model Dasar

app/models/user.rb
# app/models/user.rb
class User < ApplicationRecord
  # Validations
  validates :name, presence: true, length: { minimum: 2, maximum: 100 }
  validates :email, presence: true, uniqueness: true,
                    format: { with: URI::MailTo::EMAIL_REGEXP }
  validates :age, numericality: { greater_than: 0, less_than: 150 }

  # Associations
  has_many :posts, dependent: :destroy
  has_many :comments, dependent: :destroy

  # Scopes
  scope :active, -> { where(active: true) }
  scope :recent, -> { order(created_at: :desc).limit(10) }
  scope :by_role, ->(role) { where(role: role) }

  # Callbacks
  before_save :downcase_email

  # Instance methods
  def full_info
    "#{name} (#{email})"
  end

  def post_count
    posts.count
  end

  # Class methods
  def self.search(keyword)
    where("name LIKE ? OR email LIKE ?", "%#{keyword}%", "%#{keyword}%")
  end

  private

  def downcase_email
    self.email = email.downcase
  end
end

CRUD Operations

Rails Console β€” CRUD
# Buka Rails console:
# rails console  (atau: rails c)

# ===== CREATE =====
# Method 1: new + save
user = User.new(name: "Budi", email: "budi@mail.com", age: 25)
user.save  # => true

# Method 2: create (new + save sekaligus)
User.create(name: "Rina", email: "rina@mail.com", age: 22)

# Method 3: create! (raises error jika gagal)
User.create!(name: "Andi", email: "andi@mail.com", age: 30)

# ===== READ =====
User.all                   # Semua user
User.find(1)               # Cari berdasarkan ID
User.find_by(email: "budi@mail.com")  # Cari berdasarkan atribut
User.where(age: 25)        # Filter
User.where("age > ?", 20) # Filter dengan raw SQL
User.order(name: :asc)     # Urutkan
User.limit(5)              # Batasi 5 record
User.first                 # Record pertama
User.last                  # Record terakhir
User.count                 # Jumlah record
User.exists?(email: "budi@mail.com")  # Cek keberadaan

# Chaining queries
User.active.recent.by_role("admin").where("age > ?", 18)

# ===== UPDATE =====
user = User.find(1)
user.name = "Budi Santoso"
user.save  # => true

# Atau dengan update
user.update(name: "Budi Santoso", age: 26)

# Atau dengan update! (raises error)
user.update!(name: "Budi Santoso")

# Update multiple records
User.where(active: false).update_all(active: true)

# ===== DELETE =====
user = User.find(1)
user.destroy  # Menghapus record dan memanggil callbacks

# Delete by condition
User.where("created_at < ?", 1.year.ago).destroy_all

9. Database Migrations

Migration di Rails adalah cara untuk mengubah skema database secara terstruktur dan reversible. Migration ditulis dalam Ruby, bukan SQL!

Membuat Migration

Migration
# Generate migration
rails generate migration CreateUsers name:string email:string age:integer

# Hasilnya: db/migrate/20240101120000_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      t.string :name, null: false
      t.string :email, null: false
      t.integer :age
      t.boolean :active, default: true
      t.string :role, default: "user"
      t.timestamps  # created_at dan updated_at otomatis
    end

    # Tambahkan index
    add_index :users, :email, unique: true
    add_index :users, :role
  end
end

# Jalankan migration
rails db:migrate
# == 20240101120000 CreateUsers: migrating ===================================
# -- create_table(:users)
#    -> 0.0123s
# == 20240101120000 CreateUsers: migrated (0.0123s) =========================

Migration Commands

Migration Commands
# Jalankan semua migration yang belum dijalankan
rails db:migrate

# Rollback migration terakhir
rails db:rollback

# Rollback 3 migration terakhir
rails db:rollback STEP=3

# Jalankan migration ke versi tertentu
rails db:migrate VERSION=20240101120000

# Reset database (drop + create + migrate + seed)
rails db:reset

# Seed data dari db/seeds.rb
rails db:seed

# Lihat status migration
rails db:migrate:status

# Cek skema database saat ini
cat db/schema.rb

Menambah Kolom Baru

Migration β€” Add Column
# Generate migration untuk menambah kolom
rails generate migration AddPhoneToUsers phone:string
# Hasil: db/migrate/20240102120000_add_phone_to_users.rb

class AddPhoneToUsers < ActiveRecord::Migration[7.1]
  def change
    add_column :users, :phone, :string
    add_column :users, :address, :text
    add_column :users, :date_of_birth, :date
  end
end

# Migration untuk menambah foreign key
rails generate migration AddUserRefToPosts user:references
# Hasil: db/migrate/20240103120000_add_user_ref_to_posts.rb

class AddUserRefToPosts < ActiveRecord::Migration[7.1]
  def change
    add_reference :posts, :user, null: false, foreign_key: true
    # Atau:
    # add_column :posts, :user_id, :bigint, null: false
    # add_foreign_key :posts, :users
  end
end

# Migration untuk mengubah kolom
class ChangeAgeInUsers < ActiveRecord::Migration[7.1]
  def up
    change_column :users, :age, :integer, default: 0
  end

  def down
    change_column :users, :age, :integer, default: nil
  end
end

# Migration untuk rename kolom
class RenameNameToFullNameInUsers < ActiveRecord::Migration[7.1]
  def change
    rename_column :users, :name, :full_name
  end
end

10. Model Associations

Active Record Associations mendefinisikan hubungan antar model. Ada tiga jenis association utama: has_many, belongs_to, dan has_many :through.

Association Types

Associations
# User β€” has_many Posts
class User < ApplicationRecord
  has_many :posts, dependent: :destroy
  has_many :comments, dependent: :destroy
  has_many :post_comments, through: :posts, source: :comments
  has_one :profile, dependent: :destroy
  has_and_belongs_to_many :roles
end

# Post β€” belongs_to User, has_many Comments
class Post < ApplicationRecord
  belongs_to :user
  has_many :comments, dependent: :destroy
  has_many :tags, dependent: :destroy
  has_one_attached :image
end

# Comment β€” belongs_to User and Post
class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :post
end

# Profile β€” belongs_to User
class Profile < ApplicationRecord
  belongs_to :user
end

# Tag β€” belongs_to Post
class Tag < ApplicationRecord
  belongs_to :post
end

Menggunakan Associations

Rails Console β€” Associations
# Mendapatkan relasi
user = User.find(1)
user.posts          # Semua post milik user ini
user.comments       # Semua comment milik user ini
user.profile        # Profile milik user ini

post = Post.find(1)
post.user           # User yang memiliki post ini
post.comments       # Semua comment pada post ini

# Membuat relasi baru
user.posts.create(title: "Post Baru", body: "Isi post")
user.posts.build(title: "Draft", body: "Draft post")  # Tanpa save

# Mengakses data relasi dengan chaining
user.posts.published.recent  # Post published milik user, urut terbaru

# Eager loading β€” menghindari N+1 query problem
# BAD β€” akan menjalankan N+1 queries:
users = User.all
users.each { |u| puts u.posts.count }  # 1 query per user!

# GOOD β€” eager loading dengan includes:
users = User.includes(:posts).all
users.each { |u| puts u.posts.count }  # Hanya 2 queries!

# Preload dan eager_load
User.preload(:posts)         # 2 terpisah queries
User.eager_load(:posts)      # 1 query dengan LEFT JOIN
User.includes(:posts).where(posts: { published: true })  # Hybrid

11. Validations dan Callbacks

Validations memastikan data yang masuk ke database valid dan konsisten. Callbacks memungkinkan Anda menjalankan kode sebelum/sesudah event tertentu pada model.

Validations

app/models/post.rb
# app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user

  # Validasi dasar
  validates :title, presence: true,
                    length: { minimum: 5, maximum: 200 }
  validates :body, presence: true,
                   length: { minimum: 10 }
  validates :status, inclusion: { in: %w[draft published archived] }
  validates :slug, uniqueness: true, allow_nil: true

  # Custom validation
  validate :title_not_profanity

  # Conditional validation
  validates :publish_date, presence: true, if: :published?

  # Numericality
  validates :view_count, numericality: {
    greater_than_or_equal_to: 0,
    only_integer: true
  }

  # Format validation
  validates :slug, format: {
    with: /\A[a-z0-9\-]+\z/,
    message: "hanya boleh huruf kecil, angka, dan dash"
  }

  private

  def title_not_profanity
    bad_words = ["spam", "scam"]
    if bad_words.any? { |word| title.downcase.include?(word) }
      errors.add(:title, "mengandung kata yang tidak diizinkan")
    end
  end
end

Callbacks

Callbacks
# Callbacks tersedia dalam urutan berikut:
# before_validation    β†’ sebelum validasi
# after_validation     β†’ setelah validasi
# before_save          β†’ sebelum save (create & update)
# after_save           β†’ setelah save
# before_create        β†’ sebelum create
# after_create         β†’ setelah create
# before_update        β†’ sebelum update
# after_update         β†’ setelah update
# before_destroy       β†’ sebelum destroy
# after_destroy        β†’ setelah destroy

class Post < ApplicationRecord
  before_save :generate_slug, :set_published_at
  after_create :notify_followers
  before_destroy :archive_content

  private

  def generate_slug
    self.slug = title.parameterize if title.present? && slug.blank?
  end

  def set_published_at
    if status_changed? && status == "published"
      self.published_at = Time.current
    end
  end

  def notify_followers
    # Kirim notifikasi ke followers
    NotificationService.new_post(self)
  end

  def archive_content
    ArchivedPost.create!(
      title: title,
      body: body,
      user_id: user_id,
      archived_at: Time.current
    )
  end
end

12. Form dan CRUD

Rails memiliki helper form yang sangat powerful untuk membuat form HTML yang terintegrasi dengan model.

Form dengan form_with

app/views/users/_form.html.erb
<%# app/views/users/_form.html.erb %>

<%= form_with(model: @user, local: true) do |form| %>
  <%# Tampilkan error jika ada %>
  <% if @user.errors.any? %>
    <div class="error-messages">
      <h3><%= pluralize(@user.errors.count, "error") %> terjadi:</h3>
      <ul>
        <% @user.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="form-group">
    <%= form.label :name, "Nama Lengkap" %>
    <%= form.text_field :name, class: "form-control",
        placeholder: "Masukkan nama..." %>
  </div>

  <div class="form-group">
    <%= form.label :email, "Email" %>
    <%= form.email_field :email, class: "form-control",
        placeholder: "contoh@email.com" %>
  </div>

  <div class="form-group">
    <%= form.label :age, "Umur" %>
    <%= form.number_field :age, class: "form-control", min: 1, max: 150 %>
  </div>

  <div class="form-group">
    <%= form.label :role, "Role" %>
    <%= form.select :role,
        options_for_select([
          ["User", "user"],
          ["Admin", "admin"],
          ["Moderator", "moderator"]
        ], @user.role),
        {}, { class: "form-control" } %>
  </div>

  <div class="form-group">
    <%= form.label :bio, "Bio" %>
    <%= form.text_area :bio, class: "form-control", rows: 5 %>
  </div>

  <div class="form-group">
    <%= form.label :active, "Aktif?" %>
    <%= form.check_box :active %>
  </div>

  <div class="form-actions">
    <%= form.submit "Simpan", class: "btn btn-primary" %>
    <%= link_to "Batal", users_path, class: "btn btn-secondary" %>
  </div>
<% end %>

Show View

app/views/users/show.html.erb
<%# app/views/users/show.html.erb %>

<div class="user-detail">
  <div class="user-header">
    <h1><%= @user.name %></h1>
    <%= status_badge(@user) %>
  </div>

  <div class="user-info">
    <p><strong>Email:</strong> <%= @user.email %></p>
    <p><strong>Umur:</strong> <%= format_umur(@user.age) %></p>
    <p><strong>Role:</strong> <%= @user.role.capitalize %></p>
    <p><strong>Bergabung:</strong> <%= l(@user.created_at, format: :long) %></p>
  </div>

  <% if @user.posts.any? %>
    <h2>Posts (<%= @user.posts.count %>)</h2>
    <% @user.posts.each do |post| %>
      <div class="post-card">
        <h3><%= post.title %></h3>
        <p><%= truncate(post.body, length: 200) %></p>
        <%= link_to "Baca selengkapnya", post_path(post) %>
      </div>
    <% end %>
  <% end %>

  <div class="actions">
    <%= link_to "Edit", edit_user_path(@user), class: "btn btn-warning" %>
    <%= link_to "Hapus", user_path(@user),
        data: { turbo_method: :delete, turbo_confirm: "Yakin?" },
        class: "btn btn-danger" %>
    <%= link_to "Kembali", users_path, class: "btn btn-secondary" %>
  </div>
</div>

13. Quiz: Uji Pemahamanmu!

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

Pertanyaan 1: Apa kepanjangan dari MVC dalam Ruby on Rails?

a) Model-Variable-Controller
b) Model-View-Controller
c) Model-View-Cache
d) Module-View-Controller

Pertanyaan 2: Di folder mana file controller ditempatkan dalam struktur Rails?

a) app/models/
b) app/controllers/
c) app/views/
d) config/

Pertanyaan 3: Apa fungsi dari params.require(:user).permit(:name, :email) di Rails?

a) Memvalidasi format email
b) Menjalankan query database
c) Whitelist parameter yang diizinkan dari form (Strong Parameters)
d) Mengenkripsi data user

Pertanyaan 4: Manakah perintah yang benar untuk membuat model baru di Rails?

a) rails new model User
b) rails create model User
c) rails generate model User name:string
d) rails build model User

Pertanyaan 5: Apa fungsi dari has_many :posts, dependent: :destroy di model User?

a) Menambahkan foreign key ke tabel posts
b) Menghapus semua post milik user ketika user dihapus
c) Membuat tabel posts baru secara otomatis
d) Mengubah status post menjadi archived
πŸ” Zoom
100%
🎨 Tema