Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions lib/couchbase-orm/utilities/embeds_many.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
18 changes: 14 additions & 4 deletions lib/couchbase-orm/utilities/embeds_one.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
30 changes: 27 additions & 3 deletions spec/embeds_many_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -931,16 +931,40 @@ 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
person = PersonWithSingleDefault.new
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
50 changes: 48 additions & 2 deletions spec/embeds_one_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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' } }

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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)
Expand Down
Loading