ポートフォリオにタグ機能を実装した時のメモです。
完成イメージ
投稿画面でタグが投稿でき投稿したタグで絞り込み検索ができる機能です。
実装
今回実装する部分の関係性は以下のような関係性です。
イベントモデル(CRUD機能)は既に実装済みとします。
モデル作成
モデルを作っていきます。
$ rails g model tagmap item:references tag:references
$ rails g model tag tag_name:string
タグ投稿するのでtag
モデルにはtag_name
が必要です。
rails db:migrate
リレーション
続いてリレーション
has_many :tagmaps, dependent: :destroy
has_many :tags, through: :tagmaps
has_many :tagmaps, dependent: :destroy
has_many :events, through: :tagmaps
thoroughを使うことで、tagmaps経由でEventモデルにアクセスできるようになってます。
belongs_to :event
belongs_to :tag
タグ作成
タグを作成できるようにしていきます。
def create
@event = current_user.events.build(event_params)
#--------------------追加した部分----------------------
if params[:event][:tag_name].present?
tag_list = params[:event][:tag_name].split(nil)
@event.save_event_tag(tag_list)
end
#----------------------------------------------------
if @event.save
redirect_to event_path(@event), notice: '作成しました'
else
flash.now[:error] = '作成に失敗しました'
render :new
end
end
split
の引数にnilを指定することで以下の例のように文字列の先頭と末尾の空白文字を除いた上で「空白文字に一致する部分」で分割します。
irb(main):001:0> " あいう えお か ".split(nil)
=> ["あいう", "えお", "か"]
今回はこれを利用しsave_event_tag
というメソッドを作ってcreateできるようにします。
def save_event_tag(tags)
# 既にタグあるなら全取得
current_tags = self.tags.pluck(:tag_name) unless self.tags.nil?
# 共通要素取り出し
old_tags = current_tags - tags
new_tags = tags - current_tags
# 古いタグ削除
old_tags.each do |old_name|
self.tags.delete Tag.find_by(tag_name:old_name)
end
# 新しいタグ作成
new_tags.each do |new_name|
post_tag = Tag.find_or_create_by(tag_name:new_name)
self.tags << post_tag
end
end
タグ投稿の際に同じタグを何度も投稿しないように投稿したタグが既に存在する場合pluck
メソッドを使って一度タグを全て取得し、タグの共通要素だけを取り出し、古いタグを削除し新しくタグを作成しています。
あとはタグ投稿できるフォーム部分を作れば投稿できます。
%div
= f.label :name, 'タグ'
%div
= f.text_field :tag_name, class: 'form-control'
タグ更新
続いて投稿されたタグを更新できるようにしていきます。
def edit
@event = current_user.events.find(params[:id])
#---------------------追加部分-----------------------
@tag_list = @event.tags.pluck(:tag_name).join(",")
#---------------------------------------------------
end
def update
@event = current_user.events.find(params[:id])
#---------------------追加部分-----------------------
if params[:event][:tag_name].present?
tag_list = params[:event][:tag_name].split(nil)
@event.save_event_tag(tag_list)
end
#---------------------------------------------------
if @event.update(event_params)
@event.save_event_tag(tag_list)
redirect_to event_path(@event), notice: '更新しました'
else
flash.now[:error] = '更新に失敗しました'
render :edit
end
end
edit actionでは投稿したタグをjoin
で区切って表示しています。
update actionではcreateと同じ処理を書いています。
%div
= f.label :name, 'タグ'
%div
= f.text_field :tag_name, value: @tag_list class: 'form-control'
タグ検索
最後に検索部分です。
検索部分は既にransackを導入済みであることを想定しています。
def search
@q = Event.ransack(params[:q])
@events = @q.result(distinct: true)
if params[:tag_id].present?
@tag = Tag.find(params[:tag_id])
@events = @tag.events.order(created_at: :desc).all
end
@tag_lists = Tag.all
end
tag_id
で検索をかけています。
あとはタグ一覧として投稿したタグをHTML上で反映できるようにすれば完成です。
.tag
%p.tag-title タグ一覧
- @tag_lists.each do |list|
= link_to list.tag_name, events_search_path(tag_id: list.id)
参考