I'm using Rails 4.2. I have 3 tables like this:
class Collection < ActiveRecord::Base
has_many :shares
has_many :images, through: :shares
has_many :latest_images, -> { order(created_at: :desc).limit(10) }, class_name: 'Image', through: :shares, source: :image
end
class Share < ActiveRecord::Base
belongs_to :image
belongs_to :collection
end
class Image < ActiveRecord::Base
has_many :shares
has_many :collections, through: :shares
end
My goal is to select some collections and preload the first 10 newest cards of each collection using the latest_images
relation.
If I do simply:
collections = Collection.where(some_condition).includes(:latest_images)
The problem is latest_images will contain all cards, not only the last 10 (even if there's a limit(10)
)
collections.first.latest_images.count # => more than 10!!!
Instead, if I add the limit(10)
after loading the collections, I'll have a N+1 query problem:
collections.each { |collection| collection.latest_images.limit(10).do_something } # N+1 QUERY
Any solution?
You can avoid most n+1 queries in rails by simply eager loading associations. Eager loading allows you to load all of your associations (parent and children) once instead of n+1 times (which often happens with lazy loading, rails' default). As seen above, . includes allows nested association eager loading!
They essentially do the same thing, the only difference is what side of the relationship you are on. If a User has a Profile , then in the User class you'd have has_one :profile and in the Profile class you'd have belongs_to :user . To determine who "has" the other object, look at where the foreign key is.
The n+1 query problem is one of the most common scalability bottlenecks. It involves fetching a list of resources from a database that includes other associated resources within them. This means that we might have to query for the associated resources separately.
what is dependent :destroy. Dependent is an option of Rails collection association declaration to cascade the delete action. The :destroy is to cause the associated object to also be destroyed when its owner is destroyed.
There is a note tucked away in the associations documentation under "Eager loading of associations":
If you eager load an association with a specified :limit option, it will be ignored, returning all the associated objects.
So, it is behaving as documented even though that might not be the intuitive behaviour.
The work around is to not eager load the limited associated and to access separately afterwards. As you point out that's not ideal but it is almost certainly preferable to loading all of the associated objects without a limit.
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