Railsでおすすめ機能を作る

質問掲示板風サイトを作っている時、おすすめ機能を実装したいと思ったので作りました。

## 前提
・既読機能がある
・タグ機能がある(acts-as-taggable-on使用)
・devise使用

## 流れ
①自分が最近見た記事から30件取得
②記事に紐づいているタグを取得し、さらにその中から数が多い順に5件取得
③5件の中からランダムに1個のタグを取得
④取得したタグを含み、かつユーザー自身の投稿ではない投稿を取得
⑤他のユーザーからの既読数が多い投稿順に並べ替えて表示

##実際の記述
参考程度にお願いします。(元はコントローラーに書いていたのですが、リファクタリングの際にService層を作りそこに移行したため)

```ruby: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
```

```ruby: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

```

```ruby:config/initializers/constants.rb
#あとで修正しやすいように件数などは変数で渡しています
DEFAULT_PAGE_ITEM_COUNT = 10
RECENTLY_READ_POST_COUNT = 30
```

```html: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はお好みで調整してください)
![スクリーンショット 2021-03-05 20.32.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/831512/95fe4441-f380-05b9-31ec-6f50bd7a0f63.png)

## 終わりに
自分で作ったおすすめ機能を紹介しました。拙い機能ではありますが、自分で考えて色々調べて実装した初めての機能なのでとても思い入れがあります。
ほぼコードを載せただけになってしまいましたが誰かの参考になれば幸いです。