I have a model User
, that has_one :child
, and the Child
model has_one :toy
.
If I have a single instance of the User class user
, how can I load both the child and toy in one query?
Here's what doesn't work:
user.child.toy # 2 queries
user.includes(child: :toy) # can't call includes on a single record
user.child.includes(:toy) # same as above
user.association(:child).scope.eager_load(:toy).first # makes the appropriate query with both child and toy... but doesn't set the object on the user model.
user.child = user.association(:child).scope.eager_load(:toy).first # makes the appropriate query, but also calls another query before setting the child :(
Is there any way to do this that doesn't involve re-querying the user model. ie. I want to avoid this
User.where(id: user.id).eager_load(child: :toy).first
Relavant model declarations:
class User < ActiveRecord::Base
has_one :child
has_one :toy, through: :child
end
class Child < ActiveRecord::Base
has_one :toy
belongs_to :user
end
class Toy < ActiveRecord::Base
belongs_to :child
end
This works, but isn't ideal. I don't think I should have to declare another relation solely for this reason.
class User < ActiveRecord::Base
has_one :child
has_one :toy, through: :child
has_one :child_with_toy, ->{ eager_loads(:toy) }, class_name: "Child", foreign_key: :parent_id
end
which allows me to call user.child_with_toy
to get the Child
object, and user.child_with_toy.toy
to get the Toy
object, while only triggering one SQL query.
It can be done easier with Preloader
:
ActiveRecord::Associations::Preloader.new.preload(user, child: [:toy])
If you want to eager load an association of a singular association for an already-instantiated record, you can do this:
user.association(:child).target = user.association(:child).scope.eager_load(:toy).first
This is similar to one of the approaches you listed at the top of your question. In particular, notice the .target
part.
ActiveRecord
isn't optimized for this particular scenario, so the code is pretty ugly. For that reason, I would strongly lean toward your :child_with_toy
approach if you really need to save the query.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With