Section 5/181 menit

5. @Relationship Lanjutan: Delete Rules dan Inverse

5. @Relationship Lanjutan: Delete Rules dan Inverse

Delete Rules

swift
@Model
final class Author {
    var name: String
    
    // .cascade: hapus Author → hapus semua Post-nya
    @Relationship(deleteRule: .cascade, inverse: \Post.author)
    var posts: [Post] = []
    
    init(name: String) { self.name = name }
}

@Model
final class Post {
    var title: String
    var body: String
    
    // .nullify (default): hapus Author → Post.author = nil
    var author: Author?
    
    // .deny: hapus Tag gagal jika masih ada Post yang memakai tag ini
    @Relationship(deleteRule: .deny, inverse: \Tag.posts)
    var tags: [Tag] = []
    
    init(title: String, body: String) {
        self.title = title
        self.body = body
    }
}

@Model
final class Tag {
    @Attribute(.unique) var name: String
    var posts: [Post] = []
    
    init(name: String) { self.name = name }
}

Delete Rules Summary

Rule Perilaku saat parent dihapus Kapan digunakan
.cascade Semua children ikut terhapus Post → Comments (comment tidak berguna tanpa post)
.nullify Children masih ada, foreign key jadi nil Author → Posts (post tetap ada meski author dihapus)
.deny Penghapusan gagal jika masih ada children Category → Products (tidak boleh hapus category yang masih dipakai)
.noAction Tidak ada tindakan (data inconsistency risk) Jarang dipakai, untuk kontrol manual

Ordered Relationships

swift
@Model
final class Playlist {
    var name: String
    
    // Ordered relationship — urutan dipertahankan
    @Relationship(deleteRule: .cascade, minimumModelCount: 0, maximumModelCount: nil)
    var tracks: [Track] = []
    
    init(name: String) { self.name = name }
    
    func addTrack(_ track: Track, at index: Int? = nil) {
        if let index {
            tracks.insert(track, at: index)
        } else {
            tracks.append(track)
        }
    }
    
    func moveTrack(from source: Int, to destination: Int) {
        let track = tracks.remove(at: source)
        tracks.insert(track, at: destination)
    }
}

@Model
final class Track {
    var title: String
    var duration: TimeInterval
    var playlist: Playlist?
    
    init(title: String, duration: TimeInterval) {
        self.title = title
        self.duration = duration
    }
}

Reciprocal (Circular) Relationships

swift
// Graph relationship: User bisa follow User lain
@Model
final class UserProfile {
    @Attribute(.unique) var username: String
    var displayName: String
    
    // following: user yang kamu follow
    @Relationship(deleteRule: .nullify, inverse: \UserProfile.followers)
    var following: [UserProfile] = []
    
    // followers: user yang follow kamu (inverse dari following)
    var followers: [UserProfile] = []
    
    init(username: String, displayName: String) {
        self.username = username
        self.displayName = displayName
    }
    
    func follow(_ other: UserProfile) {
        guard !following.contains(where: { $0.username == other.username }) else { return }
        following.append(other)
        // followers di other otomatis terupdate karena inverse relationship
    }
}