#406 Public Activity
- Download:
- source codeProject Files in Zip (93.9 KB)
- mp4Full Size H.264 Video (22.1 MB)
- m4vSmaller H.264 Video (12.2 MB)
- webmFull Size VP8 Video (15.8 MB)
- ogvFull Size Theora Video (26.1 MB)
Feed aktivitas user merupakan hal yang biasa di minta untuk ada di sebuah web, seperti yang dimiliki juga oleh Github. Ini sangat baik untuk aplikasi dengan gaya social-network sehingga kita bisa melihat apa saja yang sudah dilakukan oleh user-user lain
Kita memiliki aplikasi buku masakan dimana user dapat membuat, mengedit dan membagikan resep-resep. User dapat menambahkan komentar pada resep dan menandai user lain sebagai teman. Kita akan menambahkan halaman aktivitas untuk aplikasi ini sehingga kita bisa melihat semua aktivitas teman kita dan mengetahui apa yang baru mereka lakukan, misalnya mengepost resep baru atau menambahkan komentar.
Kita akan melakukan ini dengan gem yang bernama Public Activity. Untuk menggunakannya pada aplikasi kita, kita akan menambahkannya ke dalam gemfile dan menjalankan bundle
untuk menginstalnya
gem 'public_activity'
Untuk membuat tabel database yang dibutuhkan oleh gem itu, kita perlu menjalankan generator publish_activity:migration
kemudian melakukan migrasi pada database
$ rails g public_activity:migration $ rake db:migrate
Tabel activities
akan terbuat beserta dengan model ActiveRecord untuk manajemen aktivitas. Public Activity juga compatible dengan Mongoid dan langkah ini tidak perlu dilakukan bila anda menggunakannya. Lihatlah dokumentasinya bila anda melakukan setup ini. Langkah selanjutnya adalah menyertakan modul PublicActivity::Model
pada setiap model yang ingin kita track aktivitasnya dan agar memanggil method bernamad tracked
. Kita akan melakukan ini pada model Recipe
.
class Recipe < ActiveRecord::Base include PublicActivity::Model tracked attr_accessible :description, :image_url, :name belongs_to :user has_many :comments, dependent: :destroy end
Method tracked
mengeset beberapa callback untuk secara otomatis membuat catatan aktivitas saat sebuah model dibuat, di-update ataupun dihapus. Kita akan melakukan ini pada model Comment
karena kita ingin melakukan tracking padanya. Jika sekarang kita menambahkan komentar baru pada sebuah resep akan langsung di tracking oleh Public Activity.
Halaman Aktifitas
Selanjutnya kita perlu mebuat halaman yang menampilkan aktifitas. Kita akan membuat sebuah controller baru dengan action index
untuk ini.
$ rails g controller activities index
Selanjutnya kita akan merubah file route dan mengganti route yang terbuat dengan resource activiteis.
resources :activities
Pada action index controller ini kita ingin membuat list dari semua aktifitas. Dengan memanggil PublicActivity::Activity
akan memberikan kita model ActiveRecord sehingga kita bisa melakukan query pada basis data seperti biasa. Kita akan mengurutkan hasilnya berdasarkan waktu pembuatannya sehingga aktifitas yang terbaru akan tampil diatas.
class ActivitiesController < ApplicationController def index @activities = PublicActivity::Activity.order("created_at desc") end end
Pada view kita dapat melakukan perulangan terhadap data ini dan menampilkannya. Untuk saat ini kita hanya akan melakukan inspect pada setiap aktifitas untuk melihat apa isinya.
<h1>Friends’ Activities</h1> <% @activities.each do |activity| %> <%= activity.inspect %> <% end %>
Saat kita memuat ulang halaman sekarang akan terlihat satu aktifitas yang sudah kita tambahkan.
Kita dapat melihat atribut yang berbeda, dalam aktifitas ini terdapat kolom trackable_id
dan trackable_type
. Ini merupakan asosiasi polymorphic dan kita tahu bahwa aktifitas ini terasosiasi dengan model Comment
. Disini juga terdapat beberapa asosiasi polymorphic lainnya: recipient
dan owner
. Owner adalah pengguna yang melakukan aktifitas dan kita ingin mengeset ini sehingga kita bisa menampilkan nama pengguna pada aktifitasnya. Kita akan melakukannya setelah ini.
Seperti sudah disampaikan sebelumnya, gem ini menggunakan callback untuk melakukan pencatatan aktifitas. Hal ini menimbulkan masalah karena layer model tidak memiliki akses pada user saat ini, apakah ini berarti kita tidak bisa mengeset kepemilikan saat mencatat aktivitas? Public Activity memiliki pemecahan untuk masalah ini; untuk menggunakannya kita perlu menambahkan module pada ApplicationController
.
class ApplicationController < ActionController::Base include PublicActivity::StoreController # Rest of class omitted end
Ini akan mencatat ccntroller pada setiap request dan membuat kita bisa mengaksesnya dari model. Kita akan menambahkan ini pada model Comment
dengan menyertakan opsi owner
pada tracked
.
tracked owner: ->(controller, model) { controller.current_user }
Kita set opsi ini pada lambda dan akan dievalusi setiap tracking aktivitas dilakukan. Controller dan model diteruskan pada tracked sehingga kita bisa mengeset kepemilikan sesuai dengan user yang aktif saat itu. Tapi ini masih menyisakan masalah kecil, karena current_user
merupakan private method pada ApplicationController
kita. Kita akan membuatnya menjadi public danmenggunakan hide_action
untuk membuatnya tidak dianggap sebagai sebuah action.
class ApplicationController < ActionController::Base include PublicActivity::StoreController protect_from_forgery def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] end helper_method :current_user hide_action :current_user private def require_login redirect_to login_url, alert: "You must first log in or sign up." if current_user.nil? end end
Isu potensial lain adalah pada saat Comment
memanggil controller.current_user
akan menimbulkan exception jika kita mencoba membuat record diluar request saat itu karena tidak ada controllernya. Kita akan mengecek untuk melihat apakah ada controllernya sebelum mencoba untuk mendapatkan current user darinya.
tracked owner: ->(controller, model) { controller && controller.current_user }
Pengerjaan ini tidak ideal dan rasanya agak kurang baik untuk mendapatkan akses terhadap controller dari model tetapi ini dapat bekerja. Kita akan menambahkannya ke model Recipe
sehingga kita bisa mendapatkan user dari sini juga. Kita kemudian mencoba juga menambahkan comment baru lalu mengunjungi halaman aktivitas lagi.
Sekarang kita memiliki dua aktivitas dan yang pertama, yang baru saja ditambahkan sudah mempunyai owner_id
. Kita akan memperbaiki bagian view sekarang, agar dapat dengan benar menampilkan aktivitas.
<h1>Friends’ Activities</h1> <% @activities.each do |activity| %> <div class="activity"> <%= link_to activity.owner.name, activity.owner if activity.owner %> added comment to <%= link_to activity.trackable.recipe.name, activity.trackable.recipe %> </div> <% end %>
Kita memiliki div
dengan class activity
untuk setiap aktivitas dan di dalamnya kita menampilkan nama pemilik aktivitas, tetapi hanya untuk aktivitas yang memiliki pemilik. Selanjutnya kita akan mendeskripsikan aktivitas tersebut dan ini akan sedikit sulit karena setiap aktivitas memiliki keunikan, sehingga untuk saat ini kita akan menulis saja bahwa aktivitas kita adalah komentar. Kita kemudian menampilkan nama resep dalam tautan untuk menuju ke resep itu. Untuk memperoleh resepnya kita menggunakan activity.trackable
yang merupakan asosiasi polymorphic yang mereferensikan model untuk aktivitas tersebut, dalam kasus ini merupakan model Comment
. Kita sekarang akan menambahkan styling juga terhadap tampilan aktivitas ini.
.activity { border-bottom: solid 1px #CCC; padding: 16px 0; em { color: #777; font-size: 12px; padding-left: 5px; } }
Saat kita memuat ulang halaman ini sekarang kita seharusnya dapat melihat daftar aktivitas yang ada.
Ini sudah tampak bagus tetapi kita melakukan hard-coding untuk deskripsi. Bagaimana kita bisa mengubahnya agar dapat sesuai dengan masing-masing tipe aktivitas? Public Activity menyediakan helper bernama render_activity
yang dapat kita gunakan.
<h1>Friends’ Activities</h1> <% @activities.each do |activity| %> <div class="activity"> <%= link_to activity.owner.name, activity.owner if activity.owner %> <%= render_activity activity %> </div> <% end %>
Ini akan me-render partial untuk aktivitas dengan mencarinya pada direktori public_activity
dan disana kita akan membutuhkan folder untuk setiap tipe model yang berbeda yang kita tracking. Kita ingin partial ini akan tampil saat sebuah comment dibuat, maka kita menamakannya _create.html.erb
. Di dalamnya kita akan mendeskripsikan activitas seperti yang kita lakukan sebelumnya.
added comment to <%= link_to activity.trackable.recipe.name, activity.trackable.recipe %>
Sepertinya kita sudah pernah melakukan ini sebelumnya, tetapi untuk yang sekarang kita menggunakan partial untuk setiap tipe aktivitas termasuk _destroy
dan _update
. Setelah kita selesai menambahkan ini dan mengupdate beberapa komentar, kita dapat melihat bagaimana akan kelihatan di halaman aktivitas.
Semuanya sudah tampak bagus sekarang, tetapi saat kita menghapus suatu data, misalnya sebuah resep dan mengunjungi halaman aktivitas kita akan mendapatkan pesan kesalahan.
Ini karena kita memanggil activity.trackable.recipe
untuk resep yang sudah tidak lagi ada. Ini penting untuk setiap partial agar dapat mengetahui apakah suatu data masih ada atau tidak, sehingga kita akan mengubah setiap partial menjadi seperti ini.
added comment to <% if activity.trackable %> <%= link_to activity.trackable.recipe.name, activity.trackable.recipe %> <% else %> which has since been removed <% end %>
Saat kita memuat ulang halaman, pesan kesalahan tadi akan menghilang.
Excluding Actions
Selanjutnya kita akan memperlihatkan bagaimana untuk meng-exclude action yang tidak kita inginkan untuk di track. Misalnya pembaharuan komentar tidak begitu menarik, sehingga kita tidak ingin memperlihatkannya pada daftar aktivitas. Kita dapat melakukannya dengan memberikan pilihan pada saat memanggil tracked
di model Comment
model: apakah itu only
untuk menuliskan aktivitas mana saja yang seharusnya di-tracked ataupun except
untuk menuliskan aktivitas yang di-exclude.
tracked except: :update, owner: ->(controller, model) { controller && controller.current_user }
Method tracked
mulai menjadi lebih kompleks dengan segala pilihan ini dan melakukan tracking aktivitas melalui model callback tidak selalu menjadi cara terbaik, sehingga kita akan mengubahnya agar melalui controller. Untuk melakukan ini kita akan menyertakan PublicActivity::Common
pada model, bukannya PublicActivity::Model
. Kita juga dapat menghilangkan pemanggilan tracked
disini pula.
class Comment < ActiveRecord::Base include PublicActivity::Common attr_accessible :content belongs_to :user belongs_to :recipe end
Sekarang pada CommentsController
kita dapat mencatat aktivitas saat kita melakukan penyimpanan, pembaharuan ataupun penghapusan komentar.
if @comment.save @comment.create_activity :create, owner: current_user redirect_to @recipe, notice: "Comment was created." else render :new end
Ini memberikan kita kontrol yang lebih baik terhadap kapan dan bagaimana aktivitas dibuat dan ini dimaksudkan untuk menghindari kita men-setting current user. Cara ini juga membuat kita dapat membuat aktivitas yang custom dengan lebih mudah.
Untuk menyelesaikan episode ini kita akan mengubah daftar aktivitas sehingga hanya menampilkan aktivitas dari teman kita, bukannya aktivitas semua orang. Kita dapat melakukannya dengan menambahkan scope kepada ActivitiesController
kita.
class ActivitiesController < ApplicationController def index @activities = PublicActivity::Activity.order("created_at desc").where(owner_id: current_user.friend_ids, owner_type: "User") end end
Dan sekarang hanya aktivitas yang termasuk dalam teman current user yang akan ditampilkan. Sebagai pemilik kita dapat menggunakan asosiasi polymorphic dan merupakan ide yang baik untuk memeriksa apakah owner_type
adalah User
. Saat kita memuat ulang halaman sekarang, kita tidak lagi melihat aktivitas dari user yang tidak kita tandai sebagai teman kita, tetapi saat kita menambahkan user lain yang sudah membuat komentar sebagai teman maka kita akan melihat aktivitas mereka dalam daftar.