Railsで通知機能を作る（①基本機能編）

## やったこと
Railsでアプリを作る課題を行っています。通知機能を作りましょうというタスクがありました。
初めて作る＆覚えておくと応用が効きそうなので、いかにやり方をまとめます。

なお、実行環境は以下の通りです。

- Rails `5.2.3`
- Ruby `2.6.4`

## 仕様
以下のような仕様になっています。

- `user`がフォローされた時
- `user`の投稿した`post`にコメントがついた時
- `user`の投稿に「いいね！」がされた時

上記の条件でユーザーに通知を出します。

## DB設計

こちらのようになっています。

[![Image from Gyazo](https://i.gyazo.com/d7161965ac2a7b28150940fd8f4ceabc.png)](https://gyazo.com/d7161965ac2a7b28150940fd8f4ceabc)

ポリモーフィック関連付けをしようして、`activity.subject`の形で、`Like`, `Comment`, `Relationship`のインスタンスが呼び出せるようにしています。
データベース構造の詳細はこちらの記事をご覧ください。

[Railsのポリモーフィック関連付けを理解する](https://qiita.com/tanutanu/items/726e7097c6ca4a9ca6e3)

また、`activities`テーブルの`read`カラムも利用して、既読管理も行いますが、これは、本記事ではなく、次回の記事にて紹介します。

## ゴール
上記のテーブル設計から**「`activities`のレコードを作成し、現在の`user`のものだけを集約し、それを一覧表示する」**ことが今回のゴールとなりそうです。

## 通知機能
### Model

今回は、

- `user`がフォローされた時 => **`relationships`のレコードが作られた時**
- `user`の投稿した`post`にコメントがついた時 => **`comments`のレコードが作られた時**
- `user`の投稿に「いいね！」がされた時 => **`likes`のレコードが作られたとき**

↑**上記３つのアクションが行われたときに、`activities`のレコードも同時に作られれば**良さそうです。

それは、以下の方法で実装しました。モデルメソッドに書いた内容を、３つまとめて紹介します。

```ruby
class Like < ApplicationRecord
  # 中略
  after_create_commit :create_activities

  private

  def create_activities
    Activity.create!(subject: self, user: post.user, action_type: :liked_to_own_post)
  end
end

class Relationship < ApplicationRecord
  # 中略
  after_create_commit :create_activities

  private

  def create_activities
    Activity.create!(subject: self, user: follower, action_type: :followed_me)
  end
end

lass Comment < ApplicationRecord
  # 中略
  after_create_commit :create_activities

  private

  def create_activities
    Activity.create!(subject: self, user: post.user, action_type: :commented_to_own_post)
  end
end
```

`create_activities`のメソッドを、`Like`, `Relationship`, `Comment`, のインスタンスが作られた後で呼んでいます。使用している`after_create_commit`なのですが、`after_save`と似ているコールバックです。ただし、**データベース変更のコミットが完了するまでトリガされない**点が異なるそうです。

詳しい説明は[Railsガイドのこちらの記述](https://railsguides.jp/active_record_callbacks.html#%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF)をどうぞ。

なお、`model`にはそれぞれ、**ポリモーフィック関連付け**を定義する記述もつけています。こちらは、[先に挙げたこちらの記事](https://qiita.com/tanutanu/items/726e7097c6ca4a9ca6e3)をご覧ください。

今思うと、各モデルの`create_activities`がプライベートメソッドなのは、別々のモデルに同名のメソッドを使っているからなのですね。。。

(当初は、お手本のメソッドの写経をしていましたが、今、改めて見直してみて思うです。。。)


さて、これで基本的な通知機能は実装できました。まだ`view`ができていないので、console上で実験すると...

```sh
$ rails c
> user = User.first
> user.activities
#=> Activityのインスタンスたちが取得できる
```

上記のコマンドで`user`に紐づく通知一式が取得できます。これを`each`で回せば、`view`で表示できそうです。

## 追記）Viewで通知を表示する
続けて、Viewを続けて実装します。

仕様は以下の通りです。３種類ある通知をクリックすると、それぞれ以下のページに遷移します。

- 自分がフォローされた時の通知 => フォローしてくれたユーザーの詳細ページ
- 自分の投稿へのコメントの通知 => 該当の投稿
- 自分の投稿へのいいねの通知 => 該当の投稿

それぞれの遷移先は、以下のように設定します。（本当はモデルメソッドとして実装しましたが、記事の範囲内で動作させるために、一時的にヘルパーに記載しています。）

```ruby:some_helper.rb
module SomeHelper
  
  def transition_path(activity)
    case activity.action_type.to_sym
    when :commented_to_own_post
      post_path(activity.subject.post, anchor: "js-comment-#{activity.subject.id}")
    when :liked_to_own_post
      post_path(activity.subject.post)
    when :followed_me
      user_path(activity.subject.follower)
    end
  end
end
```
そして、`action_type`（通知のタイプ）と同じ名前のビューを作り...
（ここではサンプルに１例だけ載せます）

```slim:views/mypage/activities/_liked_to_own_post.html.slim
= link_to transition_path(activity) do
  object
   = link_to activity.subject.user.username, user_path(activity.subject.user)
  | があなたの
  object
    =link_to '投稿', post_path(activity.subject.post)
  | にいいねしました
  .text-right
    = l activity.created_at, format: :short
```

`controller`にて、current_userのものだけ集約した`activity`を

```ruby:app/controllers/mypage/activities_controller.rb
class Mypage::ActivitiesController < Mypage::BaseController
  def index
    @activities = current_user.activities.order(created_at: :desc)
  end
end
```

`view`でこんな感じに回すのでした。

```slim:views/mypage/activities/index.html.slim
- if @activities.present?
  - @activities.each do |activity|
    = render "#{activity.action_type}", activity: activity
- else
  .text-center
    | お知らせはありません
```

`render`で呼び出す内容って、動的に生成できるのですね＾＾

上記の内容で、以下のようにページが表示できました。

[![Image from Gyazo](https://i.gyazo.com/ea288f731d8eae130c5e0df11441c513.png)](https://gyazo.com/ea288f731d8eae130c5e0df11441c513)

以上、簡単ですが、ユーザーへの通知の設定です。ここから、既読管理を作り込んでいきます。