Search code examples
ruby-on-railsrubyrace-condition

Heisenbug Error in Ruby Rails


I call this Heisenbug error because the error only exists if it's not observed. Here is the call :

@selected_members  = Member.where(id: params[:member_ids])
@existing_members  = list.members
@new_members       = @selected_members.where('id not in (?)', @existing_members.map(&:id))
@members_to_lose   = @existing_members.where('enterprise_members.id not in (?)', @selected_members.map(&:id))

Rails.logger.info @members_to_lose.length # Remove this line and it no longer works
list.members       = @selected_members

render json: {members_to_add: @new_members, members_to_lose: @members_to_lose}

Nothing out of the ordinary whatsoever.

If I remove the Rails.logger.info call, then @members_to_lose returns incorrectly ( as in it returns blank ).

If I place the Logger or the Debugger after the list.members = @selected_members line, then the @members_to_lose is emptied, and returns incorrectly ( as in it returns blank ).

If instead of a Rails.logger.info, I just place a debugger before the list.members line, then it will return correctly.

What is happening here? Is this a ruby race condition? I have nothing in my code that would affect this code whatsoever. List.members is a simple has_many relationship.


Solution

  • Rails queries are lazily evaluated - they are executed the moment you actually need them.

    Rails.logger.info @members_to_lose.length
    

    This triggers the query because you call the method length. When you skip this line the query gets executed at:

    render json: {members_to_add: @new_members, members_to_lose: @members_to_lose}
    

    At this point you have saved the new members.

    To execute the query add load:

    @members_to_lose   = @existing_members.where('enterprise_members.id not in (?)', @selected_members.map(&:id)).load
    

    I'd also recommend using select instead of map for better performance:

    @members_to_lose   = @existing_members.where('enterprise_members.id not in (?)', @selected_members.select('enterprise_members.id')).load