1
0

More than 1 year has passed since last update.

Rails 多対多アソシエーションでwhereメソッドを使うと値を取ることが出来なかった話

Posted at

【問題点】

アソシエーションの設定をしてるはずなのに、whereメソッドを使うと、以下のエラーが発生する

NoMethodError: undefined method `tasks' for #<ActiveRecord::Relation [#<Label id: 8, label_name: "システムテスト">]>

【結論】

whereメソッドではなく、find_byメソッドを使用する。

label = "システムテスト"
Label.find_by(label_name: label).tasks
  Label Load (0.2ms)  SELECT "labels".* FROM "labels" WHERE "labels"."label_name" = $1 LIMIT $2  [["label_name", "システムテスト"], ["LIMIT", 1]]
  Task Load (0.4ms)  SELECT "tasks".* FROM "tasks" INNER JOIN "taggings" ON "tasks"."id" = "taggings"."task_id" WHERE "taggings"."label_id" = $1  [["label_id", 8]]
=> [#<Task:0x00007f7214a94d30
  id: 16,
  title: "ssssss",
  content: "sssssscontent",
# 省略

【詳細】

Railsアプリケーションにて、tasksテーブルlabelsテーブルが多対多のアソシエーションを組んでおり、中間テーブルにはtaggingsテーブルを作成しました。
modelにはそれぞれ以下のように設定がされています。

# task.rb
  has_many :labels, through: :taggings

# tagging.rb
  belongs_to :task
  belongs_to :label

# label.rb
  has_many :tasks, through: :taggings

labelの検索テストを行うため、コンソールで以下のように入力されたラベル情報から、紐づくtasksが取得出来るかテストしました。

$ rails c
Running via Spring preloader in process 6121
Loading development environment (Rails 6.0.4.1)
[1] pry(main)> label = "システムテスト"
=> "システムテスト"
[2] pry(main)> Label.where(label_name: label).tasks
  Label Load (0.2ms)  SELECT "labels".* FROM "labels" WHERE "labels"."label_name" = $1 LIMIT $2  [["label_name", "システムテスト"], ["LIMIT", 11]]
NoMethodError: undefined method `tasks' for #<ActiveRecord::Relation [#<Label id: 8, label_name: "システムテスト">]>
from /home/vagrant/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-6.0.4.1/lib/active_record/relation/delegation.rb:110:in `method_missing'

色々試行錯誤をしている中で、偶然findメソッドであれば欲しい情報がtasksから取得出来ることがわかりました。

[3] pry(main)> Label.find(8).tasks
  Label Load (0.3ms)  SELECT "labels".* FROM "labels" WHERE "labels"."id" = $1 LIMIT $2  [["id", 8], ["LIMIT", 1]]
  Task Load (0.5ms)  SELECT "tasks".* FROM "tasks" INNER JOIN "taggings" ON "tasks"."id" = "taggings"."task_id" WHERE "taggings"."label_id" = $1  [["label_id", 8]]
=> [#<Task:0x000055e9ba50e4a8
  id: 16,
  title: "ssssss",
  content: "sssssscontent",
# 省略

しかし、今回はlabel_nameの情報からtasksの情報を取得したいため、何が違うのか比べると以下のような違いがあることがわかりました。

[4] pry(main)> Label.find(8)
  Label Load (0.4ms)  SELECT "labels".* FROM "labels" WHERE "labels"."id" = $1 LIMIT $2  [["id", 8], ["LIMIT", 1]]
=> #<Label:0x000055e9b9527eb8 id: 8, label_name: "システムテスト">
[5] pry(main)> Label.where(label_name: label)
  Label Load (0.3ms)  SELECT "labels".* FROM "labels" WHERE "labels"."label_name" = $1  [["label_name", "システムテスト"]]
=> [#<Label:0x000055e9b96473c0 id: 8, label_name: "システムテスト">]

実行結果を比べると、whereメソッドで検索したほうは配列で結果が帰ってきていました。
でもなぜこのような違いが発生するのかわからず、以下のように調べてみると理由が見えてきました。

[6] pry(main)> Label.find(8).class
  Label Load (0.4ms)  SELECT "labels".* FROM "labels" WHERE "labels"."id" = $1 LIMIT $2  [["id", 8], ["LIMIT", 1]]
=> Label(id: integer, label_name: string)
[7] pry(main)> Label.where(label_name: label).class
=> Label::ActiveRecord_Relation

findメソッドはActiveRecordを通してSQL文が発行された結果が帰ってきていますが、
whereメソッドはSQLが発行されておらず、代わりにLabel::ActiveRecord_Relationという値が返ってきました。

ActiveRecord_Relationって誰やねんということで、ここまで来ると先人の方々の記事が沢山出てきて調べやすくなりました。
特に下記の方はまったく同じ状況の記事を書いて頂いていて、とても参考になりました。ありがとうございます…!
ActiveRecord の find と where の違い。
【続・find と where の違い 】ActiveRecord::Relation を学ぶ。

結論としては理由がRailsガイドに書いてありました。
Retrieving Objects from the Database

Finder methods that return a collection, such as where and group, return an instance of ActiveRecord::Relation. Methods that find a single entity, such as find and first, return a single instance of the model.

さらに単一のオブジェクトを返すメソッドの中にfind_byがありました。

2.1.5 find_by
The find_by method finds the first record matching some conditions.

ActiveRecordでDBを操作する際は使用するメソッドがActive Recordのfinder methodなのか、単一オブジェクトを返すメソッドなのか、意識したほうが良さそうです。良い勉強になりました。

1
0
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
1
0