has_many: unexpected behaviour

I've been using has_many for quite a while now and thought I understood it fairly well (I'm on Edge Rails).

But I've come across some unexpected behaviour, and I wonder if anyone could shed some light on it.

If I assign a collection to a variable seems to change as the collection changes. IS that expected?

Quick example

Let's say:

human has_many :cats cat belongs_to :human and in this example bob is a Human with id 2 with 1 cat

bob = Human.find(2) bobs_cats = bob.cats bobs_cats.size #=> 1 bob.cats.create(:name => "Tibbles") bobs_cats.size #=> 2

So, the variable bob_cats is changing as the collection changes.

However if I replace the second line with bobs_cats = bob.cats.find(:all) it behaves I would expect, i.e. behaves as an array of Cat objects, and is not affected by changes to the collection.

Can anyone shed any light on this?

yes, they are the same because of this line

bobs_cats = bob.cats

they are the same instance. check the objects id's and you will see

bob.cats.object_id

=> 18829402

bobs_cats.object_id

=> 18829402

you should see something similar.

if you want a separate object, use .dup for duplicate

bobs_cats = bob.cats.dup bobs_cats.object_id

=> 19619092

bob.cats.object_id

=> 18829402

now there are two distinct objects

Hi --

I've been using has_many for quite a while now and thought I understood it fairly well (I'm on Edge Rails).

But I've come across some unexpected behaviour, and I wonder if anyone could shed some light on it.

If I assign a collection to a variable seems to change as the collection changes. IS that expected?

Quick example

Let's say:

human has_many :cats cat belongs_to :human and in this example bob is a Human with id 2 with 1 cat

bob = Human.find(2) bobs_cats = bob.cats bobs_cats.size #=> 1 bob.cats.create(:name => "Tibbles") bobs_cats.size #=> 2

So, the variable bob_cats is changing as the collection changes.

However if I replace the second line with bobs_cats = bob.cats.find(:all) it behaves I would expect, i.e. behaves as an array of Cat objects, and is not affected by changes to the collection.

Can anyone shed any light on this?

yes, they are the same because of this line

bobs_cats = bob.cats

they are the same instance. check the objects id's and you will see

bob.cats.object_id

=> 18829402

bobs_cats.object_id

=> 18829402

you should see something similar.

if you want a separate object, use .dup for duplicate

bobs_cats = bob.cats.dup bobs_cats.object_id

=> 19619092

bob.cats.object_id

=> 18829402

now there are two distinct objects

Just to elaborate a little further: this is happening because the object in question is an association proxy object. It gets created the first time, and then cached:

=> #<Tag:0xb75f6d1c @attributes={"body"=>"Device", "id"=>"1"}>

things = tag.things

=> <things not loaded yet> # (Ugh, but that's another story :slight_smile:

things.object_id

=> -609253418

tag.things.object_id

=> -609253418

And when you call tag.things again, you get the same object:

tag.things.object_id

=> -609253418

It gets a little weirder here:

tag.things.reload

=> [#<Thing:0xb75e99dc @readonly=true, @attributes={"thing_id"=>"4", "tag_id"=>"1", "id"=>"4"}>, #<Thing:0xb75e99c8 @readonly=true, @attributes={"thing_id"=>"1", "tag_id"=>"1", "id"=>"21"}>, #<Thing:0xb75e99a0 @readonly=true, @attributes={"thing_id"=>"2", "tag_id"=>"1", "id"=>"22"}>]

tag.things.object_id

=> -609268288

things.object_id

=> -609268288

The object_id of things has changed, though things has not been reassigned. Rather bizarre from the Ruby point of view, and not particularly to my taste; I prefer to know what's going to happen when I assign to a variable, and to be the one who decides if and when the binding will change. But it's unlikely to cause trouble in practice, as far as I can tell.

David

OK. That makes sense. Guess I never quite connected (in my mind) the Array of AR objects that is bob.cats and the bob.cats.create or whatever. I'm guess the additional has_many methods are available to the array through some sort of method_missing magic?

Thanks for your help. Chris

Chris Hall wrote:

dblack@wobblini.net wrote:

Hi --

I've been using has_many for quite a while now and thought I understood it fairly well (I'm on Edge Rails).

But I've come across some unexpected behaviour, and I wonder if anyone could shed some light on it.

If I assign a collection to a variable seems to change as the collection changes. IS that expected?

Quick example

Let's say:

human has_many :cats cat belongs_to :human and in this example bob is a Human with id 2 with 1 cat

bob = Human.find(2) bobs_cats = bob.cats bobs_cats.size #=> 1 bob.cats.create(:name => "Tibbles") bobs_cats.size #=> 2

So, the variable bob_cats is changing as the collection changes.

However if I replace the second line with bobs_cats = bob.cats.find(:all) it behaves I would expect, i.e. behaves as an array of Cat objects, and is not affected by changes to the collection.

Can anyone shed any light on this?       

yes, they are the same because of this line

bobs_cats = bob.cats

they are the same instance. check the objects id's and you will see

bob.cats.object_id         

=> 18829402     

bobs_cats.object_id         

=> 18829402     you should see something similar.

if you want a separate object, use .dup for duplicate

bobs_cats = bob.cats.dup bobs_cats.object_id         

=> 19619092     

bob.cats.object_id         

=> 18829402

now there are two distinct objects      Just to elaborate a little further: this is happening because the object in question is an association proxy object. It gets created the first time, and then cached:

=> #<Tag:0xb75f6d1c @attributes={"body"=>"Device", "id"=>"1"}>   

things = tag.things       

=> <things not loaded yet> # (Ugh, but that's another story :slight_smile:   

things.object_id       

=> -609253418   

tag.things.object_id       

=> -609253418

And when you call tag.things again, you get the same object:

tag.things.object_id       

=> -609253418

It gets a little weirder here:

tag.things.reload       

=> [#<Thing:0xb75e99dc @readonly=true, @attributes={"thing_id"=>"4", "tag_id"=>"1", "id"=>"4"}>, #<Thing:0xb75e99c8 @readonly=true, @attributes={"thing_id"=>"1", "tag_id"=>"1", "id"=>"21"}>, #<Thing:0xb75e99a0 @readonly=true, @attributes={"thing_id"=>"2", "tag_id"=>"1", "id"=>"22"}>]   

tag.things.object_id       

=> -609268288   

things.object_id       

=> -609268288

The object_id of things has changed, though things has not been reassigned. Rather bizarre from the Ruby point of view, and not particularly to my taste; I prefer to know what's going to happen when I assign to a variable, and to be the one who decides if and when the binding will change. But it's unlikely to cause trouble in practice, as far as I can tell.

David

David

Thanks for this. Have been getting so much into the Ruby aspect of Rails recently (not least thanks to your excellent book) sort of forgot about some of the strange things that Rails does. Cheers Chris