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 :

[code language=”ruby”]class User < ActiveRecord::Base has_many :posts, -> { order “created_at DESC” }

def latest_title posts.exists? && posts.first.title endend[/code]

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.

[code language=”ruby”]undefined title of nil:NilClass[/code]

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 :

[code language=”ruby”]users = User.includes(:posts).order(created_at: :desc).limit(10000)[/code]

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:

[code language=”ruby”]posts.any? && posts.first.title[/code]

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

Author: Agung