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