Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy EmbeddedObject on initializing an unmanaged object #7301

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ x.y.z Release notes (yyyy-MM-dd)
* `UserPublisher` incorrectly bounced all notifications to the main thread instead
of setting up the Combine publisher to correctly receive on the main thread.
([#8132](https://github.com/realm/realm-swift/issues/8132), since 10.21.0)
* Copy EmbeddedObject for embedded object property on initializing unmanaged object ([#6921](https://github.com/realm/realm-cocoa/issues/6921).
* Copying an embedded object to an unmanaged object will not throw ([#6921](https://github.com/realm/realm-cocoa/issues/6921).

<!-- ### Breaking Changes - ONLY INCLUDE FOR NEW MAJOR version -->

### Compatibility
Expand Down
5 changes: 4 additions & 1 deletion Realm/RLMObjectBase.mm
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ - (void)dealloc {
}

static id coerceToObjectType(id obj, Class cls, RLMSchema *schema) {
if ([obj isKindOfClass:cls]) {
if ([obj isKindOfClass:cls] && (![(id)cls isEmbedded] || ![obj realm])) {
return obj;
}
obj = RLMBridgeSwiftValue(obj) ?: obj;
Expand Down Expand Up @@ -537,6 +537,9 @@ id RLMObjectThaw(RLMObjectBase *obj) {
}

id RLMValidatedValueForProperty(id object, NSString *key, NSString *className) {
if ([object isKindOfClass:[RLMObjectBase class]] && ![[[object objectSchema] className] isEqualToString:className]) {
@throw RLMException(@"Invalid value: cannot initialize '%@' with value '%@'", className, object);
}
@try {
return [object valueForKey:key];
}
Expand Down
2 changes: 1 addition & 1 deletion Realm/Tests/DictionaryPropertyTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ - (void)testUnmanaged {
XCTAssertThrows([intDictionary.intObjDictionary sortedResultsUsingKeyPath:@"intCol" ascending:YES], @"Should throw on unmanaged RLMDictionary");

// test unmanaged with literals
__unused DictionaryPropertyObject *obj = [[DictionaryPropertyObject alloc] initWithValue:@[@{}, @{}, @{}, @{@"one": [[IntObject alloc] initWithValue:@[@1]]}]];
__unused DictionaryPropertyObject *obj = [[DictionaryPropertyObject alloc] initWithValue:@[@{}, @{}, @{}, @{@"one": [[EmbeddedIntObject alloc] initWithValue:@[@1]]}, @{@"one": [[IntObject alloc] initWithValue:@[@1]]}]];
}

- (void)testUnmanagedComparision {
Expand Down
8 changes: 8 additions & 0 deletions Realm/Tests/ObjectCreationTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,14 @@ - (void)testInitValidatesNumberTypes {
XCTAssertNoThrow(([[NumberObject alloc] initWithValue:@{@"doubleObj": @DBL_MAX}]));
}

- (void)testInitEmbeddedProperty {
NSArray *failVal = @[@{}, @{}, @{}, @{@"one": [[IntObject alloc] init]}];
XCTAssertThrows([[DictionaryPropertyObject alloc] initWithValue:failVal]);

NSArray *passVal = @[@{}, @{}, @{}, @{@"one": [[EmbeddedIntObject alloc] init]}];
XCTAssertNoThrow([[DictionaryPropertyObject alloc] initWithValue:passVal]);
}

#pragma mark - Create

- (void)testCreateWithArray {
Expand Down
53 changes: 53 additions & 0 deletions RealmSwift/Tests/ObjectCreationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,51 @@ class ObjectCreationTests: TestCase {
realm.cancelWrite()
}

func testCopyEmbeddedObjectFromManagedObjectInSameRealm() {
let realm = try! Realm()
try! realm.write {
let parent = realm.create(EmbeddedParentObject.self, value: [
"object": ["value": 1],
"array": [[2]],
"map": ["some": [3]]
])
// Copy managed object
let copyA = EmbeddedParentObject(value: parent)
realm.add(copyA)

XCTAssertNotEqual(parent, copyA)
XCTAssertEqual(copyA.object!.value, 1)
XCTAssertEqual(copyA.array.count, 1)
XCTAssertEqual(copyA.map.values.count, 1)

let copyB = EmbeddedParentObject()
// Explicit copy of object
copyB.object = EmbeddedTreeObject1(value: parent.object!)
realm.add(copyB)

XCTAssertNotEqual(parent, copyB)
XCTAssertEqual(copyB.object!.value, 1)

let copyC = EmbeddedParentObject()
// Assign of EmbeddedObject
copyC.object = parent.object
assertThrows(realm.add(copyC), "Cannot set a link to an existing managed embedded object")

let parentUnmanaged = EmbeddedParentObject(value: [
"object": ["value": 4],
"array": [[5]],
"map": ["some": [6]]
])
// Do not copy unmanaged object
let copyD = EmbeddedParentObject(value: parentUnmanaged)
XCTAssertTrue(copyD.object === parentUnmanaged.object)
realm.add(copyD)
assertThrows(realm.add(parentUnmanaged), "Cannot set a link to an existing managed embedded object")
pavel-ship-it marked this conversation as resolved.
Show resolved Hide resolved

realm.cancelWrite()
}
}

func testCreateEmbeddedFromManagedObjectInDifferentRealm() {
let realmA = realmWithTestPath()
let realmB = try! Realm()
Expand All @@ -893,6 +938,14 @@ class ObjectCreationTests: TestCase {
realmB.cancelWrite()
}

func testInitEmbeddedProperty() {
let failVal: [Any] = [[], ["one": SwiftIntObject()]]
assertThrows(SwiftDictionaryObject(value: failVal))

let passVal: [Any] = [[], ["one": EmbeddedSwiftIntObject()]]
XCTAssertNoThrow(SwiftDictionaryObject(value: passVal))
}

// test null object
// test null list

Expand Down
9 changes: 9 additions & 0 deletions RealmSwift/Tests/SwiftTestObjects.swift
Original file line number Diff line number Diff line change
Expand Up @@ -856,3 +856,12 @@ class EmbeddedTreeObject3: EmbeddedObject, EmbeddedTreeObject {
let parent3 = LinkingObjects(fromType: EmbeddedTreeObject2.self, property: "child")
let parent4 = LinkingObjects(fromType: EmbeddedTreeObject2.self, property: "children")
}

class EmbeddedSwiftIntObject: EmbeddedObject {
@objc dynamic var intCol = 0
}

class SwiftDictionaryObject: Object {
let intDict = Map<String, SwiftIntObject?>()
let embedIntDict = Map<String, EmbeddedSwiftIntObject?>()
}
1 change: 1 addition & 0 deletions examples/installation/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ case "$COMMAND" in
;;

test-*-*-carthage)
export REALM_CARTHAGE_ARM_DISABLED='YES'
xctest "$PLATFORM" "$LANGUAGE" CarthageExample
;;

Expand Down