1

I've tried mapping, flat mapping, creating an EventLoopFuture of Post, etc... I'm trying to return two db.queries that create separate arrays of content to be rendered on the req.view.render, which returns an EventLoopFuture<View>.

My error is: Cannot convert value of type 'EventLoopFuture<[Posts]>' to expected argument type '[Posts]'.

Here is my code that gets called by the router:

func blogView(req: Request) throws -> EventLoopFuture<View> {
    struct PageContext: Encodable {
        var posts: [Posts]
        var categories: [Categories]
        var title: String
    }
    struct Posts: Encodable {
        var posts: BlogPostModel.ViewContext
        var category: BlogCategoryModel.ViewContext
    }
    struct Categories: Encodable {
        var categoryList: BlogCategoryModel.ViewContext
    }

    let posts = BlogPostModel.query(on: req.db)
        .sort(\.$date, .descending)
        .with(\.$category)
        .all()
        .mapEach {Posts(posts: $0.viewContext, category: $0.category.viewContext)}
    let categories = BlogCategoryModel.query(on: req.db)
        .sort(\.$title)
        .all()
        .mapEach {Categories(categoryList: $0.viewContext)}
    let returnContext = PageContext(posts: posts,categories: categories, title: categories)
    return req.view.render("Blog/Frontend/Blog", returnContext)
}

EDIT: re my comment below. This snippet returns the Posts (and associated Categories). Trying to add a second db.query to the return.

func blogView(req: Request) throws -> EventLoopFuture<View> {
    struct Context: Encodable {
        struct PostWithCategory: Encodable {
            var category: BlogCategoryModel.ViewContext
            var post: BlogPostModel.ViewContext }
        let title: String
        let items: [PostWithCategory]
    }

    return BlogPostModel.query(on: req.db)
        .sort(\.$date, .descending)
        .with(\.$category)
        .all()
        .mapEach {
            Context.PostWithCategory(category: $0.category.viewContext, post: $0.viewContext) 
        }
        .flatMap {
            let context = Context(title: "Blog Posts", items: $0)
            return req.view.render("Blog/Frontend/Blog", context)
        }
}
Caleb Kleveter
  • 11,170
  • 8
  • 62
  • 92
DIV
  • 223
  • 1
  • 2
  • 11
  • Seems clear enough. `posts` is a Future, so you can't assign it as a PageContext's `posts`. I don't know vapor but it looks to me like you have a chain of operators from your Future and you need to get the _output_ of that Future — it needs to actually _run_. You need to _respond_ to the Future running, for example in a `whenComplete` operator. – matt Jul 21 '20 at 20:16
  • yes I get that... I 'm struggling with the vapor syntax. I'm trying to flatten the posts. I would use a .flatMap() in the chain after the .mapeach to flatten the future post to get the post however, it then throws an error on the .mapEach, complaining about NewValue not being inferred. – DIV Jul 21 '20 at 20:31
  • I don't think it's the syntax that's giving you problems. I think you are not grasping what a Future and its accompanying pipeline _is_. It's a chain of operations that will happen at some time in the unknown future. At the end of that chain — not outside the chain, but _in_ it, as its final operator — you can receive the value that pops out the end of the pipeline. That is where you would attach the `whenComplete` operator, and it is in the `whenComplete` operator that you would then create your Posts object. – matt Jul 21 '20 at 20:35
  • But please note that the last line of your code reveals an even greater misconception. You cannot _return_ the value that is generated by this pipeline, for the simple reason that the pipeline will run _in the future_. That's what a Future is! The method will return _before_ the Future has a chance to run and generate its output. So your entire approach here, if I'm not mistaken, is barking up a very wrong tree. (I'm just guessing based on my knowledge of what "asynchronous" means and what a Future is in other frameworks such as Combine.) – matt Jul 21 '20 at 20:36
  • yes... I get that... Here is code that works... I'm just trying to add a second db.query to the chain before it gets returned... This snippet gets my Posts (and associated categories) and returns the values to the Future assigned to the req. What I'm trying to do is also return the array of Categories (not just those assigned to posts). Pls see the snippet I added in my edit just now... – DIV Jul 21 '20 at 20:45
  • I appreciate your help Matt... I'm working with chaining an ".and", which may get me the two futures together before I flatmap them both so I can return the future view. again... I appreciate you weighing in... – DIV Jul 21 '20 at 21:15

1 Answers1

4

Your issue is that your PageContext type has properties of types [Post] and [Categories], but you are trying to initialize a PageContext using EventLoopFuture<[Post]> and EventLoopFuture<[Categories]>. What you can do is use NIO's .and method to combine 2 EventLoopFuture objects into 1:

posts.and(categories) // EventLoopFuture<([Post], [Categories])>

You can then map on this new EventLoopFuture to get the tuple and create your PageContext instance:

return posts.and(categories).flatMap { posts, categories in
    let returnContext = PageContext(posts: posts, categories: categories, title: "Blog Posts")
    return req.view.render("Blog/Frontend/Blog", context)
}
Caleb Kleveter
  • 11,170
  • 8
  • 62
  • 92
  • 1
    Yes! thanks Caleb. I knew I needed to combine the two futures... just wasn't sure how to .flatten both at once. Really appreciate you laying out the full closure too!! @Caleb++. – DIV Jul 22 '20 at 14:48