考え方
各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型の場合
rails g model ReadCount user_id:integer book_id:integer
references書く場合はこちら
若干書き方が違う( 〜_idが不要になる)ので注意
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
よって下のように設定します
has_many :read_counts, dependent: :destroy
has_many :read_counts, dependent: :destroy
#references型なら入力済みの筈
belongs_to :user
belongs_to :book
3.contorollerへの記述
今回はBookの閲覧数なので、新しくcontorollerは作成しません
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
#上2行を纏めて書くとこちら
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〜を実行する
という処理で実装出来ます。纏めると以下の通りです。
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つの投稿に付き1人の1日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が一致するものが無いかを探す。あとは上と一緒
という処理を組み合わせ実装出来ます。纏めると以下の通りです。
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で閲覧数を求めることが出来ます
後は必要な箇所に追記してください
<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>
<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>
最後に
ご覧いただいた方、ありがとうございました
意見、感想等ありましたらご自由にご記入ください