Allister
Allister

Reputation: 851

Limiting has_one relationships in Rails

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

Upvotes: 0

Views: 180

Answers (2)

Jesuspc
Jesuspc

Reputation: 1734

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.

Upvotes: 1

yez
yez

Reputation: 2378

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

Upvotes: 1

Related Questions