Rails|モデルに複数のタグをつける方法
目標
学校情報を作成時、学生の国籍を半角スペース区切りで複数入力できる。
学校情報詳細ページで、学生の国籍がカンマ区切りで表示される。
学校情報編集ページで、学生の国籍が半角スペース区切りで表示される。
開発環境
ruby 3.1.2p20
Rails 6.1.7.4
Cloud9
前提
Schoolモデルを作成済み
schools_controllerを作成済み
全体の流れ
1日本語学校は、複数の学生の国籍を持つ。
1学生の国籍は、複数の日本語学校を持つ。
日本語学校と学生の国籍は多対多の関係になるので、中間テーブル(国籍タグづけ)を作成する。
ER図
モデルについて
モデルを作成する。(※私はモデル名を無駄に長くしてしまいました...これは失敗です><)
$ rails g model student_nationality_tag nationality:string
$ rails g model student_nationality_tagging schools:references student_nationality_tag:references
DB反映を忘れずに。
$ rails db:migrate
各モデルにアソシエーションを記述する。schoolモデルには、後で使うメソッドも追記する。
has_many :student_nationality_taggings, dependent: :destroy
has_many :student_nationality_tags, through: :student_nationality_taggings
def create_tags(input_tags)
input_tags.each do |tag|
new_tag = StudentNationalityTag.find_or_create_by(nationality: tag)
student_nationality_tags << new_tag
end
end
def update_tags(input_tags)
registered_tags = student_nationality_tags.pluck(:nationality)
new_tags = input_tags - registered_tags
destroy_tags = registered_tags - input_tags
new_tags.each do |tag|
new_tag = StudentNationalityTag.find_or_create_by(nationality: tag)
student_nationality_tags << new_tag
end
destroy_tags.each do |tag|
tag_id = StudentNationalityTag.find_by(nationality: tag)
destroy_tagging = StudentNationalityTagging.find_by(student_nationality_tag_id: tag_id, school_id: id)
destroy_tagging.destroy
end
end
has_many :student_nationality_taggings, dependent: :destroy
has_many :schools, through: :student_nationality_taggings
belongs_to :school
belongs_to :student_nationality_tag
ルーティングの記述
新しく作成したモデルは、schoolの新規作成時や更新時に一緒に作成・更新されるので、個別のルーティングは不要。
resources :schools
コントローラの記述
class Admin::SchoolsController < ApplicationController
before_action :authenticate_admin!
def new
@school = School.new
end
def create
school = School.new(school_params)
input_tags = tag_params[:nationality].split(' ')
school.create_tags(input_tags)
if school.save
flash[:notice] = "学校情報を登録しました"
redirect_to admin_schools_path
else
flash[:alert] = school.errors.full_messages.join(", ")
render :new
end
end
def edit
@school = School.find(params[:id])
end
def update
school = School.find(params[:id])
if school.update(school_params)
input_tags = tag_params[:nationality].split(' ')
school.update_tags(input_tags)
flash[:notice] = "学校情報を更新しました"
redirect_to admin_school_path(school.id)
else
@school = School.find(params[:id])
flash[:alert] = school.errors.full_messages.join(", ")
render :edit
end
end
def show
@school = School.find(params[:id])
end
private
def school_params
params.require(:school).permit(:name, :name_kana, :name_en, :summary, :station, :address, :hp, :facebook, :twitter, :instagram, :tiktok, :youtube, :condition, :anual_fee, :have_dormitory, :dormitory_fee, :is_open, :prefecture, :image, :image_from)
end
def tag_params
params.require(:school).permit(:nationality)
end
end
createアクション
input_tags = tag_params[:nationality].split(' ')
tag_params
で取得した[:nationality]
情報をスペースで区切って、タグの配列を作成。input_tags
にその配列を代入する。
school.create_tags(input_tags)
これはshcoolモデルで定義したメソッド。新しく入力したタグを、student_nationality_tagから探す/存在しない場合は作成する。
updateアクション
school.update_tags(input_tags)
これはschoolモデルで定義したメソッド。
registared_tags に登録済みのタグを代入。
new_tags に新しく入力されたタグを代入し、追加。
destroy_tags に登録済みのタグ - 新しく入力されたタグ を代入し、削除。
showアクション
ビューの記述
....
<div class="row mt-2">
<div class="col-md-5 text-right">
<%= f.label :学生の国籍 %>(半角スペースで区切ってください)
</div>
<div class="col-md-5 offset-md-1">
<%= f.text_field :nationality, placeholder: "インドネシア" %>
</div>
</div>
....
....
<div class="row mt-2">
<div class="col-md-5 text-right">
<%= f.label :学生の国籍 %>(半角スペースで区切ってください)
</div>
<div class="col-md-5 offset-md-1">
<%= f.text_field :nationality, value: @school.student_nationality_tags.map(&:nationality).join(" "), class: "form-control" %>
</div>
</div>
....
学校情報編集画面では、登録済みのタグを半角スペース区切りで表示する。
@school.student_nationality_tags.map(&:nationality).join(" ")
について、以下補足。
@school.student_nationality_tags
@schoolの持つすべてのstudent_nationality_tags
を取得。
.map(&:nationality)
@school.student_nationality_tags
のそれぞれからnationality
カラムのデータを取り出し、配列を作成。
.join(" ")
作成した配列をスペース区切りの1つの文字列にする。
....
<div class="row">
<div class="col-md-3">
<p>【学生の国籍】</p>
</div>
<div class="col-md-9">
<ul class="list-unstyled">
<%= @school.student_nationality_tags.map { |tag| tag.nationality }.join(", ") %>
</ul>
</div>
</div>
....
<%= @school.student_nationality_tags.map { |tag| tag.nationality }.join(", ") %>
この部分について、以下補足。
@school.student_nationality_tags
@schoolの持つすべてのstudent_nationality_tags
を取得。
.map { |tag| tag.nationality }
@school.student_nationality_tags
の各要素のnationality
カラムのデータを取り出して、配列を作成。
.join(", ")
作成した配列を,
で区切って、つなげる。
参考にさせていただいた記事
Discussion