Delete relationship entry but keep it as a property

I have a main model that looks like this (I’m only including the relevant parts):

@Model
final class Food {
    var title: String
    
    @Relationship(deleteRule: .nullify)
    var category: Category?
}

@Model
final class Category {
    @Attribute(.unique) var title: String
    var notes: String
    var color: String
    ...
}

Now, let’s say I create foods with different categories. Later, if a user decides to delete a category, I want the category to be deleted from the database so users will not see it when trying to create new food. However, it should still be persisted as part of foods that users had created already. The only workaround I have found so far is to not use category, but instead use something like categoryTitle, categoryNotes, categoryColor, which makes things finicky.

I tried to make it work, but it crashes. Can this be solved somehow, or is this not how it works?

  • 1

    It sounds like a bad idea and not how you should work with relational data. One possible option could be to have some flag on Category that tells if it still can be selected. Then you could use this flag to filter out inactive categories but for Food items using it the category can still be viewed. But the best/simples solution is to use the relationship as intended, either a Category instance is fully available or it is deleted.

    – 

In your current setup, you’ve defined a relationship between Food and Category using @Relationship(deleteRule: .nullify), which means that when a Category is deleted, the corresponding reference in Food will be set to nil (nullified).

final class Food: Model {
    @ID(key: .id) var id: UUID?
    @Field(key: "title") var title: String
    
    // Persisted properties for category
    @Field(key: "categoryTitle") var categoryTitle: String
    @Field(key: "categoryNotes") var categoryNotes: String
    @Field(key: "categoryColor") var categoryColor: String
    
    // Initialize food with category information
    init(id: UUID? = nil, title: String, categoryTitle: String, categoryNotes: String, categoryColor: String) {
        self.id = id
        self.title = title
        self.categoryTitle = categoryTitle
        self.categoryNotes = categoryNotes
        self.categoryColor = categoryColor
    }
}

final class Category: Model {
    @ID(key: .id) var id: UUID?
    @Field(key: "title") var title: String
    @Field(key: "notes") var notes: String
    @Field(key: "color") var color: String
    
    // Intercept deletion and handle it manually
    func delete(on database: Database) -> EventLoopFuture<Void> {
        return Food.query(on: database)
            .filter(\.$categoryTitle == self.title) // Assuming categoryTitle is used to store category information in Food
            .set(\.$categoryTitle, to: "Deleted")
            .update()
            .flatMap { _ in super.delete(on: database) }
    }
}

This isn’t directly supported by Fluent’s delete rules. So try above

Leave a Comment