Created
September 27, 2011 06:56
-
-
Save FND/1244491 to your computer and use it in GitHub Desktop.
test case for eager loading of nested associations with DataMapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Gemfile.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# encoding: UTF-8 | |
# test case for eager loading of nested associations with DataMapper | |
require 'rubygems' | |
require 'dm-core' | |
require 'dm-constraints' | |
require 'dm-migrations' | |
require 'eager_loading' | |
DataMapper::Logger.new($stdout, :debug) | |
DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/db.sqlite") | |
class Person | |
include DataMapper::Resource | |
property :id, Serial | |
property :name, String, :required => true | |
has n, :vehicles | |
end | |
class Vehicle | |
include DataMapper::Resource | |
property :id, Serial | |
property :name, String, :required => true | |
has n, :components | |
end | |
class Component | |
include DataMapper::Resource | |
property :id, Serial | |
property :name, String, :required => true | |
belongs_to :manufacturer | |
end | |
class Manufacturer | |
include DataMapper::Resource | |
property :id, Serial | |
property :name, String, :required => true | |
end | |
DataMapper.auto_migrate! | |
# generate test data | |
Person.create(:name => "FND", :vehicles => [ | |
Vehicle.create(:name => "Taurus", :components => [ | |
Component.create(:name => "engine", | |
:manufacturer => Manufacturer.create(:name => "Ford")), | |
Component.create(:name => "radio", | |
:manufacturer => Manufacturer.create(:name => "Bose")) | |
]), | |
Vehicle.create(:name => "fixie", :components => [ | |
Component.create(:name => "frame", | |
:manufacturer => Manufacturer.create(:name => "Campagnolo")), | |
Component.create(:name => "breaks", | |
:manufacturer => Manufacturer.create(:name => "Shimano")) | |
]) | |
]) | |
Person.create(:name => "tillsc", :vehicles => [ | |
Vehicle.create(:name => "Golf", :components => [ | |
Component.create(:name => "engine", | |
:manufacturer => Manufacturer.create(:name => "VW")) | |
]) | |
]) | |
# retrieve data | |
puts "", "[INFO] test case A" | |
person = Person.get!(1) | |
puts person.vehicles.components.manufacturer.map(&:name).join(", ") | |
puts "", "[INFO] test case B" | |
people = Person.all | |
people.each do |person| | |
person.vehicles.each do |vehicle| | |
puts sprintf("%-10s %-10s", person.name, vehicle.name) | |
end | |
end | |
puts "", "[INFO] test case C ===== /!\ n+1 hazard ====" | |
people = Person.all | |
people.each do |person| | |
person.vehicles.each do |vehicle| | |
vehicle.components.each do |component| | |
puts sprintf("%-10s %-10s %-10s", person.name, vehicle.name, component.name) | |
end | |
end | |
end | |
puts "", "[INFO] test case D" | |
people = Person.all | |
people.eager_load(Person.vehicles.components).each do |person| | |
person.vehicles.each do |vehicle| | |
vehicle.components.each do |component| | |
puts sprintf("%-10s %-10s %-10s", person.name, vehicle.name, component.name) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# manual eager loading for DataMapper | |
# adapted from Chris Corbyn: https://gist.github.com/1244491#gistcomment-56797 | |
module EagerLoading | |
def eager_load(query_path) | |
scope = self | |
query_path.relationships.each do |relation| | |
source_key = relation.source_key.first # TODO: rename | |
target_key = relation.target_key.first # TODO: rename | |
# for each level in the query path, collect all the resources referencing | |
# keys at the current scope | |
next_scope = relation.target_model.all(target_key.name => scope. | |
collect(&:"#{source_key.name}")) | |
# map target keys to the resources that exist for them | |
links = next_scope.inject({}) do |map, resource| | |
map.merge(target_key.get(resource) => [resource]) { |k, v1, v2| v1 + v2 } | |
end | |
# now pre-load those from the map | |
scope.each do |parent| | |
if links.key?(source_key.get(parent)) | |
parent.instance_variable_set(:"@#{relation.name}", | |
links[source_key.get(parent)]) | |
end | |
end | |
# and step into the next nesting level | |
scope = next_scope | |
end | |
self | |
end | |
end | |
DataMapper::Collection.send(:include, EagerLoading) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
source :rubygems | |
DM_VERSION = '~> 1.2.0.rc2' | |
gem 'dm-core', DM_VERSION | |
gem 'dm-constraints', DM_VERSION | |
gem 'dm-migrations', DM_VERSION | |
gem 'dm-sqlite-adapter', DM_VERSION |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env sh | |
rm db.sqlite | |
# reformat SQL queries for readability | |
bundle exec ./dm_el.rb | perl -pe 's#SELECT .*? (FROM ".*?" )(.*)#\1SELECT ... \2#' |
@d11wtq—looks like Relationship#eager_load
does work; check out my fork of this gist: https://gist.github.com/1297105
Hey guys, I know this is an old thread, but check this out: https://gist.github.com/3100034
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hmm, I was just looking through some of
Relationship
, and I noticed#eager_load
, which looks like exactly the API for this situation. So if I'm reading it correctly, you could simplify the whole thing to:(BTW, I've vacillated, and I now prefer passing the
Query::Path
to#load
instead of#initialize
(reverting to how you had it before). It seems more coherent, even though EagerLoader is probably still a single-use object.)It's too late here for me to start hacking on this right now, but I'm going to try to make time to poke at it tomorrow.