質問掲示板風サイトを作っている時、おすすめ機能を実装したいと思ったので作りました。
前提
・既読機能がある
・タグ機能がある(acts-as-taggable-on使用)
・devise使用
流れ
①自分が最近見た記事から30件取得
②記事に紐づいているタグを取得し、さらにその中から数が多い順に5件取得
③5件の中からランダムに1個のタグを取得
④取得したタグを含み、かつユーザー自身の投稿ではない投稿を取得
⑤他のユーザーからの既読数が多い投稿順に並べ替えて表示
##実際の記述
参考程度にお願いします。(元はコントローラーに書いていたのですが、リファクタリングの際にService層を作りそこに移行したため)
app/controllers/top_pages_controller.rb
#Service層とやりとりするための記述です。実際の流れは次にあるのでそちらを参考にするといいと思います。
def index
if user_signed_in?
@recommend_tag = Posts::Postservices.recommend_tag(current_user)
@recommend_posts = Posts::Postservices.recommend_posts(current_user, @recommend_tag, params).includes(:user, :taggings)
end
end
app/services/posts/postservices.rb
module Posts
module Postservices
def self.recommend_tag(current_user)
#①自分が最近見た記事から30件取得
recommends = current_user.already_reads.order("created_at DESC").limit(RECENTLY_READ_POST_COUNT)
#②記事に紐づいているタグを取得し...
array = Array.new
recommends.each do |reco|
reco = Post.find_by(id: reco.post_id)
reco = reco.tag_counts_on(:tags).pluck(:name) #(Postモデルからtagsカラムのタグ名を取得。tag_counts_onはacts-as-taggable-onのメソッド。)
array.push(reco)
end
#②...数が多いものから順に5件取得&③5件の中からランダムに1個のタグを取得
return array.compact
.flatten
.group_by{|e| e}
.sort_by{|_,v|-v.size}
.map(&:first)[0..4]
.sample
end
def self.recommend_posts(user_id, recommend_tag, params)
#④取得したタグを含み、かつユーザー自身の投稿ではない投稿を取得
posts = Post.tagged_with(recommend_tag).where.not(user_id: user_id)
#⑤他のユーザーからの既読数が多い順に並べ替えて表示
return posts.select('posts.*', 'count(already_reads.id) AS already_reads')
.left_joins(:already_reads)
.group('posts.id')
.order('already_reads desc')
.order('created_at desc')
.limit(DEFAULT_PAGE_ITEM_COUNT)
.page(params[:page]).per(DEFAULT_PAGE_ITEM_COUNT)
end
end
end
config/initializers/constants.rb
#あとで修正しやすいように件数などは変数で渡しています
DEFAULT_PAGE_ITEM_COUNT = 10
RECENTLY_READ_POST_COUNT = 30
app/views/top_pages/index.html.rb
<!-- viewの参考程度においときます... -->
<div class="main-contents">
<% if user_signed_in? && @recommend_posts.present?%>
<h3 class="title-index"><%=@recommend_tag%>に興味がある人におすすめ</h3>
<% @recommend_posts.each do |reco| %>
<div class="post-index">
<div class="overall-container">
<%= link_to post_path(reco.id) do %>
<div class="title"><%= reco.title %></div>
<% end %>
<div class="tag-container">
<% reco.tag_list.each do |tag| %>
<span class="post-tag"><%= link_to tag, root_path(tag_name: tag) %></span>
<% end %>
</div>
<div class="other-container">
<% if user_signed_in? %>
<div class="already-read"><%= render 'shared/alreadyread', post: reco %></div>
<% end %>
<div class="comments-count"><i class="far fa-comment"></i> <%= reco.comments.count %></div>
<div class="likes-count"><i class="far fa-heart"></i> <%= reco.likes.count %></div>
<div class="user-info">
<% if reco.user.image? %>
<div><%= image_tag post.reco.image.url, class: "user-index-icon" %></div>
<% else %>
<div><%= image_tag "/assets/default.jpg", class: "user-index-icon" %></div>
<% end %>
<div class="user-name"> <%= reco.user.name %></div>
</div>
</div>
</div>
</div>
<% end %>
<div class="paginate"><%= paginate @recommend_posts %></div>
<% end %>
</div>
↓こんな感じで表示できます(CSSはお好みで調整してください)
終わりに
自分で作ったおすすめ機能を紹介しました。拙い機能ではありますが、自分で考えて色々調べて実装した初めての機能なのでとても思い入れがあります。
ほぼコードを載せただけになってしまいましたが誰かの参考になれば幸いです。