Dane O'Connor
Dane O'Connor

Reputation: 77378

Why do I need to reload this child association?

I often run into an issue when wiring active record associations and I'm looking to finally understand what's causing it (rather than just working around it).

When associating children to parents via parent.children<<, references to the children correctly update themselves. This doesn't work the same if the relationship is established in reverse (if done via child.parent=).

Why does this happen? Is there a way to make the relationship bi-directional?

I've tried inverse_of in the past with no success. I'm hoping its because I just don't understand enough about what's going on here.

Example:

Given these Models:

class Task < ActiveRecord::Base
  belongs_to :batch

  attr_accessor :state
end

class Batch < ActiveRecord::Base
  has_many :tasks

  def change_tasks
     tasks.each { |x| x.state = "started" }
  end
end

Why does the first spec fail but the second pass? Can I make the first pass? For some reason I need to reload in the first spec but not in the second.

describe "avoiding reload" do

  context "when association established via child.parent=" do
    it "updates child references" do
      b = Batch.create
      t = Task.create(batch: b)

      b.change_tasks

      b.tasks[0].state.should == "started" # passes
      t.state.should == "started" # fails!?
      t.reload.state.should == "started" # passes, note the reload
    end
  end

  context "when association established via parent.children<<" do
    it "updates child references" do
      b = Batch.create
      t = Task.create
      b.tasks << t

      b.change_tasks

      b.tasks[0].state.should == "started" # passes
      t.state.should == "started" # passes
    end
  end

end

Thanks for the help.

Upvotes: 2

Views: 1219

Answers (2)

dimuch
dimuch

Reputation: 12818

Short answer is the objects are different in the first spec and second one.

In the first spec you have 2 different objects (try to check in rails console):

t.object_id != b.tasks[0].object_id

While both objects refer to same record, they are different. You have changed b.tasks[0], but t is unchanged till reloading.

In the second spec there is only object:

t.object_id == b.tasks[0].object_id   

So any updates of t will be reflected in b.tasks[0], and vice verse.

Upvotes: 1

Mark Bolusmjak
Mark Bolusmjak

Reputation: 24419

It has to do with your order of operations.

In the first test you call Task.create. create takes the parameters and saves to the database and returns the result post save. The reference it has to it's batch will not necessarily be the same batch you have a handle on in the scope of your test.

In the second test, you assign the task after you call create. Then access it immediately. So the same task t is references within change_tasks and during t.state.should == "started".

Upvotes: 1

Related Questions