1
2

More than 3 years have passed since last update.

通知機能実装

Last updated at Posted at 2020-08-17

自分用にまとめます。

モデルの作成

$ rails g model Notification visiter_id:integer visited_id:integer
  micropost_id:integer comment_id:integer action:string checked:boolean
20200724090751_create_notifications.rb
class CreateNotifications < ActiveRecord::Migration[5.2]
  def change
    create_table :notifications do |t|
      # 通知を送ったユーザーのid
      t.integer :visiter_id
      # 通知を受け取ったユーザーのid
      t.integer :visited_id
      # いいねされた投稿のid
      t.integer :item_id
      # 投稿へのコメントのid
      t.integer :comment_id
      # 通知の種類(いいね、コメント、フォロー)
      t.string :action
      # 通知を確認したかどうか(初期値をfalseに設定)
      t.boolean :checked, default: false, null: false

      t.timestamps
    end
  end
end
$ rails db:migrate

モデルの関連付け

user.rb
  # 自分が作った通知(自分がいいねやフォローなどをしたら作成される通知)
  has_many :active_notifications, class_name: 'Notification', foreign_key: 'visiter_id', dependent: :destroy
  # 自分宛の通知(自分以外の人がいいねやフォローをしてくれたら作成される通知)
  has_many :passive_notifications, class_name: 'Notification', foreign_key: 'visited_id', dependent: :destroy
post.rb
  has_many :notifications, dependent: :destroy
notification.rb

  # 新しい通知が上に来るように指定
  default_scope -> { order(created_at: :desc) }

  belongs_to :post, optional: true
  belongs_to :comment, optional: true

  # 関連するカラム名(visiter_id/visited_id)とモデル名(User)を指定している
  belongs_to :visiter, class_name: 'User', foreign_key: 'visiter_id', optional: true
  belongs_to :visited, class_name: 'User', foreign_key: 'visited_id', optional: true

通知メソッド作成

いいね通知

post.rb

  def create_notification_like(current_user)
    # すでにいいねの通知をしたことがないかを検索
    liked = Notification.where(visiter_id: current_user.id, visited_id: user_id, post_id: id, action: 'like')
    # いいねの通知をしたことがなければ、新規通知作成
    if liked.blank?
      notification = current_user.active_notifications.new(post_id: id, visited_id: user_id, action: 'like')
      # 自分の投稿に対するいいねは通知が行かないようにする
      if notification.visiter_id == notification.visited_id
        notification.checked = true
      end
      notification.save if notification.valid?
    end
  end

コメント通知

post.rb
  def create_notification_comment(current_user, comment_id)
    # 投稿にコメントした自分以外のユーザーをすべて取得し、全員に通知を送る
    commented_ids = Comment.select(:user_id).where(post_id: id).where.not(user_id: current_user.id).distinct
    commented_ids.each do |commented_id|
      save_notification_comment(current_user, comment_id, commented_id['user_id'])
    end
    # まだ誰もコメントしていない場合は、投稿者に通知を送る
    save_notification_comment(current_user, comment_id, user_id) if commented_id.blank?
  end

  def save_notification_comment(current_user, comment_id, visited_id)
    # コメントは複数回することがあるため、複数回通知する
    notification = current_user.active_notifications.new(post_id: id, comment_id: comment_id, visited_id: visited_id, action: 'comment')
    # 自分の投稿に対するコメントの場合は、通知済みとする
    if notification.visiter_id == notification.visited_id
      notification.checked = true
    end
    notification.save if notification.valid?
  end

フォロー通知

user.rb
  def create_notification_follow!(current_user)
    # すでにフォローの通知をしたことがないかを検索
    followed = Notification.where(visiter_id: current_user.id, visited_id: id, action: 'follow')
    # いいねの通知をしたことがなければ、新規通知作成
    if followed.blank?
      notification = current_user.active_notifications.new(visited_id: id, action: 'follow')
      notification.save if notification.valid?
    end
  end

コントローラで定義

いいね

likes_controller.rb
  def create
    @post = Post.find(params[:post_id])
    unless @post.liked_by?(current_user)
      @like = current_user.likes.new(post_id: @post.id)
      @like.save
      # 追記
      @post.create_notification_like(current_user)
    end
  end

コメント

comments_controller.rb
  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.new(comment_params)
    @comment.user_id = current_user.id
    @comments = @post.comments.all
    if @comment.save
      # 追記
      @post.create_notification_comment(current_user, @comment.id)
      render :create
    else
      @post = Post.find(params[:post_id])
      @comments = @post.comments.all
      render 'posts/show'
    end
  end

フォロー

relationships_controller.rb
  def follow
    @user = User.find(params[:id])
    current_user.follow(params[:id])
    #追記
    @user.create_notification_follow(current_user)
    @posts = @user.posts.all
    render :create
  end

コントローラの作成

$ rails g controller notifications
config/routes.rb
resources :notifications, only: :index
notifications_controller.rb
class NotificationsController < ApplicationController

  def index
    # current_userが受け取った通知一覧
    @notifications = current_user.passive_notifications
    # @notificationの中でまだ確認していない通知
    @notifications.where(checked: false).each do |notification|
      notification.update_attributes(checked: true)
    end
  end

  def destroy_all
    #通知を全削除
    @notifications = current_user.passive_notifications.destroy_all
    redirect_to notifications_path
  end

end

ビューの作成

notifications/index.html.slim
.notification-visual
  .container
    .row
      .col-lg-5
      .col-lg-2.text-center
        h4.mt-5.mb-4.font-weight-bold.notification-caption 通知

    .row
      .col-lg-3
      .col-lg-6
        .frame-notification
          = link_to notifications_destroy_all_path, method: :delete do
            i.fas.fa-trash style="color: black;"
                h7 style="color: black;" 全削除
            <hr>
          - notifications = @notifications.where.not(visiter_id: current_user.id)
          - if notifications.exists?
            # _notification.html.slimファイルにパーシャルで飛ばす
            = render notifications
          - else
            p 通知はありません
notifications/_notification.html.slim
- visiter = notification.visiter
- visited = notification.visited

.form-inline
  span
    = link_to user_path(visiter) do
      = attachment_image_tag visiter, :image, format: 'jpeg', fallback: "noimage.png", size: "40x30", class: "mr-2"
      = visiter.name
    = ' さんが'

    - case notification.action
    - when 'follow' then
      = "あなたをフォローしました"
    - when 'like' then
      = link_to 'あなたの投稿', notification.post, style: "font-weight: bold;"
      = "にいいねしました"
    - when 'comment' then
      - if notification.post.user_id == visited.id
        = link_to "あなたの投稿", notification.post, style: "font-weight: bold;"
      - else
        span
          = link_to post_path(notification.post) do
            = attachment_image_tag notification.post.user, :image, format: 'jpeg', fallback: "noimage.png", size: "25x20", class: "mr-1 ml-1"
            strong
              = notification.post.user.name + 'さんの投稿'
      = "にコメントしました"
      p.text-muted.mb-0
        = Comment.find_by(id: notification.comment_id)&.comment

.small.text-muted.text-right
  = time_ago_in_words(notification.created_at).upcase
hr


通知がきたら通知のボタン部分に丸いアイコンを表示させる

layout/_user.html.slim
-if unchecked_notifications.any?
  li.btn.btn-light.home-btn= link_to notifications_path, class: 'list' do
    i.fas.fa-circle.n-circle
    i.fas.fa-bell 
    |  通知 
-else
  li.btn.btn-light.home-btn= link_to notifications_path, class: 'list' do
    i.fas.fa-bell
    |  通知 
notifications_helper.rb
module NotificationsHelper
  def unchecked_notifications
    @notifications = current_user.passive_notifications.where(checked: false)
  end
end

完成!

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2