Vapor 4 - eager loading

Hey,

i'm trying to understand how and when exactly eager loading happens. Let's imagine we have following User model:

final class User: Model, Content, Authenticatable {
    static let schema = "users"

    enum Keys {
        static let id = "id"
        static let name = "name"
        static let surname = "surname"
        static let email = "email"
        static let passwordHash = "password_hash"
        static let createdAt = "created_at"
        static let updatedAt = "updated_at"
    }

    @ID(key: Keys.id)
    var id: Int?

    @Field(key: Keys.name)
    var name: String

    @Field(key: Keys.surname)
    var surname: String?

    @Field(key: Keys.email)
    var email: String

    @Field(key: Keys.passwordHash)
    var passwordHash: String

    @Timestamp(key: Keys.createdAt, on: .create)
    var createdAt

    @Timestamp(key: Keys.updatedAt, on: .update)
    var updatedAt

    @Children(for: \.$owner)
    var wallets: [Wallet] // wallets that user owns

    @Siblings(through: WalletGuest.self, from: \.$guest, to: \.$wallet)
    var guestWallets: [Wallet] // wallets that user visits in

    @Children(for: \.$author)
    var ownedTransactions: [Transaction]

    // with neccessary inits as well
}

extension User {
    struct Public: Content { // so that I can share User object without passwordHash
        var id: Int?
        var name: String
        var surname: String?
        var email: String

        var createdAt: Date?
        var updatedAt: Date?

        var wallets: [Wallet] // wallets that user owns
        var guestWallets: [Wallet] // wallets that user visits in

        var ownedTransactions: [Transaction]
    }

    func makePublic() -> Public {
        Public(id: self.id, name: self.name, surname: self.surname, email: self.email, createdAt: self.createdAt, updatedAt: self.updatedAt, wallets: self.wallets, guestWallets: self.guestWallets, ownedTransactions: self.ownedTransactions)
    }
}

And we have following route:

tokenProtected.get("me") { req -> EventLoopFuture<User.Public> in
        let thisUser = try req.auth.require(User.self)

        return thisUser
}

Now I have 2 questions:

  1. this endpoint works fine, however I'm wondering if there's any way I could load missing relation data (from childrens or siblings) into User object like wallets without needing to do whole Query chaining like in second version ?
    I did try:
    print(thisUser.wallets)
    but it crashes with information that I should use it through $ but then
    print(thisUser.$wallets)
    I'm not sure how could I use it, other than through query:
    try thisUser.$wallets.query(on: req.db).all()
    especially when I want to get access into all relation fields inside same object.

  2. Is there any way I could do "deeper" eager loads ?

Wallet looks like this:

final class Wallet: Model, Content {
    static let schema = "wallets"
    // other properties
    @Siblings(through: WalletGuest.self, from: \.$wallet, to: \.$guest)
    var guests: [User] // users that visit this wallet
}

and now I want to print User object with it's wallets and guests of these wallets so:

   User.query(on: req.db)
            .filter(\.$id == thisUser.id!)
            .with(\.$wallets)
            .with(\.$wallets.$guests) // something like that

could give me such response

{
  "id": 1,
  "name": "User Foo",
  "wallets": [
     {
        "id": 1,
        "guests": [
            {
               "id": "5",
               "name": "User Joe",
            }
        ]
     }
  ]
}
1 Like

Hey @macshur, not sure if you're still looking for an answer, but I think the following code should work:

User.query(on: req.db).with(\.$wallets, { $0.with(\.$guests) }).all()

You can pass in your second eager load into the closure.

1 Like