jgaskins / perpetuity

Persistence gem for Ruby objects using the Data Mapper pattern
http://jgaskins.org/blog/2012/04/20/data-mapper-vs-active-record/
MIT License
250 stars 14 forks source link

Mapper#load_association! performance #12

Closed jgaskins closed 11 years ago

jgaskins commented 11 years ago

When retrieving an object from the DB that references another object in the DB, we have to send load_association! to the mapper to convert the reference object into its actual value. When retrieving individual 1:1-associated objects, this is fine, but the current implementation results in N+1 queries. We need a way to load entire sets of objects.

Use cases

A forum topic with many posts.

Currently, we load the topic, then send queries for each post object. We should be able to fire off something like a mapper.select { id.in? posts.map(&:id) } call. Something like this:

mapper.load_association! topic, :posts`

In this case, the posts object is an array, so we detect that and run with it.

List of users that each have a profile object.

Each user has a 1:1 association with a profile object.

mapper.load_association! users, :profile

In this case, the first parameter is the array. We could hold a hash-like object that uses the classes of the referenced objects as keys and the IDs as an array of values. This wouldn't necessarily be a single query, but would allow users to store objects that weren't all the same class. We would use the minimum number of queries necessary, which would always be the number of distinct classes of objects used for that particular attribute in that particular set of objects.

Example:

{ UserProfile => [1, 2, 3], AdminProfile => [4, 5, 6] } { Profile => [1, 2, 3, 4, 5, 6] }

In the first example, we must use multiple queries, but the second example would only be one. Here's some pseudocode:

references = users.map(&attribute)
references.each do |klass, ids|
  Perpetuity[klass].select { id.in? ids }
end

Articles containing lists of tags

The ActiveRecord has_and_belongs_to_many association.

mapper.load_association! articles, :tags

This would maybe need to be done using some sort of identity map? I'm not sure. I'll plug away at it soon.

jgaskins commented 11 years ago

Fixed by d5d5625bbf0113c0bccbc1f001adf17ef06dc760