Pre-populating Related Tables in Vapor

Let’s say we have two related tables: Heat and Technique:


Heat ↤⇉ Technique


Their relation is one - to - many, so for one heat (dry, moist) there are many cooking techniques. But vice versa does not apply.


If we want to pre-populate Technique table, we first need to have Heat table populated.

We also need a Heat.ID for each Technique entity.


Let’s assume that we have the Heat table populated, and that the Technique data looks something like this:

  static let techniques  = [
    "moist" : [
      (name: "sweat", desc: "To cook in fat without browning."),
      (name: "braise", desc: "To cook in a small amount of liquid, after browning"),
      (name: "steam", desc: "To cook foods by exposing directly to steam.")
    ],
    "dry" : [
      (name: "grill", desc: "An underneath cooking method on an open grid."),
      (name: "saute", desc: "To cook quickly with high heat in a small amount of fat"),
      (name: "roast", desc: "To cook foods by surrounding them by, hot, dry air.")
    ],
    "raw" : [
      (name: "raw", desc: "To consume raw.")
    ]
  ]

  1. First we need to fetch the the ID of Heat entity, by it’s name.
  1. Then insert all Technique entities into the database, with the Heat.ID.
  1. We also need to do the reverse.

Fetching the parent’s ID

  static func getHeatID(on connection: PostgreSQLConnection, heatName: String) -> Future<Heat.ID> {
    do {
      // First look up the heat by its name
      return try Heat.query(on: connection)
        .filter(\Heat.name == heatName)
        .first()
        .map(to: Heat.ID.self) { heat in
          guard let heat = heat else {
            throw FluentError(
              identifier: "PopulateTechniques_noSuchHeat",
              reason: "No heat named \(heatName) exists!",
              source: .capture()
            )
          }
          
          // Once we have found the heat, return it's id
          return heat.id!
      }
    }
    catch {
      return connection.eventLoop.newFailedFuture(error: error)
    }
  }

Add entities

  static func addTechniques(on connection: PostgreSQLConnection, toHeatWithName heatName: String, heatTechniques: [(name: String, desc: String)]) -> Future<Void> {
    // Look up the heat's ID
    return getHeatID(on: connection, heatName: heatName)
      .flatMap(to: Void.self) { heatID in
        // Add each technique to the heat
        let futures = heatTechniques.map { touple -> EventLoopFuture<Void> in
          // Insert the Technique
          let name = touple.0
          let desc = touple.1
          return Technique(name: name, description: desc, heatID: heatID)
            .create(on: connection)
            .map(to: Void.self) { _ in return }
        }
        return Future<Void>.andAll(futures, eventLoop: connection.eventLoop)
    }
  }

Remove entities

  static func deleteTechniques(on connection: PostgreSQLConnection, forHeatWithName heatName: String, heatTechniques: [(name: String, desc: String)]) -> Future<Void> {
    // Look up the heat's ID
    return getHeatID(on: connection, heatName: heatName)
      .flatMap(to: Void.self) { heatID in
        // Delete each technique from the heat
        let futures = try heatTechniques.map { touple -> EventLoopFuture<Void> in
          // DELETE the technique if it exists
          let name = touple.0
          return try Technique.query(on: connection)
            .filter(\.heatID == heatID)
            .filter(\.name == name)
            .delete()
        }
        return Future<Void>.andAll(futures, eventLoop: connection.eventLoop)
    }
  }

Implement Migration Requirements

  static func prepare(on connection: PostgreSQLConnection) -> Future<Void> {
    // Insert all heats from techniques
    let futures = techniques.map { heatName, techniqueTouples in
      return addTechniques(on: connection, toHeatWithName: heatName, heatTechniques: techniqueTouples)
    }
    return Future<Void>.andAll(futures, eventLoop: connection.eventLoop)
  }

  static func revert(on connection: PostgreSQLConnection) -> Future<Void> {
    let futures = techniques.map { heatName, techniqueTouples in
      return deleteTechniques(on: connection, forHeatWithName: heatName, heatTechniques: techniqueTouples)
    }
    return Future<Void>.andAll(futures, eventLoop: connection.eventLoop)
  }

Prev: Pre-populating a Database with Data in Vapor

Next: Generic Migrations in Fluent


Vapor 3 Tutorial Start




#pub #vapor #postgresql #ddl #one-to-many #fluent #many-to-one #migration #db #one-to-many #many-to-one #pre-populate