« Joel on Interns |  main  | Apple Bug Report Someday: Application Activation fails in certain occasions »

Rails Tidbits: Association Proxies

Posted by dom on Sun Dec 11 14:43:51 +0100 2005

I’m astounded how sure I can be about how things worked, and then be totally wrong. That was the case with Rails and association behaviour. After the latest upgrade, I thought something broke which in fact never worked the way I thought it works.

As I was sure about it and thought it was a newly introduced bug I quickly pested the rails folks with this Ticket 3178 bug-report. At least I provided my informations, but sorry for the big hassle. Now to some explanations why this snipped works this way:

>> comment = Comment.find :first
=> #<Comment:0x2735284 @attributes={"name"=>nil, "id"=>"1", "user_id"=>"1", "status_id"=>"1"}>
>> old_value = comment.status
=> #<Status:0x2730a40 @attributes={"name"=>"ok", "id"=>"1"}>
>> new_value = Status.find 2
=> #<Status:0x272a794 @attributes={"name"=>"not ok", "id"=>"2"}>
>> comment.status = new_value
=> #<Status:0x272a794 @attributes={"name"=>"not ok", "id"=>"2"}>
>> old_value
=> #<Status:0x272a794 @attributes={"name"=>"not ok", "id"=>"2"}>

As you see the old_value somehow magically sticks to the value of comment.status, not something you would expect. Especially because even the object ids are different. Why is this the case?

Acutally its rather simple, when you add belongs_to to your model, then accessor methods are created. These accessor methods have a little twist on them, that is the object actually returned is not a model object, but an AssociationProxy object (active_record/associations/association_proxy.rb)- this association proxy really acts as the targeted model object via mainly this piece of code:

alias_method :proxy_respond_to?, :respond_to?
def respond_to?(symbol, include_priv = false)
  proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
end

This code actually redirects all of the methodes to the target of the proxy. Pretty neat. So while looking like an actual model object what you have is a proxy object. Moreover, the setter method added by the belongs_to method doesn’t replace that proxy, but merely updates its target. So, one side effect is the behaviour I thought was an bug. Now that I know, I can easily cope with it. However, I think it would be more appropriate if the Accessor method actually would return the target of the association, instead of the Proxy. On the other hand by this behaviour the Proxy can delay the way to the database.

=> #<Comment:0x2749bd0 @attributes={"name"=>nil, "id"=>"1", "user_id"=>"1", "status_id"=>"1"}>
>> old_value = comment.status.target
=> #<Status:0x27470d8 @attributes={"name"=>"ok", "id"=>"1"}>
>> new_value = Status.find 2
=> #<Status:0x27449c8 @attributes={"name"=>"not ok", "id"=>"2"}>
>> comment.status = new_value
=> #<Status:0x27449c8 @attributes={"name"=>"not ok", "id"=>"2"}>
>> old_value
=> #<Status:0x27470d8 @attributes={"name"=>"ok", "id"=>"1"}>

So bottom line: be aware that model objects returned by association accessors aren’t the real deal, but proxies, which update with the model.

no responses to 'Rails Tidbits: Association Proxies'

write a comment.... (textile enabled)
author
email
url
Body