タグ付け機能[バックエンド]

What

タグ付け機能の実装例を紹介します。

DB テーブルはこんな感じ。 f:id:hellow_takuya:20200225001141p:plain

Strategy

文字列 (タグ) をparamsで保管しておいて、mapメソッドで都度都度保存
その時に、中間テーブルにもデータを入れておく

このようにすれば、元からあるWorkテーブルと無関係にタグを実装できます。

Solution

モデル側の記述

中間テーブルを作成する関係上、以下のように定義しておきます。
バリデーションや依存関係 (dependent系) はよしなに。

# work.rb

class Work < ApplicationRecord
  has_many :tag_works
  has_many :tags, through: :tag_works
end
# tag.rb

class Tag < ApplicationRecord
  has_many :tag_works
  has_many :works, through: :tag_works
end
# tag_work.rb

class TagWork < ApplicationRecord
  belongs_to :work
  belongs_to :tag
end

view側の記述

私の場合は、works tableに新しくレコードを追加するときに、タグも保存しておきたかったので、form_with(model: @work)内に追記しました。

記事とか画像とかが元々投稿できていたら、form_with内に何か足したりする必要はありません。

= form_with(model: @work, url: '/new', local: true) do |f|
  ...
  = f.text_field :all_tags, class:'p-Post__box', required: true
  ...

コントローラー側の記述

コントローラーを触りますが、送信ボタンを押した時に、paramsに値が入っているか確かめましょう。

  def new
    @work = Work.new
  end

   def create
    binding.pry
    @work = Work.new(pile_create_params)
    if @work.save
    else
      ...
    end
  end

ターミナルで、paramsと打ちます。

> params
>> ... "work" => {"..." => "...", ..., "all_tags" => "..."}

params[:work][:all_tags]とかparams.require(:work).permit(:all_tags)などと打てば、入力したタグが取り出せると思います。 それをprivateメソッド以下にtag_paramsとして定義してやります。

これでタグ入力した値を取得できました。

  private
  def tag_params
    params.require(:work).permit(:all_tags)
  end

タグが1個だけの場合は、そのまま保存すれば完成です。 例えば、以下のように書いてやります。

  def create
    @work = Work.new(pile_create_params)
    if @work.save
      tag = Tag.find_or_create_by!(name: tag_params[:all_tags].name)
      TagWork.find_or_create_by!(tag_id: tag.id, work_id: @work.id)
    end
  end

タグが複数の場合は何で区切るかを決める必要があります。
例えば、#で区切ることを考えます。
splitメソッドで文字列を#毎に区切り配列にします。

tag_params[:all_tags].split("#")

あとはmapメソッドで同じ処理を繰り返せば、完成です。
合わせるとこんな感じになります。
stripメソッドで空白を消すのもありです。

   def create
    @work = Work.new(pile_create_params)
    if @work.save
      tag_params[:all_tags].split("#").map do |name|
        tag = Tag.find_or_create_by!(name: name.strip)
        TagWork.find_or_create_by!(tag_id: tag.id, work_id: @work.id)
      end
      redirect_to root_url
    else
      ...
    end
  end

大いに参考にしました
https://www.sitepoint.com/tagging-scratch-rails/