vince
vince

Reputation: 2878

how to check if has_one exists without loading associated model

I have a simple has_one relationship

class User < ApplicationRecord
  has_one :detail
  has_many :courses
end

class Detail < ApplicationRecord
  belongs_to :user
end

I need to check to see if a user has a detail. Since I can do User.first.courses.exists? it was surprising that User.first.detail.exists? doesn't work. I could do !User.first.detail.nil? but I only need to check if a user detail exists and not load it's very hefty detail model.

What's a good way to check if a has_one association exists without retrieving the entire associated model?

Upvotes: 3

Views: 3387

Answers (4)

Samuel Vega
Samuel Vega

Reputation: 181

Rails way it to load the object and check if it's present. But still, you can access the association scope to use it without loading the associated object:

user = User.find(1)

user.association(:detail).loaded? # => false

# Triggers the SQL you need: just check if a Detail record associated with that user exists.
user.association(:detail).scope.exists?

user.association(:detail).loaded? # => false (we still didn't load the associated object)

user.detail.id # Does DB request to get Detail

user.association(:detail).loaded? # => true

Upvotes: 0

James
James

Reputation: 679

Detail.where(user_id: User.first.id).exists?

This doesn't allocate a Detail object if all you want to know is if a user has a detail or not.

Upvotes: 3

Jay El-Kaake
Jay El-Kaake

Reputation: 647

To check if an association is loaded without hitting the DB you can use:

model.association(:association_name).loaded? for has_one/belongs_to associations and model.many_things.loaded? for has_many associations.

Checking if has_one/belongs_to association has been loaded:

user = User.find(1)

user.association(:detail).loaded? # => false

user.detail.id # Does DB request to get Detail

user.association(:detail).loaded? # => true
With Eager loading

Eager loading lets you load the data in advance, so as expected the association is marked as loaded:

user = User.eager_load(:detail).find(1)
user.detail.loaded? # => true

Checking if has_many association has been loaded

With has_many associations,

user = User.find(1)
user.association(:courses).loaded? # => false

user.courses.load # Does DB query to get all users' courses
user.association(:courses).loaded? # => true
Doing .first or some other queries won't always load

Keep in mind that it does not mark as loaded in all cases:

user.courses.first.id # Does DB query to get first course ID
user.association(:courses).loaded? # => false (still!)

This is because it is actually doing a SELECT * LIMIT 1 query when you do the .first for efficiency-sake.

With Eager loading
user = User.eager_load(:courses).find(1)
user.association(:courses).loaded? # => true

When to use the loaded? method?

The only use case I can think of is when you don't want to trigger a DB call but you want to check to see if the data has been loaded already and do something with it only if it has.

In my case I wanted to output some data for logging purposes, but only if it was not adding an extra DB query by calling the association.

Upvotes: 2

Huynh
Huynh

Reputation: 80

You can use: User.first.detail.present?

Upvotes: -1

Related Questions