🦓

[Rails]public_activityを使ってタイムラインっぽいものを作る

2023/10/01に公開

はじめに

public_activitygemを使ってタイムラインっぽいものを作っていきます。
Railsのバージョンは5.0以上であればgem使用可能となります。

public_activity gem は、アクティビティフィード機能を実装するためのGemです。
このgemがActivitiesテーブルが作成し、アプリ内で指定したユーザーやリソースのCRUDアクションをトラッキングし、それを表示するためのロジックやメソッドを用意してくれてます。
https://github.com/public-activity/public_activity

環境

Rails 7.0.7
ruby 3.2.1

tl;dr

1. gemをインストールする
2. マイグレーションを実行し、DB上にテーブルを作成する
3. トラッキングしたいモデルにtrackedメソッドを追加する
4. public_activityのヘルパーメソッドを使用しビューにアクティビティを表示する
5. カスタマイズ

ユーザー認証はdeviseを使ってます。gemのreadme通りに進めていきます。

gemをインストールする

Gemfile
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にこちらの記述を追加します。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include PublicActivity::StoreController 
end

トラッキングしたいモデルファイルにこちらの記述を追加します。
Devise以外のgemを使っている場合current_userを適切な変数名に書き換えてください。

app/models/post.rb
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 が返すユーザーによって所有されます。

https://github.com/public-activity/public_activity/blob/main/lib/public_activity/roles/tracked.rb#L49-L58

@activities変数を定義する

先に作成したダッシュボードコントローラーのhomeアクション内に表示させたいアクティビティを定義します。

app/controllers/dashboard_controller.rb
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

ビィーに表示する

app/views/dashboard/home.html.erb
<% @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になります。

app/helpers/activity_helper.rb
module ActivityHelper
    def action_name(activity)
      t("public_activity.#{activity.key}")
    end
end

日本語を追加する

アクティビティと対応するアクションの日本語を追加します。

config/locales/ja.yml
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.erbpublic_activity/post/_update.html.erbpublic_activity/post/_destroy.html.erbを作成することができます。

そうするとアクティビティ一覧のビューをもっと簡潔に書くことができます。

app/views/public_activity/posts/index.html.erb
<% @activities.each do |activity| %>
  <%= render_activity activity %>
<% end %>

https://www.rubydoc.info/gems/public_activity/PublicActivity/Renderable

特定のアクティビティを非表示にする

# 全体的に非表示にする
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

https://blog.corsego.com/gem-public-activity-complete-guide

カスタムアクションを作成する

CRUDアクション以外に、カスタムアクションを作成することができます。

# changeアクションに対するアクティビティを作成する
# どちらの書き方でも大丈夫
@user.create_activity :change
@user.create_activity action: :change

https://github.com/public-activity/public_activity/blob/main/lib/public_activity/common.rb#L250-L275

終わりに

タイムラインっぽかったものができました。
Activityテーブルのデータが増えてしまうので、定期的に古いアクティビティのデータの削除も必要になってきますね。
一旦ここまでにします。

Discussion