read

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.

MORE
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 resultset 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.

Blog Logo

Agung Prasetyo


Published

Image

devblog of @sgt_mactavish

Back to Overview