Vapor 4 Unable to Update Optional Parent with Put HTPP Request - swift

I have a model called jurisdictions that houses political jurisdictions. Now theres jurisdictions can sit within other jurisdictions, so I have an optional parent jurisdiction...
Now I can create new jurisdictions just fine, but if I attempt to use a put request to update a jurisdictions parent, I get the following error:
FluentKit/OptionalParent.swift:20: Fatal error: OptionalParent relation OptionalParent<Jurisdiction, Jurisdiction>(key: parent_jurisdiction_id) is get-only.
2022-05-18 13:39:12.421882-0700 Run[32583:5555712] FluentKit/OptionalParent.swift:20: Fatal error: OptionalParent relation OptionalParent<Jurisdiction, Jurisdiction>(key: parent_jurisdiction_id) is get-only.```
Jurisdictions Model
import Fluent
import Vapor
final class Jurisdiction: Model, Content {
static let schema = "jurisdictions"
#ID(key: .id)
var id: UUID?
#OptionalParent(key: "parent_jurisdiction_id")
var jurisdiction: Jurisdiction?
#Field(key: "name")
var name: String
#Field(key: "scope")
var scope: String
#Children(for: \.$jurisdiction)
var jurisdictions: [Jurisdiction]
init() { }
init(name:String, scope:String,parentJurisdictionID:UUID? = nil) {
self.name = name
self.scope = scope
self.$jurisdiction.id = parentJurisdictionID
}
}
Jurisdiction Migration
import Foundation
import Vapor
import Fluent
import FluentPostgresDriver
struct CreateJurisdiction: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema("jurisdictions")
.id()
//parent fields
//attribute fields
.field("name", .string, .required)
.field("scope",.string, .required)
//make unique on:
.unique(on: "name", "scope")
//create scheme
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema("jurisdictions").delete()
}
}
struct AddParentJurisdictionToJurisdictions: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema("jurisdictions")
.field("parent_jurisdiction_id", .uuid, .references("jurisdictions", "id"))
.update()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema("jurisdictions")
.deleteField("parent_jurisdiction_id")
.delete()
}
}
Jurisdiction Controller
import Foundation
import Vapor
import Fluent
import FluentPostgresDriver
struct JurisdictionsController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let jurisdictions = routes.grouped("jurisdictions")
jurisdictions.put(use: update)
}
}
//
func update(req:Request) throws -> EventLoopFuture<HTTPStatus> {
let jurisdiction = try req.content.decode(Jurisdiction.self)
return Jurisdiction.find(jurisdiction.id,on: req.db)
.unwrap(or:Abort(.notFound))
.flatMap {
$0.name = jurisdiction.name
$0.scope = jurisdiction.scope
$0.jurisdiction = $0.jurisdiction //if I remove this line it works
return $0.update(on: req.db).transform(to: .ok)
}
}
}
using a put http request looking like this: http://localhost:8080/jurisdictions with the following content produces the error...don't know what to do here.
{
"scope": "locasl",
"id": "0C6D5F6E-0244-4ABE-847F-2AF89CA27C30",
"jurisdiction": {
"id": "128133B0-25FE-4B6C-B211-CE01AA236AF9"
},
"name": "local"
}

With Fluent and relations if you're trying to set properties you need to do it via the property wrapper rather than the property itself. So in your case it should be
$0.$jurisdiction.id = jurisdiction.id
This allows you to update the relation with only the parent ID rather than requiring the whole parent, in the same way as the initialiser.

Related

Manually modifying model property values in vapor 4 response

I have a vapor 4 application. I do a query from database for getting some items and I want to perform some manual calculation based on the returned values before finishing the request. here a sample code of what I am trying to achieve.
final class Todo: Model, Content {
static var schema: String = "todos"
#ID(custom: .id)
var id: Int?
#Field(key: "title")
var title: String
var someValue: Int?
}
/// Allows `Todo` to be used as a dynamic migration.
struct CreateTodo: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema(Todo.schema)
.field("id", .int, .identifier(auto: true))
.field("title", .string, .required)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema(Todo.schema).delete()
}
}
final class TodoController:RouteCollection{
func boot(routes: RoutesBuilder) throws {
routes.get("tmp", use: temp)
}
func temp(_ req:Request) throws -> EventLoopFuture<[Todo]> {
Todo.query(on: req.db).all().map { todos in
todos.map {
$0.someValue = (0...10).randomElement()!
return $0
}
}
}
}
The problem is that those manual changes, aren't available in response. In this case someValue property.
Thanks.
[
{
"title": "item 1",
"id": 1
},
{
"title": "item 2",
"id": 2
}
]
The problem you're hitting is that Models override the Codable implementations. This allows you to do things like passing around parents and not adding children etc.
However, this breaks your case. What you should do, is create a new type if you want to return a Todo with another field that isn't stored in the database, something like:
struct TodoResponse: Content {
let id: Int
let title: String
let someValue: Int
}
And then convert from your database type to your response type in your route handler (this is a pretty common pattern and the recommended way to do it in Vapor)

How to save a model with a specific id in Vapor 3

I'm trying to add some seed data to a table in a migration. For this table I don't want the id value to be generated automatically, but I want to set it manually for each record. How can this be done? My current code looks like the bellow, but the record isn't inserted to the database and no error is thrown when the migration runs.
Model class:
import Foundation
import FluentPostgreSQL
import Vapor
final class PropertyType: PostgreSQLModel {
var id: Int?
var name: String
init(id: Int, name: String) {
self.id = id
self.name = name
}
}
extension PropertyType: Migration { }
extension PropertyType: Content { }
extension PropertyType: Parameter { }
Migration class:
import FluentPostgreSQL
import Vapor
struct AddPropertyTypes: Migration {
typealias Database = PostgreSQLDatabase
static func prepare(on conn: PostgreSQLConnection) -> Future<Void> {
let propertyType1 = PropertyType(id: 1, name: "Einfamilienhaus")
return propertyType.save(on: conn).transform(to: ())
}
static func revert(on conn: PostgreSQLConnection) -> Future<Void> {
let futures = [1].map { id in
return CodePropertyType.query(on: conn).filter(\CodePropertyType.id == id)
.delete()
}
return futures.flatten(on: conn)
}
}
Just replace
propertyType.save(on: conn)
with
propertyType.create(on: conn)

creating a parent child relationship in vapor 3

I am using Vapor 3 to try and create just a sample project where I have a dish, the parent, and the reviews for the dish, the child. All the tutorials that I have been seeing haven't been very clear on how to create the relationship or they are using it in conjecture with leaf. I do not want to use leaf for this, I just want to be able to show all the reviews when for the dish when I give it's id, and it seems that it is different than it was for vapor 2.
My 2 models are Dish and Review
Dish.swift: The parent,
import Foundation
import Vapor
import FluentSQLite
final class Dish: Content {
var id: Int?
var name: String
var course: String
var price: Double
var imageURL: String
var description: String
init(name: String, course: String, price: Double, imageURL: String, description: String) {
self.name = name
self.course = course
self.price = price
self.imageURL = imageURL
self.description = description
}
}
extension Dish {
var reviews: Children<Dish, Review> {
return children(\.dishId)
}
}
extension Dish: Parameter { }
extension Dish: SQLiteModel {
static let entity: String = "Dishes"
}
extension Dish: Migration { }
Review.swift, the child,
import Foundation
import Vapor
import FluentSQLite
final class Review: Content {
var id: Int?
var title: String
var body: String
var dishId: Dish.ID
init(title: String, body: String, dishId: Dish.ID) {
self.title = title
self.body = body
self.dishId = dishId
}
}
extension Review {
var dish: Parent<Review, Dish> {
return parent(\.dishId)
}
}
extension Review: Migration { }
extension Review: SQLiteModel {
static let entity: String = "Reviews"
}
extension Review: Parameter { }
the controller for Dish, DishController,
import Foundation
import Vapor
import FluentSQLite
class DishesController: RouteCollection {
func boot(router: Router) throws {
let dishesRoutes = router.grouped("api/dishes")
dishesRoutes.get("/", use: getAll)
dishesRoutes.get(Dish.parameter, use: getById)
dishesRoutes.post(Dish.self, at: "/", use: createDish)
dishesRoutes.delete(Dish.parameter, use: deleteDish)
}
func deleteDish(req: Request) throws -> Future<Dish> {
return try req.parameters.next(Dish.self).delete(on: req)
}
func createDish(req: Request, dish: Dish) -> Future<Dish> {
return dish.save(on: req)
}
func getAll(req: Request) -> Future<[Dish]> {
return Dish.query(on: req).all()
}
func getById(req: Request) throws -> Future<Dish> {
return try req.parameters.next(Dish.self)
}
}
and the controller for reviews. ReviewController,
import Foundation
import Vapor
import FluentSQLite
class ReviewController: RouteCollection {
func boot(router: Router) throws {
let reviewRoutes = router.grouped("api/reviews")
reviewRoutes.get("/", use: getAll)
reviewRoutes.get(Review.parameter, use: getById)
reviewRoutes.post(Review.self, at: "/", use: createReview)
reviewRoutes.delete(Review.parameter, use: deleteReview)
}
func deleteReview(req: Request) throws -> Future<Review> {
return try req.parameters.next(Review.self).delete(on: req)
}
func createReview(req: Request, review: Review) -> Future<Review> {
return review.save(on: req)
}
func getAll(req: Request) -> Future<[Review]> {
return Review.query(on: req).all()
}
func getById(req: Request) throws -> Future<Review> {
return try req.parameters.next(Review.self)
}
}
this is the routes.swift,
import Vapor
/// Register your application's routes here.
public func routes(_ router: Router) throws {
router.get("/reviews", Dish.parameter,"dish") { request -> Future<Dish> in
return try request.parameters.next(Review.self).flatMap(to: Dish.self) { review in
return review.dish.get(on: request)
}
}
let dishesController = DishesController()
try router.register(collection: dishesController)
let reviewController = ReviewController()
try router.register(collection: reviewController)
}
I just want a simple one to many relationship where one dish can have many reviews, but when I use postman to try and access the reviews for the particular dish, all I get is an error. I know that I used the correct syntax in postman because I can use all the other requests from the controllers just fine, just not any for the relationships. Please tell me what i am missing, because I am getting confused as to what I am doing wrong. If there is anything else I can add please ask.
Thank you very much
If you want to access all reviews for the particular dish, try the following code.
router.get("/dish", Dish.parameter,"reviews") { request -> Future<[Review]> in
return try request.parameters.next(Dish.self).flatMap(to: [Review].self) { (dish) in
return try dish.reviews.query(on: request).all()
}
}
Now In postman, pass a dish id as below:
GET: http://localhost:8080/dish/1/reviews

How to make a Nested Json Model in Vapor Swift Server , Below are the Code for the reference..?

Friend model contain the address reference. Address is saving but not able to retrieve from the end point.
Friend Model
import Foundation
import Vapor
import Fluent
struct Friend: Model {
var exists: Bool = false
var id: Node?
var name: String
var age: Int
var email: String
var residence: FriendAddress
init(name: String, age: Int, email: String, residence: FriendAddress) {
self.name = name
self.age = age
self.email = email
self.residence = residence
}
// NodeInitializable
init(node: Node, in context: Context) throws {
id = try node.extract("_id")
name = try node.extract("name")
age = try node.extract("age")
email = try node.extract("email")
residence = try node.extract("residence")
}
// NodeRepresentable
func makeNode(context: Context) throws -> Node {
return try Node(node: ["_id": id,
"name": name,
"age": age,
"email": email,
"residence": residence
])
}
// Preparation
static func prepare(_ database: Database) throws {
try database.create("friends") { friends in
friends.id()
friends.string("name")
friends.int("age")
friends.string("email")
friends.string("residence")
}
}
static func revert(_ database: Database) throws {
try database.delete("friends")
}
}
Friend Address Model contains owner_id acting as a foreign key (Friend Model)
Friend Addresss Model
import Foundation
import Vapor
import Fluent
struct FriendAddress: Model {
var id: Node?
var owner_id: Node?
var address: String
var address2: String
var pinCode: Int
init(address: String, address2: String, pinCode: Int, owner_id: Node? = nil ) {
self.address = address
self.address2 = address2
self.pinCode = pinCode
self.owner_id = owner_id
}
// NodeInitializable
init(node: Node, in context: Context) throws {
id = try node.extract("_id")
address = try node.extract("address")
address2 = try node.extract("address2")
pinCode = try node.extract("pinCode")
owner_id = try node.extract("owner_id")
}
// NodeRepresentable
func makeNode(context: Context) throws -> Node {
return try Node(node: [
"_id": id,
"address": address,
"address2": address2,
"pinCode": pinCode,
"owner_id": owner_id])
}
// Preparation
static func prepare(_ database: Database) throws {
try database.create("friendAddress") { friendaddress in
friendaddress.id()
friendaddress.string("address")
friendaddress.int("address2")
friendaddress.string("pinCode")
friendaddress.parent(Friend.self, optional: false)
}
}
static func revert(_ database: Database) throws {
try database.delete("friendAddress")
}
}
main.swift
import Vapor
import VaporMongo
import Fluent
import Foundation
let drop = Droplet()
//drop.preparations.append(Friend.self)
drop.preparations = [Friend.self, FriendAddress.self, Pivot<Friend, FriendAddress>.self]
do {
try drop.addProvider(VaporMongo.Provider.self)
} catch {
assertionFailure("Error adding provider: \(error)")
}
let friendController = FriendController()
friendController.addRoutes(drop: drop)
drop.resource("posts", PostController())
drop.run()

What does a `Model` class look like that has a relation?

Using Vapor I want to store a relationship to children. I haven't been able to find any examples of what the class should look like and I'm just guessing on what to do. Can anyone provide an example of a class that has a relationship to a list of other Model objects?
import Vapor
import Fluent
import Foundation
final class Store: Model {
// MARK: - Model
var id: Node?
var exists: Bool = false
var locationIDs: [Node] = [] // No idea if this is right
var name: String
init(name: String, locationIDs: [Node] = []) {
self.id = nil
self.name = name
self.locationIDs = locationIDs
}
init(node: Node, in context: Context) throws {
id = try node.extract("id")
name = try node.extract("name")
// ???
}
func makeNode(context: Context) throws -> Node {
return try Node(node: [
"id": id,
"name": name
// ???
])
}
static func prepare(_ database: Database) throws {
try database.create( "stores" ) { creator in
creator.id()
creator.string("name")
// creator.id("", optional: false) // ???
}
}
static func revert(_ database: Database) throws {
try database.delete("stores")
}
}
Here is my implementation of a User and Group relationship. A user belongs to a group and group has many users, so a simple 1 -> Many relationship.
final public class User: Model {
public var id: Int?
var emailAddress: String
var username: String
var groupId: Int
}
extension User {
var group: Parent<User, Group> {
return parent(\.groupId)
}
}
final public class Group: Model {
public var id: Int?
var name: String
}
extension Group {
var users: Children<Group, Users> {
return children(\.groupId)
}
}
Many <-> Many are similar but use a pivot table in the middle of the relationship.