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

Delete rule for related entities #59

Merged
merged 4 commits into from
Aug 16, 2024
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
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Although primarily in-memory, SwiftletModel’s data model is Codable, allowing
* [Model Definitions](#model-definitions)
- [How to Save Entities](#how-to-save-entities)
- [How to Delete Entities](#how-to-delete-entities)
* [How to cascade delete](#how-to-cascade-delete)
* [Relationship DeleteRule](#relationship-deleterule)
- [How to Query Entities](#how-to-query-entities)
* [Query with nested models](#query-with-nested-models)
* [Related models query](#related-models-query)
Expand Down Expand Up @@ -219,22 +219,22 @@ chat.delete(from: &context)

Calling `delete(...)` will 
- remove the current instance from the context
- it will nullify all relations
- it will nullify all relations or cascade delete depending on `DeleteRule` attribute
- call `willDelete(...)` and `didDelete(...)` callbacks when needed.

### How to cascade delete
### Relationship DeleteRule

There is a `willDelete(...)` callback that can be utilized for cascade deletion implementation:
DeleteRule allows to specify how the related entities would be treated when current entity is deleted:
- nullify (the default option)
- cascade

```swift
extension Message {
func willDelete(from context: inout Context) throws {
try delete(\.$attachment, inverse: \.$message, from: &context)
}
}

@Relationship(deleteRule: .cascade, inverse: \.message)
var attachment: Attachment?

```
The method is throwing to be able to perform some additional checks before deletion 
and throw an error if something has gone wrong.


## How to Query Entities

Expand Down
7 changes: 7 additions & 0 deletions Sources/SwiftletModel/Relationship/RelationTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ public typealias ToManyRelation<T: EntityModelProtocol,

public enum Relations { }

public extension Relations {
enum DeleteRule {
case cascade
case nullify
}
}

//MARK: - Directionality

public protocol DirectionalityProtocol { }
Expand Down
12 changes: 8 additions & 4 deletions Sources/SwiftletModel/Relationship/Relationship.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public extension Relationship where Directionality == Relations.Mutual,
Constraints == Relations.Optional,
Cardinality == Relations.ToOne<Entity> {

init<EnclosingType>(inverse: KeyPath<Entity, EnclosingType?>) {
init<EnclosingType>(deleteRule: Relations.DeleteRule = .nullify,
inverse: KeyPath<Entity, EnclosingType?>) {
self.init(relation: .none)
}
}
Expand All @@ -48,6 +49,7 @@ public extension Relationship where Directionality == Relations.Mutual,
Cardinality == Relations.ToOne<Entity> {

init<EnclosingType>(_ constraint: Constraint<Constraints>,
deleteRule: Relations.DeleteRule = .nullify,
inverse: KeyPath<Entity, EnclosingType?>) {
self.init(relation: .none)
}
Expand All @@ -57,7 +59,8 @@ public extension Relationship where Directionality == Relations.Mutual,
Constraints == Relations.Required,
Cardinality == Relations.ToMany<Entity> {

init<EnclosingType>(inverse: KeyPath<Entity, EnclosingType?>) {
init<EnclosingType>(deleteRule: Relations.DeleteRule = .nullify,
inverse: KeyPath<Entity, EnclosingType?>) {
self.init(relation: .none)
}
}
Expand All @@ -77,7 +80,8 @@ public extension Relationship where Directionality == Relations.OneWay,
public extension Relationship where Directionality == Relations.OneWay,
Cardinality == Relations.ToOne<Entity> {

init(_ constraint: Constraint<Constraints> = .optional) {
init(_ constraint: Constraint<Constraints> = .optional,
deleteRule: Relations.DeleteRule = .nullify) {
self.init(relation: .none)
}
}
Expand All @@ -86,7 +90,7 @@ public extension Relationship where Directionality == Relations.OneWay,
Cardinality == Relations.ToMany<Entity>,
Constraints == Relations.Required {

init() {
init(deleteRule: Relations.DeleteRule = .nullify) {
self.init(relation: .none)
}

Expand Down
19 changes: 14 additions & 5 deletions Sources/SwiftletModelMacros/EntityModelMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public extension EntityModelMacro {
relationshipAttributes: relationshipAttributes,
optionalProperties: optionalProperties
)

return [syntax]
}
}
Expand Down Expand Up @@ -105,7 +105,14 @@ extension FunctionDeclSyntax {
try willDelete(from: &context)
context.remove(Self.self, id: id)
\(raw: attributes
.map { "detach(\($0.keyPathAttributes.attribute), in: &context)" }
.map {
switch $0.deleteRule {
case .nullify:
"detach(\($0.keyPathAttributes.attribute), in: &context)"
case .cascade:
"try delete(\($0.keyPathAttributes.attribute), from: &context)"
}
}
.joined(separator: "\n")
)
try didDelete(from: &context)
Expand Down Expand Up @@ -199,7 +206,8 @@ private extension VariableDeclSyntax {
propertyName: property,
keyPathAttributes: RelationshipAttributes.KeyPathAttributes(
propertyIdentifier: property
)
),
deleteRule: .nullify
)
}

Expand All @@ -209,12 +217,13 @@ private extension VariableDeclSyntax {
keyPathAttributes: RelationshipAttributes.KeyPathAttributes(
propertyIdentifier: property,
labeledExprListSyntax: keyPathsExprList
)
),
deleteRule: RelationshipAttributes.DeleteRuleAttribute(labeledExprListSyntax: keyPathsExprList)
)
}
return nil
}

func optionalPropertiesAttributes() -> PropertyAttributes? {
for attribute in attributes {

Expand Down
30 changes: 26 additions & 4 deletions Sources/SwiftletModelMacros/RelationshipAttributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,43 @@ struct RelationshipAttributes {
let relationWrapperType: WrapperType
let propertyName: String
let keyPathAttributes: KeyPathAttributes
let deleteRule: DeleteRuleAttribute
}

extension RelationshipAttributes {

enum WrapperType: String, CaseIterable {
case relationship = "Relationship"

var title: String {
rawValue
}

static let allCasesTitleSet: Set<String> = {
Set(Self.allCases.map { $0.title })
}()
}

enum DeleteRuleAttribute: String, CaseIterable {
static let deleteRule = "deleteRule"

case cascade
case nullify

init?(_ expressionString: String) {
let value = Self.allCases.first { expressionString.contains($0.rawValue) }
guard let value else {
return nil
}

self = value
}

init(labeledExprListSyntax: LabeledExprListSyntax) {
self = labeledExprListSyntax
.filter { $0.labelString?.contains(DeleteRuleAttribute.deleteRule) ?? false }
.compactMap { DeleteRuleAttribute($0.expressionString) }
.first ?? .nullify
}
}


enum KeyPathAttributes {
case labeledExpressionList(String)
case propertyIdentifier(String)
Expand Down
6 changes: 1 addition & 5 deletions Tests/SwiftletModelTests/Models/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct Message: Codable, Sendable {
@Relationship(inverse: \.messages)
var chat: Chat?

@Relationship(inverse: \.message)
@Relationship(deleteRule: .cascade, inverse: \.message)
var attachment: Attachment?

@Relationship(inverse: \.replyTo)
Expand All @@ -30,10 +30,6 @@ struct Message: Codable, Sendable {

@Relationship
var viewedBy: [User]? = nil

func willDelete(from context: inout Context) throws {
try delete(\.$attachment, inverse: \.$message, from: &context)
}
}

extension Query where Entity == Message {
Expand Down