Hi --
Why is this so hard to understand? Rails isn't really doing anything magic
or really even tricky here -- just "unexpected".
You are just confusing references and objects.
No, that's not what's going on. ActiveRecord is doing something quite
tricky.
=> "original"
No surprise there. Let's change the contents of the page.
=> "forgery"
There's nothing analogous to this in the ActiveRecord example.
...and check in with our variable (aka, object reference):
=> "forgery"
Yup. Still refers to the same String object as o.page. Now let's do an
assignment:
=> "forgery"
o.page now refers to a new object. But o.page = "restoration" is really just
sugar for o.page=("restoration"), right?
That's where ActiveRecord, in the equivalent operation, does something
very different. Imagine that when you examined p, you got
"restoration". That would be the same as was AR is doing.
=> nil
Make p refer to the same String object as o.page again:
=> "restoration"
Now this "assignment" calls the newly defined page= method:
=> "faux"
So the way that variables are references to objects and the syntactic sugar
that makes 'o.page = foo' look like assignment even though it's sending foo
to the page= method of the o object are just colliding in your brain.
I'm not sure why you have to express this in terms of confusion on the
part of the people who don't like it. I think we're all well aware of
=-terminated methods and all the rest of it. So let's leave the
editorials about people's brains out of it.
Anyway.... No one is saying that you *can't* do this in Ruby, only
that what AR does is unexpected, unidiomatic, and inconsistent. The
= syntactic sugar does, indeed, allow you to write arbitrary methods:
class C
def thing=(n)
puts "Ha ha!"
end
end
but the reason the sugar exists is to make assignment-like things look
like assignments. The thing that AR does is not assignment-like, in
any traditional or idiomatic sense. Yes, you *can* do a
non-assignment-based operation (like String#replace) in a =-method...
but, at the risk of sounding old-fashioned, I simply think that here:
old_thing = obj.thing
obj.thing = Thing.create
it's reasonable to expect old_thing to refer to the original
obj.thing, and not the new one. In ActiveRecord, that's not what
happens.
It's not an object-vs.-reference thing, either. There's a class
called ActiveRecord::Associations::AssociationProxy which, I believe,
is responsible for the way this works. old_thing is a proxy to
obj.thing. If you assign it differently:
old_thing = Thing.find(obj.thing.id)
obj.thing = Thing.create
old_thing does not change, because the proxy class is not involved.
All of this is on top of the usual object/reference stuff in Ruby; in
fact, it's a kind of super-reference that's being created (which is
precisely *not* the model that Ruby is built on -- pointers to
pointers, so to speak -- though as I've said the issue is not whether
or not it can be done).
As I've said before, if there's a rationale for AR doing it the way it
does (other than the fact that it's possible to do it that way, which
I doubt is what lies behind it), I would of course be very interested
in hearing it.
David
--
David A. Black | (e-mail address removed)
Author of "Ruby for Rails" [1] | Ruby/Rails training & consultancy [3]
DABlog (DAB's Weblog) [2] | Co-director, Ruby Central, Inc. [4]
[1]
http://www.manning.com/black | [3]
http://www.rubypowerandlight.com
[2]
http://dablog.rubypal.com | [4]
http://www.rubycentral.org