Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 5 years have passed since last update.

タスクと関連付けしたタグの検索をコントローラからモデルに移すにあたって調べたこと

Last updated at Posted at 2020-04-02

多対多で関連付けたデータの検索をコントローラからモデルに移したい

タスク管理アプリに、label(タグ)の検索機能を実装中。参考記事ではコントローラ内にロジックを置いているが、タスク名・ステータスの検索機能と同様、モデルで同じことがしたい。

参考記事
Railsでラベル機能(タグ付け)を実装する - Qiita

tasks_controller.rb
@tasks = @tasks
.search_with_name(params[:name])
.search_with_status(params[:status])
.joins(:labels).where(labels: { id: params[:label_id] }) if params[:label_id].present?

タスク名・ステータスでの検索(実装済み)

models/task.rb
scope :search_with_name, -> (name) {
    return if name.blank?
    where('name LIKE ?', "%#{name}%")
  }
  scope :search_with_status, -> (status) {
    return if status.blank?
    where(status: status)
  }

やったこと

  • モデルに同じようなスコープを書いてみる。
models/task.rb
  scope :search_with_label, -> (label) {
    return if label.blank?
    joins(:labels).where(id: label)
  }

この状態でラベルで検索をすると、一件も取得されない。

発行されているSQLを見てみると

Task Load (0.4ms)  SELECT  "tasks".* FROM "tasks"
INNER JOIN "labelings" ON "labelings"."task_id" = "tasks"."id"
INNER JOIN "labels" ON "labels"."id" = "labelings"."label_id"
WHERE "tasks"."user_id" = $1 AND "tasks"."id" = $2
ORDER BY "tasks"."created_at" DESC
LIMIT $3 OFFSET $4
[["user_id", 21], ["id", 1], ["LIMIT", 5], ["OFFSET", 0]]

labelがロードされてない。
joins(:labels)のあとのwhereが、そのままlabelsにかかってるものだと思ってたけど、どうやらtasksにかかってるよう。

このページを参考に修正。

関連するモデルの条件で検索したい - Qiita

models/task.rb
  scope :search_with_label, -> (label) {
    return if label.blank?
    joins(:labels).where('labels.id = ?', label)
  }

where(id: label)where('labels.id = ?', label)に。

Task Load (0.4ms)  
SELECT  "tasks".* FROM "tasks"
INNER JOIN "labelings" ON "labelings"."task_id" = "tasks"."id"
INNER JOIN "labels" ON "labels"."id" = "labelings"."label_id"
WHERE "tasks"."user_id" = $1 AND (labels.id = '1')
ORDER BY "tasks"."created_at" DESC
LIMIT $2 OFFSET $3
[["user_id", 21], ["LIMIT", 5], ["OFFSET", 0]]

5行目、whereで絞り込んでいるところで、labels.id = '1'のものを取ってくるようなSQLになってる。

Label Load (0.2ms)  
SELECT "labels".* FROM "labels"
INNER JOIN "labelings" ON "labels"."id" = "labelings"."label_id"
WHERE "labelings"."task_id" = $1
[["task_id", 16]]

その後のSQLでLabel Loadがしっかりかかっている。
labelingテーブルを見てtask_idが16,label_idが1のものを確認している?

ところでSQLの$マークって何だ

SQL の構文

あとに数字が続くドルマーク($)は、関数定義 の本体中の位置パラメータを表すために使われます。

ちょっとよくわからない……。

SQLを見てみる

 Task Load (0.4ms)  
 SELECT  "tasks".* FROM "tasks"
 INNER JOIN "labelings" ON "labelings"."task_id" = "tasks"."id"
 INNER JOIN "labels" ON "labels"."id" = "labelings"."label_id"
 WHERE "tasks"."user_id" = $1 AND "tasks"."id" = $2
 ORDER BY "tasks"."created_at" DESC
 LIMIT $3 OFFSET $4
 [["user_id", 21], ["id", 1], ["LIMIT", 5], ["OFFSET", 0]]

WHERE "tasks"."user_id" = $1には最終行の1番前にある"user_id", 21が、

AND "tasks"."id" = $2には"id", 1が、

LIMIT $3 OFFSET $4にはそれぞれ"LIMIT", 5"OFFSET", 0が入りそう。

ここ($マーク内)に送られてきたパラメータ、変数が入りますよっていう印みたいなものという認識で合ってる……?

そもそもどこから送られてきてる

検索の実装はindexページ。indexページでは自分の作成したタスクのみの表示となっている。

Task Loadの前に発行されているUser LoadのSQL文を見ると、

User Load (0.2ms)  
SELECT  "users".* FROM "users"
WHERE "users"."id" = $1 LIMIT $2  [["id", 21], ["LIMIT", 1]]
   app/controllers/application_controller.rb:12

矢印でご丁寧に場所を教えてくれてるみたい?見に行ってみる。

application_controller.rb
def current_user
  @current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
end

このメソッドにより、sessionからuser_idを持ってきてるのかな?
コントローラーで実際にcurrent_user.tasksとして現在ログインしてるユーザーのタスク全てを取ってきてるから多分そう。

tasks_controller.rb
def index
  @tasks = current_user.tasks

  @tasks = @tasks
  .search_with_name(params[:name])
  .search_with_status(params[:status])
  .search_with_label(params[:label])

  if params[:sort]
    @tasks = @tasks.page(params[:page]).per(PER).order(params[:sort])
  else
    @tasks = @tasks.page(params[:page]).per(PER).default_order
  end
end

発行されたSQL全文

Processing by TasksController#index as HTML
  Parameters: {"utf8"=>"✓", "name"=>"", "status"=>"", "label"=>"1", "search"=>"true", "commit"=>"検索"}
  User Load (0.3ms)
  SELECT  "users".*
  FROM "users"
  WHERE "users"."id" = $1 LIMIT $2
  [["id", 21], ["LIMIT", 1]]
   app/controllers/application_controller.rb:12
  Rendering tasks/index.html.slim within layouts/application
  
   (0.2ms) SELECT "labels"."name", "labels"."id" FROM "labels"
   app/views/tasks/index.html.slim:12
  
  Task Load (0.4ms)  
  SELECT  "tasks".*
  FROM "tasks"
  INNER JOIN "labelings" ON "labelings"."task_id" = "tasks"."id"
  INNER JOIN "labels" ON "labels"."id" = "labelings"."label_id"
  WHERE "tasks"."user_id" = $1 AND (labels.id = '1')
  ORDER BY "tasks"."created_at" DESC
  LIMIT $2
  OFFSET $3  
  [["user_id", 21], ["LIMIT", 5], ["OFFSET", 0]]
   app/views/tasks/index.html.slim:31
  
  Label Load (0.2ms)
  SELECT "labels".*
  FROM "labels"
  INNER JOIN "labelings" ON "labels"."id" = "labelings"."label_id"
  WHERE "labelings"."task_id" = $1  
  [["task_id", 16]]
   app/views/tasks/index.html.slim:38

SQLの11行目はラベルでの検索が書かれているところ。index.html.slimの12行目は

index.html.slim
h1 = link_to t('.title'), root_path, class: 'text-decoration-none text-reset'
/ ここはlazy lookupで呼び出し

= form_with(method: :get, local: true, url: tasks_path) do |f|
  = f.label :name_search, t('.name_search'), value: params[:name]
  = f.search_field :name, placeholder: t('.name_search'), class: 'form-control'

  = f.label :status_search, t('.status_search'), value: params[:status]
  = f.select :status, Task.enum_options_for_select(:status), class: 'form_control', include_blank: true, selected: ''

  = f.label :label_search, "ラベルで検索", value: params[:label]
  = f.select :label, Label.pluck(:name, :id), { include_blank: true}

  = f.hidden_field :search, value: true
  div.search_button = f.submit(t('.search'), class: 'btn btn-secondary')

Label.pluck(:name, :id)でselectボックスに選択肢として入れるlabelを取ってきてる。Label.nameLabel.idが入った配列を返してる。

確認

[1] pry(main)> Label.pluck(:name, :id)
   (0.4ms)  SELECT "labels"."name", "labels"."id" FROM "labels"
=> [["勉強", 1]]

f.selectについて

参考

フォーム(form) | Railsドキュメント

f.select(メソッド名, 要素(配列 or ハッシュ) [, オプション or HTML属性 or イベント属性])

要素の配列、ハッシュは、第1引数がセレクトボックスに表示される表示名で、第2引数が実際に送られる

この場合は

表示名:勉強
送られる値:id = 1

となる。

参考URL

Railsでラベル機能(タグ付け)を実装する - Qiita
【Rails】完全理解 formでセレクトボックスをつくるselectの使い方 | Always be myself
【Rails入門】joinsの使い方まとめ | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト
フォーム(form) | Railsドキュメント
関連するモデルの条件で検索したい - Qiita

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?