0
votes

I have two ActiveRecord models like so:

class User < ActiveRecord::Base
  has_one :contact_information
end

class ContactInformation < ActiveRecord::Base
  belongs_to :user
end

This is my setup for a One-to-One relationship between the user and contact information table.

The issue I am encountering is when I create a new contact_information entry that points to a user that already has a existing contact_information, the new record is created including the relationship, meaning I have two contact_information records pointing to the same user, even though it is a one-to-one relationship.

Rails seems to pick the first record and returns it.

For example in irb:

> User.create
> ContactInformation.create(user: User.last)
> ContactInformation.create(user: User.last)
> ContactInformation.all
=> #<ActiveRecord::Relation [#<ContactInformation id: 3, created_at: "2016-01-04 22:28:11", updated_at: "2016-01-04 22:28:11", user_id: 2>, #<ContactInformation id: 4, created_at: "2016-01-04 22:28:18", updated_at: "2016-01-04 22:28:18", user_id: 2>]>

Both with user_id set to 2.

The ideal scenario would be a validation failure from contact_information when a record is created for a user that already has a contact_information.

I came up with this initial custom validation method for the Contact Information model that kind of works for creation (Need to make one for update)

validate :check_parent, on: :create

def check_parent
    if user.contact_information.id.nil?
        errors.add(:user, "already has contact information")
    end
end

I am wondering if there is a more rails-y way of solving this problem? Or will I need to create a custom validator like this for all has_one relationships?

Thanks

2
I think if you instead use the user.contact_info=ContactInfo.create() it will unlink the previously linked and replace it. Not sure if you want that, because now you have an orphaned ContactInfo. Instead of replacing it, or adding a new one, you could just edit the existing? Or in other words: what is the use-case for this question? E.g. in a form I would always show the has_one as extra fields for the owner. Or I would allow multiple contact-info's to exist anyway (why only one?). - nathanvda

2 Answers

1
votes

A few options are available to you.

You could use validates_uniqueness_of on your ContactInformation model:

class ContactInformation < ActiveRecord::Base
  validates_uniqueness_of :user_id
end

However, this will query the database to ensure that no duplicates exist on every save and could be very slow.

Another option is to have your database handle this for you. Depending on what you're using ActiveRecord with, adding a uniqueness constraint on the user_id column of your contact_informations table could be valuable.

I would suggest not using validates_uniqueness_of if your dataset is large, otherwise it might be perfect.

Make sure you at least add an index to the user_id column on that table:

class AddIndexOnContactInformationToUserId < ActiveRecord::Migration
  add_index :contact_information, :user_id
end
1
votes

You will need to have a validation but you don't need a custom one:

class ContactInformation < ActiveRecord::Base
  belongs_to :user
  validates_uniqueness_of :user_id
end

You may also want to add a unique index in the database for that column. It would not only make the query performant but will also protect your app against race conditions in case you are running multiple instances of it in parallel.