通知機能つけてみた


<h2>概要</h2>

ToDoアプリに「いいね」や「コメント」などがついた時の通知を受け取れるようになるためのコードを書いた。
前提として通知させたい機能は既に制作したものとする。
model => controller => view の順でプログラムしていこうと思う

<h4>完成イメージ</h4>
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359351/eb13dc01-9d83-b6f2-80dd-370e4f1cae85.png">
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359351/1c867867-e2f1-a18b-0bf2-aae4846306d6.png">

<h2>model</h2>

```ruby:create_notices_rb
class CreateNotices < ActiveRecord::Migration[7.0]
    def change
        create_table :notices do |t|
          t.references :receivor_id, foreign_key: { to_table: :users }, null: false
          t.references :sender_id, foreign_key: { to_table: :users }, null: false
          t.references :to_dos, null: false, foreign_key: true
          t.integer :action, null: false
          t.integer :action_id, null: false
          t.boolean :checked, null: false
          
          t.timestamps
        end
    end
```

references型を簡単に説明すると他のテーブルと関係性がある型である。
今回は receivor_id, sender_id は users テーブルに to_do_id は to_dos テーブルの主キーしか使えないように指定している。いろいろメリットがあるが、こうするとでもし receivor_id で指定した user  がテーブル上から delete されたら receivor_id を持つ notice も削除されるようになると思う。たぶん

rails db:migrate を忘れずに

<h4>modelの詳細</h4>
<table>
<tr>
<th>receivor_id</th>
<th>sender_id</th>
<th>to_do_id</th>
</tr>
<tr>
<td>通知をうけとる user の id</td>
<td>通知を送った user の id</td>
<td>「いいね」されたtodo の id</td>
</tr>
<tr>
<th>action</th>
<th>action_id</th>
<th>checked</th>
</tr>
<tr>
<td>「いいね」か「コメント」か</td>
<td>各 action のテーブルの id</td>
<td>ユーザーが確認したかどうか</td>
</tr>
</table>

```ruby:notice.rb
class Notice < ApplicationRecord
    belongs_to :user
    belongs_to :to_do
end
```
to_do.rb と user.rb には下記を追記しておく
has_many :notices, dependent: :destroy
こうすることで上記した関連した user, to_do が削除されたら notice も駆除されるようになる

これで model は終了したので controller のプログラムしていこうと思う

<h2>controller</h2>

```ruby:nices_controller.rb

def create
    @nice = Nice.new(
      user_id: session[:user_id],
      to_do_id: params[:to_do_id]
    )
    @nice.save
    @todo = ToDo.find(params[:to_do_id])
    @todo.nice += 1
    @todo.save
    #ここから追記
    notice_create_nice(@nice)
    #ここまで
    redirect_back fallback_location: '/'
end




private
    def notice_create_nice(data)
        @notice = Notice.new(
          receivor_id: ToDo.find(data.to_do_id).user_id,
          sender_id: session[:user_id], 
          to_do_id: params[:to_do_id],
          action: 0, 
          action_id: data.id 
        )
        # 自分投稿にいいねしたときの対処
        if @notice.receivor_id == @notice.sender_id
          @notice.checked = true
        else
          @notice.checked = false
        end
        # 過去にいいねしてるかどうかの確認
        notice_serch = Notice.where(receivor_id: @notice.receivor_id, sender_id: @notice.sender_id, action: @notice.action, to_do_id: @notice.to_do_id)
        if notice_serch.blank?
          @notice.save!
        end
    end
```

<h4>controllerの詳細</h4>

ここでは「いいね」した時の通知機能だけ書いています。
もし他に追加したい通知機能がある場合は各自でプログラムしてください。
まず create は自分の好きなように書いてください
工夫した点は自分の投稿に「いいね」した時、過去に「いいね」されたかどうか
前者は、自分の投稿に「いいね」した時通知来ても「知ってるよ！」と思うので、 checked を true にして対処しました。
後者は、間違えて「いいね」を二回以上送った時に通知が来てもまたも「さっき見たわ！」と思うので、 where メソッドにて複数条件をかき確認している。

「コメント」されたときも通知を送りたい人は<a>#過去にいいねしてるかどうかの確認</a>のところを抜いて書けばいいと思います。 

@notice.action = 1 にすることを忘れずにね

controller は終わったので view をプログラムしていこうと思う。

<h2>view</h2>

```ruby:application_helper.rb
module ApplicationHelper
  def notices_create
    @notices = Notice.where(receivor_id: session[:user_id], checked: false)
  end
end
```

```ruby:application.html.erb
<% notices_create %>
<% if @notices.blank? %>
    <div class="home-user-alert">
        <p style="padding-left: 20px;">お知らせはありません</p>
    </div>
<% else %> 
    <div class="for-alert"></div>
    <div class="home-user-alert">
    <ul class="notice-ul">
    <% @notices.each do |notice| %>
    <%= link_to "/tasks/notice/#{notice.id}/checked" do %> 
    #<%= link_to "リンク" do %> <% end %>　でendまでをリンク指定できる
    <li class="notice-li">
        <% if notice.action == 0 %>
            <%= あなたの投稿が応援されました。 %>
            <!-- ここでN+1問題が発生してるので直したい -->     
            <%= ToDo.find(notice.to_do_id).text %>
        <% elsif notice.action == 1 %>
            <%= あなたの投稿にコメントされました。 %>
            <!-- ここでN+1問題が発生してるので直したい　part2 -->
            <%= Comment.find(notice.action_id).text %>
        <% elsif notice.action == 2 %>
            <%= あなたのコメントがポリシー違反のため削除されました。 %>
        <% end %>
    </li>
    <% end %>
    <% end %>
    </ul>
    </div>
<% end %>
```

```css:applivcation.css
.for-alert {
  height: 12px;
  width: 12px;
  background-color: #3ecdc6;
  border-radius: 6px;
  z-index: 10;
  position: absolute;
  top: 45px;
  right: 200px;
}

.home-user-alert {
  z-index: 20;
  border: black solid 2px;
  border-radius: 20px;
  background-color: white;
  width: 280px;
  max-height: 400px;
  position: absolute;
  z-index: 20;
  right: 200px;
  display: none;
  overflow-y: scroll; 
}

.box-active {
  display: block;
}

.notice-li {
  border-bottom: gray 1px solid;
  padding-bottom: 4px;
  padding: 8px;
}

.notice-ul a:last-child li {
  border-bottom:  0px;
}
    
```

```js:application.js
import jquery from "jquery";
window.$ = jquery;
  $('.fa-bell').on('click', function() {
    if($('.home-user-alert').hasClass('box-active')) {
      $('.home-user-alert').removeClass('box-active');
    } else {
      $('.home-user-alert').addClass('box-active');
    };
  })

```

<h4>この章の詳細</h4>
最初のhelper.rbの記載だが、いつもならcontrollerに書いていますが、applicationのcontrollerが見つからなかったので今回はhelperに書いた。（正確なやり方はわからない。今後調べたい。）


次にapplication.html.erbのコードだが、前章から記載しているとおり、notice.actionにてアクションを判別してからそれごとに文章を出力している。　

cssとjsは好きなように書いてください。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359351/eb13dc01-9d83-b6f2-80dd-370e4f1cae85.png">
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359351/1c867867-e2f1-a18b-0bf2-aae4846306d6.png">

これでベルの部分を押すと通知欄が出てくるようになりましたね。

<h2>あとがき</h2>
今回はポートフォリオに通知機能を追加してみましたが、今まで学習してきた内容の総復習みたいでかなり苦戦しました。もう少し綺麗なコードも書けると思うので、もし振り返る機会があれば復習してみたいと思いました。
もしご不明な点、間違いなどありましたらお知らせしていただければ幸いです。今後とも精進しましょう。
長々と読んでいただいてありがとうございます。