Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

Heisenberg principal in ActiveRecord::Relation: observing relation changes it

#6179

I have isolated a case where calling ActiveRecord::Relation#all (or evidently anything operation that forces evaluation) changes its state to that it subsequently produces incorrect results. A self-contained test example is attached. Observed in Ruby 1.9.2 / Rails 3.0.0

The general synopsis:

A RefThing table has two fks: obj1_id and obj2_id. The following method correctly create a RefThing and returns 1:

def works(obj1, obj2)
  relation1 = RefThing.where(:obj1_id => obj1.id)
  relation1.where(:obj2_id => obj2.id).create
  relation1.size
end

The following method returns zero instead, even though it correctly creates a RefThing. Note the ONLY difference is the call to relation1.all before the create:

def broke(obj1, obj2)
  relation1 = RefThing.where(:obj1_id => obj1.id)
  relation1.all    # forces evaluation of relation1
  relation1.where(:obj2_id => obj2.id).create
  relation1.size
end

[As an aside: it's not just that relation1.size is returning 0 -- it's simply empty, and attempts to use it for accessing fields from RefThing are futile. And yes, it took me a long time to isolate this from a much larger code body! :)]

Reported by fearless_fool · December 16th, 2010 @ 05:45 AM

State: new
Milestone: none
Assigned to: nobody
Importance: none

Activity

  1. fearless_fool
    fearless_fool
    • Assigned user set to Aaron Patterson

    Assigning this to Aaron, since he appears to be the relationship maven. If I'm violating lighthouse etiquette by assigning the ticket, please excuse me - I'm not sure of the process.

    December 17th, 2010 @ 07:53 PM

  2. Samuel Kadolph
    Samuel Kadolph
    • Assigned user cleared.
    • Tag cleared.

    For future reference, misusing a physics term isn't helpful.

    This is simply because the results of the relation are being cached and currently models created with create are not added into the relation's cache.

    ruby-1.9.2-p0 > r = Widget.scoped
     => [#<Widget id: 1>]
    ruby-1.9.2-p0 > r
     => [#<Widget id: 1>]
    ruby-1.9.2-p0 > r.create
     => #<Widget id: 2>
    ruby-1.9.2-p0 > r
     => [#<Widget id: 1>]
    

    Calling reload on the relation proves this.

    ruby-1.9.2-p0 > r.reload
     => [#<Widget id: 1,>, #<Widget id: 2>]
    

    December 17th, 2010 @ 08:15 PM

  3. fearless_fool
    fearless_fool

    @SK:

    The Heisenberg reference was intended to enliven your day, even if not totally accurate. But -- absent any documentation about how relations are cached -- one would agree that having a relation change its state merely by observing it is kind of astonishing.

    So, to make sure I understand, what was happening in the OP was that in the working example, relation1 was NOT being cached because nothing had forced its evaluation. In the broken example, relation1 was getting cached in its empty state and not getting reloaded after the create, so it appeared to be empty.

    I can live with that if it's documented somewhere. If not, please (please) point me to the appropriate section on relations and I'd be happy to contribute to improving them.

    • ff

    December 17th, 2010 @ 10:38 PM