LoginSignup
121
141

More than 1 year has passed since last update.

[Rails]通知機能の実装

Last updated at Posted at 2020-02-16

実装すること

自分の投稿にいいねされた時・コメントをもらった時、フォローされた時に通知が来るようにします。

完成イメージ

通知を未確認の状態では、ヘッダーのNOTICEにマークが付きます。
通知を確認すると、マークが消えます。
通知イメージ(マーク).png
ユーザーは自分への通知を見ることができます。
通知はまとめて全削除することができます。
通知一覧.png

ER図

Notificationテーブル
item_id : いいねされた投稿のid
visiter_id : 通知を送ったユーザーのid
visited_id : 通知を送られたユーザーのid
comment_id : 投稿へのコメントのid
action : 通知の種類(フォロー、いいね、コメント)
checked : 通知を送られたユーザーが通知を確認したかどうか default: false

通知ER図.png

モデルの作成

Userモデル、Itemモデルは作成した前提で進めていきます。
作成手順は下記リンク先で説明しております。
[Rails]Ajaxを用いて非同期で投稿機能といいね機能の実装https://qiita.com/yuto_1014/items/78d8b52d33a12ec33448

$ rails g model Notification visiter_id:integer visited_id:integer item_id:integer comment_id:integer action:string checked:boolean

マイグレーションファイルの追記

checked(通知を送られたユーザーが通知を確認したかどうか)のdefault: false,null: falseを追記します。

app/db/migrate/20200119094201_create_notification.rb
class CreateNotifications < ActiveRecord::Migration[5.2]
  def change
    create_table :notifications do |t|
      t.integer :visiter_id
      t.integer :visited_id
      t.integer :item_id
      t.integer :comment_id
      t.string :action
      t.boolean :checked, default: false, null: false ←追記

      t.timestamps
    end
  end
end
$ rails db:migrate

アソシエーションの確認

Userモデル

・自分が作った通知(active_notifications)と自分宛の通知(passive_notifications)の関連付けメソッドを実装します。
active_notificationsでは、class_name: "Notification"でNotificationモデルの、foreign_key: "visiter_id" で、visiter_idを参考に、active_notificationsモデルへアクセスするようにしています。
passive_notificationsも同じです。

app/models/user.rb
class User < ApplicationRecord

   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

end

Itemモデル

app/models/item.rb
class Item < ApplicationRecord

   has_many :notifications, dependent: :destroy

end

Notificationモデル

optional: trueはitem_idにnilを許容するものです。railsではbelongs_toのつけられたカラムには自動的にallow_nil: falseが付与されます。フォロー通知ではitem_idは関与しないためnilとなるので、このオプションをつけないとフォロー通知が有効になりません。

app/models/notification.rb
class Notification < ApplicationRecord

    #スコープ(新着順)
	default_scope->{order(created_at: :desc)}

  	belongs_to :item, optional: true
  	belongs_to :comment, optional: true
  	belongs_to :visiter, class_name: 'User', foreign_key: 'visiter_id', optional: true
	belongs_to :visited, class_name: 'User', foreign_key: 'visited_id', optional: true

end

コントローラーの作成

$ rails g controller notifications

ルーティングの設定

config/routes.rb
Rails.application.routes.draw do

    resources :notifications, only: :index

end

通知メソッドの作成

Itemモデル(いいねとコメントのメソッド)

distinctメソッドは、重複レコードを1つにまとめるためのメソッドです。

app/models/item.rb
    def create_notification_by(current_user)
	    notification = current_user.active_notifications.new(
	      item_id: id,
	      visited_id: user_id,
	      action: "like"
	    )
	    notification.save if notification.valid?
  	end

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

  	def save_notification_comment!(current_user, comment_id, visited_id)
        # コメントは複数回することが考えられるため、1つの投稿に複数回通知する
        notification = current_user.active_notifications.new(
          item_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モデル(フォローのメソッド)

app/models/user.rb
 #フォロー時の通知
 def create_notification_follow!(current_user)
    temp = Notification.where(["visiter_id = ? and visited_id = ? and action = ? ",current_user.id, id, 'follow'])
    if temp.blank?
      notification = current_user.active_notifications.new(
        visited_id: id,
        action: 'follow'
      )
      notification.save if notification.valid?
    end
  end

いいねの通知(likes_controller)

いいねの作成と同時に通知も作成されます。

app/controllers/likes_controller.rb
  def create
      like = current_user.likes.new(item_id: @item.id)
      like.save
      @item = Item.find(params[:item_id])
      #通知の作成
      @item.create_notification_by(current_user)
      respond_to do |format|
        format.html {redirect_to request.referrer}
        format.js
      end
  end

コメントの通知(comments_controller)

app/controllers/comments_controller.rb
def create
    @item = Item.find(params[:item_id])
    #投稿に紐づいたコメントを作成
    @comment = @item.comments.build(comment_params)
    @comment.user_id = current_user.id
    @comment_item = @comment.item
    if @comment.save
      #通知の作成
      @comment_item.create_notification_comment!(current_user, @comment.id)
      render :index
    end
end

フォローの通知(relationships_contoroller)

app/controllers/comments_controller.rb
def create
	  @user = User.find(params[:following_id])
	  current_user.follow(@user)
	  #通知の作成
	  @user.create_notification_follow!(current_user)
end

通知一覧ページの作成

notification_controller

・indexを開いたらchecked:falseが全てchecked:trueになるようにしています。
・ログインユーザーの通知全削除では、destroy_allメソッドを使用しています。
参考:https://pikawaka.com/rails/destroy_all

app/controllers/notification_controller.rb
class Users::NotificationsController < ApplicationController

	def index
      #current_userの投稿に紐づいた通知一覧
    	@notifications = current_user.passive_notifications
      #@notificationの中でまだ確認していない(indexに一度も遷移していない)通知のみ
    	@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 users_notifications_path
  	end

end

通知一覧(notifications/index.html.erb)

通知内容は、パーシャルにします。
通知一覧.png

app/views/notifications/index.html
<h3 class="text-center">通知</h3>
    <%= link_to destroy_all_users_notifications_path, method: :delete do %>
        <i class="fas fa-trash" style="color: black;"></i>
        <h7 style="color: black;">全削除</h7>
    <% end %>
    <hr>
<% if @notifications.exists? %>
    <div class="users-index">
        <%= render @notifications %>
    </div>
<% else %>
    <p>通知はありません</p>
<% end %>

通知内容

notification_helper

通知内容によって、表示内容を分岐させます。

app/helpers/notifications_helper.rb
module Users::NotificationsHelper

   def notification_form(notification)
	  @visiter = notification.visiter
	  @comment = nil
	  your_item = link_to 'あなたの投稿', users_item_path(notification), style:"font-weight: bold;"
	  @visiter_comment = notification.comment_id
	  #notification.actionがfollowかlikeかcommentか
	  case notification.action
	    when "follow" then
	      tag.a(notification.visiter.name, href:users_user_path(@visiter), style:"font-weight: bold;")+"があなたをフォローしました"
	    when "like" then
	      tag.a(notification.visiter.name, href:users_user_path(@visiter), style:"font-weight: bold;")+"が"+tag.a('あなたの投稿', href:users_item_path(notification.item_id), style:"font-weight: bold;")+"にいいねしました"
	    when "comment" then
	    	@comment = Comment.find_by(id: @visiter_comment)&.content
	    	tag.a(@visiter.name, href:users_user_path(@visiter), style:"font-weight: bold;")+"が"+tag.a('あなたの投稿', href:users_item_path(notification.item_id), style:"font-weight: bold;")+"にコメントしました"
	  end
	end

end

notifications/_notification.html.erb

app/views/notifications/_notification.html
<% visiter = notification.visiter %>
<% item = notification.item %>

<div class="user-view clearfix ">
  <%= link_to users_user_path(notification.visiter) do %>
    <%= attachment_image_tag visiter, :profile_image, :fill,100,100, format: "jpeg", fallback: "no_image.jpg", size: "50x50", class:"profile-img-circle" %>
  <% end %>
  <%= notification_form(notification) %><span class="moderate-font"><%= " (#{time_ago_in_words(notification.created_at)} )" %></span>
  <br>
  <% if !@comment.nil? %>
    <p class="moderate-font text-center" style="color: #C0C0C0;"><%= @comment %></p>
  <% end %>
</div>

未確認の通知があるときはマークで知らせる

notifications_helper

未確認の通知(checked:falseの通知)を示すunchecked_notificationsメソッドをnotifications_helperに書きます。

app/helpers/notifications_helper.rb
def unchecked_notifications
    @notifications = current_user.passive_notifications.where(checked: false)
end

layouts/application.html.erb

未確認の通知があるときは、黄色いマークで知らせます。

通知イメージ(マーク).png
app/views/layouts/application.html
<% if unchecked_notifications.any? %>
    <i class="fa fa-circle" style="color: gold;"></i>
<% end %>
<li>
    <%= link_to "NOTICE", users_notifications_path, class: "btn-default" %>
</li>

最後に

最後までご覧いただきありがとうございます。
初学者ですので間違っていたり、分かりづらい部分もあるかと思います。
何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。
いいねとコメントとフォローは下記リンク先で実装しています。

[Rails]Ajaxを用いて非同期で投稿機能といいね機能の実装
https://qiita.com/yuto_1014/items/78d8b52d33a12ec33448
[Rails]Ajaxを用いて非同期でコメント機能の実装
https://qiita.com/yuto_1014/items/c7d6213139a48833e21a
[Rails]Ajaxを用いて非同期でフォロー機能の実装
https://qiita.com/yuto_1014/items/8d508b84fd0c2316ba01

参考

【学習アウトプット4】通知機能の作り方
https://qiita.com/tktk0430/items/bdb8fbcf4ce3258b2d41

121
141
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
121
141