require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper')) if HAS_SQLITE3 describe DataMapper::Associations do before :all do db1 = File.expand_path(File.join(File.dirname(__FILE__), "custom_db1_sqlite3.db")) db2 = File.expand_path(File.join(File.dirname(__FILE__), "custom_db2_sqlite3.db")) FileUtils.touch(db1) FileUtils.touch(db2) DataMapper.setup(:custom_db1, "sqlite3://#{db1}") DataMapper.setup(:custom_db2, "sqlite3://#{db2}") class CustomParent include DataMapper::Resource def self.default_repository_name :custom_db1 end property :id, Serial property :name, String repository(:custom_db2) do has n, :custom_childs end end class CustomChild include DataMapper::Resource def self.default_repository_name :custom_db2 end property :id, Serial property :name, String repository(:custom_db1) do belongs_to :custom_parent end end end before :each do [ CustomChild, CustomParent ].each { |m| m.auto_migrate! } parent = CustomParent.create(:name => "mother") child1 = parent.custom_childs.create(:name => "son") child2 = parent.custom_childs.create(:name => "daughter") @parent = CustomParent.first(:name => "mother") @child1 = CustomChild.first(:name => "son") @child2 = CustomChild.first(:name => "daughter") end it "should be able to handle has_many relationships to other repositories" do @parent.custom_childs.size.should == 2 @parent.custom_childs.include?(@child1).should == true @parent.custom_childs.include?(@child2).should == true @parent.custom_childs.delete(@child1) @parent.custom_childs.save @parent.reload @parent.custom_childs.size.should == 1 @parent.custom_childs.include?(@child2).should == true end it "should be able to handle belongs_to relationships to other repositories" do @child1.custom_parent.should == @parent @child2.custom_parent.should == @parent @child1.custom_parent = nil @child1.save @child1.reload @child1.custom_parent.should == nil @parent.reload @parent.custom_childs.size.should == 1 @parent.custom_childs.include?(@child2).should == true end end end if ADAPTER repository(ADAPTER) do class Machine include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String has n, :areas has n, :fussy_areas, :class_name => 'Area', :rating.gte => 3, :type => 'particular' end class Area include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String property :rating, Integer property :type, String belongs_to :machine end class Pie include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :sky end class Sky include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String has 1, :pie end class Ultrahost include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String has n, :ultraslices, :order => [:id.desc] end class Ultraslice include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :ultrahost end class Node include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String has n, :children, :class_name => 'Node', :child_key => [ :parent_id ] belongs_to :parent, :class_name => 'Node', :child_key => [ :parent_id ] end class MadeUpThing include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :area belongs_to :machine end module Models class Project include DataMapper::Resource def self.default_repository_name ADAPTER end property :title, String, :length => 255, :key => true property :summary, DataMapper::Types::Text has n, :tasks has 1, :goal end class Goal include DataMapper::Resource def self.default_repository_name ADAPTER end property :title, String, :length => 255, :key => true property :summary, DataMapper::Types::Text belongs_to :project end class Task include DataMapper::Resource def self.default_repository_name ADAPTER end property :title, String, :length => 255, :key => true property :description, DataMapper::Types::Text belongs_to :project end end class Galaxy include DataMapper::Resource def self.default_repository_name ADAPTER end property :name, String, :key => true, :length => 255 property :size, Float, :key => true, :precision => 15, :scale => 6 end class Star include DataMapper::Resource def self.default_repository_name ADAPTER end belongs_to :galaxy end end describe DataMapper::Associations do describe 'namespaced associations' do before do Models::Project.auto_migrate!(ADAPTER) Models::Task.auto_migrate!(ADAPTER) Models::Goal.auto_migrate!(ADAPTER) end it 'should allow namespaced classes in parent and child for many <=> one' do m = Models::Project.new(:title => 'p1', :summary => 'sum1') m.tasks << Models::Task.new(:title => 't1', :description => 'desc 1') m.save t = Models::Task.first(:title => 't1') t.project.should_not be_nil t.project.title.should == 'p1' t.project.tasks.size.should == 1 p = Models::Project.first(:title => 'p1') p.tasks.size.should == 1 p.tasks[0].title.should == 't1' end it 'should allow namespaced classes in parent and child for one <=> one' do g = Models::Goal.new(:title => "g2", :summary => "desc 2") p = Models::Project.create(:title => "p2", :summary => "sum 2", :goal => g) pp = Models::Project.first(:title => 'p2') pp.goal.title.should == "g2" g = Models::Goal.first(:title => "g2") g.project.should_not be_nil g.project.title.should == 'p2' g.project.goal.should_not be_nil end end describe 'many to one associations' do before do Machine.auto_migrate!(ADAPTER) Area.auto_migrate!(ADAPTER) MadeUpThing.auto_migrate!(ADAPTER) machine1 = Machine.create(:name => 'machine1') machine2 = Machine.create(:name => 'machine2') area1 = Area.create(:name => 'area1', :machine => machine1) area2 = Area.create(:name => 'area2') end it '#belongs_to' do area = Area.new area.should respond_to(:machine) area.should respond_to(:machine=) end it 'should load without the parent' it 'should allow substituting the parent' do area1 = Area.first(:name => 'area1') machine2 = Machine.first(:name => 'machine2') area1.machine = machine2 area1.save Area.first(:name => 'area1').machine.should == machine2 end it 'should save both the object and parent if both are new' do area1 = Area.new(:name => 'area1') area1.machine = Machine.new(:name => 'machine1') area1.save area1.machine_id.should == area1.machine.id end it '#belongs_to with namespaced models' do repository(ADAPTER) do module FlightlessBirds class Ostrich include DataMapper::Resource property :id, Serial property :name, String belongs_to :sky # there's something sad about this :'( end end FlightlessBirds::Ostrich.properties.slice(:sky_id).should_not be_empty end end it 'should load the associated instance' do machine1 = Machine.first(:name => 'machine1') Area.first(:name => 'area1').machine.should == machine1 end it 'should save the association key in the child' do machine2 = Machine.first(:name => 'machine2') Area.create(:name => 'area3', :machine => machine2) Area.first(:name => 'area3').machine.should == machine2 end it 'should set the association key immediately' do machine = Machine.first(:name => 'machine1') Area.new(:machine => machine).machine_id.should == machine.id end it "should be able to set an association obtained from another association" do machine1 = Machine.first(:name => 'machine1') area1 = Area.first(:name => 'area1') area1.machine = machine1 m = MadeUpThing.create(:machine => area1.machine, :name => "Weird") m.machine_id.should == machine1.id end it 'should save the parent upon saving of child' do e = Machine.new(:name => 'machine10') y = Area.create(:name => 'area10', :machine => e) y.machine.name.should == 'machine10' Machine.first(:name => 'machine10').should_not be_nil end it 'should set and retrieve associations on not yet saved objects' do e = Machine.create(:name => 'machine10') y = e.areas.build(:name => 'area10') y.machine.name.should == 'machine10' end it 'should convert NULL parent ids into nils' do Area.first(:name => 'area2').machine.should be_nil end it 'should save nil parents as NULL ids' do y1 = Area.create(:id => 20, :name => 'area20') y2 = Area.create(:id => 30, :name => 'area30', :machine => nil) y1.id.should == 20 y1.machine.should be_nil y2.id.should == 30 y2.machine.should be_nil end it 'should respect length on foreign keys' do property = Star.relationships[:galaxy].child_key[:galaxy_name] property.length.should == 255 end it 'should respect precision and scale on foreign keys' do property = Star.relationships[:galaxy].child_key[:galaxy_size] property.precision.should == 15 property.scale.should == 6 end it 'should be reloaded when calling Resource#reload' do e = Machine.new(:name => 'machine40') y = Area.create(:name => 'area40', :machine => e) y.send(:machine_association).should_receive(:reload).once lambda { y.reload }.should_not raise_error end it "should have machine when created using machine_id" do m = Machine.create(:name => 'machineX') a = Area.new(:machine_id => m.id) a.machine.should == m end it "should not have a machine when orphaned" do a = Area.new(:machine_id => 42) a.machine.should be_nil end end describe 'one to one associations' do before do Sky.auto_migrate!(ADAPTER) Pie.auto_migrate!(ADAPTER) pie1 = Pie.create(:name => 'pie1') pie2 = Pie.create(:name => 'pie2') sky1 = Sky.create(:name => 'sky1', :pie => pie1) end it '#has 1' do s = Sky.new s.should respond_to(:pie) s.should respond_to(:pie=) end it 'should allow substituting the child' do sky1 = Sky.first(:name => 'sky1') pie1 = Pie.first(:name => 'pie1') pie2 = Pie.first(:name => 'pie2') sky1.pie.should == pie1 pie2.sky.should be_nil sky1.pie = pie2 sky1.save pie2.sky.should == sky1 pie1.reload.sky.should be_nil end it 'should load the associated instance' do sky1 = Sky.first(:name => 'sky1') pie1 = Pie.first(:name => 'pie1') sky1.pie.should == pie1 end it 'should save the association key in the child' do pie2 = Pie.first(:name => 'pie2') sky2 = Sky.create(:id => 2, :name => 'sky2', :pie => pie2) pie2.sky.should == sky2 end it 'should save the children upon saving of parent' do p = Pie.new(:id => 10, :name => 'pie10') s = Sky.create(:id => 10, :name => 'sky10', :pie => p) p.sky.should == s Pie.first(:name => 'pie10').should_not be_nil end it 'should save nil parents as NULL ids' do p1 = Pie.create(:id => 20, :name => 'pie20') p2 = Pie.create(:id => 30, :name => 'pie30', :sky => nil) p1.id.should == 20 p1.sky.should be_nil p2.id.should == 30 p2.sky.should be_nil end it 'should be reloaded when calling Resource#reload' do pie = Pie.first(:name => 'pie1') pie.send(:sky_association).should_receive(:reload).once lambda { pie.reload }.should_not raise_error end end describe 'one to many associations' do before do Ultrahost.auto_migrate!(ADAPTER) Ultraslice.auto_migrate!(ADAPTER) Machine.auto_migrate!(ADAPTER) Area.auto_migrate!(ADAPTER) ultrahost1 = Ultrahost.create(:name => 'ultrahost1') ultrahost2 = Ultrahost.create(:name => 'ultrahost2') ultraslice1 = Ultraslice.create(:name => 'ultraslice1', :ultrahost => ultrahost1) ultraslice2 = Ultraslice.create(:name => 'ultraslice2', :ultrahost => ultrahost1) ultraslice3 = Ultraslice.create(:name => 'ultraslice3') end it '#has n' do h = Ultrahost.new h.should respond_to(:ultraslices) end it 'should allow removal of a child through a loaded association' do ultrahost1 = Ultrahost.first(:name => 'ultrahost1') ultraslice2 = ultrahost1.ultraslices.first ultrahost1.ultraslices.size.should == 2 ultrahost1.ultraslices.delete(ultraslice2) ultrahost1.ultraslices.size.should == 1 ultraslice2 = Ultraslice.first(:name => 'ultraslice2') ultraslice2.ultrahost.should_not be_nil ultrahost1.save ultraslice2.reload.ultrahost.should be_nil end it 'should use the IdentityMap correctly' do repository(ADAPTER) do ultrahost1 = Ultrahost.first(:name => 'ultrahost1') ultraslice = ultrahost1.ultraslices.first ultraslice2 = ultrahost1.ultraslices(:order => [:id]).last # should be the same as 1 ultraslice3 = Ultraslice.get(2) # should be the same as 1 ultraslice.object_id.should == ultraslice2.object_id ultraslice.object_id.should == ultraslice3.object_id end end it '#<< should add exactly the parameters' do machine = Machine.new(:name => 'my machine') 4.times do |i| machine.areas << Area.new(:name => "area nr #{i}") end machine.save machine.areas.size.should == 4 4.times do |i| machine.areas.any? do |area| area.name == "area nr #{i}" end.should == true end machine = Machine.get!(machine.id) machine.areas.size.should == 4 4.times do |i| machine.areas.any? do |area| area.name == "area nr #{i}" end.should == true end end it "#<< should add the correct number of elements if they are created" do machine = Machine.create(:name => 'my machine') 4.times do |i| machine.areas << Area.create(:name => "area nr #{i}", :machine => machine) end machine.areas.size.should == 4 end it '#<< should add default values for relationships that have conditions' do # it should add default values machine = Machine.new(:name => 'my machine') machine.fussy_areas << Area.new(:name => 'area 1', :rating => 4 ) machine.save Area.first(:name => 'area 1').type.should == 'particular' # it should not add default values if the condition's property already has a value machine.fussy_areas << Area.new(:name => 'area 2', :rating => 4, :type => 'not particular') machine.save Area.first(:name => 'area 2').type.should == 'not particular' # it should ignore non :eql conditions machine.fussy_areas << Area.new(:name => 'area 3') machine.save Area.first(:name => 'area 3').rating.should == nil end it 'should load the associated instances, in the correct order' do ultrahost1 = Ultrahost.first(:name => 'ultrahost1') ultrahost1.ultraslices.should_not be_nil ultrahost1.ultraslices.size.should == 2 ultrahost1.ultraslices.first.name.should == 'ultraslice2' # ordered by [:id.desc] ultrahost1.ultraslices.last.name.should == 'ultraslice1' ultraslice3 = Ultraslice.first(:name => 'ultraslice3') ultraslice3.ultrahost.should be_nil end it 'should add and save the associated instance' do ultrahost1 = Ultrahost.first(:name => 'ultrahost1') ultrahost1.ultraslices << Ultraslice.new(:id => 4, :name => 'ultraslice4') ultrahost1.save Ultraslice.first(:name => 'ultraslice4').ultrahost.should == ultrahost1 end it 'should not save the associated instance if the parent is not saved' do h = Ultrahost.new(:id => 10, :name => 'ultrahost10') h.ultraslices << Ultraslice.new(:id => 10, :name => 'ultraslice10') Ultraslice.first(:name => 'ultraslice10').should be_nil end it 'should save the associated instance upon saving of parent' do h = Ultrahost.new(:id => 10, :name => 'ultrahost10') h.ultraslices << Ultraslice.new(:id => 10, :name => 'ultraslice10') h.save s = Ultraslice.first(:name => 'ultraslice10') s.should_not be_nil s.ultrahost.should == h end it 'should save the associated instances upon saving of parent when mass-assigned' do h = Ultrahost.create(:id => 10, :name => 'ultrahost10', :ultraslices => [ Ultraslice.new(:id => 10, :name => 'ultraslice10') ]) s = Ultraslice.first(:name => 'ultraslice10') s.should_not be_nil s.ultrahost.should == h end it 'should have finder-functionality' do h = Ultrahost.first(:name => 'ultrahost1') h.ultraslices.should have(2).entries s = h.ultraslices.all(:name => 'ultraslice2') s.should have(1).entries s.first.id.should == 2 h.ultraslices.first(:name => 'ultraslice2').should == s.first end it 'should be reloaded when calling Resource#reload' do ultrahost = Ultrahost.first(:name => 'ultrahost1') ultrahost.send(:ultraslices_association).should_receive(:reload).once lambda { ultrahost.reload }.should_not raise_error end end describe 'many-to-one and one-to-many associations combined' do before do Node.auto_migrate!(ADAPTER) Node.create(:name => 'r1') Node.create(:name => 'r2') Node.create(:name => 'r1c1', :parent_id => 1) Node.create(:name => 'r1c2', :parent_id => 1) Node.create(:name => 'r1c3', :parent_id => 1) Node.create(:name => 'r1c1c1', :parent_id => 3) end it 'should properly set #parent' do r1 = Node.get 1 r1.parent.should be_nil n3 = Node.get 3 n3.parent.should == r1 n6 = Node.get 6 n6.parent.should == n3 end it 'should properly set #children' do r1 = Node.get(1) off = r1.children off.size.should == 3 off.include?(Node.get(3)).should be_true off.include?(Node.get(4)).should be_true off.include?(Node.get(5)).should be_true end it 'should allow to create root nodes' do r = Node.create(:name => 'newroot') r.parent.should be_nil r.children.size.should == 0 end it 'should properly delete nodes' do r1 = Node.get 1 r1.children.size.should == 3 r1.children.delete(Node.get(4)) r1.save Node.get(4).parent.should be_nil r1.children.size.should == 2 end end describe 'through-associations' do before :all do repository(ADAPTER) do module Sweets class Shop include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String has n, :cakes # has n has n, :recipes, :through => :cakes # has n => has 1 has n, :ingredients, :through => :cakes # has n => has 1 => has n has n, :creators, :through => :cakes # has n => has 1 => has 1 has n, :ultraslices, :through => :cakes # has n => has n has n, :bites, :through => :cakes # has n => has n => has n has n, :shapes, :through => :cakes # has n => has n => has 1 has n, :customers, :through => :cakes # has n => belongs_to (pending) has 1, :shop_owner # has 1 has 1, :wife, :through => :shop_owner # has 1 => has 1 has 1, :ring, :through => :shop_owner # has 1 => has 1 => has 1 has n, :coats, :through => :shop_owner # has 1 => has 1 => has n has n, :children, :through => :shop_owner # has 1 => has n has n, :toys, :through => :shop_owner # has 1 => has n => has n has n, :boogers, :through => :shop_owner # has 1 => has n => has 1 end class ShopOwner include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :shop has 1, :wife has n, :children has n, :toys, :through => :children has n, :boogers, :through => :children has n, :coats, :through => :wife has 1, :ring, :through => :wife has n, :schools, :through => :children end class Wife include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :shop_owner has 1, :ring has n, :coats end class Coat include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :wife end class Ring include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :wife end class Child include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :shop_owner has n, :toys has 1, :booger end class Booger include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :child end class Toy include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :child end class Cake include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :shop belongs_to :customer has n, :ultraslices has n, :bites, :through => :ultraslices has 1, :recipe has n, :ingredients, :through => :recipe has 1, :creator, :through => :recipe has n, :shapes, :through => :ultraslices end class Recipe include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :cake has n, :ingredients has 1, :creator end class Customer include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String has n, :cakes end class Creator include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :recipe end class Ingredient include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :recipe end class Ultraslice include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :size, Integer belongs_to :cake has n, :bites has 1, :shape end class Shape include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :ultraslice end class Bite include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String belongs_to :ultraslice end DataMapper::Resource.descendants.each do |descendant| descendant.auto_migrate!(ADAPTER) if descendant.name =~ /^Sweets::/ end betsys = Shop.new(:name => "Betsy's") betsys.save # # has n # german_chocolate = Cake.new(:name => 'German Chocolate') betsys.cakes << german_chocolate german_chocolate.save short_cake = Cake.new(:name => 'Short Cake') betsys.cakes << short_cake short_cake.save # has n => belongs_to old_customer = Customer.new(:name => 'John Johnsen') old_customer.cakes << german_chocolate old_customer.cakes << short_cake german_chocolate.save short_cake.save old_customer.save # has n => has 1 schwarzwald = Recipe.new(:name => 'Schwarzwald Cake') schwarzwald.save german_chocolate.recipe = schwarzwald german_chocolate.save shortys_special = Recipe.new(:name => "Shorty's Special") shortys_special.save short_cake.recipe = shortys_special short_cake.save # has n => has 1 => has 1 runar = Creator.new(:name => 'Runar') schwarzwald.creator = runar runar.save berit = Creator.new(:name => 'Berit') shortys_special.creator = berit berit.save # has n => has 1 => has n 4.times do |i| schwarzwald.ingredients << Ingredient.new(:name => "Secret ingredient nr #{i}") end 6.times do |i| shortys_special.ingredients << Ingredient.new(:name => "Well known ingredient nr #{i}") end # has n => has n 10.times do |i| german_chocolate.ultraslices << Ultraslice.new(:size => i) end 5.times do |i| short_cake.ultraslices << Ultraslice.new(:size => i) end german_chocolate.ultraslices.size.should == 10 # has n => has n => has 1 german_chocolate.ultraslices.each do |ultraslice| shape = Shape.new(:name => 'square') ultraslice.shape = shape shape.save end short_cake.ultraslices.each do |ultraslice| shape = Shape.new(:name => 'round') ultraslice.shape = shape shape.save end # has n => has n => has n german_chocolate.ultraslices.each do |ultraslice| 6.times do |i| ultraslice.bites << Bite.new(:name => "Big bite nr #{i}") end end short_cake.ultraslices.each do |ultraslice| 3.times do |i| ultraslice.bites << Bite.new(:name => "Small bite nr #{i}") end end # # has 1 # betsy = ShopOwner.new(:name => 'Betsy') betsys.shop_owner = betsy betsys.save # has 1 => has 1 barry = Wife.new(:name => 'Barry') betsy.wife = barry barry.save # has 1 => has 1 => has 1 golden = Ring.new(:name => 'golden') barry.ring = golden golden.save # has 1 => has 1 => has n 3.times do |i| barry.coats << Coat.new(:name => "Fancy coat nr #{i}") end barry.save # has 1 => has n 5.times do |i| betsy.children << Child.new(:name => "Snotling nr #{i}") end betsy.save # has 1 => has n => has n betsy.children.each do |child| 4.times do |i| child.toys << Toy.new(:name => "Cheap toy nr #{i}") end child.save end # has 1 => has n => has 1 betsy.children.each do |child| booger = Booger.new(:name => 'Nasty booger') child.booger = booger child.save end end end end # # has n # it 'should return the right children for has n => has n relationships' do Sweets::Shop.first.ultraslices.size.should == 15 10.times do |i| Sweets::Shop.first.ultraslices.select do |ultraslice| ultraslice.cake == Sweets::Cake.first(:name => 'German Chocolate') && ultraslice.size == i end end end it 'should return the right children for has n => has n => has 1' do Sweets::Shop.first.shapes.size.should == 15 Sweets::Shop.first.shapes.select do |shape| shape.name == 'square' end.size.should == 10 Sweets::Shop.first.shapes.select do |shape| shape.name == 'round' end.size.should == 5 end it 'should return the right children for has n => has n => has n' do Sweets::Shop.first.bites.size.should == 75 Sweets::Shop.first.bites.select do |bite| bite.ultraslice.cake == Sweets::Cake.first(:name => 'German Chocolate') end.size.should == 60 Sweets::Shop.first.bites.select do |bite| bite.ultraslice.cake == Sweets::Cake.first(:name => 'Short Cake') end.size.should == 15 end it 'should return the right children for has n => belongs_to relationships' do Sweets::Customer.first.cakes.size.should == 2 customers = Sweets::Shop.first.customers.select do |customer| customer.name == 'John Johnsen' end customers.size.should == 1 # another example can be found here: http://pastie.textmate.org/private/tt1hf1syfsytyxdgo4qxawfl end it 'should return the right children for has n => has 1 relationships' do Sweets::Shop.first.recipes.size.should == 2 Sweets::Shop.first.recipes.select do |recipe| recipe.name == 'Schwarzwald Cake' end.size.should == 1 Sweets::Shop.first.recipes.select do |recipe| recipe.name == "Shorty's Special" end.size.should == 1 end it 'should return the right children for has n => has 1 => has 1 relationships' do Sweets::Shop.first.creators.size.should == 2 Sweets::Shop.first.creators.any? do |creator| creator.name == 'Runar' end.should == true Sweets::Shop.first.creators.any? do |creator| creator.name == 'Berit' end.should == true end it 'should return the right children for has n => has 1 => has n relationships' do Sweets::Shop.first.ingredients.size.should == 10 4.times do |i| Sweets::Shop.first.ingredients.any? do |ingredient| ingredient.name == "Secret ingredient nr #{i}" && ingredient.recipe.cake == Sweets::Cake.first(:name => 'German Chocolate') end.should == true end 6.times do |i| Sweets::Shop.first.ingredients.any? do |ingredient| ingredient.name == "Well known ingredient nr #{i}" && ingredient.recipe.cake == Sweets::Cake.first(:name => 'Short Cake') end.should == true end end # # has 1 # it 'should return the right children for has 1 => has 1 relationships' do Sweets::Shop.first.wife.should == Sweets::Wife.first end it 'should return the right children for has 1 => has 1 => has 1 relationships' do Sweets::Shop.first.ring.should == Sweets::Ring.first end it 'should return the right children for has 1 => has 1 => has n relationships' do Sweets::Shop.first.coats.size.should == 3 3.times do |i| Sweets::Shop.first.coats.any? do |coat| coat.name == "Fancy coat nr #{i}" end.should == true end end it 'should return the right children for has 1 => has n relationships' do Sweets::Shop.first.children.size.should == 5 5.times do |i| Sweets::Shop.first.children.any? do |child| child.name == "Snotling nr #{i}" end.should == true end end it 'should return the right children for has 1 => has n => has 1 relationships' do Sweets::Shop.first.boogers.size.should == 5 Sweets::Shop.first.boogers.inject(Set.new) do |sum, booger| sum << booger.child_id end.size.should == 5 end it 'should return the right children for has 1 => has n => has n relationships' do Sweets::Shop.first.toys.size.should == 20 5.times do |child_nr| 4.times do |toy_nr| Sweets::Shop.first.toys.any? do |toy| toy.name == "Cheap toy nr #{toy_nr}" && toy.child = Sweets::Child.first(:name => "Snotling nr #{child_nr}") end.should == true end end end # # misc # it 'should join tables in the right order during has 1 => has n => has 1 queries' do child = Sweets::Shop.first.children(:name => 'Snotling nr 3').booger(:name.like => 'ooger') child.should_not be_nil end it 'should raise exception if you try to change it' do lambda do Sweets::Shop.first.wife = Sweets::Wife.new(:name => 'Larry') end.should raise_error(DataMapper::Associations::ImmutableAssociationError) end it 'should be reloaded when calling Resource#reload' do betsys = Sweets::Shop.first(:name => "Betsy's") betsys.send(:customers_association).should_receive(:reload).once lambda { betsys.reload }.should_not raise_error end end if false # Many to many not yet implemented describe "many to many associations" do before(:all) do class RightItem include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String has n..n, :left_items end class LeftItem include DataMapper::Resource def self.default_repository_name ADAPTER end property :id, Serial property :name, String has n..n, :right_items end RightItem.auto_migrate! LeftItem.auto_migrate! end def create_item_pair(number) @ri = RightItem.new(:name => "ri#{number}") @li = LeftItem.new(:name => "li#{number}") end it "should add to the association from the left" do pending "Waiting on Many To Many to be implemented" do create_item_pair "0000" @ri.save; @li.save @ri.should_not be_new_record @li.should_not be_new_record @li.right_items << @ri @li.right_items.should include(@ri) @li.reload @ri.reload @li.right_items.should include(@ri) end end it "should add to the association from the right" do create_item_pair "0010" @ri.save; @li.save @ri.should_not be_new_record @li.should_not be_new_record @ri.left_items << @li @ri.left_items.should include(@li) @li.reload @ri.reload @ri.left_items.should include(@li) end it "should load the associated collection from the either side" do pending "Waiting on Many To Many to be implemented" do create_item_pair "0020" @ri.save; @li.save @ri.left_items << @li @ri.reload; @li.reload @ri.left_items.should include(@li) @li.right_items.should include(@ri) end end it "should load the associated collection from the right" do pending "Waiting on Many To Many to be implemented" do create_item_pair "0030" @ri.save; @li.save @li.right_items << @li @ri.reload; @li.reload @ri.left_items.should include(@li) @li.right_items.should include(@ri) end end it "should save the left side of the association if new record" do pending "Waiting on Many To Many to be implemented" do create_item_pair "0040" @ri.save @li.should be_new_record @ri.left_items << @li @li.should_not be_new_record end end it "should save the right side of the association if new record" do pending "Waiting on Many To Many to be implemented" do create_item_pair "0050" @li.save @ri.should be_new_record @li.right_items << @ri @ri.should_not be_new_record end end it "should save both side of the association if new record" do pending "Waiting on Many To Many to be implemented" do create_item_pair "0060" @li.should be_new_record @ri.should be_new_record @ri.left_items << @li @ri.should_not be_new_record @li.should_not be_new_record end end it "should remove an item from the left collection without destroying the item" do pending "Waiting on Many To Many to be implemented" do create_item_pair "0070" @li.save; @ri.save @ri.left_items << @li @ri.reload; @li.reload @ri.left_items.should include(@li) @ri.left_items.delete(@li) @ri.left_items.should_not include(@li) @li.reload LeftItem.get(@li.id).should_not be_nil end end it "should remove an item from the right collection without destroying the item" do pending "Waiting on Many To Many to be implemented" do create_item_pair "0080" @li.save; @ri.save @li.right_items << @ri @li.reload; @ri.reload @li.right_items.should include(@ri) @li.right_items.delete(@ri) @li.right_items.should_not include(@ri) @ri.reload RightItem.get(@ri.id).should_not be_nil end end it "should remove the item from the collection when an item is deleted" do pending "Waiting on Many To Many to be implemented" do create_item_pair "0090" @li.save; @ri.save @ri.left_items << @li @ri.reload; @li.reload @ri.left_items.should include(@li) @li.destroy @ri.reload @ri.left_items.should_not include(@li) end end end end end end