Rails 5.2でタグ投稿機能の実装のためgem「acts-as-taggable-on」を使ってみた

自作の投稿機能付きアプリを作っている際にタグ機能があったらいいなぁと思ったので、色々調べて導入してみました!
その際にいくつか困ったことが起きたので、備忘録として載せておきます。

スポンサーリンク

タグ機能の実装方法について

大まかに
①自分で実装する
②gemを使う
の2選択肢があって、正直最初は前者で実装しようとしたのですが、手軽に使えるgemでチョチョイと入れてしまおうという悪魔の誘惑に勝てず、後者でいくことにしました。
(これが後に地獄となることに。。。)

参考にした記事

railsでタグ機能を実装する
タグ機能を実現するための便利なデータベース設計を3つ紹介

acts-as-taggable-onに決めた理由

単純にスターの数が多かったのと、導入にあたっての日本語に記事がたくさんあったからですね!
やはり日本語の記事が多いと導入コストが大幅に下がります。

導入

日本語の記事があればいいというものの、まずはgithubのRead.meを読みましょう。

mbleigh/acts-as-taggable-on

また、日本語の参考にした記事も下記に置いておきます!
動的なタグ生成をするgem「acts-as-taggable-on」を使ってみました

gem のbundle install

Gemfile

gem 'acts-as-taggable-on', '~> 6.0'

ターミナル

bundle install

Mysqlを使っている場合のmigrate

DBをmysqlにしている場合は初期導入で色々問題があるようなのでmigrateを実行する前にrake acts_as_taggable_on_engine:install:migrationsを実行します。

※tagsテーブルのnameカラムは’binary encoded string'(パソコンで扱う2真数の文字列)として読み込まれるので、’utf8_bin’で読まなければいけないらしいです。
ターミナル

rake acts_as_taggable_on_engine:tag_names:collate_bin

rake acts_as_taggable_on_engine:install:migrations

rake db:migrate

今回おきたエラー

ターミナル

rake acts_as_taggable_on_engine:install:migrations

で6つmigrationファイルが作成されたのですが、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`
/Users/tech-camp/projects/tech_faq/db/migrate/20191124071633_add_missing_unique_indices.acts_as_taggable_on_engine.rb:11:in `up'

とエラーが吐かれて止まりました。
中身をみてみるとindexを付与したり削除したりを繰り返していて一旦うまくいっている範囲だけでイケるだろうと思ってテストしたらNGが出ます。

※その場合のエラーは’tagging_count’がないよ!!って感じです。

結論 不要なadd_indexやremove_indexをコメントアウトする。

結果、remove_indexができない、tagging_countカラムが必要
という2点に絞り、必要なファイルだけ置いておきます。
一旦自分で使う時のファイルだけまとめておきます。
①acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb
②add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb

①acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb

# This migration comes from acts_as_taggable_on_engine (originally 1)
if ActiveRecord.gem_version >= Gem::Version.new('5.0')
  class ActsAsTaggableOnMigration < ActiveRecord::Migration[4.2]; end
else
  class ActsAsTaggableOnMigration < ActiveRecord::Migration; end
end
ActsAsTaggableOnMigration.class_eval do
  def self.up
    create_table ActsAsTaggableOn.tags_table do |t|
      t.string :name
      t.timestamps
    end

    create_table ActsAsTaggableOn.taggings_table do |t|
      t.references :tag, foreign_key: { to_table: ActsAsTaggableOn.tags_table }

      # You should make sure that the column created is
      # long enough to store the required class names.
      t.references :taggable, polymorphic: true
      t.references :tagger, polymorphic: true

      # Limit is created to prevent MySQL error on index
      # length for MyISAM table type: http://bit.ly/vgW2Ql
      t.string :context, limit: 128

      t.datetime :created_at
    end

    add_index ActsAsTaggableOn.taggings_table, :tag_id
    add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :context], name: 'taggings_taggable_context_idx'
  end

  def self.down
    drop_table ActsAsTaggableOn.taggings_table
    drop_table ActsAsTaggableOn.tags_table
  end
end

②add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb

# This migration comes from acts_as_taggable_on_engine (originally 3)
if ActiveRecord.gem_version >= Gem::Version.new('5.0')
  class AddTaggingsCounterCacheToTags < ActiveRecord::Migration[4.2]; end
else
  class AddTaggingsCounterCacheToTags < ActiveRecord::Migration; end
end
AddTaggingsCounterCacheToTags.class_eval do
  def self.up
    add_column ActsAsTaggableOn.tags_table, :taggings_count, :integer, default: 0

    # ActsAsTaggableOn::Tag.reset_column_information
    # ActsAsTaggableOn::Tag.find_each do |tag|
    #   ActsAsTaggableOn::Tag.reset_counters(tag.id, ActsAsTaggableOn.taggings_table)
    # end
  end

  def self.down
    remove_column ActsAsTaggableOn.tags_table, :taggings_count
  end
end

といった形でmigrationファイルを微調整しました。
migrate実行しただけで上手く導入できるようにしてもらいたかった・・・笑

モデル追記

今回は記事投稿機能だったので、articleモデルに下記を追加

class article < ActiveRecord::Base
  <  省略  >
  acts_as_taggable
  # acts_as_taggable_on :skills, :interests これは使わない
end

投稿用ページにtag_listを送れるように追記

タグそのものはtag_listとして配列に格納されるようなので、tag_listをコントローラーに送れるようにします。

<%= form_for @article, url: {controller: "post", action: "create" } do |f| %>
<%# 省略 %>
  <div class="tags">
    <%= f.label :tag_list %>
    <%= text_field_tag 'article[tag_list]', @article.tag_list.join(",") %>
  </div>
  <%= f.submit "送信する" %>  
<% end %>

controllerでストロングパラメーターをセット

※初心者向けに
form_forに@articleを渡しているので、articleをrequireしています。

 private
  def article_params
    params.require("article").permit(:tag_list)
  end

表示画面にタグを出す

tag_listは配列なのでeach文で取り出します!!

<% article.tag_list.each do |tag| %>
    <%= tag %>
<% end %>

終わりに

作業量は少なかったけど、gemは自分で実装していない分どのような形で実装するのか分からないのでエラーが出たらストレスが溜まりますね笑

一旦実装して落ち着いたら中間テーブルを使って自分で実装してみます!

未経験者から最強のRailsエンジニアになるためのロードマップ
webアプリエンジニアになるためにどの程度学習すべきかをまとめた記事です。
タイトルとURLをコピーしました