Rails タグ付け機能
はじめに
ポートフォリオ作成中のプログラミング初学者です🔰
今日は複数のタグ付け&検索機能を実装しました!
もし間違いなどあればぜひ教えてください🥲
完成イメージ
レイアウトはまだあまり整えられておらず、見づらくてすみません!🥲
-
⭐️タグを複数つけて投稿できる
-
⭐️投稿一覧画面でタグの一覧と記事数を表示
-
⭐️タグのリンクを押すと、そのタグがついている投稿を表示できる
-
⭐️投稿詳細画面
テーブル定義
PostWorkout(トレーニング投稿):WorkoutTag(トレーニングタグ)=1:N
WorkoutTag(トレーニングタグ):PostWorkout(トレーニング投稿)=1:N
の多対多になるので、
中間テーブルとしてPostWorkoutTagを用意してそれぞれの外部キーを持たせています。
モデル/マイグレーションファイル作成
- モデル作成
rails g model WorkoutTag
rails g model PostWorkoutTag
マイグレーションファイル
class CreatePostWorkoutTags < ActiveRecord::Migration[6.1]
def change
create_table :post_workout_tags do |t|
t.references :post_workout, null: false, foreign_key: true
t.references :workout_tag, null: false, foreign_key: true
t.timestamps
end
# 同じタグは2回保存出来ない
add_index :post_workout_tags, [:post_workout_id,:workout_tag_id],unique: true
end
end
class CreateWorkoutTags < ActiveRecord::Migration[6.1]
def change
create_table :workout_tags do |t|
t.string :name, null: false
t.timestamps
end
add_index :workout_tags, :name, unique:true
end
end
⬇️
rails db:migrate
モデルファイル
アソシエーションの設定
class WorkoutTag < ApplicationRecord
has_many :post_workout_tags, dependent: :destroy
has_many :post_workouts, through: :post_workout_tags
validates :name, presence:true, length:{maximum:50}
end
through: :post_workout_tags
は、2つのモデル間の関連がpost_workout_tags
モデルを通じて行われることを示しています。
class PostWorkoutTag < ApplicationRecord
belongs_to :post_workout
belongs_to :workout_tag
end
class PostWorkout < ApplicationRecord
:
# タグのリレーションのみ記載
has_many :post_workout_tags, dependent: :destroy
has_many :workout_tags, through: :post_workout_tags
:
end
コントローラー記述
class Public::PostWorkoutsController < ApplicationController
def create
@post_workout = PostWorkout.new(post_workout_params)
@post_workout.end_user_id = current_end_user.id
# 受け取った値を,で区切って配列にする
tag_list = params[:post_workout][:name].split(',')
if @post_workout.save
@post_workout.save_workout_tags(tag_list)
redirect_to post_workouts_path, notice:'投稿が完了しました'
else
render :new
end
end
end
- ⭐️
tag_list = params[:post_workout][:name].split(',')
このコードは、params[:post_workout][:name]
から取得した文字列を ,(コンマ)で分割し、分割された結果を配列に格納してtag_list
という変数に代入しています。
split()メソッドとは
RubyのStringクラスのインスタンスメソッドで、文字列を特定の区切り文字で分割し、その結果を配列として返します。引数には区切り文字(デリミタ)を指定します。
例えば、split(',')というメソッドを呼び出すと、カンマ(,)を区切り文字として文字列を分割します。
str = "apple,banana,orange"
array = str.split(',')
# array => ["apple", "banana", "orange"]
- ⭐️
save_workout_tags
については、モデルファイルで定義しています。
タグで入力した値を配列に収めてる(tag_list)を引数としてモデルファイルに渡します。
モデルファイル(save_workout_tags)
class PostWorkout < ApplicationRecord
:
def save_workout_tags(tags)
# タグが存在していれば、タグの名前を配列として全て取得
current_tags = self.workout_tags.pluck(:name) unless self.workout_tags.nil?
# 現在取得したタグから送られてきたタグを除いてoldtagとする
old_tags = current_tags - tags
# 送信されてきたタグから現在存在するタグを除いたタグをnewとする
new_tags = tags - current_tags
# 古いタグを消す
old_tags.each do |old_name|
self.workout_tags.delete WorkoutTag.find_by(name:old_name)
end
# 新しいタグを保存
new_tags.each do |new_name|
workout_tag = WorkoutTag.find_or_create_by(name:new_name)
self.workout_tags << workout_tag
end
end
:
end
- ⭐️
current_tags = self.workout_tags.pluck(:name) unless self.workout_tags.nil?
このコードは、
「現在のインスタンスに関連付けられたタグが存在する場合には、そのタグの名前をcurrent_tagsに、リストとして取得する。タグが存在しない場合には何もしない」という意味です。
コードを分解してみる
-
self.tags
: selfは現在のインスタンス(たとえば、特定のブログポストまたは特定の商品など)を参照します。self.tagsは、そのインスタンスに関連付けられたタグの集合を返します。 -
self.tags.pluck(:name)
: pluckメソッドは、指定したカラムの値を配列として取得するActiveRecordのメソッドです。ここではnameというカラム(タグの名前を表す)の値を取得します。したがって、self.tags.pluck(:name)は、現在のインスタンスに関連付けられたすべてのタグ名のリストを返します。
pluckメソッドが便利な件について -
unless self.tags.nil?
: unlessはRubyの条件分岐の一つで、「〜でない限り」という意味です。つまり、self.tags.nil?がfalse(self.tagsがnilでない)の場合に、その後のコードが実行されます。これはself.tagsがnil(つまり、現在のインスタンスに関連付けられたタグが存在しない)場合には、タグ名を取得しようとするとエラーになるため、そのエラーを防ぐためのものです。
検索・タグの表示
ルーティング記述
- 検索で使用するルーティングとコントローラーを記述します
# タグの検索で使用する
get "search_tag" => "post_workouts#search_tag"
controller
class Public::PostWorkoutsController < ApplicationController
:
def search_tag
#検索結果画面でもタグ一覧表示
@tag_list = WorkoutTag.all
#検索されたタグを受け取る
@tag = WorkoutTag.find(params[:workout_tag_id])
#検索されたタグに紐づく投稿を表示
@post_workouts = @tag.post_workouts
end
:
end
投稿一覧画面
- タグを表示します!(seedなどでデータを先に作っておかないと表示されません!)
controller
class Public::PostWorkoutsController < ApplicationController
def index
@post_workouts = PostWorkout.all
@tag_list = WorkoutTag.all
end
end
view
:
<!--タグリスト-->
<% @tag_list.each do |list|%>
<i class="fa-sharp fa-solid fa-tag"></i>
<%=link_to list.name,search_tag_path(workout_tag_id: list.id) %>
<%="(#{list.post_workouts.count})" %>
<% end %>
:
投稿詳細画面
controller
class Public::PostWorkoutsController < ApplicationController
def show
@post_workout = PostWorkout.find(params[:id])
@tag_list = @post_workout.workout_tags.pluck(:name).join(',')
@post_workout_tags = @post_workout.workout_tags
end
end
view
:
<!-- タグリスト -->
<% @post_workout_tags.each do |tag| %>
<i class="fa-sharp fa-solid fa-tag"></i>
<%= link_to tag.name,search_tag_path(workout_tag_id: tag.id) %>
<% end %>
:
投稿編集画面
元々作っていたフィットネス投稿フォームの部分テンプレートに下記を足しました!
view
<%= form_with model: post_workout , local: true do |f| %>
:
# ここを追加
<label>タグ(,で区切ると複数タグ登録できます)</label>
<%= f.text_field :name,value: @tag_list, class: 'form-control' %>
:
<% if post_workout.new_record? %>
<%= f.submit '新規作成', class: "btn btn-success mt-4" %>
<% else %>
<%= f.submit '更新する', class: "btn btn-success mt-4" %>
<% end %>
:
<% end %>
value: @tag_list
にしてあげないと、フォームに何も入ってない状態になってしまいます。
controller
class Public::PostWorkoutsController < ApplicationController
:
def edit
@post_workout = PostWorkout.find(params[:id])
@tag_list = @post_workout.workout_tags.pluck(:name).join(',')
end
def update
@post_workout = PostWorkout.find(params[:id])
tag_list=params[:post_workout][:name].split(',')
if @post_workout.update(post_workout_params)
@post_workout.save_workout_tags(tag_list)
redirect_to post_workouts_path
else
render :edit
end
end
:
end
- ⭐️
@tag_list = @post_workout.workout_tags.pluck(:name).join(',')
このコードは、現在の@post_workout(特定のワークアウト投稿)に関連づけられた全てのworkout_tags(ワークアウトタグ)の名前を配列で取得し、それらをカンマ(,)でつなげた一つの文字列にして、その結果を@tag_listに格納しています。
結果として、@tag_listには例えば "tag1,tag2,tag3" のような形式の文字列が格納されます。
joinメソッドとは
joinメソッドはRubyの配列(Array)クラスに定義されたメソッドの一つで、配列の要素を指定した区切り文字で結合し、一つの文字列を作成します。
- 例えば、次のような配列があるとします:
array = ["apple", "banana", "cherry"]
- joinメソッドを使用して、これらの要素をカンマで結合することができます:
joined_string = array.join(", ")
# これにより、joined_stringは "apple, banana, cherry" という文字列になります。
この場合、配列の各要素はカンマとスペース(", ")で結合され、一つの文字列が作成されます。
- また、引数を指定しないでjoinメソッドを呼び出すと、配列の要素は何も挿入されずに結合されます:
joined_string = array.join
# これにより、joined_stringは "applebananacherry" という文字列になります。
この場合、配列の各要素は直接連結され、一つの文字列が作成されます。
検索結果の表示
view
:
<h2>タグが<%=@tag.name%>の投稿一覧</h2>
<!--タグリスト-->
<% @tag_list.each do |list|%>
<i class="fa-sharp fa-solid fa-tag"></i>
<%=link_to list.name,search_tag_path(workout_tag_id: list.id) %>
<%="(#{list.post_workouts.count})" %>
<% end %>
:
参考にさせていただいた記事🌱
さいごに
説明を省略してしまっているところもあるのですが、
この流れで実装できるはずです!
もし間違いや抜けているところなどあれば教えていただけますと幸いです🙇🏻♀️
Discussion