質問編集履歴

1

より具体的な例を示すため、全文を改修しました。

2022/10/25 05:45

投稿

s.a.kura
s.a.kura

スコア5

test CHANGED
File without changes
test CHANGED
@@ -4,60 +4,160 @@
4
4
  外部結合の結合条件として、eager_loadでデフォルトの [テーブル1].id = [テーブル2].テーブル1_id のほか、
5
5
  自身で結合条件を追加したいのですが、うまくいきません。
6
6
 
7
+ #### 開発環境
8
+ - OS: Linux (Windows Subsystem for Linux)
9
+ - 言語: ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
10
+ - フレームワーク: Rails 6.0.3.4
11
+ - DBMS: mariadb Ver 15.1 Distrib 10.1.48-MariaDB, for debian-linux-gnu (x86_64) using readline 5.2
12
+
7
13
  ### 実現したいこと
8
14
 
9
- eager_loadメソッドを使、結合条件に動的な値を使って外部結合したいです。
15
+ eager_loadメソッドを使った外部結合をするにあたり、結合条件に動的な値を使たいです。
10
16
 
11
17
  #### 詳細
12
18
  例として、以下のような3つのテーブルを想定します。
13
19
 
14
- usersテーブル
20
+ - 利用者テーブル(users
15
- id, name
21
+ |id|name|created_at|updated_at|
22
+ |--:|:--|:--|:--|
23
+ |1|利用者1|2022/10/25 11:46|2022/10/25 11:46|
24
+ |2|利用者2|2022/10/25 11:47|2022/10/25 11:47|
25
+ |3|利用者3|2022/10/25 11:47|2022/10/25 11:47|
16
26
 
17
- booksテーブル
18
- id, user_id, library_id, title
27
+ - 貸出履歴テーブル(lending_records)
28
+ |id|user_id|book_id|lent_at|returned_at|created_at|updated_at|
29
+ |--:|--:|--:|:--|:--|:--|:--|
30
+ |1|1|1|2022-10-01 9:00|2022-10-15|2022/10/25 11:47|2022/10/25 11:47|
31
+ |2|1|2|2022-10-01 9:00|2022-10-15|2022/10/25 11:47|2022/10/25 11:47|
32
+ |3|2|1|2022-10-16 15:30|2022-10-30|2022/10/25 11:48|2022/10/25 11:48|
19
33
 
20
- librariesテーブル
34
+ - 蔵書テーブル(books)
35
+ |id|title|author|arrived_at|created_at|updated_at|
21
- id, name
36
+ |--:|:--|:--|:--|:--|:--|
37
+ |1|吾輩は猫である|夏目漱石|2022-09-30|2022/10/25 11:47|2022/10/25 11:47|
38
+ |2|たけくらべ|樋口一葉|2022-09-30|2022/10/25 11:47|2022/10/25 11:47|
39
+ |3|人間失格|太宰治|2022-10-20|2022/10/25 11:47|2022/10/25 11:47|
22
40
 
23
- このとき、eager_loadメソッドを使って、library_idを動的に指定して外部結合するSQLの発行をしたです。
41
+ このとき、eager_loadメソッドを使って、以下のSQLにお
42
+
43
+ 0. 結合条件`` `lending_records`.`book_id` = 2 AND ``の追加
44
+ 0. 動的なbook_idの指定
45
+ をしたいです。
46
+
24
- ``` SQL1
47
+ ``` SQL
48
+ SELECT
49
+ `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3,
50
+ `lending_records`.`id` AS t1_r0, `lending_records`.`user_id` AS t1_r1, `lending_records`.`book_id` AS t1_r2, `lending_records`.`lent_at` AS t1_r3,
51
+ `lending_records`.`returned_at` AS t1_r4, `lending_records`.`created_at` AS t1_r5, `lending_records`.`updated_at` AS t1_r6,
52
+ `books`.`id` AS t2_r0, `books`.`title` AS t2_r1, `books`.`author` AS t2_r2, `books`.`arrived_at` AS t2_r3, `books`.`created_at` AS t2_r4, `books`.`updated_at` AS t2_r5
53
+ FROM `users`
54
+ LEFT OUTER JOIN `lending_records` ON `lending_records`.`book_id` = 2 AND `lending_records`.`user_id` = `users`.`id`
25
- SELECT * FROM users LEFT OUTER JOIN books ON books.user_id = users.id AND library_id = 1
55
+ LEFT OUTER JOIN `books` ON `books`.`id` = `lending_records`.`book_id`
26
56
  ```
27
- ※SELECT句の"*"は、実際にはeager_loadにより名付けされるカラム一通りの文字列が入ります
28
- SQL1 のSQL文にて、最後のイコール文をマジックナンバーでなく、変数として渡したいです。
29
57
 
58
+ - SQLの実行結果
59
+ |t0_r0|t0_r1|t1_r0|t1_r1|t1_r2|t1_r3|t1_r4|t2_r0|t2_r1|t2_r2|t2_r3|
60
+ |--:|:--|--:|--:|--:|:--|:--|--:|:--|:--|:--|
61
+ |1|利用者1|2|1|2|2022/10/1 9:00|2022/10/15|2|たけくらべ|樋口一葉|2022-09-30|
62
+ |2|利用者2|NULL|NULL|NULL|NULL|NULL|NULL|NULL|NULL|NULL|
63
+ |3|利用者3|NULL|NULL|NULL|NULL|NULL|NULL|NULL|NULL|NULL|
30
64
 
65
+ (画面幅を考慮し、各テーブルのcreated_at, updated_at列は省略しています)
66
+
31
- ### 同じ状況のように思われる質問
67
+ ### 似た状況のように思われる質問
32
68
  https://stackoverflow.com/questions/41532444/eager-load-for-custom-joins-in-activerecord
33
69
 
34
70
  ### 試したこと
35
71
 
36
- 実際には他テーブルもeager_loadしたり、複数where句をチェンしたりするため、
72
+ user.rb `has_many :lending_records` 行へスコプ追加。
73
+
74
+ - 各モデルファイル
37
- ```
75
+ ``` Ruby
38
- library_id_ex = 1
76
+ # user.rb
39
- User.eager_load(:books).where(books: { library_id: library_id_ex })
40
- ```
41
- や、
42
- ```
43
- User.eager_load(:books).joins("AND books.library_id = #{library_id}")
44
- ```
45
- といった方法では解決できません。
46
- 上記の同じ状況のように思われる質問への回答を参考にいろいろ試したところ、
47
- ```
48
77
  class User < ApplicationRecord
49
- has_many :books, -> { self.where(library_id: 1) }
78
+ has_many :lending_records, -> { self.where(book_id: 2) }
50
- (略)
51
79
  end
52
80
  ```
53
- と書くと実装したいSQLが発行されることは分かりましたが、
81
+ ``` Ruby
54
- library_idを動的に指定することができません。
82
+ # lending_record.rb
55
- 例えば変数として渡した値を、library_idの絞り込み条件に使う、ということが
83
+ class LendingRecord < ApplicationRecord
56
- そもそもできるのかわからず困っています。
84
+ belongs_to :user
85
+ belongs_to :book
86
+ end
87
+ ```
88
+ ``` Ruby
89
+ # book.rb
90
+ class Book < ApplicationRecord
91
+ has_many :lending_records
92
+ end
93
+ ```
57
94
 
95
+ - 実行したコード
96
+ ``` Ruby
97
+ User.eager_load(lending_records: :book)
98
+ ```
99
+
100
+ この方法で、[実現したいこと](#実現したいこと)に記載したSQLが発行され、期待通りの結果が得られることは分かりました。
101
+ しかし、この方法ではbook_idを動的に指定することができません。
102
+ has_manyのスコープの中で変数を扱うことはできるのでしょうか。
103
+
58
- ### 補足情報(開発環境)
104
+ ### 補足情報
105
+
106
+ 以下①は想定通りにレコードが取れず、②は使うことを避けたいです。
107
+
108
+ ① 結合した後にWHERE句で絞り込む
109
+ ② joinsメソッドで結合条件を付加する
110
+
111
+ ##### ① 結合した後にWHERE句で絞り込む
112
+ この場合は以下の結果になります。
113
+ - モデルファイル
114
+ ``` Ruby
115
+ # user.rb
116
+ class User < ApplicationRecord
117
+ has_many :lending_records
118
+ end
119
+ ```
120
+ (lending_record.rb および book.rb は上記と同様)
121
+
122
+ - 実行したコード
123
+ ``` Ruby
124
+ User.eager_load(lending_records: :book).where(lending_records: { book_id: 2 })
125
+ ```
126
+
127
+ - 発行されるSQL
128
+ ``` SQL
129
+ SELECT
130
+ `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3,
131
+ `lending_records`.`id` AS t1_r0, `lending_records`.`user_id` AS t1_r1, `lending_records`.`book_id` AS t1_r2, `lending_records`.`lent_at` AS t1_r3,
132
+ `lending_records`.`returned_at` AS t1_r4, `lending_records`.`created_at` AS t1_r5, `lending_records`.`updated_at` AS t1_r6, `books`.`id` AS t2_r0,
133
+ `books`.`title` AS t2_r1, `books`.`author` AS t2_r2, `books`.`arrived_at` AS t2_r3, `books`.`created_at` AS t2_r4, `books`.`updated_at` AS t2_r5
134
+ FROM `users`
135
+ LEFT OUTER JOIN `lending_records` ON `lending_records`.`user_id` = `users`.`id`
136
+ LEFT OUTER JOIN `books` ON `books`.`id` = `lending_records`.`book_id`
137
+ WHERE `lending_records`.`book_id` = 2
138
+ ```
139
+
140
+ - SQLの実行結果
59
- - ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
141
+ |t0_r0|t0_r1|t1_r0|t1_r1|t1_r2|t1_r3|t1_r4|t2_r0|t2_r1|t2_r2|t2_r3|
142
+ |--:|:--|--:|--:|--:|:--|:--|--:|:--|:--|:--|
143
+ |1|利用者1|2|1|2|2022/10/1 9:00|2022/10/15|2|たけくらべ|樋口一葉|2022-09-30|
144
+
145
+ (画面幅を考慮し、各テーブルのcreated_at, updated_at列は省略しています)
146
+
147
+ このように、結合条件に指定した場合とは異なる結果となります。
148
+
149
+ ##### ② joinsメソッドで結合条件を付加する
150
+ 作成しているWebアプリでは、例えば
151
+ ``` Ruby
152
+ model = User.eager_load(lending_records: :book).joins("AND `lending_records`.`book_id` = 2")
60
- - Rails 6.0.3.4
153
+ if hoge.nil?
154
+ model = model.eager_load(:other_tables)
155
+ end
156
+ ```
157
+ といった形でチェーンして使うことを想定しています。
158
+ この場合に、LEFT OUTER JOIN句はチェーンした順でSQLに追加されていく仕様かと思います。
159
+ そのためjoinsメソッドで書いた文字列が期待する位置にならないおそれがあることからjoinsメソッドでの対応は避けたいです。
160
+ (.eager_load(lending_records: :book)を必ず最後にチェーンするようにすれば期待通りのSQLが得られるかとは思いますが、保守の観点から避けたい)
61
161
 
62
162
  以上、よろしくお願いいたします。
63
163