LoginSignup
52
37

More than 5 years have passed since last update.

has_manyをeager_load/includesするときの注意点

Last updated at Posted at 2015-10-16

eager_loadはJOINしてキャッシュもしてくれるActiveRecordのメソッドだが、この仕様を正しくイメージできてなかったのではまった.
has_manyに対してIN句検索したときに一部のhas_manyデータのみがキャッシュされて意図しない結果となった.

joinsではキャッシュされないためこの問題は起こらないが、当然キャッシュされないので後から追加クエリが発行される.
includesは条件によってeager_loadと同じ動きをするため今回の用途だと同じ問題が発生する.
ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い

再現

超簡単なhas_many関連を作成する.

class User < ActiveRecord::Base
  has_many :roles
end

class Role < ActiveRecord::Base
  belongs_to :user
end
user = User.create(name: "test")
  # id=17
user.roles.build(name: "programmer").save
user.roles.build(name: "designer").save
user.roles.build(name: "infra").save

スクリーンショット 2015-10-16 10.53.56.png

"programmer","designer","infra"をひもづけたのでuser.rolesは当然3件とれてほしい

User.find(17).roles.length
  # => 3

しかしprogrammerを含むuser一覧を取得すると、rolesの件数がおかしくなる. これはjoinでマッチした一部のデータのみがキャッシュされてしまうため.

User.eager_load(:roles).where(roles: { name: ["programmer"] }).find(17).roles.length
  # => 1
User.eager_load(:roles).where(roles: { name: ["programmer","designer"] }).find(17).roles.length
  # => 2
User.eager_load(:roles).where(roles: { name: ["programmer","designer","infra"] }).find(17).roles.length
  # => 3

前述のとおりincludesでも同じ問題が起きる.

User.includes(:roles).where(roles: { name: ["programmer"] }).find(17).roles.length
  # => 1

解決策を考えてみたけど、キャッシュさせないまたはキャッシュを捨てるしか思いつかない.
追記:コメント欄にキャッシュもしつつすべてのhas_manyを取得する解決策を記載いただきました。@jnchitoさんありがとうございます。

User.joins(:roles).where(roles: { name: ["programmer"] }).find(17).roles.length
  # => 3
  # joinsはキャッシュしないためこの問題発生しない
User.includes(:roles).where(roles: { name: ["programmer"] }).find(17).roles(true).length
  # => 3
  # roles(true)とするとforce_reloadになるのでキャッシュを捨てて強制的にSQL発行される

References

Environment

% rails -v
Rails 4.1.9
52
37
4

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
52
37