Problem Using ActiveRecord #exists? In Eager Load Resultset

So few weeks ago i’ve got problem in a rails apps. New Relic reports about 500 error, where the main cause is accessing method in nil object. Here is code snippet, causing the error :

class User < ActiveRecord::Base
  has_many :posts, -> { order "created_at DESC" }

  def latest_title
    posts.exists? && posts.first.title
  end
end

From the code above, user object have a method latest_title it just check if user has posts and fetch the first post’s title. But somehow its produce weird error when calls user.latest_title.

undefined title of nil:NilClass

the posts.exists? returns true but when try accessing the first post its return nil, what a ridiculous error. Based on error trace, i found the user object is generated by eager_load query, so the query look like this :

users = User.includes(:posts).order(created_at: :desc).limit(10000)

the query eager_load posts association into user object!!!. So i found the conclusion why the error happen.

  • Users result set are eager loaded the posts association into memory.
  • For the first time the query performed there are user who dont have posts, because using eager load the posts association stored as empty collection into user object.
  • Inside #latest_title method its perform checking using exists? which is surprising to me is that exists? method always check directly to database.
  • The error come when timing of resultset generated is slightly difference with operation insertion post tho user who doesn’t have posts. The eager load & insertion happen concurrently.
  • Because eager load the posts association still empty collection, but when exists? performed its directly check into database which is return true.
  • So what actually happen is like [].first.title which is produce the error 🙂
    Based on that conclusion, there are 2 options. First is do the query without eager load, second is instead use exists? please use any? which is come from ruby’s enumerable, it doesn’t directly check the database. And now latest_title method implementation look like:
posts.any? && posts.first.title

Just need to be gently and careful when using eager load.

Author: Agung

Problem Using ActiveRecord #exists? In Eager Load Resultset

Tinggalkan Balasan

Isikan data di bawah atau klik salah satu ikon untuk log in:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s