Model with property not present in Request
Opened this issue · 3 comments
I have a simple Post
model
// Post.swift
final class Post: MySQLModel, Content, Parameter {
var id: Int?
var title: String
var body: String
var userId: User.ID
}
extension Post: Submittable {
convenience init(_ create: Post.Create) throws {
self.init(id: nil, title: create.title, body: create.body, userId: create.userId)
}
func update(_ update: Post.Submission) throws {
self.title = update.title ?? self.title
self.body = update.body ?? self.body
}
struct Submission: SubmissionType {
let title: String?
let body: String?
}
struct Create: Decodable {
let title: String
let body: String
let userId: User.ID
}
}
extension Post.Submission {
func fieldEntries() throws -> [FieldEntry<Post>] {
return try [
makeFieldEntry(keyPath: \.title, label: "Title", validators: [.count(5...)], isRequired: true),
makeFieldEntry(keyPath: \.body, label: "Body", validators: [.count(10...)], isRequired: true),
]
}
init(_ post: Post?) {
title = post?.title
body = post?.body
}
}
// PostController.swift
...
func create(_ req: Request) throws -> Future<Either<Post, SubmissionValidationError>> {
let user = try req.authenticated(User.self)
let userId = user?.id
return try req.content.decode(Post.Submission.self)
.createValid(on: req)
.save(on: req)
.promoteErrors()
}
...
userId
property needs to be added after a User
obtained from token
. It can not be sent in request parameters. It was the code before using Submissions
func create(_ req: Request) throws -> Future<Post> {
let user = try req.authenticated(User.self)
let userId = user?.id
let postData = try req.content.syncDecode(Post.CreatePost.self)
return Post(title: postData.title, body: postData.body, userId: userId!).save(on: req)
}
Not all the properties of a model are sent via the request, some of them have to be calculated or fetched from DB and then use to create/save the model. Something like willCreate
or willSave
hook will be useful for these kind of situations.
Your situation seems like one that will be common. What if the Request
would be passed into the init(_ create: Create)
and update(_ update: Update)
methods? Then you could authenticate there (or do any other database lookup) and set the user id. Would that solve your problem?
@siemensikkema Thanks, that would solve the problem.
But I think it will cause some inconsistency in codes, as the Request
would pass to init
, all the code that should be written in Controller
section, have to be written in Model
, or some duplicate code will be present in Model
.
Is it possible to append optional parameters to create
and update
methods?
Like this:
init(_ create: Post.Create, userId: userId)
or pass a dictionary like in NotificationCenter
's userInfo
How about the following:
let userID = try req.authenticated(User.self).requireID()
return try req.content
.decode(Post.Submission.self)
.validate(inContext: .create, on: req)
.map { submission in
Create(submission: submission, userId: userID)
}
.map(T.S.init)
For update you can simply change the user after updateValid
and before save
.