acts-as-taggable-onはタグ付けするのに便利なgemなのですが、登録済みのタグをチェックボックスで表示してチェックを外すと削除できるようにしようとしたら色々手こずったのでやり方を公開しようと思います。
環境
- rails 4.1.10
- acts-as-taggable-on 3.4.2
acts-as-taggable-onの基本
タグの追加と削除の方法
# タグの追加
@resource.tag_list.add('hoge,huga')
# タグの削除
@resource.tag_list.remove('hoge,huga')
やろうとしたこと
- 追加するタグはtext_fieldに記述 。コンマでもスペースでも区切れるようにする。
- 登録済みのタグはチェックボックス付きの一覧にし、チェックボックスを外したらタグを外すようにする
まずは失敗例
チェックボックスでタグを配列として取得し、tag_listに上書きすればいいじゃんと思い、やってみたら失敗しました。
@resource.tag_list = params[:tag_list]
# => Error: undefined method `new' for [" ", " ", ","]:Array
上記のようにtag_list
に直接書き込もうとしたらエラーが発生しました。
※以前はこの方法でやってたらできてたのに、ある日突然バグるようになりました。acts-as-taggable-onのバージョンアップによるものか、railsのバージョンアップによるものかはわかりません……。
成功したコード
複数のモデルに同じタグ付けしたので、controllerのconcernsにモジュールを作りました。
update_tagsというアクションでタグの更新を行います。
###ルーティング
Itemというモデルにタグ更新アクションを追加します。
またrails4より更新系のアクションはputではなくpatchが推奨になりましたのでpatchを指定します。
resources :items do
patch 'update_tags', on: :member
end
View
ポイントはチェックボックスのmultipleをtrueにすることと、tagがtag_listに含まれていたらチェックするようにすることです。
= form_for @resource, url: {action: 'update_tags', method: :patch} do |f|
= f.text_field :add_tags, :value => nil, :class => 'add_tag'
- @resource.tag_counts.each do |tag|
= f.check_box :tag_list,
{ multiple: true, checked: @resource.tag_list.include?(tag.to_s) }, tag, nil
= f.label tag, tag, class: 'tag'
= f.submit
Controller
まずconcernsにTagManagerというモジュールを作り、そこにupdate_tagsというアクションを設定しました。
module TagManager
extend ActiveSupport::Concern
included do
def update_tags
# チェックボックスの中身
tag_list = params[model_name.downcase][:tag_list]
# 消されたタグ = 元のタグ - チェックされたタグ
remove_tags = @resource.tag_list - tag_list
@resource.tag_list.remove(remove_tags) if remove_tags
# 追加されたタグ
add_tags = params[model_name.downcase][:add_tags]
if add_tags
# default_parserはコンマですが、半角/全角スペースもタグの区切りとみなします
add_tags_array = add_tags.gsub(/[[:blank:]]+/, ',').split(',')
@resource.tag_list.add(add_tags_array)
end
if @resource.save
flash[:notice] = 'タグを更新しました'
else
flash[:notice] = 'タグの更新に失敗しました'
end
redirect_to :back
end
end
end
Itemコントローラーではポイントとなる点だけ抜粋してコードを載せます。
ポイント
- concernsに作ったTagManagerを読み込み。
- strong_parametersでadd_tagsとtag_listを追加。
tag_listはチェックボックスの内容を配列で取るのでtag_list: []
とします。
class ItemsController < ApplicationController
include TagManager
*** 略 ***
private
def item_params
params.require(:item).permit(
:name, :category_id, :subcategory_id, :authors, :add_tags, tag_list: [])
end
end
今回はconcernsにモジュールを作って各resourceに対してアクションという方法を取ったのですが、これ書きながらtags_controllerとかに一つ新しいアクションを追加する方が良いような気もしてきました。
ま、とりあえず今回はconcernsの勉強も兼ねてこの形でということで。
ツッコミ随時歓迎です。