require 'spec_helper' share_examples_for 'it creates a one accessor' do describe 'accessor' do describe 'when there is no associated resource' do describe 'without a query' do before :all do @return = @car.__send__(@name) end it 'should return nil' do @return.should be_nil end end describe 'with a query' do before :all do @return = @car.__send__(@name, :id => 99) end it 'should return nil' do @return.should be_nil end end end describe 'when there is an associated resource' do before :all do @expected = @model.new @car.__send__("#{@name}=", @expected) end describe 'without a query' do before :all do @return = @car.__send__(@name) end it 'should return a Resource' do @return.should be_kind_of(DataMapper::Resource) end it 'should return the expected Resource' do @return.should equal(@expected) end end describe 'with a query' do before :all do @car.save # save @car and @expected to set @expected.id @expected.id.should_not be_nil @return = @car.__send__(@name, :id => @expected.id) end it 'should return a Resource' do @return.should be_kind_of(DataMapper::Resource) end it 'should return the expected Resource' do @return.should == @expected end end end describe 'when the target model is scoped' do before :all do @resource = @model.new @car.__send__("#{@name}=", @resource) @car.save # set the model scope to not match the expected resource @model.default_scope.update(:id.not => @resource.id) @return = @car.model.get(*@car.key).__send__(@name) end it 'should return nil' do @return.should be_nil end end end end share_examples_for 'it creates a one mutator' do describe 'mutator' do describe 'when setting a Resource' do before :all do @expected = @model.new @return = @car.__send__("#{@name}=", @expected) end it 'should return the expected Resource' do @return.should equal(@expected) end it 'should set the Resource' do @car.__send__(@name).should equal(@expected) end it 'should relate associated Resource' do relationship = Car.relationships[@name] many_to_one = relationship.kind_of?(DataMapper::Associations::ManyToOne::Relationship) one_to_one_through = relationship.kind_of?(DataMapper::Associations::OneToOne::Relationship) && relationship.respond_to?(:through) pending_if many_to_one || one_to_one_through do @expected.car.should == @car end end it 'should persist the Resource' do @car.save.should be(true) @car.model.get(*@car.key).__send__(@name).should == @expected end it 'should persist the associated Resource' do @car.save.should be(true) @expected.should be_saved @expected.model.get(*@expected.key).car.should == @car end end describe 'when setting a Hash' do before :all do @car.__send__("#{@name}=", @model.new) attributes = { :id => 10 } @expected = @model.new(attributes) @return = @car.__send__("#{@name}=", attributes) end it 'should return the expected Resource' do @return.should == @expected end it 'should set the Resource' do @car.__send__(@name).should equal(@return) end it 'should relate associated Resource' do relationship = Car.relationships[@name] many_to_one = relationship.kind_of?(DataMapper::Associations::ManyToOne::Relationship) one_to_one_through = relationship.kind_of?(DataMapper::Associations::OneToOne::Relationship) && relationship.respond_to?(:through) pending_if many_to_one || one_to_one_through do @return.car.should == @car end end it 'should persist the Resource' do @car.save.should be(true) @car.model.get(*@car.key).__send__(@name).should == @return end it 'should persist the associated Resource' do @car.save.should be(true) @return.should be_saved @return.model.get(*@return.key).car.should == @car end end describe 'when setting nil' do before :all do @car.__send__("#{@name}=", @model.new) @return = @car.__send__("#{@name}=", nil) end it 'should return nil' do @return.should be_nil end it 'should set nil' do @car.__send__(@name).should be_nil end it 'should persist as nil' do @car.save.should be(true) @car.model.get(*@car.key).__send__(@name).should be_nil end end describe 'when changing the Resource' do before :all do @car.__send__("#{@name}=", @model.new) @expected = @model.new @return = @car.__send__("#{@name}=", @expected) end it 'should return the expected Resource' do @return.should equal(@expected) end it 'should set the Resource' do @car.__send__(@name).should equal(@expected) end it 'should relate associated Resource' do relationship = Car.relationships[@name] many_to_one = relationship.kind_of?(DataMapper::Associations::ManyToOne::Relationship) one_to_one_through = relationship.kind_of?(DataMapper::Associations::OneToOne::Relationship) && relationship.respond_to?(:through) pending_if 'should create back-reference', many_to_one || one_to_one_through do @expected.car.should == @car end end it 'should persist the Resource' do @car.save.should be(true) @car.model.get(*@car.key).__send__(@name).should == @expected end it 'should persist the associated Resource' do @car.save.should be(true) @expected.should be_saved @expected.model.get(*@expected.key).car.should == @car end end end end share_examples_for 'it creates a many accessor' do describe 'accessor' do describe 'when there is no child resource and the source is saved' do before :all do @car.save.should be(true) @return = @car.__send__(@name) end it 'should return a Collection' do @return.should be_kind_of(DataMapper::Collection) end it 'should return an empty Collection' do @return.should be_empty end end describe 'when there is no child resource and the source is not saved' do before :all do @return = @car.__send__(@name) end it 'should return a Collection' do @return.should be_kind_of(DataMapper::Collection) end it 'should return an empty Collection' do @return.should be_empty end end describe 'when there is a child resource' do before :all do @return = nil @expected = @model.new @car.__send__("#{@name}=", [ @expected ]) @return = @car.__send__(@name) end it 'should return a Collection' do @return.should be_kind_of(DataMapper::Collection) end it 'should return expected Resources' do @return.should == [ @expected ] end end describe 'when the target model is scoped' do before :all do 2.times { @car.__send__(@name).new } @car.save @expected = @car.__send__(@name).first @expected.should_not be_nil # set the model scope to only return the first record @model.default_scope.update( Hash[ @model.key(@repository.name).zip(@expected.key) ] ) @return = @car.model.get(*@car.key).__send__(@name) end it 'should return a Collection' do @return.should be_kind_of(DataMapper::Collection) end it 'should return expected Resources' do @return.should == [ @expected ] end end end end share_examples_for 'it creates a many mutator' do describe 'mutator' do describe 'when setting an Array of Resources' do before :all do @expected = [ @model.new ] @return = @car.__send__("#{@name}=", @expected) end it 'should return the expected Collection' do @return.should == @expected end it 'should set the Collection' do @car.__send__(@name).should == @expected @car.__send__(@name).zip(@expected) { |value, expected| value.should equal(expected) } end it 'should relate the associated Collection' do pending_if Car.relationships[@name].kind_of?(DataMapper::Associations::ManyToMany::Relationship) do @expected.each { |resource| resource.car.should == @car } end end it 'should persist the Collection' do @car.save.should be(true) @car.model.get(*@car.key).__send__(@name).should == @expected end it 'should persist the associated Resource' do @car.save.should be(true) @expected.each { |resource| resource.should be_saved } @expected.each { |resource| resource.model.get(*resource.key).car.should == @car } end end describe 'when setting an Array of Hashes' do before :all do attributes = { :id => 11 } @hashes = [ attributes ] @expected = [ @model.new(attributes) ] @return = @car.__send__("#{@name}=", @hashes) end it 'should return the expected Collection' do @return.should == @expected end it 'should set the Collection' do @car.__send__(@name).should == @return end it 'should relate the associated Collection' do pending_if Car.relationships[@name].kind_of?(DataMapper::Associations::ManyToMany::Relationship) do @return.each { |resource| resource.car.should == @car } end end it 'should persist the Collection' do @car.save.should be(true) @car.model.get(*@car.key).__send__(@name).should == @return end it 'should persist the associated Resource' do @car.save.should be(true) @return.each { |resource| resource.should be_saved } @return.each { |resource| resource.model.get(*resource.key).car.should == @car } end end describe 'when setting an empty collection' do before :all do @car.__send__("#{@name}=", [ @model.new ]) @return = @car.__send__("#{@name}=", []) end it 'should return a Collection' do @return.should be_kind_of(DataMapper::Collection) end it 'should set an empty Collection' do @car.__send__(@name).should be_empty end it 'should persist as an empty Collection' do @car.save.should be(true) @car.model.get(*@car.key).__send__(@name).should be_empty end end describe 'when changing an associated collection' do before :all do @car.__send__("#{@name}=", [ @model.new ]) @expected = [ @model.new ] @return = @car.__send__("#{@name}=", @expected) end it 'should return the expected Resource' do @return.should == @expected end it 'should set the Resource' do @car.__send__(@name).should == @expected @car.__send__(@name).zip(@expected) { |value, expected| value.should equal(expected) } end it 'should relate associated Resource' do pending_if Car.relationships[@name].kind_of?(DataMapper::Associations::ManyToMany::Relationship) do @expected.each { |resource| resource.car.should == @car } end end it 'should persist the Resource' do @car.save.should be(true) @car.model.get(*@car.key).__send__(@name).should == @expected end it 'should persist the associated Resource' do @car.save.should be(true) @expected.each { |resource| resource.should be_saved } @expected.each { |resource| resource.model.get(*resource.key).car.should == @car } end end end end describe DataMapper::Associations do before :all do class ::Car include DataMapper::Resource property :id, Serial property :name, String end class ::Engine include DataMapper::Resource property :id, Serial end class ::Door include DataMapper::Resource property :id, Serial end class ::Window include DataMapper::Resource property :id, Serial end end def n 1.0/0 end it { Engine.should respond_to(:belongs_to) } describe '#belongs_to' do before :all do @model = Engine @name = :engine Car.belongs_to(@name, :required => false) Engine.has(1, :car) DataMapper.finalize end supported_by :all do before :all do @car = Car.new end it { @car.should respond_to(@name) } it_should_behave_like 'it creates a one accessor' it { @car.should respond_to("#{@name}=") } it_should_behave_like 'it creates a one mutator' describe 'with a :key option' do before :all do @relationship = Car.belongs_to("#{@name}_with_key".to_sym, @model, :required => false, :key => true) DataMapper.finalize end it 'should create a foreign key that is part of the key' do @relationship.child_key.each do |property| property.should be_key end end end describe 'with a :unique option' do let(:unique) { [ :one, :two, :three ] } before :all do @relationship = Car.belongs_to("#{@name}_with_unique".to_sym, @model, :unique => unique) DataMapper.finalize end it 'should create a foreign key that is unique' do @relationship.child_key.each do |property| property.should be_unique end end it 'should create a foreign key that has a unique index' do @relationship.child_key.each do |property| property.unique_index.should equal(unique) end end end end # TODO: refactor these specs into above structure once they pass describe 'pending query specs' do before :all do Car.has(1, :engine) Engine.belongs_to(:car) DataMapper.finalize end supported_by :all do describe 'querying for a parent resource when only the foreign key is set' do before :all do # create a car that would be returned if the query is not # scoped properly to retrieve @car Car.create @car = Car.create engine = Engine.new(:car_id => @car.id) @return = engine.car end it 'should return a Resource' do @return.should be_kind_of(DataMapper::Resource) end it 'should return expected Resource' do @return.should eql(@car) end end describe 'querying for a parent resource' do before :all do @car = Car.create @engine = Engine.create(:car => @car) @resource = @engine.car(:id => @car.id) end it 'should return a Resource' do @resource.should be_kind_of(DataMapper::Resource) end it 'should return expected Resource' do @resource.should eql(@car) end end describe 'querying for a parent resource that does not exist' do before :all do @car = Car.create @engine = Engine.create(:car => @car) @resource = @engine.car(:id.not => @car.id) end it 'should return nil' do @resource.should be_nil end end describe 'changing the parent resource' do before :all do @car = Car.create @engine = Engine.new @engine.car = @car end it 'should set the associated foreign key' do @engine.car_id.should == @car.id end it 'should add the engine object to the car' do pending 'Changing a belongs_to parent should add the object to the correct association' do @car.engines.should be_include(@engine) end end end describe 'changing the parent foreign key' do before :all do @car = Car.create @engine = Engine.new(:car_id => @car.id) end it 'should set the associated resource' do @engine.car.should eql(@car) end end describe 'changing an existing resource through the relation' do before :all do @car1 = Car.create @car2 = Car.create @engine = Engine.create(:car => @car1) @engine.car = @car2 end it 'should also change the foreign key' do @engine.car_id.should == @car2.id end it 'should add the engine to the car' do pending 'Changing a belongs_to parent should add the object to the correct association' do @car2.engines.should be_include(@engine) end end end describe 'changing an existing resource through the relation' do before :all do @car1 = Car.create @car2 = Car.create @engine = Engine.create(:car => @car1) @engine.car_id = @car2.id end it 'should also change the foreign key' do @engine.car.should eql(@car2) end it 'should add the engine to the car' do pending 'a change to the foreign key should also change the related object' do @car2.engines.should be_include(@engine) end end end end end describe 'with a model' do before :all do Engine.belongs_to(:vehicle, Car) DataMapper.finalize end it 'should set the relationship target model' do Engine.relationships[:vehicle].target_model.should == Car end end describe 'with a :model option' do before :all do Engine.belongs_to(:vehicle, :model => Car) DataMapper.finalize end it 'should set the relationship target model' do Engine.relationships[:vehicle].target_model.should == Car end end describe 'with a single element as :child_key option' do before :all do Engine.belongs_to(:vehicle, :model => Car, :child_key => :bike_id) DataMapper.finalize end it 'should set the relationship child key' do Engine.relationships[:vehicle].child_key.map { |property| property.name }.should == [:bike_id] end end describe 'with an array as :child_key option' do before :all do Engine.belongs_to(:vehicle, :model => Car, :child_key => [:bike_id]) DataMapper.finalize end it 'should set the relationship child key' do Engine.relationships[:vehicle].child_key.map { |property| property.name }.should == [:bike_id] end end describe 'with a single element as :parent_key option' do before :all do Engine.belongs_to(:vehicle, :model => Car, :parent_key => :name) DataMapper.finalize end it 'should set the relationship parent key' do Engine.relationships[:vehicle].parent_key.map { |property| property.name }.should == [:name] end end describe 'with an array as :parent_key option' do before :all do Engine.belongs_to(:vehicle, :model => Car, :parent_key => [:name]) DataMapper.finalize end it 'should set the relationship parent key' do Engine.relationships[:vehicle].parent_key.map { |property| property.name }.should == [:name] end end end it { Car.should respond_to(:has) } describe '#has' do describe '1' do before :all do @model = Engine @name = :engine Car.has(1, @name) Engine.belongs_to(:car) DataMapper.finalize end supported_by :all do before :all do @car = Car.new end it { @car.should respond_to(@name) } it_should_behave_like 'it creates a one accessor' it { @car.should respond_to("#{@name}=") } it_should_behave_like 'it creates a one mutator' end end describe '1 through' do before :all do @model = Engine @name = :engine Car.has(1, @name, :through => DataMapper::Resource) Engine.has(1, :car, :through => DataMapper::Resource) DataMapper.finalize end supported_by :all do before :all do @no_join = defined?(DataMapper::Adapters::InMemoryAdapter) && @adapter.kind_of?(DataMapper::Adapters::InMemoryAdapter) || defined?(DataMapper::Adapters::YamlAdapter) && @adapter.kind_of?(DataMapper::Adapters::YamlAdapter) end before :all do @car = Car.new end before do pending if @no_join end it { @car.should respond_to(@name) } it_should_behave_like 'it creates a one accessor' it { @car.should respond_to("#{@name}=") } it_should_behave_like 'it creates a one mutator' end end describe 'n..n' do before :all do @model = Door @name = :doors Car.has(1..4, @name) Door.belongs_to(:car, :required => false) DataMapper.finalize end supported_by :all do before :all do @car = Car.new end it { @car.should respond_to(@name) } it_should_behave_like 'it creates a many accessor' it { @car.should respond_to("#{@name}=") } it_should_behave_like 'it creates a many mutator' end end describe 'n..n through' do before :all do @model = Window @name = :windows Window.has(1, :car, :through => DataMapper::Resource) Car.has(1..4, :windows, :through => DataMapper::Resource) DataMapper.finalize end supported_by :all do before :all do @no_join = defined?(DataMapper::Adapters::InMemoryAdapter) && @adapter.kind_of?(DataMapper::Adapters::InMemoryAdapter) || defined?(DataMapper::Adapters::YamlAdapter) && @adapter.kind_of?(DataMapper::Adapters::YamlAdapter) end before :all do @car = Car.new end before do pending if @no_join end it { @car.should respond_to(@name) } it_should_behave_like 'it creates a many accessor' it { @car.should respond_to("#{@name}=") } it_should_behave_like 'it creates a many mutator' end end describe 'when the 3rd argument is a Model' do before :all do Car.has(1, :engine, Engine) DataMapper.finalize end it 'should set the relationship target model' do Car.relationships[:engine].target_model.should == Engine end end describe 'when the 3rd argument is a String' do before :all do Car.has(1, :engine, 'Engine') DataMapper.finalize end it 'should set the relationship target model' do Car.relationships[:engine].target_model.should == Engine end end it 'should raise an exception if the cardinality is not understood' do lambda { Car.has(n..n, :doors) }.should raise_error(ArgumentError) end it 'should raise an exception if the minimum constraint is larger than the maximum' do lambda { Car.has(2..1, :doors) }.should raise_error(ArgumentError) end end describe 'property prefix inference' do describe 'when a relationship has an inverse' do before :all do @engine_relationship = Car.has(1, :engine, :inverse => Engine.belongs_to(:sports_car, Car)) DataMapper.finalize end supported_by :all do it 'should have a child key prefix the same as the inverse relationship' do @engine_relationship.child_key.map { |property| property.name }.should == [ :sports_car_id ] end end end describe 'when a relationship does not have an inverse' do before :all do @engine_relationship = Car.has(1, :engine) DataMapper.finalize end supported_by :all do it 'should have a child key prefix inferred from the source model name' do @engine_relationship.child_key.map { |property| property.name }.should == [ :car_id ] end end end describe 'when a relationship is inherited' do describe 'has an inverse' do before :all do Car.property(:type, DataMapper::Property::Discriminator) class ::ElectricCar < Car; end Car.has(1, :engine, :inverse => Engine.belongs_to(:sports_car, Car)) DataMapper.finalize end supported_by :all do before :all do @engine_relationship = ElectricCar.relationships(@repository.name)[:engine] end it 'should have a source model equal to the ancestor' do @engine_relationship.source_model.should equal(Car) end it 'should have a child key prefix the same as the inverse relationship' do @engine_relationship.child_key.map { |property| property.name }.should == [ :sports_car_id ] end end end describe 'does not have an inverse' do before :all do Car.property(:type, DataMapper::Property::Discriminator) class ::ElectricCar < Car; end Car.has(1, :engine) DataMapper.finalize end supported_by :all do before :all do @engine_relationship = ElectricCar.relationships(@repository.name)[:engine] end it 'should have a source model equal to the ancestor' do @engine_relationship.source_model.should equal(Car) end it 'should have a child key prefix inferred from the source model name' do @engine_relationship.child_key.map { |property| property.name }.should == [ :car_id ] end end end end describe "when a subclass defines it's own relationship" do describe 'has an inverse' do before :all do Car.property(:type, DataMapper::Property::Discriminator) class ::ElectricCar < Car; end ElectricCar.has(1, :engine, :inverse => Engine.belongs_to(:sports_car, Car)) DataMapper.finalize end supported_by :all do before :all do @engine_relationship = ElectricCar.relationships(@repository.name)[:engine] end it 'should have a source model equal to the descendant' do @engine_relationship.source_model.should equal(ElectricCar) end it 'should have a child key prefix the same as the inverse relationship' do @engine_relationship.child_key.map { |property| property.name }.should == [ :sports_car_id ] end end end describe 'does not have an inverse' do before :all do Car.property(:type, DataMapper::Property::Discriminator) class ::ElectricCar < Car; end ElectricCar.has(1, :engine) DataMapper.finalize end supported_by :all do before :all do @engine_relationship = ElectricCar.relationships(@repository.name)[:engine] end it 'should have a source model equal to the descendant' do @engine_relationship.source_model.should equal(ElectricCar) end it 'should have a child key prefix inferred from the source model name' do @engine_relationship.child_key.map { |property| property.name }.should == [ :electric_car_id ] end end end end end describe 'child is also a parent' do before :all do class ::Employee include DataMapper::Resource property :id, Serial property :name, String belongs_to :company end class ::Company include DataMapper::Resource property :id, Serial property :name, String belongs_to :owner, Employee, :required => false has n, :employees end DataMapper.finalize end supported_by :all do before :all do @company = Company.create(:name => 'ACME Inc.') @employee = @company.employees.create(:name => 'Wil E. Coyote') end it 'should save the child as a parent' do lambda { @company.owner = @employee @company.save.should be(true) }.should_not raise_error end end end end