简体   繁体   中英

Rails - build_association is not working for a has_one and belongs_to relationship

I have two models

class Subscription < ActiveRecord::Base
  belongs_to :client
end

class Client < ActiveRecord::Base
  has_one :subscription
end

but when I try to create a parent from the child eg sub.build_client the foreign key does not get set eg

>> sub = Subscription.new
=> #<Subscription id: nil, token: nil, user_id: nil, created_at: nil, updated_at: nil, cancelled: nil, active: nil, client_id: nil>
>> sub.save(false);
?> client = sub.build_client
=> #<Client id: nil, server_id: nil, ip: nil, created_at: nil, updated_at: nil>
>> client.save(false)
=> true
>> sub.client_id
=> nil
>> sub
=> #<Subscription id: 4, token: nil, user_id: nil, created_at: "2010-01-11 06:07:45", updated_at: "2010-01-11 06:07:45", cancelled: nil, active: nil, client_id: nil>

It does work if I do client.build_subscription

?> client = Client.new
=> #<Client id: nil, server_id: nil, ip: nil, created_at: nil, updated_at: nil>
>> client.save(false)
=> true
>> sub = client.build_subscription
=> #<Subscription id: nil, token: nil, user_id: nil, created_at: nil, updated_at: nil, cancelled: nil, active: nil, client_id: 4>
>> sub.save(false)
=> true
>> sub
=> #<Subscription id: 5, token: nil, user_id: nil, created_at: "2010-01-11 06:09:32", updated_at: "2010-01-11 06:09:32", cancelled: nil, active: nil, client_id: 4>
>> client
=> #<Client id: 4, server_id: nil, ip: nil, created_at: "2010-01-11 06:09:02", updated_at: "2010-01-11 06:09:02">
>> ^C

Ive spent 3 hours fiddling and got nowhere fast. Can anyone explain what I'm doing wrong, things to check etc

According to your model associations, a Subscription is a child of a Client .

If you create a subscription first and then create a client as per your first example, Rails has no way of setting a client_id foreign key value within the subscriptions table because at that point you haven't created the client record, so there's nothing to associate with the subscription. That's why you have to create the parent record (ie a client) first and then associate it with a child subscription record using the build_subscription method.

it doesn't work both ways, a parent can build a child with the build_association method but not vice versa.

read up : http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_one

In my projects I have many similar associations, but I often forbid setting client_id to null, so I can't create child object without parent object.

Try:

sub = Subscription.new
sub.build_client
sub.save

It will create and save both objects.

This seems to have been fixed in Rails 4.2.5 or earlier. (I'm not sure what the earliest version with the fix is. I've only tested 4.2.5.)

When the parent is saved, after the parent record is inserted, an update is automatically executed to add the parent id to the child record.

However I haven't found any documentation about this behavior, the relevant code, or automated tests, so I'm not sure if it was intentionally fixed and this behavior should be relied on. It might be best to stick with the solutions in the other answers here.

This also seems to be still broken for has_many in Rails 4.2.5.

In one of the comments, you ask why the build_client method is there. When you save the client and then check the client_id on sub, it is nil, but if you would have also called

sub.save(validate: false)

and then checked the client_id on sub, it would exist. So although it required an extra save, it still does the magic of setting up the foreign key for sub.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM