[Rails]ページの閲覧数をカウントする方法


## 考え方
ER図はこちら
作成するモデルは**ReadCount**のみです
![ER図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950107/aadc0815-d6f2-038d-d652-fae30956ded3.png)

各Bookのshowページにアクセスした時に以下の処理を行う
・本のidと訪れたユーザーのidをReadCountに登録する

閲覧数を表示するには
・該当するBookを探す Book.find(params[:id])等
・アソシエーションを用いて該当するbook_idを持つReadCountのデータを数える
例：book_idが1の場合
book.find(1).read_counts.count

前提条件
ruby 3.1.2
Rails 6.1.7
device、bootstrap導入済み(bootstrapは見た目の問題なので機能には関係なし)
User及びBookモデル作成済み

# 1.モデルの作成

integer型か、既存のテーブルを参照するreferences型のどちらかで記述
integer型の場合
```ターミナルの書き方.rb
rails g model ReadCount user_id:integer book_id:integer
```

references書く場合はこちら
若干書き方が違う(　〜_idが不要になる)ので注意
```ターミナル書き方.rb
rails g model ReadCount user:references book:references
```
migrationファイルを確認して問題なければ、migrateも忘れずに
```
rails db:migrate
```
# 2.アソシエーションの設定
1人のユーザーは複数の閲覧データを持つので
user : read_count = 1 : N
1つの本は複数の閲覧データを持つので
book : read_count = 1 : N
よって下のように設定します

```user.rb
  has_many :read_counts, dependent: :destroy
```
```book.rb
  has_many :read_counts, dependent: :destroy
```
```read_count.rb
  #references型なら入力済みの筈
  belongs_to :user
  belongs_to :book
```
# 3.contorollerへの記述
今回はBookの閲覧数なので、新しくcontorollerは作成しません
```books_controller.rb
class BooksController < ApplicationController
 def show
    @book = Book.find(params[:id])#bookを該当するidで取得
    read_count = ReadCount.new(book_id: @book.id, user_id: current_user.id)
    #ReadCountを新しく作成し、book_idに取得してきた本のid、user_idにcurrent_user = つまり自分のidを入力
    read_count.save
    #上２行を纏めて書くとこちら
    current_user.read_counts.create(book_id: @book.id)#createはsave不要
 end
end
```
## ※応用
このままだとアクセスするたびにReadCountが作成されてしまいます
例えば一度閲覧したユーザーを数えたくない場合

**ReadCount.find_by(user_id: current_user.id, book_id: @book.id)**
・ReadCountの中に、取得したbook_idとユーザーのidが一致するものが無いかを探し

**unless**
・合致するものがなかった場合(falseの場合)にcurrent_user.read〜を実行する
という処理で実装出来ます。纏めると以下の通りです。
```books_controller.rb
class BooksController < ApplicationController
 def show
    @book = Book.find(params[:id])
    unless ReadCount.find_by(user_id: current_user.id, book_id: @book.id)
      current_user.read_counts.create(book_id: @book.id)
    end
 end
end
```

また閲覧数のカウントを１つの投稿に付き1人の１日１回まで数えるならば
**Time.zone.now.all_day**
・今日のデータを取得
**where(created_at: Time.zone.now.all_day)**
・今日created_at:された、つまり今日作成されたReadCountのデータを抽出
**.find_by(user_id: current_user.id, book_id: @book.id)**
・Book.findで取得したbook_idとユーザーのidが一致するものが無いかを探す。あとは上と一緒
という処理を組み合わせ実装出来ます。纏めると以下の通りです。
```books_controller.rb
class BooksController < ApplicationController
 def show
    @book = Book.find(params[:id])
    unless ReadCount.where(created_at: Time.zone.now.all_day).find_by(user_id: current_user.id, book_id: @book.id)
      current_user.read_counts.create(book_id: @book.id)
    end
 end
end
```

# 4.viewページへの記述
考え方にも記載しましたが、book.read_counts.countで閲覧数を求めることが出来ます
後は必要な箇所に追記してください
```_index.html.rb
<tbody>
    <% books.each do |book| %>
      <tr>
        <td><%= link_to(book.user) do %>
          <%= image_tag book.user.get_profile_image, size:'50x50' %>
          <% end %>
        </td>
        <td><%= link_to book.title,book %></td>
        <td><%= book.body %></td>
        <td id="js-book_favorite_<%= book.id %>">
          <%= render "favorites/nice", book: book %>
        </td>
        <td>コメント数：<%= book.book_comments.count %></td>
        <td>閲覧数：<%= book.read_counts.count %></td> #ここに追加
      </tr>
    <% end %>
  </tbody>
```
```show.html.rb
<table class='table'>
  <tr>
    <td><%= link_to(@book.user) do %>
      <%= image_tag @book.user.get_profile_image, size:"100x100" %><br>
      <%= @book.user.name %>
      <% end %>
    </td>
    <td><%= link_to @book.title, @book %></td>
    <td><%= @book.body %></td>
    <td id="js-book_favorite_<%= @book.id %>">
      <%= render 'favorites/nice', book: @book %>
    </td>
    <td>コメント数：<%= @book.book_comments.count %></td>
    <td>閲覧数：<%= @book.read_counts.count %></td> #ここに追加
    <% if @book.user.id == current_user.id %>
      <td><%= link_to 'Edit', edit_book_path(@book), class: "btn btn-sm btn-success" %></td>
      <td><%= link_to 'Destroy', book_path, method: :delete, data: { confirm: '本当に消しますか？' }, class: "btn btn-sm btn-danger"%></td>
    <% end %>
  </tr>
</table>
```

# 最後に
ご覧いただいた方、ありがとうございました
意見、感想等ありましたらご自由にご記入ください