Day 14 — Relasi Eloquent: One-to-Many & Eager Loading
Fase: 2 — Mini Project Asset Management | Minggu: 3 | Hari: Kamis
🎯 Tujuan Hari Ini
Peserta memahami secara mendalam cara kerja Relasi Eloquent dan mengatasi masalah umum yang sering terjadi: N+1 Query Problem, kemudian mengoptimalkannya dengan Eager Loading.
📋 Task Wajib
1. Review & Verifikasi Relasi
Buka app/Models/Category.php dan app/Models/Asset.php. Pastikan relasi sudah terdefinisi dengan benar:
// Category.php
public function assets() {
return $this->hasMany(Asset::class); // 1 category → banyak assets
}
// Asset.php
public function category() {
return $this->belongsTo(Category::class); // 1 asset → 1 category
}2. N+1 Query Problem: Masalah Tersembunyi
Apa itu N+1 Problem?
Perhatikan kode ini di AssetController@index:
// ❌ SALAH - Ini N+1 Problem!
$assets = Asset::all(); // 1 query untuk semua aset
// Lalu di view, setiap baris kita panggil:
// $asset->category->name <-- ini menjalankan 1 query BARU per baris!Jika ada 100 aset, maka ada 1 + 100 = 101 query! Ini sangat lambat.
Solusi: Eager Loading dengan with()
// ✅ BENAR - Eager Loading
$assets = Asset::with('category')->get();
// Hanya 2 query total: 1 untuk aset, 1 untuk semua kategori sekaligus3. Implementasi Halaman Detail Category (Show)
Buat resources/views/categories/show.blade.php:
@extends('layouts.app')
@section('title', 'Detail Kategori')
@section('content')
<div style="margin-bottom:20px;">
<a href="{{ route('categories.index') }}" style="color:#666; text-decoration:none;">← Kembali ke Kategori</a>
</div>
<div style="background:white; padding:30px; border-radius:10px; margin-bottom:24px; box-shadow:0 2px 8px rgba(0,0,0,0.07);">
<h1>📂 {{ $category->name }}</h1>
<p style="color:#666; margin-top:8px;">{{ $category->description ?? 'Tidak ada deskripsi.' }}</p>
<div style="margin-top:16px; display:flex; gap:20px;">
<span style="color:#999; font-size:13px;">Dibuat: {{ $category->created_at->format('d M Y') }}</span>
<span style="background:#eef2ff; color:#4f46e5; padding:4px 12px; border-radius:20px; font-size:13px;">
{{ $category->assets->count() }} aset terdaftar
</span>
</div>
</div>
<h2 style="margin-bottom:16px;">Aset dalam Kategori Ini</h2>
@if($category->assets->count() > 0)
<div style="background:white; border-radius:10px; overflow:hidden; box-shadow:0 2px 8px rgba(0,0,0,0.07);">
<table style="width:100%; border-collapse:collapse;">
<thead>
<tr style="background:#f8f9fa;">
<th style="padding:12px 16px; text-align:left;">Kode</th>
<th style="padding:12px 16px; text-align:left;">Nama</th>
<th style="padding:12px 16px; text-align:center;">Stock</th>
<th style="padding:12px 16px; text-align:center;">Kondisi</th>
<th style="padding:12px 16px; text-align:center;">Aksi</th>
</tr>
</thead>
<tbody>
@foreach($category->assets as $asset)
<tr style="border-bottom:1px solid #f0f0f0;">
<td style="padding:12px 16px; font-family:monospace; color:#4f46e5;">{{ $asset->code }}</td>
<td style="padding:12px 16px;">{{ $asset->name }}</td>
<td style="padding:12px 16px; text-align:center; font-weight:bold;">{{ $asset->stock }}</td>
<td style="padding:12px 16px; text-align:center;">{{ ucfirst($asset->condition) }}</td>
<td style="padding:12px 16px; text-align:center;">
<a href="{{ route('assets.edit', $asset->id) }}" style="color:#0066cc;">Edit</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<div style="background:#fff3cd; padding:20px; border-radius:8px; color:#856404;">
⚠️ Kategori ini belum memiliki aset.
<a href="{{ route('assets.create') }}?category={{ $category->id }}">Tambahkan aset sekarang.</a>
</div>
@endif
@endsection4. Perbaiki Semua Controller: Gunakan Eager Loading
Update semua method yang memanggil relasi:
CategoryController@index:
$categories = Category::withCount('assets')->orderBy('name')->get();
// withCount() = satu cara efisien mendapatkan COUNT relasiAssetController@index:
$assets = Asset::with('category')->orderBy('name')->paginate(10);
// with('category') = Eager loading relasi category5. Optimalkan Dengan Nested Eager Loading
Jika di masa depan ada halaman yang butuh data lebih dalam:
// Contoh: load aset beserta kategorinya DAN loan-nya sekaligus
$assets = Asset::with(['category', 'loans'])->get();
// Atau nested: load loans beserta detail asetnya
$loans = Loan::with('asset.category')->get();
// Ini membaca: "muat loans, sertakan asetnya, dan kategori dari aset tersebut"6. Tambahkan Link Navigasi Antar Modul
Update link di halaman assets/index.blade.php: klik nama kategori agar menuju halaman detail kategori.
<td style="padding:14px 16px;">
<a href="{{ route('categories.show', $asset->category_id) }}"
style="color:#4f46e5; text-decoration:none;">
{{ $asset->category->name }}
</a>
</td>📝 Laporan ke Mentor
📌 LAPORAN HARIAN — Day 14
Nama : [Nama Lengkap]
Role : [Role Kamu]
✅ Yang saya kerjakan hari ini:
- [ ] Memahami dan bisa menjelaskan N+1 Problem
- [ ] Semua controller sudah menggunakan eager loading with()
- [ ] Halaman categories/show.blade.php selesai dan tampil daftar aset
- [ ] Link kategori di halaman aset menuju ke detail kategori
📸 Screenshot Wajib:
1. Halaman /categories/{id} menampilkan daftar aset dalam kategori
2. Tabel aset di /assets — kolom kategori adalah link yang bisa diklik
🧠 Pertanyaan Pemahaman (jawab di laporan):
Q: Apa perbedaan output antara kode ini:
(A) Asset::all()
(B) Asset::with('category')->get()
A: [Jawaban kamu]
❓ Kendala:
[Tulis jika ada]Catatan Mentor
Jika tersedia Laravel Debugbar (composer require barryvdh/laravel-debugbar --dev), install dan tunjukkan secara langsung berapa banyak query yang dijalankan dengan dan tanpa eager loading. Ini akan sangat berkesan bagi peserta dan membuat mereka memahami pentingnya optimasi.