belongs_to nonintuitive results with assignment?

class Page < ActiveRecord::Base
    has_many :lists
end

class List < ActiveRecord::Base
    belongs_to :page
end

######### controller code, inside action:
    list = List.find(params[:id])
    old_page = list.page
    list.page = new_page
    # what is the value of old_page now?

old_page now is the same as new_page! what the heck?!?

The only thing i can think of is that old_page is getting assigned some
kind of proxy object, rather than the Page object itself, and
"list.page = new_page" changes only the actual Page object pointed to
by the (same) proxy object. Am I on the right track?

The workaround is easy, of course. But would it really be that hard to
maintain normal object referencing behavior?

Not sure if I'm asking a question or just ranting. But if other people
don't see this behavior, or if there's an elegant explanation of why it
has to be this way, then I'd like to know.

-Brian

Hi --

I got bit by this too, but on reflection (no pun intended :wink: it is correct, I was actually doing it the other way around and assigning the has_many, but it is the same issue.

Just think of it as pointer assignment if you come from c/c++.

One work around in your case is to do this instead...

old_page_id= list.page.id

I did this as it was an array...

old_lists= page.lists.clone

that way I got a clone of the array of lists, not a pointer to the lists has_many proxy. (or whatever it is).

brian.estlin wrote:

Hi --

brian.estlin wrote:

class Page < ActiveRecord::Base
    has_many :lists
end

class List < ActiveRecord::Base
    belongs_to :page
end

######### controller code, inside action:
    list = List.find(params[:id])
    old_page = list.page
    list.page = new_page
    # what is the value of old_page now?

old_page now is the same as new_page! what the heck?!?

The only thing i can think of is that old_page is getting assigned some
kind of proxy object, rather than the Page object itself, and
"list.page = new_page" changes only the actual Page object pointed to
by the (same) proxy object. Am I on the right track?

The workaround is easy, of course. But would it really be that hard to
maintain normal object referencing behavior?

Not sure if I'm asking a question or just ranting. But if other people
don't see this behavior, or if there's an elegant explanation of why it
has to be this way, then I'd like to know.

I got bit by this too, but on reflection (no pun intended :wink: it is
correct, I was actually doing it the other way around and assigning
the has_many, but it is the same issue.

Just think of it as pointer assignment if you come from c/c++.

I come from Ruby :slight_smile: It seems very odd to me.

One work around in your case is to do this instead...

old_page_id= list.page.id

Hey, enjoy the syntactic sugar:

   old_page_id = list.page.id

:slight_smile:

I did this as it was an array...

old_lists= page.lists.clone

that way I got a clone of the array of lists, not a pointer to the
lists has_many proxy. (or whatever it is).

This makes for the (to me) strange situation where:

   a = list.page
   b = Page.find(a.id)

lead to two different behaviors in the face of changing list.page.
I'm not seeing what the purpose or advantage of this is.

David

dblack@wobblini.net wrote:

Just think of it as pointer assignment if you come from c/c++.

I come from Ruby :slight_smile: It seems very odd to me.

One work around in your case is to do this instead...

old_page_id= list.page.id

Hey, enjoy the syntactic sugar:

   old_page_id = list.page.id

:slight_smile:

I did this as it was an array...

old_lists= page.lists.clone

that way I got a clone of the array of lists, not a pointer to the
lists has_many proxy. (or whatever it is).

This makes for the (to me) strange situation where:

   a = list.page
   b = Page.find(a.id)

lead to two different behaviors in the face of changing list.page.
I'm not seeing what the purpose or advantage of this is.

Agreed it threw me, but I gave up worrying about the why's a long time ago, I'm just thankful I got around the problem without spending hours on it :wink: As a sage once said "I am's What I am's", or in this case "it is as it is".

Well, I'm not going to spend too much time worrying about it either.
But here's the thing that IMO makes it a bad design. If I show you the
following code:

old_page = list.page
list.page = new_page
# what is the value of old_page now?

your answer (as to what old_page is) will be *different* depending on
whether "list.page" represents an ActiveRecord association or not.
I.e., you can't tell, just by looking at the above code. You have to
go track down what "list" is and look in it's class definition to see
whether "page" is an association.

Of course, in a small single-developer application, which mine is, it's
not really an issue. But if you're handing a 50,000-line codebase to
another developer, it's kind of unfortunate. So IMO it's a code
readability/maintainability turkey.

In C pointer assignment, you at least can make a good guess from all
the little *'s what's actually going on. :wink:

-Brian

brian.estlin wrote:

Well, I'm not going to spend too much time worrying about it either.
But here's the thing that IMO makes it a bad design. If I show you the
following code:

old_page = list.page
list.page = new_page
# what is the value of old_page now?

your answer (as to what old_page is) will be *different* depending on
whether "list.page" represents an ActiveRecord association or not.
I.e., you can't tell, just by looking at the above code. You have to
go track down what "list" is and look in it's class definition to see
whether "page" is an association.

Of course, in a small single-developer application, which mine is, it's
not really an issue. But if you're handing a 50,000-line codebase to
another developer, it's kind of unfortunate. So IMO it's a code
readability/maintainability turkey.

In C pointer assignment, you at least can make a good guess from all
the little *'s what's actually going on. :wink:

-Brian

I fully agree, it is non-intuitive and very confusing, I wonder what it would take to fix it, without breaking a bunch of other stuff?

Hi --