require "spec_helper" describe Mongoid::Association::Referenced::HasOne do before do class OwnerObject; include Mongoid::Document; end class BelongingObject; include Mongoid::Document; end end after do Object.send(:remove_const, :BelongingObject) Object.send(:remove_const, :OwnerObject) end let(:has_one_class) do OwnerObject end let(:name) do :belonging_object end let(:association) do has_one_class.has_one name, options end let(:options) do { } end describe '#relation_complements' do let(:expected_complements) do [ Mongoid::Association::Referenced::BelongsTo, ] end it 'returns the relation complements' do expect(association.relation_complements).to eq(expected_complements) end end describe '#setup!' do it 'sets up a getter for the relation' do expect(Mongoid::Association::Accessors).to receive(:define_getter!).with(association) association.setup! end it 'sets up a setter for the relation' do expect(Mongoid::Association::Accessors).to receive(:define_setter!).with(association) association.setup! end it 'sets up an existence check for the relation' do expect(Mongoid::Association::Accessors).to receive(:define_existence_check!).with(association) association.setup! end it 'sets up the builder for the relation' do expect(Mongoid::Association::Builders).to receive(:define_builder!).with(association) association.setup! end it 'sets up the creator for the relation' do expect(Mongoid::Association::Builders).to receive(:define_creator!).with(association) association.setup! end context 'autosave' do context 'when the :autosave option is true' do let(:options) do { autosave: true } end let(:association) do # Note that it is necessary to create the association directly, otherwise the # setup! method will be called by the :has_one macro described_class.new(has_one_class, name, options) end it 'sets up autosave' do expect(Mongoid::Association::Referenced::AutoSave).to receive(:define_autosave!).with(association) association.setup! end end context 'when the :autosave option is false' do let(:options) do { autosave: false } end it 'does not set up autosave' do expect(Mongoid::Association::Referenced::AutoSave).not_to receive(:define_autosave!) association.setup! end end context 'when the :autosave option is not provided' do let(:association) do # Note that it is necessary to create the association directly, otherwise the # setup! method will be called by the :has_one macro described_class.new(has_one_class, name, options) end it 'does not set up autosave' do expect(Mongoid::Association::Referenced::AutoSave).not_to receive(:define_autosave!) association.setup! end end end context 'when the :validate option is true' do let(:options) do { validate: true } end let(:association) do # Note that it is necessary to create the association directly, otherwise the # setup! method will be called by the :has_one macro described_class.new(has_one_class, name, options) end it 'sets up validation' do expect(has_one_class).to receive(:validates_associated).with(name).and_call_original association.setup! end end context 'when the :validate option is false' do let(:options) do { validate: false } end it 'does not set up validation' do expect(has_one_class).not_to receive(:validates_associated) association.setup! end end context 'when the :validate option is not provided' do let(:association) do # Note that it is necessary to create the association directly, otherwise the # setup! method will be called by the :embeds_one macro described_class.new(has_one_class, name, options) end it 'sets up the validation because it uses the validation default (true)' do expect(has_one_class).to receive(:validates_associated).with(name).and_call_original association.setup! end end context 'polymorphic' do context 'when the as option is provided' do let(:options) do { as: :containable } end before do association end it 'set the polymorphic attribute on the owner class' do expect(has_one_class.polymorphic).to be(true) end end context 'when the as option is not provided' do it 'does not set the polymorphic attribute on the owner class' do expect(has_one_class.polymorphic).to be(false) end end end context 'dependent' do context 'when the dependent option is provided' do context 'when the dependent option is :delete_all' do let(:options) do { dependent: :delete_all } end let(:association) do # Note that it is necessary to create the association directly, otherwise the # setup! method will be called by the :belongs_to macro described_class.new(has_one_class, name, options) end it 'sets up the dependency' do expect(Mongoid::Association::Depending).to receive(:define_dependency!) association.setup! end end context 'when the dependent option is :destroy' do let(:options) do { dependent: :destroy } end let(:association) do # Note that it is necessary to create the association directly, otherwise the # setup! method will be called by the :belongs_to macro described_class.new(has_one_class, name, options) end it 'sets up the dependency' do expect(Mongoid::Association::Depending).to receive(:define_dependency!) association.setup! end end context 'when the dependent option is :nullify' do let(:options) do { dependent: :nullify } end let(:association) do # Note that it is necessary to create the association directly, otherwise the # setup! method will be called by the :belongs_to macro described_class.new(has_one_class, name, options) end it 'sets up the dependency' do expect(Mongoid::Association::Depending).to receive(:define_dependency!) association.setup! end end context 'when the dependent option is :restrict_with_exception' do let(:options) do { dependent: :restrict_with_exception } end let(:association) do # Note that it is necessary to create the association directly, otherwise the # setup! method will be called by the :belongs_to macro described_class.new(has_one_class, name, options) end it 'sets up the dependency' do expect(Mongoid::Association::Depending).to receive(:define_dependency!) association.setup! end end context 'when the dependent option is :restrict_with_error' do let(:options) do { dependent: :restrict_with_error } end let(:association) do # Note that it is necessary to create the association directly, otherwise the # setup! method will be called by the :belongs_to macro described_class.new(has_one_class, name, options) end it 'sets up the dependency' do expect(Mongoid::Association::Depending).to receive(:define_dependency!) association.setup! end end end context 'when the dependent option is not provided' do it 'does not set up the dependency' do expect(Mongoid::Association::Depending).not_to receive(:define_dependency!) association.setup! end end end end describe '#type' do context 'when polymorphic' do let(:options) do { as: :containable } end it 'returns the as attribute followed by "_type"' do expect(association.type).to eq("#{options[:as]}_type") end end context 'when not polymorphic' do it 'returns nil' do expect(association.type).to be_nil end end end describe '#inverse_type' do context 'when polymorphic' do let(:options) do { as: :containable } end it 'returns nil' do expect(association.inverse_type).to be_nil end end context 'when not polymorphic' do it 'returns nil' do expect(association.inverse_type).to be_nil end end end describe '#inverse_type_setter' do context 'when polymorphic' do let(:options) do { as: :containable } end it 'returns nil' do expect(association.inverse_type).to be_nil end end context 'when not polymorphic' do it 'returns nil' do expect(association.inverse_type).to be_nil end end end describe '#foreign_key' do context 'when options has foreign_key specified' do let(:options) do { foreign_key: :other_object_id } end it 'raises returns the foreign key as a String' do expect(association.foreign_key).to eq(options[:foreign_key].to_s) end end context 'when options does not have foreign_key specified' do it 'returns the default foreign key, the name of the inverse followed by "_id"' do expect(association.foreign_key).to eq("#{association.inverse}_id") end end end describe '#embedded?' do it 'returns false' do expect(association.embedded?).to be(false) end end describe '#primary_key' do context 'when the :primary_key option is specified' do let(:options) do { primary_key: 'guid' } end it 'returns the primary_key' do expect(association.primary_key).to eq(options[:primary_key]) end end context 'when the :primary_key option is not specified' do it 'returns the primary_key default' do expect(association.primary_key).to eq(Mongoid::Association::Relatable::PRIMARY_KEY_DEFAULT) end end end describe '#indexed?' do it 'returns false' do expect(association.indexed?).to be(false) end end describe '#relation' do it 'returns Mongoid::Association::Referenced::HasOne::Proxy' do expect(association.relation).to be(Mongoid::Association::Referenced::HasOne::Proxy) end end describe '#validation_default' do it 'returns true' do expect(association.validation_default).to be(true) end end describe '#name' do it 'returns the name of the relation' do expect(association.name).to be(name) end end describe '#options' do it 'returns the options' do expect(association.options).to be(options) end end describe '#merge!' do end describe '#store_as' do it 'returns nil' do expect(association.store_as).to be_nil end end describe '#touchable?' do it 'return false' do expect(association.send(:touchable?)).to be(false) end end describe '#order' do it 'returns nil' do expect(association.order).to be_nil end end describe '#as' do context 'when :as is specified in the options' do let(:options) do { as: :containable } end it 'returns the :as option' do expect(association.as).to eq(options[:as]) end end context 'when :as is not specified in the options' do it 'returns nil' do expect(association.as).to be_nil end end end describe '#polymorphic?' do context 'when :as is specified in the options' do let(:options) do { as: :containable } end it 'returns true' do expect(association.polymorphic?).to be(true) end end context 'when :as is not specified in the options' do it 'returns false' do expect(association.polymorphic?).to be(false) end end end describe '#type_setter' do context 'when polymorphic' do let(:options) do { as: :containable } end it 'returns the type followed by = as a String' do expect(association.type_setter).to eq("containable_type=") end end context 'when not polymorphic' do it 'returns nil' do expect(association.type).to be_nil end end end describe '#dependent' do context 'when the dependent option is provided' do context 'when the dependent option is :delete_all' do let(:options) do { dependent: :delete_all } end it 'returns :delete_all' do expect(association.dependent).to eq(:delete_all) end end context 'when the dependent option is :destroy' do let(:options) do { dependent: :destroy } end it 'returns :destroy' do expect(association.dependent).to eq(:destroy) end end context 'when the dependent option is :nullify' do let(:options) do { dependent: :nullify } end it 'returns :nullify' do expect(association.dependent).to eq(:nullify) end end context 'when the dependent option is :restrict_with_exception' do let(:options) do { dependent: :restrict_with_exception } end it 'returns :restrict_with_exception' do expect(association.dependent).to eq(:restrict_with_exception) end end context 'when the dependent option is :restrict_with_error' do let(:options) do { dependent: :restrict_with_error } end it 'returns :restrict_with_error' do expect(association.dependent).to eq(:restrict_with_error) end end end context 'when the dependent option is not provided' do it 'returns nil' do expect(association.dependent).to be_nil end end end describe '#inverse_type' do it 'returns nil' do expect(association.inverse_type).to be_nil end end describe '#bindable?' do it 'returns false' do expect(association.bindable?(Person.new)).to be(false) end end describe '#inverses' do context 'when polymorphic' do before do BelongingObject.belongs_to :containable, polymorphic: true end let(:options) do { as: :containable } end context 'when another object is passed to the method' do let(:instance_of_other_class) do BelongingObject.new end context 'when the relation class has only one relation whose class matches the owning class' do it 'returns the :as attribute of this association' do expect(association.inverses(instance_of_other_class)).to match_array([ :containable ]) end end context 'when :inverse_of is specified' do before do options.merge!(inverse_of: :inverse_name) end it 'returns the :inverse_of value' do expect(association.inverses(instance_of_other_class)).to eq([ :inverse_name ]) end end context 'when inverse_of is not specified' do it 'returns the :as attribute of this association' do expect(association.inverses(instance_of_other_class)).to match_array([ :containable ]) end end end context 'when another object is not passed to the method' do context 'when inverse_of is specified' do before do options.merge!(inverse_of: :inverse_name) end it 'returns the :inverse_of value' do expect(association.inverses).to eq([ :inverse_name ]) end end context 'when inverse_of is not specified' do it 'returns the :as attribute' do expect(association.inverses).to eq([ :containable ]) end end end end context 'when not polymorphic' do before do BelongingObject.belongs_to :owner_object end context 'when inverse_of is specified' do before do options.merge!(inverse_of: :inverse_name) end it 'returns the :inverse_of value' do expect(association.inverses).to eq([ :inverse_name ]) end end context 'when inverse_of is not specified' do it 'uses the inverse class to find the inverse name' do expect(association.inverses).to eq([ :owner_object ]) end end context 'when :cyclic is specified' do it 'returns the cyclic inverse name' do end end end end describe '##inverse' do context 'when polymorphic' do before do BelongingObject.belongs_to :containable, polymorphic: true end let(:options) do { as: :containable } end context 'when another object is passed to the method' do let(:instance_of_other_class) do BelongingObject.new end context 'when the relation class has only one relation whose class matches the owning class' do it 'returns the :as attribute of this association' do expect(association.inverse(instance_of_other_class)).to eq(:containable) end end context 'when :inverse_of is specified' do before do options.merge!(inverse_of: :inverse_name) end it 'returns the :inverse_of value' do expect(association.inverse(instance_of_other_class)).to eq(:inverse_name) end end context 'when inverse_of is not specified' do it 'returns the :as attribute of this association' do expect(association.inverse(instance_of_other_class)).to eq(:containable) end end end context 'when another object is not passed to the method' do context 'when inverse_of is specified' do before do options.merge!(inverse_of: :inverse_name) end it 'returns the :inverse_of value' do expect(association.inverse).to eq(:inverse_name) end end context 'when inverse_of is not specified' do it 'returns the :as attribute' do expect(association.inverse).to eq(:containable) end end end end context 'when not polymorphic' do before do BelongingObject.belongs_to :owner_object end context 'when inverse_of is specified' do before do options.merge!(inverse_of: :inverse_name) end it 'returns the :inverse_of value' do expect(association.inverse).to eq(:inverse_name) end end context 'when inverse_of is not specified' do it 'uses the inverse class to find the inverse name' do expect(association.inverse).to eq(:owner_object) end end context 'when :cyclic is specified' do it 'returns the cyclic inverse name' do end end end end describe '#inverse_association' do end describe '#autosave' do context 'when the autosave option is specified' do context 'when the autosave option is true' do let(:options) do { autosave: true } end it 'returns true' do expect(association.autosave).to be(true) end end context 'when the autosave option is false' do let(:options) do { autosave: false } end it 'returns false' do expect(association.autosave).to be(false) end end end context 'when the autosave option is not specified' do it 'returns false' do expect(association.autosave).to be(false) end end end describe '#relation_class_name' do context 'when the :class_name option is specified' do let(:options) do { class_name: 'OtherBelongingObject' } end it 'returns the class name option' do expect(association.relation_class_name).to eq('OtherBelongingObject') end end context 'when the class_name option is not specified' do it 'uses the name of the relation to deduce the class name' do expect(association.relation_class_name).to eq('BelongingObject') end end end describe '#klass' do context 'when the :class_name option is specified' do let!(:_class) do class OtherBelongingObject; end OtherBelongingObject end let(:options) do { class_name: 'OtherBelongingObject' } end it 'returns the class name option' do expect(association.klass).to eq(_class) end end context 'when the class_name option is not specified' do it 'uses the name of the relation to deduce the class name' do expect(association.klass).to eq(BelongingObject) end end end describe '#inverse_class_name' do it 'returns the name of the owner class' do expect(association.inverse_class_name).to eq(OwnerObject.name) end end describe '#inverse_class' do it 'returns the owner class' do expect(association.inverse_class).to be(OwnerObject) end end describe '#inverse_of' do context 'when :inverse_of is specified in the options' do let(:options) do { inverse_of: :a_belonging_object } end it 'returns the inverse_of value' do expect(association.inverse_of).to eq(options[:inverse_of]) end end context 'when :inverse_of is not specified in the options' do it 'returns nil' do expect(association.inverse_of).to be_nil end end end describe '#key' do it 'returns the primary key' do expect(association.key).to eq(association.primary_key) end end describe '#setter' do it 'returns a string of the name followed by =' do expect(association.setter).to eq("#{name}=") end end describe '#validate?' do context 'when :validate is specified in the options' do context 'when validate is true' do let(:options) do { validate: true } end it 'returns true' do expect(association.send(:validate?)).to be(true) end end context 'when validate is false' do let(:options) do { validate: false } end it 'returns false' do expect(association.send(:validate?)).to be(false) end end end context 'when :validate is not specified in the options' do it 'returns the validation_default' do expect(association.send(:validate?)).to eq(association.validation_default) end end end describe '#autobuilding?' do context 'when :autobuild is specified in the options' do context 'when autobuild is true' do let(:options) do { autobuild: true } end it 'returns true' do expect(association.autobuilding?).to be(true) end end context 'when autobuild is false' do let(:options) do { autobuild: false } end it 'returns true' do expect(association.autobuilding?).to be(false) end end end context 'when :validate is not specified in the options' do it 'returns false' do expect(association.autobuilding?).to be(false) end end end describe '#forced_nil_inverse?' do it 'returns false' do expect(association.forced_nil_inverse?).to be(false) end end describe '#stores_foreign_key?' do it 'returns false' do expect(association.stores_foreign_key?).to be(false) end end describe '#inverse_setter' do context 'when an inverse can be determined' do before do BelongingObject.belongs_to :owner_object end it 'returns the name of the inverse followed by =' do expect(association.inverse_setter).to eq('owner_object=') end end context 'when an inverse cannot be determined' do it 'returns nil' do expect(association.inverse_setter).to be_nil end end end describe '#extension' do context 'when a block is passed' do let(:association) do has_one_class.embeds_one name, options do; end end it 'defines an extension module' do expect(association.extension).to be_a(Module) end it 'returns the extension' do expect(association.extension).to eq( "#{has_one_class.name}::#{has_one_class.name}#{name.to_s.camelize}RelationExtension".constantize) end end context 'when an :extension is not specified in the options' do it 'returns false' do expect(association.extension).to be_nil end end end describe '#foreign_key_setter' do it 'returns the foreign key followed by "="' do expect(association.foreign_key_setter).to eq("#{association.foreign_key}=") end end describe '#destructive?' do context 'when the dependent option is provided' do context 'when the dependent option is :delete_all' do let(:options) do { dependent: :delete_all } end it 'returns true' do expect(association.destructive?).to be(true) end end context 'when the dependent option is :destroy' do let(:options) do { dependent: :destroy } end it 'returns true' do expect(association.destructive?).to be(true) end end context 'when the dependent option is :nullify' do let(:options) do { dependent: :nullify } end it 'returns false' do expect(association.destructive?).to be(false) end end context 'when the dependent option is :restrict_with_exception' do let(:options) do { dependent: :restrict_with_exception } end it 'returns false' do expect(association.destructive?).to be(false) end end context 'when the dependent option is :restrict_with_error' do let(:options) do { dependent: :restrict_with_error } end it 'returns false' do expect(association.destructive?).to be(false) end end end context 'when the dependent option is not provided' do it 'returns false' do expect(association.destructive?).to be(false) end end end describe '#nested_builder' do it 'returns an instance of Association::Nested::One' do expect(association.nested_builder({}, {})).to be_a(Mongoid::Association::Nested::One) end end describe '#path' do it 'returns an instance of Mongoid::Atomic::Paths::Root' do expect(association.path(double( :_parent => true))).to be_a(Mongoid::Atomic::Paths::Root) end end describe '#foreign_key_check' do it 'returns the nil' do expect(association.foreign_key_check).to be_nil end end describe '#create_relation' do let(:owner) do OwnerObject.new end let(:target) do BelongingObject.new end before do association BelongingObject.belongs_to :owner_object end it 'returns an the target (EmbeddedObject)' do expect(Mongoid::Association::Referenced::HasOne::Proxy).to receive(:new).and_call_original expect(association.create_relation(owner, target)).to be_a(BelongingObject) end end end