acts-as-taggable-onを使用したタグ付け機能

#acts-as-taggable-onを使用したタグ付け機能
・ここでは、gem 'acts-as-taggable-on'を使ったタグ機能の実装について書かせていただきますの、どうぞよろしくお願いします。

・僕自身が上記のgemを使ってエラーが頻繁に出たので、それらを解決したい方に是非読んでいただきたいと思います。
※また、こちらの記事の修正点がある場合はコメントして頂けると幸いです。

##開発環境
Rails 6.0.3.2
ruby 2.6.5
acts-as-taggable-on バージョン 6.0.0
データベース　mySQL

##実装内容

###1, gemを追加してインストール
```ruby:Gemfile
gem 'acts-as-taggable-on', '~> 6.0' #追加
```

```
$ bundle install
```

###2, マイグレーションファイルをインストール
```
$ rails acts_as_taggable_on_engine:install:migrations
```
成功するとマイグレーションファイルが生成される。

```
あとで記載する
```

###3, マイグレーションを実行
```
$ rails db:migrate
```

```
Mysql2::Error: Cannot drop index 'index_taggings_on_tag_id': needed in a foreign key constraint: DROP INDEX `index_taggings_on_tag_id` ON `taggings`
~
ActiveRecord::StatementInvalid: Mysql2::Error: Cannot drop index 'index_taggings_on_tag_id': needed in a foreign key constraint: DROP INDEX `index_taggings_on_tag_id` ON `taggings`
~
Mysql2::Error: Cannot drop index 'index_taggings_on_tag_id': needed in a foreign key constraint
```
しかし、ここでエラーが発生。
**※筆者は、ここで原因が分からず数日悩みました。**

調べて見るとDBを「mySQL」にしている場合は初期導入で色々問題があるようなのでmigrateを実行する前に`rails acts_as_taggable_on_engine:tag_names:collate_bin`を実行する必要があります。
※tagsテーブルのnameカラムは'binary encoded string'(パソコンで扱う2真数の文字列)として読み込まれるので、'utf8_bin'で読まなければいけないらしいです。


```
$ rails acts_as_taggable_on_engine:tag_names:collate_bin #実行
```

また、**gemのバグ**で、
`acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb`のマイグレーションファイルの15行目で外部キーを設定しているにも関わらず、

```acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb
t.references :tag, foreign_key: { to_table: ActsAsTaggableOn.tags_table }
```

`add_missing_unique_indices.acts_as_taggable_on_engine.rb`の11行目で外部キーを削除せずにインデックスを削除しようとしているためエラーが出ます。

```add_missing_unique_indices.acts_as_taggable_on_engine.rb
remove_index ActsAsTaggableOn.taggings_table, :tag_id if index_exists?(ActsAsTaggableOn.taggings_table, :tag_id)
```

ここで、GitHubのissuesから不具合報告の内容を確認
※上記の1文は削除し、下記４文を追加してください

```add_missing_unique_indices.acts_as_taggable_on_engine.rb
(add_missing_unique_indices.acts_as_taggable_on_engine.rb)
remove_index ActsAsTaggableOn.taggings_table, :tag_id if index_exists?(ActsAsTaggableOn.taggings_table, :tag_id) #削除
if index_exists?(ActsAsTaggableOn.taggings_table, :tag_id) #追加
  remove_foreign_key :taggings, :tags                      #追加
  remove_index ActsAsTaggableOn.taggings_table, :tag_id    #追加
end                                                        #追加
```

[Fix: マイグレーションがエラーになる不具合を修正](https://github.com/daikideal/joblink/commit/7b0eb095b1e2dcf0562fbbfc43bb88773434a84d)

・これでマイグレーションファイルが導入できます！
　バグ解消前にrails db:migrateを実行した場合は、ArgumentErrorが出ます。
　一度目のmigrate実行時に最初のファイルだけ読み込んでるからですね！
　その場合はrails db: rollbackでmigrateを実行する前の状態に戻しましょう。

※こういった、gemのバグは今後も考えて検索しないといけなくなりますね。。。

###4, モデルとコントローラーに記述を追加
・タグ付けしたい記事等のモデルとコントローラーを作成後、ファイルに下記のような内容で記述しましょう。
※今回は、postとしてモデルとコントローラーを作成しています。

```post.rb
class Post < ApplicationRecord
  acts_as_taggable  #追加(acts-as-taggable-onの別名)
~
end
```

```posts_controller.rb
class PostsController < ApplicationController

  def index
    @post = Post.includes(:user,:tags).all.order('created_at DESC')
  end
  
  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)

    if @post.save
      flash[:notice] = "投稿が保存できました"
      redirect_to root_path
    else    
      @post = Post.new
      flash.now[:alert] = "投稿できません、もう一度入力してください"
      render :new
    end

#略

private
  def set_post
    @post = Post.find(params[:id])
  end

  def post_params
    #tag_listをpermitに追加
    params.require(:post).permit(:title, :text, :tag_list).merge(user_id: current_user.id)
  end

end
```

###5, ビューファイル編集(保存ページ)
```new.html.haml
 # 略
= form_with(model: @post, local: true) do |f|
 # 略
  .form-contents__tag
    = f.label :tag_list
    = f.text_field :tag_list, value: @post.tag_list.join(","), autocomplete: "tag", class: "tag-field", placeholder:'タグを入力してください'
```

これでDBに値が保存されます!
tagsテーブルの[name]カラムに入力したタグと同じ名前があればレコードは増えずに、[taggings_count]のカウントが増えていきます。

###６, ビューファイル編集(一覧表示)
続いてDBに保存されたタグを表示させます。

```posts_controller.rb
  def index
    @post = Post.includes(:user,:tags).all.order('created_at DESC')
    if params[:tag_name]
      @posts = Post.tagged_with("#{params[:tag_name]}")
    end
  end
```
indexアクションにて一覧表示させます。
N+1問題を考慮してincludes(:tags)を記述しましょう。


```index.html.haml
- @post.each do |post|
  %ul.posts__left__group__post-tag
```


##最後に
長くなりましたが、acts-as-taggable-onを使用したタグ付け機能はここまでで実装ができます。

実装時には、**３,マイグレーション実行**の際には、ぜひお気をつけてください。

acts-as-taggable-onは他にもタグの使用頻度で検索を掛けれたりと便利なメソッドがあるので、一度リファレンスを見ていただけると良いかなと思います！
[GitHub acts-as-taggable-on](https://github.com/mbleigh/acts-as-taggable-on)

以下、導入編同様に参考にさせていただいた記事になります。
[Rails | acts-as-taggable-on を使ったタグ機能の実装 | 備忘録](https://qiita.com/ddd555/items/e1caa8b73d118822a0a2)