[Rails]public_activityを使ってタイムラインっぽいものを作る
はじめに
public_activity
gemを使ってタイムラインっぽいものを作っていきます。
Railsのバージョンは5.0以上であればgem使用可能となります。
public_activity
gem は、アクティビティフィード機能を実装するためのGemです。
このgemがActivities
テーブルが作成し、アプリ内で指定したユーザーやリソースのCRUDアクションをトラッキングし、それを表示するためのロジックやメソッドを用意してくれてます。
環境
Rails 7.0.7
ruby 3.2.1
tl;dr
1. gemをインストールする
2. マイグレーションを実行し、DB上にテーブルを作成する
3. トラッキングしたいモデルにtracked
メソッドを追加する
4. public_activity
のヘルパーメソッドを使用しビューにアクティビティを表示する
5. カスタマイズ
ユーザー認証はdeviseを使ってます。gemのreadme通りに進めていきます。
gemをインストールする
gem 'public_activity'
bundle install
マイグレーションを実行する
bib/rails g public_activity:migration
create db/migrate/20231001070216_create_activities.rb
ポリモーフィック関連付けのActivitiesテーブルが作成されました。
bin/rails db:migrate
== 20231001070216 CreateActivities: migrating =================================
-- create_table(:activities, {:id=>:integer})
-> 0.0494s
-- add_index(:activities, [:trackable_id, :trackable_type])
-> 0.0021s
-- add_index(:activities, [:owner_id, :owner_type])
-> 0.0014s
-- add_index(:activities, [:recipient_id, :recipient_type])
-> 0.0013s
== 20231001070216 CreateActivities: migrated (0.0544s) ========================
Activitiesテーブル
create_table "activities", id: :serial, force: :cascade do |t|
t.string "trackable_type"
t.integer "trackable_id"
t.string "owner_type"
t.integer "owner_id"
t.string "key"
t.text "parameters"
t.string "recipient_type"
t.integer "recipient_id"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["owner_id", "owner_type"], name: "index_activities_on_owner_id_and_owner_type"
t.index ["owner_type", "owner_id"], name: "index_activities_on_owner_type_and_owner_id"
t.index ["recipient_id", "recipient_type"], name: "index_activities_on_recipient_id_and_recipient_type"
t.index ["recipient_type", "recipient_id"], name: "index_activities_on_recipient_type_and_recipient_id"
t.index ["trackable_id", "trackable_type"], name: "index_activities_on_trackable_id_and_trackable_type"
t.index ["trackable_type", "trackable_id"], name: "index_activities_on_trackable_type_and_trackable_id"
end
ダッシュボードコントローラーを作成する
アクティビティはユーザーのマイページに表示させたいのでbin/rails g controller dashboard home
で作成します。
current_user
にする
アクティビティオーナーをapplication_controller.rb
にこちらの記述を追加します。
class ApplicationController < ActionController::Base
include PublicActivity::StoreController
end
トラッキングしたいモデルファイルにこちらの記述を追加します。
Devise以外のgemを使っている場合current_user
を適切な変数名に書き換えてください。
class Post < ApplicationRecord
include PublicActivity::Model
tracked owner: Proc.new{ |controller, model| controller.current_user }
has_many :activities, as: :trackable, dependent: :destroy
end
tracked
メソッドの owner
オプションは、指定したアクティビティをトラッキングする際に、そのアクティビティの所有者(オーナー)を指定するためのオプションです。
このオプションは、アクティビティがどのユーザーによって作成されたかを表示させたい時に便利です。
今回の場合は、Post
モデルにおいて、アクティビティの所有者(オーナー)は、コントローラーの current_user
メソッドが返すユーザーオブジェクトとなります。
つまり、記事( Post
)が作成、更新、削除された場合、それらのアクションに関連付けられたアクティビティは、current_user
が返すユーザーによって所有されます。
@activities
変数を定義する
先に作成したダッシュボードコントローラーのhome
アクション内に表示させたいアクティビティを定義します。
class DashboardController < ApplicationController
def home
# 全てのユーザーのアクティビティを表示する
@activities = PublicActivity::Activity.all
# current_userのアクティビティを表示する
@activities = PublicActivity::Activity.where(owner_id: current_user.id).order(created_at: :desc)
end
end
ビィーに表示する
<% @activities.each do |activity| %>
<% if activity.trackable_type == 'Post' %>
<% if activity.key == 'post.destroy' %>
# ヘルパーメソッドを使わない場合
<p><%= t('public_activity.post.destroy') %><%= activity.created_at.to_s %></p>
<% else %>
<% post = activity.trackable %>
<% owner = activity.owner %>
<% next if post.nil? %>
<div>
<h2><%= link_to post.title %></h2>
<p><%= post.body %></p>
<p><%= activity.created_at %></p>
# ヘルパーメソッドを使う場合
<p><%= action_name(activity) %></p>
<p><%= owner.email %></p>
</div>
<% end %>
<% end %>
<% end %>
<% if activity.trackable_type == 'Post' %>
:モデルによってアクティビティの表示を変えたいためif
文を追加しました(投稿の場合タイトルを表示、コメントの場合本文を表示など)。
<% if activity.key == 'post.destroy' %>
:アクションによってアクティビティの表示をカスタマイズしたい場合if
文を追加できます。
<% next if post.nil? %>
:投稿が削除されたけどアクティビティレコードがまだ残っている場合、投稿がnil
になるので次の投稿に移すための記述です。
action_name(activity)
:t('public_activity.post.destroy')
をもっとスッキリした書き方にしたいのでビュー用のヘルパーメソッドを作成します。
action_name(activity)
ヘルパーメソッド
activity.key
:アクティビティを識別するために用意されています。
post.create
, comment.update
, user.follow
などモデルと対応するアクションで構成されます。
publice_activity
のデフォルトのアクションはcreate
,update
,destroy
になります。
module ActivityHelper
def action_name(activity)
t("public_activity.#{activity.key}")
end
end
日本語を追加する
アクティビティと対応するアクションの日本語を追加します。
ja:
public_activity:
post:
create: を作成されました。
update: を更新されました。
destroy: を削除されました。
基本のセットアップが完了しました。投稿を作成してみます。
Activity
レコードも一緒に作成されましたね。
17:37:22 web.1 | PublicActivity::Activity Create (2.3ms) INSERT INTO "activities" ("trackable_type", "trackable_id", "owner_type", "owner_id", "key", "parameters", "recipient_type", "recipient_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING "id" [["trackable_type", "Post"], ["trackable_id", 5], ["owner_type", "User"], ["owner_id", 1], ["key", "post.create"], ["parameters", nil], ["recipient_type", nil], ["recipient_id", nil], ["created_at", "2023-10-01 08:37:22.759607"], ["updated_at", "2023-10-01 08:37:22.759607"]]
ダッシュボードにあるアクティビティ一覧:
ビューをパーシャル化する
アクションごとにビューを作成したい場合、public_activity/post/_create.html.erb
、public_activity/post/_update.html.erb
、public_activity/post/_destroy.html.erb
を作成することができます。
そうするとアクティビティ一覧のビューをもっと簡潔に書くことができます。
<% @activities.each do |activity| %>
<%= render_activity activity %>
<% end %>
特定のアクティビティを非表示にする
# 全体的に非表示にする
PublicActivity.enabled = false
# 投稿を作成する
Post.create(title: 'New article')
# 再度表示にする、上の投稿のアクティビティは作成されない
PublicActivity.enabled = true
# Postクラスのアクティビティを非表示にする
Post.public_activity_off
# この投稿のアクティビティは作成されない
@post = Post.create(title: 'New article')
# 投稿のコメントのアクティビティに影響を与えない
@post.comments.create(body: 'some comment!')
# 再度表示にする
Post.public_activity_on
カスタムアクションを作成する
CRUDアクション以外に、カスタムアクションを作成することができます。
# changeアクションに対するアクティビティを作成する
# どちらの書き方でも大丈夫
@user.create_activity :change
@user.create_activity action: :change
終わりに
タイムラインっぽかったものができました。
Activity
テーブルのデータが増えてしまうので、定期的に古いアクティビティのデータの削除も必要になってきますね。
一旦ここまでにします。
Discussion