Skip to content

Relationships

Jeff Hurray edited this page Apr 25, 2016 · 3 revisions

####Relationship Relationship is a generic struct used to declare relationships between models that conform to SQLiteModel. Its initialization is similar to Expression<T> in that it takes a string value which is used to build the schema.

static let myRelationship = Relationship<OtherModel>("other_model")

SQLiteModel supports:

  • Single relationships Relationship<T>
  • Optional single relationships Relationship<T?>
  • Multiple relationships Relationship<[T]>

Relationships can also be marked as unique.

static let myUniqueRelationship = Relationship<OtherModel>("other_model", unique: true)

Relationships must be added in the buildTable method of the model:

static let myRelationship = Relationship<OtherModel>("other_model")

static func buildTable(tableBuilder: TableBuilder) {
    ...
    tableBuilder.relationship(myRelationship, mappedFrom: self)
    ...
}

The mappedFrom parameter exists to declare the second half of the mapping. Most of the time you will want to map from the models type, but there are times when you may want to hold a reference to a relationship between 2 other models.

####Counts

You can get the count of any multiple relationship with the instance method countForRelationship.

struct Person {
    ...
    static let siblings = Relationship<[Person]>("siblings")
    ...
}

let somePerson = Person.fetchAll().first
let siblingCount = somePerson?.countForRelationship(Person.siblings)

###Putting it all together

To show examples of this, lets build off our Movie model.

struct Movie: SQLiteModel {
    
    var localID: SQLiteModelID = -1

    static let Title = Expression<String>("title")
    static let ReleaseDate = Expression<NSDate>("release_date")
    static let Gross = Expression<Double>("gross")
    
    // Relationships
    static let Director = Relationship<Person>("director")
    // A movie can have a prequel, but it doesn't need to have one. If it has one, that prequel is unique (2 movies cant have the same prequel)
    // Operating off the assumption that Star Wars IV is the prequel of Empire Strikes Back, but not Return of the Jedi
    static let Prequel = Relationship<Movie?>("prequel", unique: true)
    static let Actors = Relationship<[Person]>("actors")

    static func buildTable(tableBuilder: TableBuilder) {
        tableBuilder.column(Title)
        tableBuilder.column(ReleaseDate)
        tableBuilder.column(Gross, defaultValue: 0)
        
        // Relationships
        tableBuilder.relationship(Director, mappedFrom: self)
        tableBuilder.relationship(Prequel, mappedFrom: self)
        tableBuilder.relationship(Actors, mappedFrom: self)
    }
}

Relationships follow the same getter and setter pattern as normal value types. If you want to include relationships when you create a new instance, add them with the relationshipSetters parameter in the new method. Update methods also take relationship setters.

let georgeLucas = try Person.new([Person.Name <- "George Lucas"])
let luke = try Person.new([Person.Name <- "Mark Hammel"])
let leah = try Person.new([Person.Name <- "Carrie Fisher"])
let yoda = try Person.new([Person.Name <- "Yoda"])

let aNewHope = try Movie.new([
        Movie.Title <- "Episode IV: A new Hope",
    ], relationshipSetters: [
        Movie.Director <- georgeLucas,
        Movie.Actors <- [luke, leah],
    ])

let empireStrikesBack = try Movie.new([
        Movie.Title <- "Episode V: The Emprire Strikes Back",
    ], relationshipSetters: [
        Movie.Director <- georgeLucas,
        Movie.Actors <- [luke, leah, yoda],
    ])

// Set
aNewHope.set(Movie.Prequel, value: empireStrikesBack)

// is the same as 

aNewHope <| Movie.Prequel |> empireStrikesBack

// Get
let actors = empireStrikesBack.get(Movie.Actors)

// is the same as

actors = empireStrikesBack => Movie.Actors

Unlike normal value types, relationship sets and gets are not O(1) operations. while they are optimized using caches, there is still some overhead to the operations. because of this, SQLiteModel supplies the setInBackground and getInBackground methods. We also supply custom operators for these methods, the common element being a *.

#####Get in background:

empireStrikesBack.getInBackground(Movie.Actors, completion: { actors in
    print(actors)
})

// is the same as

empireStrikesBack ~* Movie.Actors ~* { actors in
    print(actors)
}

// is the same as

empireStrikesBack ~* (Movie.Actors, { actors in
    print(actors)
})

#####Set in background:

let actors = [luke, leah, yoda]
empireStrikesBack.setInBackground(Movie.Actors, value: actors)

// is the same as 

empireStrikesBack <| Movie.Actors |* actors

Clone this wiki locally