diff --git a/lib/couchbase-orm/utilities/embeds_many.rb b/lib/couchbase-orm/utilities/embeds_many.rb index cbcfb588..3e9b2b82 100644 --- a/lib/couchbase-orm/utilities/embeds_many.rb +++ b/lib/couchbase-orm/utilities/embeds_many.rb @@ -32,10 +32,10 @@ def embeds_many(name, class_name: nil, store_as: nil, validate: true, polymorphi if is_polymorphic define_polymorphic_embeds_many_reader(name, storage_key, instance_var, default) - define_polymorphic_embeds_many_writer(name, storage_key, instance_var) + define_polymorphic_embeds_many_writer(name, storage_key, instance_var, default) else define_standard_embeds_many_reader(name, storage_key, instance_var, klass_name, default) - define_standard_embeds_many_writer(name, storage_key, instance_var, klass_name) + define_standard_embeds_many_writer(name, storage_key, instance_var, klass_name, default) end define_method(:"#{name}_reset") do @@ -94,12 +94,26 @@ def define_polymorphic_embeds_many_reader(name, storage_key, instance_var, defau end end - def define_polymorphic_embeds_many_writer(name, storage_key, instance_var) + def define_polymorphic_embeds_many_writer(name, storage_key, instance_var, default_value) + wrap_array = array_wrap_lambda + + # rubocop:disable Metrics/BlockLength define_method("#{name}=") do |val| + if val.nil? + if default_value + default_obj = default_value.is_a?(Proc) ? instance_exec(&default_value) : default_value + return self.send("#{name}=", wrap_array.call(default_obj)) unless default_obj.nil? + end + + write_attribute(storage_key, []) + instance_variable_set(instance_var, []) + return + end + embedded_objects = [] serialized = [] - Array(val).each do |v| + wrap_array.call(val).each do |v| next if v.nil? obj = if v.is_a?(Hash) @@ -127,6 +141,7 @@ def define_polymorphic_embeds_many_writer(name, storage_key, instance_var) write_attribute(storage_key, serialized) instance_variable_set(instance_var, embedded_objects) end + # rubocop:enable Metrics/BlockLength end def define_standard_embeds_many_reader(name, storage_key, instance_var, klass_name, default_value) @@ -165,14 +180,27 @@ def define_standard_embeds_many_reader(name, storage_key, instance_var, klass_na end end - def define_standard_embeds_many_writer(name, storage_key, instance_var, _klass_name) + def define_standard_embeds_many_writer(name, storage_key, instance_var, _klass_name, default_value) + wrap_array = array_wrap_lambda + define_method("#{name}=") do |val| + if val.nil? + if default_value + default_obj = default_value.is_a?(Proc) ? instance_exec(&default_value) : default_value + return self.send("#{name}=", wrap_array.call(default_obj)) unless default_obj.nil? + end + + write_attribute(storage_key, []) + instance_variable_set(instance_var, []) + return + end + klass = send("_resolve_embedded_class_for_#{name}") embedded_objects = [] serialized = [] - Array(val).each do |v| + wrap_array.call(val).each do |v| obj = v.is_a?(klass) ? v : klass.new(v) obj.embedded = true raw = obj.serializable_hash diff --git a/lib/couchbase-orm/utilities/embeds_one.rb b/lib/couchbase-orm/utilities/embeds_one.rb index 23645c40..21a361dc 100644 --- a/lib/couchbase-orm/utilities/embeds_one.rb +++ b/lib/couchbase-orm/utilities/embeds_one.rb @@ -31,10 +31,10 @@ def embeds_one(name, class_name: nil, store_as: nil, validate: true, polymorphic if is_polymorphic define_polymorphic_embeds_one_reader(name, storage_key, instance_var, default) - define_polymorphic_embeds_one_writer(name, storage_key, instance_var) + define_polymorphic_embeds_one_writer(name, storage_key, instance_var, default) else define_standard_embeds_one_reader(name, storage_key, instance_var, klass_name, default) - define_standard_embeds_one_writer(name, storage_key, instance_var, klass_name) + define_standard_embeds_one_writer(name, storage_key, instance_var, klass_name, default) end define_method(:"#{name}_reset") do @@ -77,9 +77,14 @@ def define_polymorphic_embeds_one_reader(name, storage_key, instance_var, defaul end end - def define_polymorphic_embeds_one_writer(name, storage_key, instance_var) + def define_polymorphic_embeds_one_writer(name, storage_key, instance_var, default_value) define_method("#{name}=") do |val| if val.nil? + if default_value + default_obj = default_value.is_a?(Proc) ? instance_exec(&default_value) : default_value + return self.send("#{name}=", default_obj) unless default_obj.nil? + end + write_attribute(storage_key, nil) instance_variable_set(instance_var, nil) return @@ -138,9 +143,14 @@ def define_standard_embeds_one_reader(name, storage_key, instance_var, klass_nam end end - def define_standard_embeds_one_writer(name, storage_key, instance_var, _klass_name) + def define_standard_embeds_one_writer(name, storage_key, instance_var, _klass_name, default_value) define_method("#{name}=") do |val| if val.nil? + if default_value + default_obj = default_value.is_a?(Proc) ? instance_exec(&default_value) : default_value + return self.send("#{name}=", default_obj) unless default_obj.nil? + end + write_attribute(storage_key, nil) instance_variable_set(instance_var, nil) return diff --git a/spec/embeds_many_spec.rb b/spec/embeds_many_spec.rb index b4549f06..bc554c89 100644 --- a/spec/embeds_many_spec.rb +++ b/spec/embeds_many_spec.rb @@ -931,10 +931,22 @@ class PersonWithSingleDefault < CouchbaseOrm::Base expect(person.attachments.first).to be_a(VideoAttachment) end - it 'can use default with empty array behavior' do + it 'uses default when explicitly setting nil' do person = PersonWithDefaultProc.new - person.addresses = nil # Setting to nil converts to [] - expect(person.addresses).to eq([]) + person.addresses = nil + + expect(person.addresses.size).to eq(1) + expect(person.addresses.first.street).to eq('Default St') + expect(person.addresses.first.city).to eq('Default City') + end + + it 'uses default for polymorphic embeds_many when explicitly setting nil' do + person = PolymorphicPersonWithDefault.new + person.attachments = nil + + expect(person.attachments.size).to eq(1) + expect(person.attachments.first).to be_a(ImageAttachment) + expect(person.attachments.first.url).to eq('https://default.com/image.jpg') end it 'default returns array even if proc returns single object' do @@ -942,5 +954,17 @@ class PersonWithSingleDefault < CouchbaseOrm::Base expect(person.addresses).to be_a(Array) expect(person.addresses.size).to eq(1) end + + it 'uses default when fetched record has nil raw data and default returns single object' do + person = PersonWithSingleDefault.create!(addresses: nil) + + loaded = PersonWithSingleDefault.find(person.id) + expect(loaded.addresses).to be_a(Array) + expect(loaded.addresses.size).to eq(1) + expect(loaded.addresses.first).to be_a(DefaultAddress) + expect(loaded.addresses.first.street).to eq('Single') + ensure + person.destroy! if person&.persisted? + end end end diff --git a/spec/embeds_one_spec.rb b/spec/embeds_one_spec.rb index 3843f683..f653de52 100644 --- a/spec/embeds_one_spec.rb +++ b/spec/embeds_one_spec.rb @@ -124,6 +124,20 @@ class PolymorphicPostWithDefault < CouchbaseOrm::Base embeds_one :media, polymorphic: true, default: -> { DefaultImage.new(url: 'default.jpg') } end +class SendNotificationMissionFeature < CouchbaseOrm::Base + attribute :enable, :boolean +end + +class FeaturesContainer < CouchbaseOrm::Base + embeds_one :send_notification_mission, + class_name: 'SendNotificationMissionFeature', + default: -> { SendNotificationMissionFeature.new(enable: true) } +end + +class CompanyWithFeatures < CouchbaseOrm::Base + embeds_one :features, class_name: 'FeaturesContainer', default: -> { FeaturesContainer.new } +end + describe CouchbaseOrm::EmbedsOne do let(:raw_data) { { bio: 'Software Engineer' } } @@ -668,10 +682,11 @@ class PolymorphicPostWithDefault < CouchbaseOrm::Base expect(first_call).to equal(second_call) end - it 'allows explicitly setting to nil to override default' do + it 'uses default when explicitly setting nil' do user = UserWithDefaultProc.new user.profile = nil - expect(user.profile).to be_nil + expect(user.profile).to be_a(DefaultProfile) + expect(user.profile.language).to eq('en') end it 'uses default after reset when no data is present' do @@ -706,6 +721,18 @@ class PolymorphicPostWithDefault < CouchbaseOrm::Base user.destroy! if user&.persisted? end + it 'returns default value when fetched record has empty raw data' do + user = UserWithDefaultProc.new + user.send(:write_attribute, :profile, {}) + user.save! + + loaded = UserWithDefaultProc.find(user.id) + expect(loaded.profile).to be_a(DefaultProfile) + expect(loaded.profile.language).to eq('en') + ensure + user.destroy! if user&.persisted? + end + it 'persists default value to database when saved' do user = UserWithDefaultProc.new expect(user.profile.language).to eq('en') @@ -736,6 +763,25 @@ class PolymorphicPostWithDefault < CouchbaseOrm::Base expect(post.media.url).to eq('default.jpg') end + it 'instantiates nested embeds_one default when nested raw value is nil' do + company = CompanyWithFeatures.new(features: { send_notification_mission: nil }) + + expect(company.features).to be_a(FeaturesContainer) + expect(company.features.send_notification_mission).to be_a(SendNotificationMissionFeature) + expect(company.features.send_notification_mission.enable).to be true + end + + it 'instantiates nested embeds_one default when fetched company has send_notification_mission as nil' do + company = CompanyWithFeatures.create!(features: { send_notification_mission: nil }) + + loaded = CompanyWithFeatures.find(company.id) + expect(loaded.features).to be_a(FeaturesContainer) + expect(loaded.features.send_notification_mission).to be_a(SendNotificationMissionFeature) + expect(loaded.features.send_notification_mission.enable).to be true + ensure + company.destroy! if company&.persisted? + end + it 'does not use default for polymorphic when data is set' do post = PolymorphicPostWithDefault.new(media: { type: 'image', url: 'custom.jpg', caption: 'Custom' }) expect(post.media).to be_a(Image)