複数タグを登録する
タグを紐づける勉強のため、タグ機能を作成していきます。
理想は、pixivのようなタグ入力機能を目指したいです。
一覧のタグをクリックしたら、タグが自動でフォームに入力される機能ですね。
おそらく、jsで実装できると思いますが、まだ実装の目処は立ってません。
今回できること
- ruby 3.1
- rails 6.1
- アイテムを投稿するとき、タグを付けて投稿できる
- 編集するとき、タグを入れ替えられる
- タグだけ投稿できる
アソシエーション
至ってシンプルです。
アプリケーションの作成
rails new アプリケーション名
余談ですが、
今回、アプリ名をtagとつけたのが仇となり、Tagモデルが作成できませんでした。
Ttagモデルにしました。
アプリケーション名と、モデル名は一意だったのですね。知りませんでした。
コピペするので、もしかしたらttagというタイポみたいな箇所があるかもしれません。気づいたら修正してますが、もしかしたらttagのまま残ってる可能性があります。
モデルの作成
rails g model Item
rails g model ItemTag
rails g model Tag
各データベースファイルに記述します
t.string :name, null: false
t.text :item_text, null: false
def change
create_table :item_tags do |t|
t.integer :item_id, null: false
t.integer :ttag_id, null: false
t.timestamps
end
# 同じタグを2回保存するのは出来ないようにする
add_index :item_tags, [:item_id, :tag_id], unique: true
end
def change
create_table :tags do |t|
t.string :tag_name, null: false
t.timestamps
end
#同じ文字列を登録できないようにする
add_index :tags,:tag_name, unique: true
end
各モデルファイルに、記述します
has_many :item_tags, dependent: :destroy
has_many :tags, through: :item_tags
validates :name,:item_text, presence: true
#編集するとき、タグの入れ替えをする。
def save_tag(sent_tags)
unless sent_tags.empty?
self.item_tags.destroy_all
sent_tags.each do |new|
new_post_tag = Ttag.find_or_create_by(tag_name: new)
#同じタグを登録するとエラーになるので、include? で回避
unless self.tags.include?(new_post_tag)
self.ttags << new_post_tag
end
end
end
end
説明
unless sent_tags.empty?
self.item_tags.destroy_all
- もし送信されたタグが空でない場合は、そのアイテムの既存のタグをすべて削除する
sent_tags.each do |new|
new_post_tag = Ttag.find_or_create_by(tag_name: new)
self.ttags << new_post_tag
- 送信された各タグについて、Ttagモデルから既存のタグを検索し、存在しない場合は新しく作成します。
self.ttags << new_post_tag
- 左オペランドにあるオブジェクトに右オペランドにあるオブジェクトを追加する
has_many :item_tags, dependent: :destroy
has_many :items, through: :item_tags
belongs_to :tag
belongs_to :item
- throughをつけることにより中間テーブルを経由して、Itemモデルと、Tagモデルを紐づけさせてます。
コントローラの作成
rails g homes top
rails g controller items index new edit
rails g controller tags index
- アイテムの一覧ページ 作成ページ、編集するページ
- タグ一覧ページ
を作成します。
ルートファイルに記述
root to: "homes#top"
resources :items
resources :tags
resources :item_tags, only: [:index, :create, :destroy]
#なんとなくitem_tagsをルーティングしてみたけど、いらないよね
アイテムを投稿する
itemsコントローラに記述していく
def index
@items = Item.all
@tag_list = Tag.all
end
def show
@item = Item.find(params[:id])
@item_tags = @item.tags
end
def new
@item = Item.new
@tag = Tag.new
@tag_list = Tag.all
end
def create
@item = Item.new
#params
@item.name = params[:item][:name]
@item.item_text = params[:item][:item_text]
#ビューから取ってきたものを拾ってきている
#split(nil)は非推奨らしいので、半角スペース
tag_list = params[:item][:tag_name].split(" ")
if @item.save!
@item.save_tag(tag_list)
redirect_to items_path
else
redirect_to new_item_path
end
end
def edit
@item = Item.find(params[:id])
#pluck :tag_nameというカラムの値を取得する
@tags = @item.ttags.pluck(:tag_name).join(" ")
@tag_list = Ttag.all
end
def update
@item = Item.find(params[:id])
#フォームから送信されたタグ名のリスト
tag_list = params[:item][:tag_name].split(" ")
if @item.update(item_params)
#save_tagitemは、モデルで定義してある
@item.save_tag(tag_list)
redirect_to item_path(@item.id)
else
redirect_to :edit
end
end
def destroy
@item = Item.find(params[:id])
@item.destroy
redirect_to items_path
end
private
def item_params
params.require(:item).permit(:name, :item_text)
end
itemsのindexビューに記述
<h1>アイテム一覧ページ</h1>
<% @items.each do |item| %>
アイテム名:
<%= link_to item_path(item.id) do %>
<%= item.name %>
<% end %>
<br>
アイテム説明:
<%= item.item_text %>
<br>
タグ:
<% item.ttags.each do |tag_list| %>
<%= tag_list.tag_name %>
<br>
<% end %>
<% end %>
itemsのnewビューに記述
<h1>投稿画面</h1>
<%= link_to "一覧画面",root_path %>
<%= form_with model: @item do |f| %>
<p>タイトル</p>
<%= f.text_field :name %>
<p>本文</p>
<%= f.text_area :item_text %>
<p>タグ</p>
<%= f.text_field :tag_name, placeholder: "タグを複数つけるには半角スペースで区切ってください" %>
<%= f.submit "投稿" %>
<% end %>
<br>
下の一覧から、タグを押すとフォームに自動入力されるようにする。
pixivのタグ付けみたいにしたい。
<p>タグリスト</p>
<% @tag_list.each do |tag| %>
<%= tag.tag_name %>
<% end %>
itemsのeditビューに記述
<h1>編集ページ</h1>
<%= link_to "詳細ページ", item_path(@item.id) %><br>
<%= form_with model: @item do |f| %>
<%= f.text_field :name %>
<%= f.text_area :item_text %>
<%= f.text_field :tag_name,value:@tags %>
<%= f.submit "更新" %>
<% end %>
以下のタグをクリックして、上のフォームに自動追加できるようにする
<% @tag_list.each do |tag_list| %>
<%= tag_list.tag_name %>
<% end %>
タグも投稿できるようにしていく。
tagsコントローラに記述
def index
@tags = Tag.all
@tag = Tag.new
end
def create
#一度に複数を登録する
tags = tag_params[:tag_name].split(" ")
tags.each do |tag|
#create(カラム名: ブロック変数)
#find_or_create_by 同じ値が存在する場合、既存のレコードを返し
#存在しない場合には新しいレコードを作成する
Ttag.find_or_create_by(tag_name: tag)
end
redirect_to tags_path
end
def destroy
@tag = Ttag.find(params[:id])
@tag.destroy
redirect_to tags_path
end
private
def tag_params
params.require(:tag).permit(:tag_name)
end
find_or_create_by(カラム名: ブロック変数)
同じ値が存在する場合、既存のレコードを返し
存在しない場合には新しいレコードを作成する
終わり
タグ付けはできるようになった。
ここから、タグをクリックすると、そのタグがついているアイテムを検索できるようにしていく。
最終的には、pixivのようなタグ付けができるようにしていきたい。(目処はついていない)
参考にした記事