2
2

More than 1 year has passed since last update.

[Rails] タグ機能

Posted at

ポートフォリオにタグ機能を実装した時のメモです。

完成イメージ

投稿画面でタグが投稿でき投稿したタグで絞り込み検索ができる機能です。
スクリーンショット 2021-10-11 8.06.24.png

実装

今回実装する部分の関係性は以下のような関係性です。
スクリーンショット 2021-10-11 6.56.01.png
イベントモデル(CRUD機能)は既に実装済みとします。

モデル作成

モデルを作っていきます。

ターミナル
$ rails g model tagmap item:references tag:references 
$ rails g model tag tag_name:string

タグ投稿するのでtagモデルにはtag_nameが必要です。

ターミナル
rails db:migrate

リレーション

続いてリレーション

app/models/event.rb
has_many :tagmaps, dependent: :destroy
has_many :tags, through: :tagmaps
app/models/tag.rb
has_many :tagmaps, dependent: :destroy
has_many :events, through: :tagmaps

thoroughを使うことで、tagmaps経由でEventモデルにアクセスできるようになってます。

app/models/tagmap.rb
belongs_to :event
belongs_to :tag

タグ作成

タグを作成できるようにしていきます。

app/controllers/events_controller.rb
 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できるようにします。

app/models/event.rb
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メソッドを使って一度タグを全て取得し、タグの共通要素だけを取り出し、古いタグを削除し新しくタグを作成しています。

あとはタグ投稿できるフォーム部分を作れば投稿できます。

app/views/events/_form.html.haml
%div 
  = f.label :name, 'タグ'
%div
  = f.text_field :tag_name, class: 'form-control'

タグ更新

続いて投稿されたタグを更新できるようにしていきます。

app/controllers/events_controller.rb
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と同じ処理を書いています。

app/views/events/_form.html.haml
%div 
  = f.label :name, 'タグ'
%div
  = f.text_field :tag_name, value: @tag_list class: 'form-control'

タグ検索

最後に検索部分です。
検索部分は既にransackを導入済みであることを想定しています。

app/controllers/events_controller.rb
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)

参考

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2