3

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)?

Neil Slater
  • 26,512
  • 6
  • 76
  • 94

1 Answers1

8

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.

Andrew Hacking
  • 6,296
  • 31
  • 37
  • Useful information, thank you but I am not certain "It is not until you actually ask for the relationship artist.albums or associations beyond the album that additional queries are run." is true for my query - I have run my query *switched off the db server* and then iterated through the child objects in the relationships in Ruby successfully (admittedly that was around the time I asked the question, things may have changed, or my test may have had flaws). – Neil Slater Nov 11 '14 at 12:17
  • Out of the box eager() definitely does not execute related association queries until you ask for them. eager_graph() is a different beast and has the behaviour you describe. You may have had the association cached from a previous query or some other side-effect referencing the associations and triggering the load. Unless you call artist.albums(true) an association will be cached and won't reload, see: http://sequel.jeremyevans.net/rdoc/files/doc/association_basics_rdoc.html#label-Caching – Andrew Hacking Nov 11 '14 at 13:26