Rails3 activerecord update_attributes can't save foreign_key
In rails 3.0.9/ruby 1.9.2 I'm getting unexpected behaviour when i try to update model foreign keys.
I start with a fresh install
$ rails new mytest
$ rails g model User
$ rails g model Ad user_id:integer
$ rake db_migrate
Add association in app/models/Ad.rb
Class User < ActiveRecord::Base
belongs_to :user
end
Now to the fun part. I want to change the foreign key on an object directly. I enter rails console with rails c
$ u = User.create
$ a=Ad.create(:user=>u)
$ a.update_attributes(:user_id => 9999)
$ a.user_id
=> 4
开发者_StackOverflow
So that doesn't work. I try to set the object directly instead of passing it to create:
$ u = User.create
$ a=Ad.create
$ a.user = u
$ a.save
$ a.update_attributes(:user_id => 9999)
$ a.user_id
=> 5
Doesn't work.
The only thing that works is this:
$ u = User.create
$ a=Ad.create
$ a.user_id = u.id
$ a.save
$ a.update_attributes(:user_id => 9999)
$ a.user_id
=> 9999
Can someone please explain what is going on, and how can I change the foreign keys of my objects? I assume the "blocking" behaviour happens when the objects associations are instantiated and there is a conflict, so one answer is to do this:
$ Ad.find( a.id ).update_attributes( :user_id => xxxx )
That works. But it seems like a long way to go just for changing a foreign key on an object. It also takes an extra DB hit, and lastly it is quite messy code if an object internally needs to update itself. What to do?
It looks like the issue you're experiencing is because you're trying to change the user_id attribute directly. Because you've added belongs_to :user
to the Ad model, Rails has a number of additional methods to help deal with the association. Here's the page from the rails guides on the belongs_to
methods: http://edgeguides.rubyonrails.org/association_basics.html#belongs_to-association-reference
In this case you can use something like this:
u1 = User.create
u2 = User.create
a=Ad.create(:user=>u)
a.user_id
will equal u1.id
To update the association, you can do this:
a.user = u2
a.save!
and now a.user_id
should equal u2.id
Rails is doing this because it assumes that the association is more than just a foreign key as the User
object is more important than the backend foreign key.
Well, I think the proper explanation requires me to check the source code of Rails and check what it is happening with update_attributes
when you assign an object to the relationship. I will check that later.
But I guess the correct thing to do is not just update the attribute, but the whole user object. Something like:
u = User.create; a = Ad.create; a.user = u; a.save; a.user = User.find(9999); a.save
Or just manipulate the ids of your object like you noticed it worked:
u = User.create; a = Ad.create; a.user_id = u.id; a.save; a.user_id = 9999; a.save
Just unload the loaded object before doing the update
a = Ad.create( :user=>User.create )
a.user.reset # Unload association object, the foreign key is untouched
a.update_attributes( :user_id=>9999 )
a.user_id
=> 9999
You can make Factory girl create objects with unloaded associations like this
def Factory.lazy( model )
obj = Factory( model )
obj.attributes.each{ |k,v|
if assoc = k[/(.*)_id$/,1]
eager = obj.send( assoc )
eager.send("reset") unless eager.nil?
end
obj
}
精彩评论