LoginSignup
3
0

More than 5 years have passed since last update.

includesしてもN+1クエリ問題が解決しない場合はcounter cultureを検討しよう

Posted at

0.はじめに

railsでN+1問題が発生した場合、とりあえずincludesで先にデータを読み込んでおけばいいんでしょ?と思っていた。
が、上記の方法でどうしても解決できないシチュエーションに陥った。
それは、countメソッドでデータの個数を数えたいときだ。
こういう場合、counter cultureというgemを導入して解決するといいっぽい。

1.countメソッドでN+1クエリ問題が発生する具体的な例

例えば以下のように、UserとGroupがUserGroupを中間モデルとして、多対多の関係にあるとする。

user.rb
class User
  has_many :groups, through: :users_groups
end
group.rb
class Group
  has_many :users, through: :users_groups
end
users_group.rb
class UsersGroup
  belongs_to :user
  belongs_to :group
end

次に、以下のようにcountでそれぞれのユーザーが所属するグループ数を取得しようとする。

user_groups_count.html.erb
<% @users.each do |user| %>
  <%= user.groups.count %>
<% end %>

こうすると、当然のように大量のSQLクエリが発行されるが、includesで解決することはできなかった。

2.counter cultureを導入しよう

user.groups.countでN+1クエリ問題に引っかかる。。。
この問題を解決するために、counter cultureというgemを導入します。

gem 'counter_culture'
bundle install

このgemを使用すると、countで毎回数え上げるのではなく、カラムに数値を保存して取り出せるようになります。
まずは、以下のコマンドを叩いて、親であるUserモデルにusers_groups_countというカラムを追加します。

rails g counter_culture User users_groups_count
rails db:migrate

続いて、中間モデルのUsersGroupに1行追記します。

users_group.rb
class UsersGroup
  belongs_to :user
  counter_culture :user, column_name: "users_groups_count"
  belongs_to :group
end

以上で準備完了です。。
あとはuser.groups.countの代わりに、user.users_groups_countを使うことで、N+1クエリ問題を回避できます。

3.注意点

既存データに対して使用したい場合は、counter_culture_fix_countsメソッドを使って値を更新する必要があります。
User.counter_culture_fix_countsを流しておけばOKです。

4.最後に

備忘録的な意味合いが強いので、だいぶ雑に書いてしまいましたが、参考になれば幸いです。

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