1

I have read this, so I do understand the difference.

But I have inherited an app that is throwing strange behaviour (I think, perhaps I am wrong and this is normal).

There are 2 models:

class Pod < ActiveRecord::Base  
  has_one :pod_admin
end

class PodAdmin < ActiveRecord::Base  
  belongs_to :pod
end

In the rails console, I tried this:

p = Pod.find(5)

and it shows this Pod has a pod_admin_id value of 14. This is correct.

I tried to change the PodAdmin:

p.pod_admin = PodAdmin.last

and it throws this error:

NoMethodError: undefined method pod_admin_id for #<PodAdmin:0x007fa401f1e710>

Why is that? What am I missing?

EDIT

Based on comments/answers, without changing the models, I tried this:

pa = PodAdmin.last
pa.pod = p

and that works, I see the console return the last PodAdmin with a new pod_id.

BUT

pa.save

AND

p.save

both throw the same error as before.

If I look at the database schema, the Pod table has a pod_admin_id field and the PodAdmin table has a pod_id field.

I inherited this schema and I am just wondering if the original developer set this up correctly. Surely I should be able to update the relationship from either direction - isn't that the point of creating has_one and belongs_to, so you can have bi-directional relationships like this?

EDIT 2

I found the problem, which is that I had added this line to PodAdmin table instead of the Pod table:

  validates :pod_admin_id, uniqueness: {scope: :id, message: 'The Pod already has a PodAdmin'}

Apologies - but as you can see, what I am trying to achieve here is to prevent a Pod from having 2 PodAdmins. This validates does not seem to achieve that.

I can do this:

p = Pod.find(5)
pa_last = PodAdmin.last
pa_first = PodAdmin.first
pa_last = p
pa_first = p
pa_last.save
pa_first.save

and now both pa's have the same pod_id. How can I prevent that from happening?

EDIT 3

After much reading and testing and with thanks to both @Anand and @Spickerman the problem was that the previous developer put a foreign key into both tables (the has_one and the belongs_to). Only the belongs_to table should have a foreign key. Also, the relationship had been defined the wrong way round. However, fixing this does not guarantee a robust solution. I highly recommend others with similar issues read this.

Community
  • 1
  • 1
rmcsharry
  • 5,363
  • 6
  • 65
  • 108
  • 2
    You are doing it wrong. With your association set-up, `pod_admins` table should have `pod_id`. – Pavan May 13 '16 at 16:59
  • @Pavan is right, you either need a `pod_id` on the `pod_admins` table OR `Pod` should `belongs_to :pod_admin` while `PodAdmin` `has_one :pod` – Rob Di Marco May 13 '16 at 17:00
  • pod_admins table DOES have a pod_id field. I updated my question to explain - it seems I cannot update the relationship from either side. – rmcsharry May 14 '16 at 05:06

2 Answers2

1

The foreign key always belongs to the Model with the belongs_to association.

In your example PodAdmin belongs_to Pod, therefore your pod_admins table should have a pod_id column.

Or you can just change your models to the following to reflect your database schema:

class Pod < ActiveRecord::Base  
  belongs_to :pod_admin
end

class PodAdmin < ActiveRecord::Base  
  has_one :pod
end
rmcsharry
  • 5,363
  • 6
  • 65
  • 108
spickermann
  • 100,941
  • 9
  • 101
  • 131
  • The pod_admins table does have a pod_id column. I updated the question with more info. – rmcsharry May 14 '16 at 05:07
  • The foreign key must only exist in the table of the model that has the belongs_to. The other table must not have such a column (and there is also no need for that column). To prevent two PodAdmin having the same Pod, choose my second version and switch the direction if the belongs_to association. – spickermann May 14 '16 at 06:12
  • Thanks, I tried that, removing the column from the 'has_one' table. I am still able to assign more than one PodAdmin to a Pod. I will try your second version but pretty sure it will then be possible to assign more than one Pod to each PodAdmin, which should not be allowed since this is not a 1-n relationship. It seems I have to enforce this restriction at the database level. – rmcsharry May 14 '16 at 09:52
  • 1
    Sure, you will need a unique index on the database to ensure that the `pod_admin_id` is unique. You might want to check in Rails with `validates :pod_admin_id, uniqueness: true` (without a scope to the `id`!) first. But this doesn't help with all edge cases. – spickermann May 14 '16 at 11:10
1

Instead of p.pod_admin = PodAdmin.last, call PodAdmin.last.pod = p - as others have mentioned, the pod_id is in the PodAdmin table, and not the other way around.

Update:

Based on update to question, the problem is because you have foreign key references both ways - you should either have pod_id in pod_admins table or pod_admin_id in pods table, but not both. Remove one of them with a new migration, and try again

> bundle exec rails g migration RemovePodIdFromPodAdmins

# db/migrations/XXXX_remove_pod_admin_id_from_pods
def change
  remove_column :pods, :pod_admin_id
end

bundle exec rake db:migrate

Then, as suggested above, call

pa = PodAdmin.last
pa.pod = p
pa.save!
Anand
  • 3,690
  • 4
  • 33
  • 64
  • Since the belongs_to is in the PodAdmin model, shouldn't I keep that foreign key and rather remove the foreign_key from the has_one side (ie. remove PodAdminID from Pod table)? – rmcsharry May 14 '16 at 05:57
  • I removed pod_admin_id from the Pods table. I can still give 2 PodAdmins the same Pod. This is nuts. – rmcsharry May 14 '16 at 06:04
  • Yes, @rmcsharry - I am updating the response to indicate that. If you remove pod_admin_id from pods table, it will still allow two pod_admins to have same pod, because 'pod belongs_to pod_admin' means each instance can belong to some pod. But, because you have 'pod_admin has_one pod', pod.pod_admins will be undefined, whereas pod.pod_admin will be defined (though which one it will return is undefined). – Anand May 14 '16 at 06:23
  • 1
    Thanks @anand for that last comment and you pointing out that there were 2 foreign keys, as that was the fundamental mistake made by the previous developer that causes this problem in the first place. Hence I am marking yours as the answer. – rmcsharry May 17 '16 at 05:10