Reputation: 9771
Suppose we have the following Post
model in Rails:
class Post < ActiveRecord::Base
belongs_to :user
end
user
is not required, but when it is set, we want it to be a valid association (we don't want to have a post that has a user_id
that isn't in the database). Failing tests:
expect(Post.new).to be_valid
expect(Post.new(user_id: 0)).not_to be_valid
expect(Post.new(user: User.create)).to be_valid
One solution would be to do:
validates_presence_of :user, unless: "user_id.nil?"
This will do the required task but isn't really pretty as we don't want to write it for each belongs_to
we have in our app.
Is there a way to tell rails to check for the existence of the associated model? Isn't that something we want to have by default? (Why would we want an association_id
that doesn't point to anything?)
Upvotes: 1
Views: 552
Reputation: 482
To guarantee that an association is valid at the database level, MySQL and PostgreSQL have the ability to enforce the validity of foreign key columns on a table. Since Rails 4.2, these foreign key constraints can be created as part of a migration.
You could do this in at least two ways in your migration. Either pass the foreign_key
attribute to add_reference:
add_reference :posts, :user, index: true, foreign_key: true
Or follow add_reference
with add_foreign_key, which also gives you the option of resetting the key when the user is deleted:
add_reference :posts, :user, index: true
add_foreign_key :posts, :user, on_delete: :nullify
Combine the two approaches by passing a hash to the foreign_key
attribute of add_reference
(since Rails 4.2.1):
add_reference :posts, :user, index: true, foreign_key: {on_delete: :nullify}
If a query breaks the foreign key constraint by updating a user_id
to point to a non-existent user, the query will fail and raise an exception in Rails.
As you've mentioned, you could also create a validation to check that a User
exists before creating a Post
. However, this won't be as strong of a guarantee as a foreign key constraint on the database, since a User
could be deleted in the time between the validation passing and the Post
being created.
To help keep references valid at the application level, it would generally be better practice to avoid passing a user id directly to the Post
constructor. Instead, try to always use a User
object as input, for example by calling User.find(:id)
.
Upvotes: 1