From 0baa89cc5e4d0a3bd663774e855b3959c7407f2e Mon Sep 17 00:00:00 2001 From: Alexander Lang Date: Thu, 3 Sep 2026 17:30:00 +0200 Subject: [PATCH 1/3] use .json_create for couch potato objects .new does not make sense here as we are loading json from the database --- lib/couch_potato/persistence/type_caster.rb | 2 -- spec/property_spec.rb | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/couch_potato/persistence/type_caster.rb b/lib/couch_potato/persistence/type_caster.rb index cff3aa1..65ced9e 100644 --- a/lib/couch_potato/persistence/type_caster.rb +++ b/lib/couch_potato/persistence/type_caster.rb @@ -40,8 +40,6 @@ def cast_native(value, type) value.to_d unless value.blank? elsif type == Hash value.to_hash unless value.blank? - elsif type.ancestors.include?(CouchPotato::Persistence) - type.new value unless value.blank? else type.json_create value unless value.blank? end diff --git a/spec/property_spec.rb b/spec/property_spec.rb index 62a91cb..9cbb540 100644 --- a/spec/property_spec.rb +++ b/spec/property_spec.rb @@ -132,8 +132,8 @@ class SportsWatch < Watch expect(w.custom_address[0]).to be_an_instance_of Address2 end - it 'initializes an typed array property from an array of hashes' do - w = Watch.new(custom_address: [{id: 'a1', city: 'Berlin'}]) + it 'initializes a typed array property from an array of hashes' do + w = Watch.new(custom_address: [{_id: 'a1', city: 'Berlin'}]) expect(w.custom_address.map(&:class)).to eq([Address]) expect(w.custom_address.map(&:id)).to eq(['a1']) From 38458b48235e0157377b3776111e55907ddf25fb Mon Sep 17 00:00:00 2001 From: Alexander Lang Date: Thu, 3 Sep 2026 15:36:38 +0000 Subject: [PATCH 2/3] bump version --- CHANGES.md | 3 +++ lib/couch_potato/version.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index cf01881..fa499b9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ # Changes +## 1.22.2 + +- fix instantiating properties that include CouchPotato::Persistence (call .json_create instead of .new) ## 1.22.1 diff --git a/lib/couch_potato/version.rb b/lib/couch_potato/version.rb index dbe05d9..9665c10 100644 --- a/lib/couch_potato/version.rb +++ b/lib/couch_potato/version.rb @@ -1,4 +1,4 @@ module CouchPotato - VERSION = '1.22.1'.freeze + VERSION = '1.22.2'.freeze RSPEC_VERSION = '4.4.0'.freeze end From ec36caeebd27c04af38fb4fc9f7f025e9bd8bde8 Mon Sep 17 00:00:00 2001 From: Alexander Lang Date: Thu, 3 Sep 2026 18:26:02 +0000 Subject: [PATCH 3/3] call .new when assiging new objects, .to_json when loading from database --- .../persistence/simple_property.rb | 6 ++--- lib/couch_potato/persistence/type_caster.rb | 10 ++++--- spec/property_spec.rb | 26 +++++++++++++++++++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/lib/couch_potato/persistence/simple_property.rb b/lib/couch_potato/persistence/simple_property.rb index 8bcf16d..2c3206b 100644 --- a/lib/couch_potato/persistence/simple_property.rb +++ b/lib/couch_potato/persistence/simple_property.rb @@ -13,9 +13,9 @@ def load_attribute_from_document(name) end end - def assign_attribute(name, value) + def assign_attribute(name, value, from_json: false) property = self.class.properties.find_property(name) - typecasted_value = type_caster.cast(value, property.type) + typecasted_value = type_caster.cast(value, property.type, from_json:) send("#{name}_will_change!") unless @skip_dirty_tracking || typecasted_value == send(name) instance_variable_set("@#{name}", typecasted_value) end @@ -35,7 +35,7 @@ def initialize(owner_clazz, name, options = {}) end def build(object, json) - object.send(:assign_attribute, name, json[name]) + object.send(:assign_attribute, name, json[name], from_json: true) end def serialize(json, object) diff --git a/lib/couch_potato/persistence/type_caster.rb b/lib/couch_potato/persistence/type_caster.rb index 65ced9e..eb01bb0 100644 --- a/lib/couch_potato/persistence/type_caster.rb +++ b/lib/couch_potato/persistence/type_caster.rb @@ -6,14 +6,14 @@ module Persistence class TypeCaster #:nodoc: NUMBER_REGEX = /-?\d*\.?\d*/.freeze - def cast(value, type) + def cast(value, type, from_json: false) if type == :boolean cast_boolean(value) elsif type.instance_of?(Array) nested_type = type.first - value&.map { |val| cast_native(val, nested_type) } + value&.map { |val| cast_native(val, nested_type, from_json:) } else - cast_native(value, type) + cast_native(value, type, from_json:) end end @@ -29,7 +29,7 @@ def cast_boolean(value) end end - def cast_native(value, type) + def cast_native(value, type, from_json:) if type && !value.is_a?(type) if %w[Integer Bignum].include?(type.to_s) @@ -40,6 +40,8 @@ def cast_native(value, type) value.to_d unless value.blank? elsif type == Hash value.to_hash unless value.blank? + elsif type.ancestors.include?(CouchPotato::Persistence) && !from_json + type.new value unless value.blank? else type.json_create value unless value.blank? end diff --git a/spec/property_spec.rb b/spec/property_spec.rb index 9cbb540..cd8828d 100644 --- a/spec/property_spec.rb +++ b/spec/property_spec.rb @@ -198,6 +198,32 @@ def it_should_persist value expect(p.ship_address).to be_nil end + it "persists a nested object with non-couch potato attributes delegating to properties" do + class PersonWithName + include CouchPotato::Persistence + + property :first_name + property :last_name + + def name=(value) + self.first_name = value.split(' ').first + self.last_name = value.split(' ').last + end + end + class PersonContainer + include CouchPotato::Persistence + + property :person, type: PersonWithName + end + + container = PersonContainer.new(person: {name: 'John Doe'}) + CouchPotato.database.save_document!(container) + container = CouchPotato.database.load_document(container.id) + + expect(container.person.first_name).to eq('John') + expect(container.person.last_name).to eq('Doe') + end + it "should actually pass the null value down in the JSON document " do p = Person.new p.ship_address = nil