LoginSignup
8
6

More than 3 years have passed since last update.

[Rails]1対1対多の場合のdelegateとhas_many-throughの挙動の違い

Last updated at Posted at 2020-09-09

下記のように1対1対多の関係のモデルがあるとします。

  • UserとExamineeは1対1
  • ExamineeとTestは1対多
class User
  has_one :examinee
end

class Examinee
  belongs_to :user
  has_many :tests
end

class Test
  belongs_to :examinee
end

では、Userモデルから関連するTestモデルを取得したいときはどのように実装しますか?

様々なやり方がありますが、ActiveRecordの便利機能delegateを使うか、has_many-throughを使うことが多いのではないでしょうか?
どちらもやりたいことは達成できますが、発行されるクエリが少し違うので紹介します。

delegate

delegateを使うとメソッドを別クラスに委譲することが出来ます。
詳細はRailsガイドを参照してください。
3.4.1 delegate

今回の場合、下記のように実装します。

app/models/user.rb
delegate :tests, to: :examinee

実行すると下記の通り2つのクエリーが発行されます。
まず委譲先のexamineeを取得(1つ目のクエリー)して、その後、examinee.testsを実行(2つ目のクエリー)する挙動になっています。

irb> user.tests
Examinee Load SELECT `examinees`.* FROM `examinees` WHERE `examinees`.`user_id` = 1 LIMIT 1
Test Load SELECT `tests`.* FROM `tests` WHERE `tests`.`examinee_id` = 1

has_many-through

has_many-throughは多対多の時に使われることが多いですが、今回のように1対多の場合も利用できます。

詳細はRailsガイドを参照してください。
2.4 has_many :through 関連付け

今回の場合、下記のように実装します。

app/models/user.rb
has_many :tests, through: :examinee

実行すると下記の通り1つのクエリーが発行されます。
こちらの場合はjoinしたクエリーが1つだけ発行されます。
この機能が多対多に対応するように実装されていると考えると、deletgateのように2段階では効率よく取得できないのでjoinで取得しているんだなと理解できると思います。

irb> user.tests
Test Load SELECT `tests`.* FROM `tests` INNER JOIN `examinees` ON `tests`.`examinee_id` = `examinees`.`id` WHERE `examinees`.`user_id` = 1

最後に

2クエリーで取得するほうが良いのか、joinされた1クエリーで取得するほうが良いのかは実行環境によるので一概に良し悪しは判断出来ません。
というか大抵の場合はどちらで書いても問題なく動作するのでぶっちゃけどちらでもよいと思います。

ただ、ブラックボックス的に見ると同じことをしているように見えても、今回のように内部で発行されるクエリーが違ったりします。
たまにはこういう細かな違いを機能の成り立ちや目的などを考えならが確認してみると面白いと思います。

8
6
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
8
6