Skip to content

Instantly share code, notes, and snippets.

@FND
Created September 27, 2011 06:56
Show Gist options
  • Save FND/1244491 to your computer and use it in GitHub Desktop.
Save FND/1244491 to your computer and use it in GitHub Desktop.
test case for eager loading of nested associations with DataMapper
Gemfile.lock
#!/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
# 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)
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
#!/usr/bin/env sh
rm db.sqlite
# reformat SQL queries for readability
bundle exec ./dm_el.rb | perl -pe 's#SELECT .*? (FROM ".*?" )(.*)#\1SELECT ... \2#'
@emmanuel
Copy link

@d11wtq—looks like Relationship#eager_load does work; check out my fork of this gist: https://gist.github.com/1297105

@dkubb
Copy link

dkubb commented Jul 24, 2012

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