実装すること
自分の投稿にいいねされた時・コメントをもらった時、フォローされた時に通知が来るようにします。
完成イメージ
通知を未確認の状態では、ヘッダーのNOTICEにマークが付きます。
通知を確認すると、マークが消えます。
ユーザーは自分への通知を見ることができます。
通知はまとめて全削除することができます。
ER図
Notificationテーブル
item_id : いいねされた投稿のid
visiter_id : 通知を送ったユーザーのid
visited_id : 通知を送られたユーザーのid
comment_id : 投稿へのコメントのid
action : 通知の種類(フォロー、いいね、コメント)
checked : 通知を送られたユーザーが通知を確認したかどうか default: false
モデルの作成
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を追記します。
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も同じです。
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モデル
class Item < ApplicationRecord
has_many :notifications, dependent: :destroy
end
Notificationモデル
optional: true
はitem_idにnilを許容するものです。railsではbelongs_toのつけられたカラムには自動的にallow_nil: falseが付与されます。フォロー通知ではitem_idは関与しないためnilとなるので、このオプションをつけないとフォロー通知が有効になりません。
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
ルーティングの設定
Rails.application.routes.draw do
resources :notifications, only: :index
end
通知メソッドの作成
Itemモデル(いいねとコメントのメソッド)
distinctメソッド
は、重複レコードを1つにまとめるためのメソッドです。
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モデル(フォローのメソッド)
#フォロー時の通知
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)
いいねの作成と同時に通知も作成されます。
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)
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)
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
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)
<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
通知内容によって、表示内容を分岐させます。
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
<% 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に書きます。
def unchecked_notifications
@notifications = current_user.passive_notifications.where(checked: false)
end
layouts/application.html.erb
未確認の通知があるときは、黄色いマークで知らせます。
<% 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