##【問題点】
アソシエーションの設定をしてるはずなのに、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]
(https://guides.rubyonrails.org/active_record_querying.html#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なのか、単一オブジェクトを返すメソッドなのか、意識したほうが良さそうです。良い勉強になりました。