前提
関連付け:users -< products
1
2
3
4
5
class UsersController < ApplicationController
def index
@users = User . all
end
end
1
2
3
4
< % @users.each do |user| %>
< %= user.display_name %>
(< %= user.products.count %>)
< % end %>
この場合、user.products.count
で毎回クエリが発行されてしまいます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Started GET "/users" for 127 . 0 . 0 . 1 at 2021 - 11 - 25 08 : 47 : 04 + 0900
Processing by UsersController # index as HTML
Rendering layout layouts / application . html . slim
Rendering users / index . html . erb within layouts / application
User Load ( 2 . 7 ms ) SELECT ` users ` . * FROM ` users `
↳ app / views / users / index . html . erb : 1
( 5 . 7 ms ) SELECT COUNT ( * ) FROM ` products ` WHERE 'products`.`user_id` = 10
↳ app/views/users/index.html.erb:3
(4.5ms) SELECT COUNT(*) FROM `products` WHERE ' products ` . ` user_id ` = 9
↳ app / views / users / index . html . erb : 3
( 1 . 5 ms ) SELECT COUNT ( * ) FROM ` products ` WHERE 'products`.`user_id` = 8
↳ app/views/users/index.html.erb:3
(2.4ms) SELECT COUNT(*) FROM `products` WHERE ' products ` . ` user_id ` = 7
↳ app / views / users / index . html . erb : 3
( 5 . 8 ms ) SELECT COUNT ( * ) FROM ` products ` WHERE 'products`.`user_id` = 6
↳ app/views/users/index.html.erb:3
(1.4ms) SELECT COUNT(*) FROM `products` WHERE ' products ` . ` user_id ` = 5
↳ app / views / users / index . html . erb : 3
( 14 . 6 ms ) SELECT COUNT ( * ) FROM ` products ` WHERE 'products`.`user_id` = 4
↳ app/views/users/index.html.erb:3
(3.3ms) SELECT COUNT(*) FROM `products` WHERE ' products ` . ` user_id ` = 3
↳ app / views / users / index . html . erb : 3
( 13 . 1 ms ) SELECT COUNT ( * ) FROM ` products ` WHERE 'products`.`user_id` = 2
↳ app/views/users/index.html.erb:3
(16.5ms) SELECT COUNT(*) FROM `products` WHERE ' products ` . ` user_id ` = 1
↳ app / views / users / index . html . erb : 3
リファクタリング
group_of
でハッシュを作る
1
2
3
4
5
6
class UsersController < ApplicationController
def index
@users = User . active . with_attached_avatar . order ( created_at : :desc )
@product_count_hash = Product . joins ( :users ) . group ( 'products.user_id' ) . count
end
end
1
2
3
4
5
6
7
8
9
10
11
12
[ 1 ] pry ( main ) > @ product_count_hash = Product . joins (: users ). group ( 'users.user_id' ). count
( 0 . 9 ms ) SELECT COUNT ( * ) AS count_all , ` products ` . ` user_id ` AS products_user_id FROM ` products ` INNER JOIN ` users ` ON ` users ` . ` product_id ` = ` products ` . ` id ` GROUP BY ` products ` . ` user_id `
=> { 1 => 1 ,
2 => 8 ,
3 => 1 ,
4 => 1 ,
5 => 1 ,
6 => 1 ,
7 => 1 ,
8 => 1 ,
9 => 1 ,
10 => 2 }
で、{ user_id => product数 }
の形式のハッシュを取得できます。
なので、@product_count_hash[user_id]
でcountが取得できます。
1
2
3
4
< % @users.each do |user| %>
< %= user.display_name %>
(< %= @product_count_hash[user.id] %>)
< % end %>
参考:RailsのN+1対策ガイド
【Rails】index_byとgroup_byを用いて取り回しのきくハッシュを作成する にちょっと似ている
SQLのselectでカラムを作る
1
2
3
4
5
class UsersController < ApplicationController
def index
@users = User . joins ( :products ) . select ( "users.*, COUNT(products.id) AS count_of_products" ) . group ( :id )
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[ 1 ] pry ( main ) > users = User . joins ( :user_products ) . select ( "users.*, COUNT(user_products.id) AS count_of_products" ) . group ( :id )
User Load ( 7 . 4 ms ) SELECT users . * , COUNT ( user_products . id ) AS count_of_products FROM `users` INNER JOIN `user_products` ON `user_products` . ` user_id ` = ` users `.` id ` GROUP BY ` users `.` id `
=> [#<User:0x00007fa6b6e99260
id: 1,
name: "example1",
email: "example1@example.com",
created_at: Sun, 22 Aug 2021 15:31:40.337199000 JST +09:00,
updated_at: Thu, 23 Sep 2021 23:01:14.072035000 JST +09:00>,
#<User:0x00007fa6becd0d18
id: 2,
name: "example2",
email: "example2@example.com",
created_at: Sun, 22 Aug 2021 15:31:40.340775000 JST +09:00,
updated_at: Thu, 23 Sep 2021 23:01:14.136119000 JST +09:00>
...
]
[2] pry(main)> users.class
=> User::ActiveRecord_Relation
[3] pry(main)> users.first.count_of_products
=> 1
select
で取得した値はメソッドとして使える。
1
2
3
4
< % @users.each do |user| %>
< %= user.display_name %>
(< %= user.count_of_products %>)
< % end %>
@daidai3110 さんに教えていただきました。
以下の書籍に載っていたとのことです。