Sejak memasang "dark" theme, saya cenderung menjadi malas menulis. Untuk sementara, dark theme saya disable dulu yaa. Terima kasih (^_^) (bandithijo, 2024/09/15) ●
Prerequisite
Ruby 2.6.3
Rails 5.2.3
PostgreSQL 11.5
Prakata
Counter Cache, apa itu?
Merupakan field/kolom yang akan menyimpan hasil perhitungan dari tabel yang berasosiasi dengan dirinya (tabel yang memiliki field/kolom counter cache).
Dengan menggunakan counter cache, kita akan memperoleh keuntungan dimana object yang kita buat tidak perlu memanggil database untuk mendapatkan suatu nilai total, misal COUNT(*)
query, jadi cukup dengan mengakses counter cache field saja.
Contohnya kalau dalam Rails project seperti ini.
@user.posts.size
Atau
@user.posts.count
Kedua Active Record di atas akan menghasilkan SQL query serperti ini kira-kira.
SELECT COUNT(*) FROM "articles" WHERE "articles"."author_id" = $1
Nah, penggunaan size()
dan count()
ini akan menambah query COUNT(*)
yang artinya akan menambahkan query baru untuk memanggil database.
Namun, pada catatan ini, saya tidak membahas secara mendalam mengenai Counter Cache, teman-teman dapat membacanya pada blog dari teman-teman yang lain.
Saya akan membahas kasus yang mungkin cukup unik, yang saya alami.
Permasalahan
Seperti definisi yang sudah saya jelaskan di atas. Counter Cache adalah salah satu options dari banyak options yang dapat kita gunakan apabila kita menggunakan Active Record Association yaitu belongs_to
.1.
Active Record Association, sesuai namanya, Association, adalah hubungan/koneksi antara 2 Active Record model.
Yang artinya options counter cache dapat kita gunakan pada model yang saling berasosiasi.
Nah, sedangkan, saya ingin menggunakannya hanya pada 1 model.
Apakah bisa? Tentu saja bisa. Saya cukup lama mencari solusi ini.
Sekenario
Saya ingin membuat fitur “Invite Friend” yang nantinya akan terdapat 2 buah field.
- Field
inviting_user_id
, akan menampung data berupa id user yang menginvite user tersebut. - Field
inviting_users_count
, akan menampung berapa banyak user yang berhasil diinvite.
Di sini, saya tidak menggunakan model lain selain model user.
Yang mana, seharusnya, untuk dapat menggunakan counter_cache, kita harus memiliki dua model yang saling berasosiasi menggunakan belongs_to
.
Pemecahan Masalah
Pertama-tama seperti halnya convetion pada counter cache, saya perlu manambahkan kolom baru pada tabel yang ingin saya buatkan counter cachenya.
Buat migration untuk menambahkan kolom counter cache.
$ rails g migration add_inviting_user_to_users
create 20191122174434_add_inviting_user_count_to_users.rb
Setelah file migrasi jadi, saya akan menambahkan dua buah kolom.
1
2
3
4
5
6
class AddInvitingUsersCountToUsers < ActiveRecord::Migration[5.2]
def change
add_column :users, :inviting_user_id, :integer
add_column :users, :inviting_users_count, :integer, default: 0, null: false
end
end
Untuk field inviting_users_count
adalah field yang saya siapkan untuk counter cache, yang harus mengikuti aturan penamaan kolom untuk counter cache. yaitu, penamaannya harus plural (jamak).
Kemudian, jalankan migrationnya.
$ rails db:migrate
Apabila berhasil, berikut ini adalah bentuk dari skema database setelah migrasi berhasil kita jalankan.
1
2
3
4
5
6
7
create_table "users", force: :cascade do |t|
...
...
...
t.integer "inviting_user_id"
t.integer "inviting_users_count", default: 0, null: false
end
# Users table
+------------------------+-----------------------------+-----------------------------------------------------+
| Column | Type | Modifiers |
|------------------------+-----------------------------+-----------------------------------------------------|
| id | bigint | not null default nextval('users_id_seq'::regclass) |
| email | character varying | not null default ''::character varying |
| encrypted_password | character varying | not null default ''::character varying |
| created_at | timestamp without time zone | not null |
| updated_at | timestamp without time zone | not null |
| full_name | character varying | |
| inviting_user_id | integer | |
| inviting_users_count | integer | not null default 0 |
+------------------------+-----------------------------+-----------------------------------------------------+
Selanjutnya tinggal membuat asosiasi pada user model.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User < ApplicationRecord
# ...
# ...
has_many :inviting_users,
class_name: 'User',
foreign_key: :inviting_user_id
belongs_to :inviting_user,
class_name: 'User',
counter_cache: :inviting_users_count,
optional: true
# ...
# ...
end
Saya rasa, sudah jelas dari kode di atas. Bagaimana relasi antar kedua field dalam satu model dapat terjadi.
Kira-kira begini cerita asosiasi yang terjadi.
User memiliki banyak :inviting_user
, yang berasal dari User
class, yang akan ditempatkan pada field :inviting_user_id
.
Data pada :inviting_user
merupakan data milik User, yang berasal dari User
class, yang akan ditempatkan pada field :inviting_users_count
, asosiasi ini bersifat optional: true
, sehingga, apabila tidak terdapat asosiasi dengan object :inviting_user
, data user tetap akan dibuat.
Tujuan dari optional: true
adalah untuk melewati validasi presence apabila data baru akan dibuat. Karena secara default pada Rails 5, belongs_to
akan bernilai optional: false
.
Apabila tidak, maka user baru yang tidak memiliki relasi dengan user lain, tidak akan dapat dibuat.
Begini kira-kira hasilnya.
# Users table
+------+-----------------------+--------------------+------------------------+
| id | full_name | inviting_user_id | inviting_users_count |
|------+-----------------------+--------------------+------------------------|
| 1 | Rizqi Assyaufi | <null> | 1 |
| 2 | Baik Budiman | 1 | 0 |
Selesai!
Mudah-mudahan catatan kali ini dapat bermanfaat bagi teman-teman yang memerlukan.
Dokumentasi lebih lengkap dapat dibaca pada daftar referensi yang saya sertakan di bawah.
Terima kasih
(^_^)
Referensi
-
guides.rubyonrails.org/association_basics.html#options-for-belongs-to
Diakses tanggal: 2019/12/13 -
guides.rubyonrails.org/association_basics.html#options-for-has-many-counter-cache
Diakses tanggal: 2019/12/13 -
stackoverflow.com/questions/35265225/rails-counter-cache-on-the-same-model
Diakses tanggal: 2019/12/13
Lisensi
Atribusi-NonKomersial-BerbagiSerupa 4.0 Internasional (CC BY-NC-SA 4.0)
Penulis
My journey kicks off from reading textbooks as a former Medical Student to digging bugs as a Software Engineer – a delightful rollercoaster of career twists. Embracing failure with the grace of a Cat avoiding water, I've seamlessly transitioned from Stethoscope to Keyboard. Armed with ability for learning and adapting faster than a Heart Beat, I'm on a mission to turn Code into a Product.
- Rizqi Nur Assyaufi