Rails | acts-as-taggable-on を使ったタグ機能の実装 | 備忘録

# gemを使ったタグ機能実装　備忘録

タグ機能をgemを使って実装してみました。
毎回自分自身調べて実装しているので、いい加減備忘録として記事にしてみました。


前半の内容は、すでに多くの人が記事にしている基本的なタグ機能の実装内容です。
後半は、タグによる絞り込み検索をやってみました。初心者の自分にとっては、結構苦戦しましたが、理解すれば意外とシンプルな構造になっています。



ちなみに下記のリンクを参考にして実装しています。
<参考リンク>
[公式リファレンス：acts-as-taggable-on](https://github.com/mbleigh/acts-as-taggable-on)
[Railsでacts-as-taggable-onを使ってタグ管理を行う - Rails Webook](https://ruby-rails.hatenadiary.com/entry/20150225/1424858414)


## 開発環境
Rails バージョン5.2.2
ruby バージョン2.5.3
acts-as-taggable-on バージョン　6.0.0　　→ 今回使用したタグ機能のgem
bootstrap4

## アプリケーション詳細
ベースは現場Railsのタスク管理アプリを使用しています。

## acts-as-taggable-on について
* タグ機能を実装できるgem
以前gemを使用せずにタグ機能を実装した時は、中間テーブルを作ったりと色々やりましたが、このgemを使うと一瞬でできます。

* 当たり前ですが、色んなメソッドが用意されている

`find_related_skills`　　同じタグに関連するタスクが表示される
`taggings_count`　メソッドではないですが、該当のタグの使用回数が表示されている
`tag_counts`　全てのタグデータを取得できる
などなど.....他にもまだありました。

使い方等はリファレンスを参照してください。

* 同じ名前で登録したタグの場合は、同じIDになるようになっている

例：`Rails`と別々のタスクで登録しても同じ`id`になる

# 実装内容

## 1. Gemfileに追加して、インストール

```ruby:gemfile
gem 'acts-as-taggable-on', '~> 6.0' #追加
```
```
$ bundle install
```
```
$ rails acts_as_taggable_on_engine:install:migrations
```
```
$ rails db:migrate
```

## 2. モデルとコントローラーに追加


対象のモデルとコントローラーに下記を追加する

```ruby:task.rb
class Task < ActiveRecord::Base
  acts_as_taggable #追加
  # acts_as_taggable_on :tags　と同じ意味のエイリアス
  # tags のなかにIDやら名前などが入る。イメージ的には親情報。

	#略
	
end
```
```ruby:tasks_controller.rb
class TasksController < ApplicationController

	#略
	
  private

  def task_params
    params.require(:user).permit(:name, :description, :tag_list) 
    #tag_list を追加
  end
	
	#略
	
end

```

## 3. 各ビューファイルの編集

まず、各Viewファイルを下記のように編集していく。
### ① タスク登録フォームにてタグを入力できるようにする

```ruby:_form.html.slim
= form_with model: task, local:true do |f|
  .form-group
    = f.label :name 
    = f.text_field :name, class: 'form-control', id: 'task_name'
  .form-group
    = f.label :description 
    = f.text_area :description, rows: 5, class: 'form-control', id: 'task_description'
    #タグ入力フォームを追加
  .form-group 
    = f.label :tag_list
    = f.text_field :tag_list, value: @task.tag_list.join(','), class: "form-control"
  
  .form-group
    = f.label :image 
    = f.file_field :image, class: 'form-control'
  = f.submit nil, class: 'btn btn-primary'
```

### ② 入力されたタグが配列として`tag_list`に代入されていく。

<img width="1153" alt="スクリーンショット 2019-02-21 15.35.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/308269/0b196397-27c2-e81a-1e8f-628b30c3b815.png">

`@task.tag_list.join(',')`のおかけで`,`で区切って複数のタグを入力できる

ここで、
`rails, php, js, python`
と入力した場合は、
`tag_list = ["rails","php","js","python"]`
という配列になる。

### ③ タスク一覧画面にタグを表示させる

```ruby:index.html.slim
 - @tasks.each do |task|
     tr id="task-#{task.id}"
        td= link_to task.name, task_path(task)
        td= task.created_at
	    #タグ表示欄を追加
        td= render 'tasks/tag_list', tag_list: task.tag_list
        
	    td
          = link_to '編集', edit_task_path(task), class: 'btn btn-primary mr-3'
          = link_to '削除', task, method: :delete, remote: true, data: {confirm: "タスク「#{task.name}」を削除します。よろしいですか？"}, class: 'btn btn-danger delete', id: "delete_link_#{task.id}"
```

タスク詳細画面でも同じ表示の仕方をするので`render`を利用してパーシャルファイルで処理してもらう。
パーシャルに流す前の処理としては、
`task`には@tasksから取り出した一つのタスクデータが入っている。
`tag_list`をメソッドとして使用すると、タスクに付いているタグが取り出せる。
`tag_list`に`task.tag_list`で取り出したタグをパラメータとして渡してあげる。

### ④ 受け取ったパラメータを繰り返し処理し、一つ一つを分けて表示させる

```ruby:_tag_list.html.slim
- tag_list.each do |tag|
  span.badge.badge-primary.mr-1.p-2= tag
```

この時、`tag_list`に入っているタグを`each`で一つ一つ独立させている

<img width="1121" alt="スクリーンショット 2019-02-21 15.37.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/308269/3ec1531c-efb1-d6ea-6f85-b9dd303bb855.png">



ここまで実装すると、上記の画像のようにタグが表示されます。
もちろん編集も可能です。

ここまでが基本的なタグ機能の実装です。


## 4. タグの絞り込み機能の追加
タグをクリックすると同じタグが付いたタスクを絞り込めるように実装していきます。

gemを使わずにタグ機能を実装した時は、この絞り込み機能がめちゃくちゃ苦戦しました。
ただ、gemを使う場合は、逆に完成されているために何をどう変えたらいいのか、どう使ったらいいのかわからなかったので苦戦しましたが、、、

実装内容は以下の通りです。
やっていることは結構シンプルです。


先ほど実装したものを少しだけいじります。

### ① タグにリンクをつける
`link_to`メソッドを使用してリンクにします。
このブロック内の`tag`には一つだけのタグ名が入っている状況です。

```ruby:_tag_list.html.slim
- tag_list.each do |tag|
  span.badge.badge-primary.mr-1.p-2
    = link_to tag, tasks_path(tag_name: tag), class: "text-white"
```

### ② リンク先にパラメータを渡す
この`tag`のタグ名をクリックした時の遷移先をタスク一覧画面にする。
その際、`tag_name`に`tag`のタグ名をパラメータとして渡してあげる。

例：　ここで`tag`に`Rails`というタグが入っていた場合は、`Rails`というタグ名が一覧画面へパラメータとして渡される。

### ③ コントローラーで処理する
```ruby:tasks_controller.rb
def index
    #略
    #タグ絞り込み
    if params[:tag_name]
      @tasks = Task.tagged_with("#{params[:tag_name]}")
    end
end
```
tasksコントローラーのindexアクションにて処理します。

ここで便利なメソッドが登場。
`tagged_with("タグ名")`で絞り込みができます。
（もちろん公式リファレンスにも載っています。）

なので、もし`tag_name`に値が入っている場合は（タグをクリックされたら）
受け取った`tag_name`を`tagged_with("タグ名")`の`タグ名`に入れて絞り込みを実行してくれます。
処理が実行されると、同じ`tag_name`を持ったタスクが一覧で表示される仕組みです。

URLも`http://localhost:3000/tasks?tag_name=Rails`とクリックしたタグ名で絞り込みができていることがわかります。

**例**：　もし`Rails`というタグをクリックしたら
`tag_name`は`Rails`という値を持って、indexアクションに渡されます。
そして、`tagged_with("Rails")`という形になり、絞り込みが実行される

# さいごに

gemは非常に便利ですが、少し変えようと思ったときに、何をしていいかわからなくなるのが現状でした。
ですが、リファレンスをしっかり読み、構造を少しでも理解すれば意外と頑張れるとうこともわかりました。
これからはリファレンスファーストを意識して取り組んでいければと思います。

読んでいただき、ありがとうございました。

備忘録として記事を書きましたが、もし間違っている内容等あればお気軽に編集リクエストをしていただければと思います。

以上です。