Hi --

On Thu, 2 Nov 2006, Rob Biedenharn wrote:

> 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.

>>> require 'ostruct'
> => true
>>> o = OpenStruct.new('page' => "original", 'cover' => "hard")
> => #<OpenStruct cover="hard", page="original">
>>> p = o.page
> => "original"
>
> No surprise there.  Let's change the contents of the page.
>
>>> o.page.replace("forgery")
> => "forgery"

There's nothing analogous to this in the ActiveRecord example.

> ...and check in with our variable (aka, object reference):
>
>>> p
> => "forgery"
>
> Yup. Still refers to the same String object as o.page.  Now let's do an 
> assignment:
>
>>> o.page = "restoration"
> => "restoration"
>>> p
> => "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.

>>> def o.page=(kind); self.page.replace(kind); end
> => nil
>
> Make p refer to the same String object as o.page again:
>
>>> p = o.page
> => "restoration"
>>> p
> => "restoration"
>>> o.page
> => "restoration"
>
> Now this "assignment" calls the newly defined page= method:
>
>>> o.page = "imitation"
> => "imitation"
>>> p
> => "imitation"
>>> p.replace("faux")
> => "faux"
>>> o.page
> => "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 | dblack / wobblini.net
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