I am building the data model for a small application, and would like to take advantage of eager loading in some methods - i.e. those where I know in advance that certain associations are going to get used.
I have read the Sequel::Model::Association guide to the .eager
method, but it has confused me a little. A typical example might be:
Artist.eager( :albums => :tracks ).all
which loads all the Artist objects with all their albums fields preloaded, and all the tracks pre-loaded, using just three queries. So far, so good.
But say I want to load a single Artist by its primary key, and still have the albums + tracks pre-loaded (still three queries, potentially a lot less than following the associations for each album)? I cannot see any example of that. A little experimentation gives me
Artist.eager( :albums => :tracks ).where( :id => id ).all.first
which seems to at least work. I confirmed the eager loading by calling this, then switching off the db, and showing I could still access the associations.
However, I feel like I have missed something. The construct, having to pass in the primary key to the where
clause, get a full dataset then ask for first item seems quite awkward. I am looking for something like this:
Artist.eager( :albums => :tracks )[ id ]
. . . a simple way of declaring I want to load a single object, and eager load some of its associations.
I have found that I can create a custom association like this:
def eager_albums
albums_dataset.eager( :tracks ).all
end
but that is awkward to use, because the code has to ask for the association in a different way.
My question: Does my construct Artist.eager( :albums => :tracks ).where( :id => id ).all.first
do what I think it does in Sequel, and could I do better (simpler code)?
Calling Artist.eager( :albums => :tracks ).where( :id => id ).all.first
fetches all artists with an id equal to 'id' and then extracts the first artist from the result array (calling all
materializes the array). Typically this query will only be returning an array with a single artist.
It is not until you actually ask for the relationship artist.albums
or associations beyond the album that additional queries are run.
You could just do Artist.eager( :albums => :tracks ).where( :id => id ).first
I use the tactical eager loading plugin so its pretty much unnecessary to use eager at all since it detects when an association is being used from an model instance which is part of a larger result set and does the eager load of related models for the entire result set automatically. Your query now becomes Artist[id]
, or Artist[id].albums.all
Another handy plugin I use is the association proxies plugin so I don't have to deal with using association.some_array_method
vs association_dataset.some_ds_method
And finally the dataset associations plugin allows you to describe a query starting from some model or model class and chain through associations to a destination model whilst allowing you to define constraints (or not) along the way.
For example "find all tracks longer than 3 minutes on any album released in the past 2 weeks by artists born in the UK":
Artist.where(country_of_birth: 'UK').albums.where{ release_date > Date.today - 14}.tracks.where{ duration > 3.minutes }.all
Hope this sheds some light.