Skip to content

Commit

Permalink
RCOCOA-2238: Allow defining models with only computed properties (#8484)
Browse files Browse the repository at this point in the history
* Add an empty changelog section

* Lift restriction on objects with computed properties

* Fix lint violations, rename class

* Address CR comments
  • Loading branch information
nirinchev authored Mar 5, 2024
1 parent a561458 commit 4b3c72a
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 4 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
x.y.z Release notes (yyyy-MM-dd)
=============================================================
### Enhancements
* None.
* Lifted a limitation that would prevent declaring a model with only computed properties. ([#8414](https://github.com/realm/realm-swift/issues/8414))

### Fixed
* <How to hit and notice issue? what was the impact?> ([#????](https://github.com/realm/realm-swift/issues/????), since v?.?.?)
Expand Down
5 changes: 3 additions & 2 deletions Realm/RLMObjectSchema.mm
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,9 @@ + (instancetype)schemaForObjectClass:(Class)objectClass {

if ([objectClass shouldIncludeInDefaultSchema]
&& schema.isSwiftClass
&& schema.properties.count == 0) {
@throw RLMException(@"No properties are defined for '%@'. Did you remember to mark them with '@objc' in your model?", schema.className);
&& schema.properties.count == 0
&& schema.computedProperties.count == 0) {
@throw RLMException(@"No properties are defined for '%@'. Did you remember to mark them with '@objc' or '@Persisted' in your model?", schema.className);
}
return schema;
}
Expand Down
39 changes: 38 additions & 1 deletion Realm/Tests/Swift/SwiftSchemaTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,24 @@ class NoProps: FakeObject {
// no @objc properties
}

class OnlyComputedSource: RLMObject {
@objc dynamic var link: OnlyComputedTarget?
}

class OnlyComputedTarget: RLMObject {
@objc dynamic var backlinks: RLMLinkingObjects<OnlyComputedSource>?

override class func linkingObjectsProperties() -> [String : RLMPropertyDescriptor] {
return ["backlinks": RLMPropertyDescriptor(with: OnlyComputedSource.self, propertyName: "link")]
}
}

class OnlyComputedNoBacklinksProps: FakeObject {
var computedProperty: String {
return "Test_String"
}
}

@MainActor
class RequiresObjcName: RLMObject {
static var enable = false
Expand Down Expand Up @@ -170,7 +188,26 @@ class SwiftRLMSchemaTests: RLMMultiProcessTestCase {

func testShouldRaiseObjectWithoutProperties() {
assertThrowsWithReasonMatching(RLMObjectSchema(forObjectClass: NoProps.self),
"No properties are defined for 'NoProps'. Did you remember to mark them with '@objc' in your model?")
"No properties are defined for 'NoProps'. Did you remember to mark them with '@objc' or '@Persisted' in your model?")
}

func testShouldNotThrowForObjectWithOnlyBacklinksProps() {
let config = RLMRealmConfiguration.default()
config.objectClasses = [OnlyComputedTarget.self, OnlyComputedSource.self]
config.inMemoryIdentifier = #function
let r = try! RLMRealm(configuration: config)
try! r.transaction {
_ = OnlyComputedTarget.create(in: r, withValue: [])
}

let schema = OnlyComputedTarget().objectSchema
XCTAssertEqual(schema.computedProperties.count, 1)
XCTAssertEqual(schema.properties.count, 0)
}

func testShouldThrowForObjectWithOnlyComputedNoBacklinksProps() {
assertThrowsWithReasonMatching(RLMObjectSchema(forObjectClass: OnlyComputedNoBacklinksProps.self),
"No properties are defined for 'OnlyComputedNoBacklinksProps'. Did you remember to mark them with '@objc' or '@Persisted' in your model?")
}

func testSchemaInitWithLinkedToObjectUsingInitWithValue() {
Expand Down
40 changes: 40 additions & 0 deletions RealmSwift/Tests/SwiftLinkTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,44 @@ class SwiftLinkTests: TestCase {

XCTAssertEqual(0, owners.count)
}

func testLinkingObjectsWithNoPersistedProps() {
let realm = realmWithTestPath()

let target = OnlyComputedProps()

let source1 = LinkToOnlyComputed()
source1.value = 1
source1.link = target

XCTAssertEqual(target.backlinks.count, 0, "Linking objects are not available until the object is persisted")

try! realm.write {
realm.add(source1)
}

XCTAssertEqual(target.backlinks.count, 1)
XCTAssertEqual(target.backlinks.first!.value, source1.value)

let source2 = LinkToOnlyComputed()
source2.value = 2
source2.link = target

XCTAssertEqual(target.backlinks.count, 1, "Linking objects to an unpersisted object are not available")
try! realm.write {
realm.add(source2)
}

XCTAssertEqual(target.backlinks.count, 2)
XCTAssertTrue(target.backlinks.contains(where: { $0.value == 2 }))

let targetWithNoLinks = OnlyComputedProps()
try! realm.write {
// Implicitly verify we can persist a RealmObject with no persisted properties and
// no objects linking to it
realm.add(targetWithNoLinks)
}

XCTAssertEqual(targetWithNoLinks.backlinks.count, 0, "No object is linking to targetWithNoLinks")
}
}
9 changes: 9 additions & 0 deletions RealmSwift/Tests/SwiftTestObjects.swift
Original file line number Diff line number Diff line change
Expand Up @@ -868,3 +868,12 @@ class ObjectWithNestedEmbeddedObject: Object {
private class PrivateObjectSubclass: Object {
@objc dynamic var value = 0
}

class LinkToOnlyComputed: Object {
@Persisted var value: Int = 0
@Persisted var link: OnlyComputedProps?
}

class OnlyComputedProps: Object {
@Persisted(originProperty: "link") var backlinks: LinkingObjects<LinkToOnlyComputed>
}

0 comments on commit 4b3c72a

Please sign in to comment.