Complex Swift Data Relationships...

I am struggling with exactly how to set up SwiftData relationships, beyond the single relationship model...

Let's say I have a school. Each school offers a set of classes. Each class is taught by one teacher and attended by several students. Teachers may teach more than one class, but only at one school. Similarly students may attend more than one class, but only at one school. Classes themselves may be offered at more than one school.

Can someone create a class for School, SchoolClass, Teacher, and Student with id, name, and relationships... I have tried it unsuccessfully about 10 different ways at this point.

My most recent is below... I am struggling getting beyond a school listing in the app, and I'll cross that bridge next. I am just wondering if all the trouble I am having is because I am not smart with the class definitions. And wondering if this is to complex for SwiftData and CoreData is the requirement. This is not a real app, just my way of really trying to get a handle on Swift Data models and Navigation.

I am very new to Swift, and will take any and all suggestions with enthusiasm! Thanks for taking the time.

import Foundation
import SwiftData
@Model
class School: Identifiable {
    var id: UUID = UUID()
    var name: String
    var mascot: String
    var teachers: [Teacher]
    var schoolClasses: [SchoolClass]
    
    init (name: String, mascot: String = "", teachers: [Teacher] = [], schoolClasses: [SchoolClass] = []) {
        self.name = name
        self.mascot = mascot
        self.teachers = teachers
    }
    
    class SchoolClass: Identifiable {
        var id: UUID = UUID()
        var name: String
        var teacher: Teacher?
        var students: [Student] = []
        
        init (name: String, teacher: Teacher? = nil, students: [Student] = []) {
            self.name = name
            self.teacher = teacher
            self.students = students
        }
    }
    
    class Teacher: Identifiable {
        var id: UUID = UUID()
        var name: String
        var tenured: Bool
        var school: School?
        var students: [Student] = []
        
        init (name: String, tenured: Bool = false, students: [Student] = []) {
            self.name = name
            self.tenured = tenured
            self.students = students
        }
    }
    class Student: Identifiable {
        var id: UUID = UUID()
        var name: String
        var grade: Int?
        var teacher: Teacher?
        
        init (name: String, grade: Int? = nil, teacher: Teacher? = nil) {
            self.name = name
            self.grade = grade
            self.teacher = teacher
        }
    }
}

You haven't told us exactly what your issue is but something I spotted with your design that I don't think is correct is the relationship between Teacher and Student. As I see it your design will be simpler and more correct if you complete remove that relationship.

  • A teacher holds (has) one or more classes
  • A student attends (has) one or more classes

So indirectly you have the relationship between Teacher and Student via the SchoolClass model.

A bit unrelated but I prefer to use the @Relationship macro with the inverted argument for one of the relationship properties since it makes the code clearer for me (and it is also helpful for SwiftData)

Thanks... That is exactly the type of feedback I am looking for...

Again, not a real app, but thinking of how to make it easiest to get a class roster, a list of all students for a class or for a school, a list of all teachers for a school, etc. Is this affected later from making this change?

Some will be easy to get like all teachers for a school but others will require a little more work. One important thing to remember is that you add relationships to define your models and how they relate and not for helping with some queries you might want to implement.

That said, one further relationship to consider is between School and Student so that a student can only take classes that belongs to the school they are assigned to.

Another day of refining this model gets me to the following code... Please see if my comments are correct, and if I am missing something...

import Foundation
import SwiftData
// Define School
//  Each School will offer a list of [Schoolclass]
//    Each Schoolclass will have one Teacher and a list of [Student]
//    Each Teacher may teach multiple classes, but only at one unique school
//    Each Student will attend multiple classes but only at one unique school
//      Bonus!!  Start with the assumption that each SchoolClass instance is unique and only offered in one school, but want to expand so that a particular SchoolClass may be offered at more than one school, but have unique Teacher and [Student] for that class at each school

@Model
class School: Identifiable {
    var id: UUID = UUID()
    var name: String
    var mascot: String
    var schoolClasses: [SchoolClass]
    
    // Every school that is created will have a class list, but it may be an empty list
    init (name: String, mascot: String = "",  schoolClasses: [SchoolClass] = []) {
        self.name = name
        self.mascot = mascot
        self.schoolClasses = schoolClasses
    }
}

@Model
class SchoolClass: Identifiable {
    var id: UUID = UUID()
    var name: String
    @Relationship(deleteRule: .cascade, inverse: \School.schoolClasses ) var school: School
    //  If I delete a class, it will delete it from the list of classes offered by its associated school.
    var teacher: Teacher?
    var students: [Student]
    
    //Every class will have a student list, but it may be empty.  Teacher may exist or be nil
    init(id: UUID, name: String, school: School, teacher: Teacher, students: [Student] = []) {
        self.id = id
        self.name = name
        self.school = school
        self.teacher = teacher
        self.students = students
    }
}

@Model
class Teacher: Identifiable {
    var id: UUID = UUID()
    var name: String
    var email: String
    @Relationship(deleteRule: .nullify, inverse: \SchoolClass.teacher ) var schoolClasses: [SchoolClass]
    //If a teacher is deleted, leave any class they are in, and replace teacher with nil
    //Every teacher created will have a class list, but it may be empty
    init(name: String, email: String, schoolClasses: [SchoolClass] = []) {
        self.name = name
        self.email = email
        self.schoolClasses = schoolClasses
    }
}

@Model
class Student: Identifiable {
    var id: UUID = UUID()
    var name: String
    var email: String
    @Relationship(deleteRule: .cascade, inverse: \SchoolClass.students ) var schoolClasses: [SchoolClass]
    //If a student is deleted, remove them from the student listing of every class
    //Every Student created will have a class list, but it may be empty
    init(name: String, email: String, schoolClasses: [SchoolClass] = []) {
        self.name = name
        self.email = email
        self.schoolClasses = schoolClasses
    }
}

I also am curious if anyone has any observations on the following...

  1. Does it matter putting @Relationship everywhere to explicitly specify a delete rule? Did I use it correctly? specifically what happens if I delete a school, will it behave as I commented in code?
  2. How to enforce the teachers and students are only associated with one school
  3. any changes I should make to be able to associate a class with multiple schools, but have a separate teacher and student list for each school? The thought process is to offer a "transcript" for a student so that if they move from one school to another, they are credited for the class. The real idea is simply for me to figure out how to do many to many relationships.

Thanks again to anyone who comments back. I get the single one to many relationship ok, but I cannot find much on complicated relationships like this.

Another few small refinements - school delete rules set up I think

import Foundation
import SwiftData
// Define School
//  Each School will offer a list of [Schoolclass]
//    Each Schoolclass will have one Teacher and a list of [Student]
//    Each Teacher may teach multiple classes, but only at one unique school
//    Each Student will attend multiple classes but only at one unique school
//      Bonus!!  Start with the assumption that each SchoolClass instance is unique and only offered in one school, but want to expand so that a particular SchoolClass may be offered at more than one school, but have unique Teacher and [Student] for that class at each school

@Model
class School: Identifiable {
    var id: UUID = UUID()
    var name: String
    var mascot: String
    @Relationship(deleteRule: .cascade) var schoolClasses: [SchoolClass]
    // If a school is deleted, delete the classes associated with it, but leave the students and teachers all with an empty class list.
    // Every school that is created will have a class list, but it may be an empty list
    init (name: String, mascot: String = "",  schoolClasses: [SchoolClass] = []) {
        self.name = name
        self.mascot = mascot
        self.schoolClasses = schoolClasses
    }
}

@Model
class SchoolClass: Identifiable {
    var id: UUID = UUID()
    var name: String
    @Relationship(deleteRule: .cascade, inverse: \School.schoolClasses ) var school: School
    //  If I delete a class, it will delete it from the list of classes offered by its associated school.
    //  It should delete this class name from the list of classes a teacher is teaching, or the list of classes a each student is attending...
    @Relationship(deleteRule: .cascade) var teacher: Teacher?
    @Relationship(deleteRule: .cascade ) var students: [Student]
    
    //Every class will have a student list, but it may be empty.  Teacher may exist or be nil
    init(id: UUID, name: String, school: School, teacher: Teacher, students: [Student] = []) {
        self.id = id
        self.name = name
        self.school = school
        self.teacher = teacher
        self.students = students
    }
}

@Model
class Teacher: Identifiable {
    var id: UUID = UUID()
    var name: String
    var email: String
    @Relationship(deleteRule: .nullify, inverse: \SchoolClass.teacher ) var schoolClasses: [SchoolClass]
    //If a teacher is deleted, leave any class they are in, and replace teacher with nil
    //Every teacher created will have a class list, but it may be empty
    init(name: String, email: String, schoolClasses: [SchoolClass] = []) {
        self.name = name
        self.email = email
        self.schoolClasses = schoolClasses
    }
}

@Model
class Student: Identifiable {
    var id: UUID = UUID()
    var name: String
    var email: String
    @Relationship(deleteRule: .cascade, inverse: \SchoolClass.students ) var schoolClasses: [SchoolClass]
    //If a student is deleted, remove them from the student listing of every class
    //Every Student created will have a class list, but it may be empty
    init(name: String, email: String, schoolClasses: [SchoolClass] = []) {
        self.name = name
        self.email = email
        self.schoolClasses = schoolClasses
    }
}

It looks much better but be careful with those delete rules, why do you want to delete the teacher and students if you delete a class? Both the teacher and students might be in other classes, right?

I would use nullify in most cases an only use cascade if I know that was exactly what I wanted to do.

Complex Swift Data Relationships...
 
 
Q