【Rails6】紐づいた商品の数が多い上位10件のタグ名を並べたい

# はじめに
タグを表示する際のモデルの記述がよく分からなくなったので、確認も兼ねてまとめていこうと思います。

# 作成手順
登録数が多い順にタグを並べて、こんな感じの左ナビを作成します。

![2024-06-06タグ左ナビ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3762011/f597e254-6b36-75a6-1bf5-4b4e39a4670c.png)

## tagとitem_tagの関連付け
タグと商品との関連付けはこんな感じです。

![2024-06-06タグ検索.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3762011/bb23fec9-c24a-9d39-b7e3-cbb7b9b47d5e.png)

商品に複数のタグを登録できるようにしたかったので、間に中間テーブルを挟んで実装しています。
今回はタグの登録方法などに関しては省略します。

https://qiita.com/ki_87/items/a344ea566c88b10b950c

タグ機能についてまとめられている記事がありました。
こちらと同じような感じで実装しています。

:::note warn
今回作成するアプリでは、前提として、ショップ(Shop)に<b>退会機能</b>があり、商品(Item)に<b>公開/非公開</b>を指定する機能があります。
:::

## 【モデル】上位10件のタグをモデルで取得

今回の山場はコントローラの記述です。
<b>self.tag_rank_item</b>では商品に紐づけられた上位10件のタグのデータを呼び出しています。

```ruby:tagモデル
def self.tag_rank_item
    # 退会していないショップの公開中の商品を取得
    active_shops = Shop.where(is_active: true)
    active_items = Item.where(is_active: true, shop_id: active_shops).select(:id)
    
    # 商品と紐づけられた数が上位10件のタグを取得
    joins(:item_tags)
      .where(item_tags: { item_id: active_items })
      .group(:id)
      .order('COUNT(item_tags.tag_id) DESC')
      .limit(10)
end
```
:::note info
joinsとは、データベースのテーブルを結びつける方法です。
これにより、関連するデータを一度に取り出すことができます。

この後のコントローラの記述に登場しますが、このメソッドを呼び出す際に<b>「Tag.tag_rank_item」</b>と記述します。
つまり、Tag（self）テーブルとItemTagテーブルを結びつけることで、両方のテーブルの情報を一緒に扱うことができるようになるんです。
:::

具体的には…、

* <b>TagテーブルとItemTagテーブルを結びつける</b>ことで、各タグがどの商品に関連しているかの情報を取得
* <b>退会していないショップの公開中の商品</b>という条件に一致するデータをフィルタリング
* <b>group(:id)</b>でタグIDごとにグループ化
* タグの使用回数が<b>多い順に降順</b>で並べ替え
* <b>limit(10)</b>で上位10個のタグを取得

という処理を行っています。


## 【モデル】紐づけられた商品の数をカウントするメソッドを作成
こちらも同じくtagモデルに記載しています。
タグ名の横に（10）のように表示する、タグに紐づいた商品をカウントするためのメソッドです。

```ruby:tagモデル
def tag_count
    active_shops = Shop.where(is_active: true)
    active_items = Item.where(is_active: true, shop_id: active_shops).select(:id)
    item_tags.where(item_id: active_items).count
end
```
こちらでも同じように、<b>退会していないショップの公開中の商品</b>という条件に一致するデータをフィルタリングしています。
上2行は先ほどのtag_rank_itemと同じ記述なので、まとめることもできそうです。

最後に、item_tagsテーブルで、条件に合ったタグを取得しています。

これで準備が整ったので、次はコントローラから作成したメソッドを呼び出して、ビューに渡すためのデータを用意しましょう。

## 【コントローラ】ビューに渡すデータの作成

```ruby:コントローラ
  def set_tag_rank
    @tag_rank = Tag.tag_rank_item
  end
```
ここの記述はとても単純です。
先ほどモデルに定義した<b>self.tag_rank_item</b>の<b>self</b>の部分に<b>Tag</b>が入る形になるんですね。


## 【ビュー】左ナビの作成

ゴールが見えてきました。
後はビューが受け取ったデータを表示するだけです。

```html:左ナビのビュー
<table class="table">
  <thead>
    <tr>
      <th class="text-center bg-member <%= (admin_signed_in? ? "bg-admin" : "" ) %> <%= (shop_signed_in? ? "bg-shop" : "" ) %>">人気商品</th>
    </tr>
  </thead>
  <tbody>
    <tr class="tr-menu" onclick='window.location="<%= root_path %>"' role="link">
      <td>アイテム一覧</td>
    </tr>
    
    <!--タグの呼び出し-->
    <% tag_rank.each do |tag| %>
    <tr class="tr-menu" onclick='window.location="<%= "/search?model=tag&content=#{tag.name}" %>"' role="link">
      <td>
        <%= tag.name %> <small>(<%= tag.tag_count %>)</small>
      </td>
    </tr>
    <% end %>

    <tr>
      <td>
    　　<!--検索フォームの呼び出し-->
        <%= render 'public/searches/tag_form' %>
      </td>
    </tr>
  </tbody>
</table>
```
モデルで定義したtag_countメソッドを呼び出してタグに紐づいた商品の数を表示しています。

ちなみに、今回はテーブルの行全体をリンクにしたくて<&#8203;tr>タグにリンクを指定しているのですが、こちらの記事を参考にさせていただきました。

https://qiita.com/tkht2401/items/da7427c7154de62b6679


## おまけ…CSSの設定

※Deviseでログイン機能を実装しているので、ログインユーザーごとにテーマカラーを変更する設定を行っています。
```css:CSSでの設定
// 背景色
.bg-member {
    background-color: #b0a88d!important;
    color: #fff!important;
}

.bg-shop{
    background-color: #8CAFB1!important;
    color: #fff!important;
}

.bg-admin{
    background-color: #ccc!important;
}

// 左ナビのリンク
.tr-menu {
    cursor: pointer;
      td {
      background-color: #F9F9F9!important;
      font-weight: bold!important;
      color: #467675!important;
  }
}

.tr-menu td:hover{
    color: #E15C67!important;
    background-color: #F9EDEF!important;
}

.tr-menu td::before{
    content: '';
    border-top: 6px solid transparent;
    border-bottom: 6px solid transparent;
    border-left: 8px solid #467675;
    display: inline-block;
    padding-left: 5px;
}
```
こちらに関連するclass名は少ないですが、本来はBootstrapを適応しているのでtableタグに付けられたtableクラスはBootstrapの設定を読み込んでいます。

また、なんとなく:beforeの部分でタグ名の前に▶を表示する記述をborderを使って指定していますが、アイコンや文字記号を使用しても問題ない気がします。

CSSの記述がよく分かっていなかった頃によくコピペして使っていて、その名残でつい使ってしまっただけなので…。

# おわりに
タグをカウントする際のメソッドは、最初、直接コントローラに記述していましたが、いろんなビューで必要なことに気づいてモデルにまとめました。

こちらに関しては、データの呼び出し方もそうですが、どこに記述すれば良いのかという点も頭を悩ませたポイントでした。
左ナビは複数のビューで必要になってきますし、まだもう少しスッキリ書く方法はありそうだな…と思いながらコントローラを眺めています。