スクールの課題で、タスク管理アプリを作成中、ラベルの検索機能を実装した。初学者の私にとっては複雑な内容だったので、備忘録として残しておく。
まず、前提
バージョン
- ruby 3.2.2
- Rails 6.1.7.6
tasksテーブルとlabelsテーブルは多対多の関係。間にconnectionsテーブルという中間テーブルを作成している。
今回のテーマは、ラベルの検索機能。(タスク名のあいまい検索機能とステータス検索機能の部分は既に実装済の状態)
完成したtasks_controller.rb
は以下。
class TasksController < ApplicationController
def index
if params[:task].present?
if params[:task][:name] && params[:task][:status].present?
@tasks = current_user.tasks.search(params[:task][:name],
params[:task][:status])
elsif params[:task][:name]
@tasks = current_user.tasks.name_search(params[:task][:name])
elsif params[:task][:status]
@tasks = current_user.tasks.status_search(params[:task][:status])
elsif params[:task][:id]
label = Label.find(params[:task][:id])
label_task_id = label.connections.pluck(:task_id)
@tasks = Task.where(id: label_task_id)
end
end
if params[:sort_expired]
@tasks = current_user.tasks.all.order(timelimit: "ASC")
elsif params[:sort_rank]
@tasks = current_user.tasks.all.order(rank: "ASC")
elsif params[:task]
#@tasks = @tasksということ(検索機能で絞られたタスクがそのまま、ということ)
elsif
@tasks = current_user.tasks.all.order(created_at: "DESC")
end
@task = Task.new
end
省略
private
def task_params
params.require(:task).permit(:name, :content, :timelimit, :status, :rank, { label_ids: [] })
end
end
form_forで見た目の検索部分を作る
まず、index.html.erb
ファイルに、検索部分を作る。
スクールのテキストに公式カンファレンスに載っている下記コードを参考に実装してみましょうとある。
<%= form_for @post do |f| %>
<%= f.select :person_id, Person.all.collect { |p| [ p.name, p.id ] }, include_blank: true %>
<%= f.submit %>
<% end %>
色々とネットで検索してみたけど、これをどう置き換えれば良いのかさっぱりわからない。しかし、
Person.all.collect { |p| [ p.name, p.id ]
の部分は、今回ラベルの検索なのだから、Person
部分はLabel
になるような気がした。
なので、
Label.all.collect { |label| [ label.name, label.id ]
としてみる。
続いて、
<%= form_for @post do |f| %>
の部分。@postを、task
コントローラー内に作っている検索機能なので、@taskに変える。
index
アクション内に@taskがないので、設定する。
@task = Task.new
これで実行してみる!
<%= form_for @task do |f| %>
<%= f.select :id, Label.all.collect { |Label| [ Label.name, Label.id ] }, include_blank: true %>
<%= f.submit %>
<% end %>
すると、なぜだかnew.html.erb
が出てしまった!なぜだ!index.html.erb
が開かれてほしいのに。
ログを確認してみると、
postメソッドで、URL「/tasks」に飛んでいる。
postメソッドで、URL「/tasks」だと、create
アクションに飛んでしまうので、index
アクションになるようにしなければならない。(create
アクションでnew
に飛ぶように設定していた)
そのため、get
メソッドになるようオプションをつける。
<%= form_for @task, method: :get do |f| %>
<%= f.select :id, Label.all.collect { |Label| [ Label.name, Label.id ] }, include_blank: true %>
<%= f.submit %>
<% end %>
そうすると、お見事!index
アクションへ飛ぶようになりました!
また、
<%= f.select :id 〜省略〜
の:id
の部分。ここもどうするか迷ったのだけど、どうやら、名前をつけるためな雰囲気があるので何でも良い気がしたけど、実態を把握するために色々試してみた。
まず、id
の場合だと、
続いて、name
にしてみると、
<%= f.select :name 〜省略〜
機能面には何も変化が無いので、やっぱり予想通り、名前の役割な感じ。でも、またまた試しにhogehoge
にしてみた。すると、
エラーになってしまった!! どうやら、form_for
の後の@taskと関連のあるものにしないとダメらしいです。これについては、まだ理解が甘い気がするので、以後続いて学習していきたい。
コントローラー内に検索機能のロジックを作っていく
実際に実装したのは
elsif params[:task][:id]
label = Label.find(params[:task][:id])
label_task_id = label.connections.pluck(:task_id)
@tasks = Task.where(id: label_task_id)
end
この部分です。
さて、どのようにロジックを作り上げていったかというと、
まず、
これでわかるように、ラベル検索をした場合、上記のようなパラメータが送られてくる。つまり、params[:task][:id]
が存在する=true になるので、
elsif params[:task][:id]
と条件付けをする。
そして、
label = Label.find(params[:task][:id])
params[:task][:id]
には、検索したいラベルのid
が入っている。(下記画像参照。binding.pry
を実行して確認した。)
なので、変数label
には、id
が2のlabels
テーブルのデータが入る。
続いて、
label_task_id = label.connections.pluck(:task_id)
について。
まず、label.connections
はアソシエーションになっているので、そのlabel
に紐づいているtask
のデータを取り出すことができる。つまり今回はid
が2のlabel
に紐づいているtask
のデータを取り出すことができる。
そして、特定のカラムの値だけ取得したい場合に使えるpluck
メソッドを使って、label.connections.pluck(:task_id)
とすることで、id
が2のlabel
に紐づいているtask
のデータの中の、task_id
を配列で取得することができ、それを変数label_task_id
の中に入れている。そして、
@tasks = Task.where(id: label_task_id)
で、そのid
がlabel_task_id
のtask
を探して@tasksに入れられ、無事に、検索したいlabel
に紐づいているtaskだけが検索されるのでした!!
初学者の私にとってはとても新鮮な発見がいっぱいで、面白かったです!
では本日は以上!