Server Side Swift with Vapor - Part 14: Fluent | Ray Wenderlich

In this video you will learn how to take advantage of Fluent to perform powerful queries on your databases.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/4493-server-side-swift-with-vapor/lessons/14

I tried diving a little into the query docs and how it is constructed. Can you explain a little more on how you did req.query[String.self, at: "term"]? The definition of it through Xcode says it returns a QueryContainer(query: http.uri.query ?? "", container: self)

Perhaps my Swift isn’t as good as I thought it was!

Learning lots, thank you

@simonqq the QueryContainer is the computed property on Request for a query. So anything in the URL after the ?foo=bar, which defaults to an empty string if none is supplied. The query container has a subscript implementation here which follows the chain of functions in that file to pull out the query at with the key provided, using the type provided

func search(req : Request) throws -> Future<[Acronym]> {
    guard let searchTerm = req.query[String.self, at : "term"] else {
        throw Abort(.badRequest, reason: "Missing search term in request")
    }
    return Acronym.query(on: req).filter(\.short == searchTerm).all()
}

error :
return Acronym.query(on: req).filter(.short == searchTerm).all()
Type of expression is ambiguous without more context

can you help me?

@lashkari did you import Fluent at the top?

3 Likes

no i’m don’t import Fluent

thanks

1 Like

I got an error trying to test the search:

[ ERROR ] FluentError.invalidID: Could not convert parameter search?term=OMG to type Int (Model.swift:258)

and

[ ERROR ] FluentError.invalidID: Could not convert parameter search?term=Oh%20My%20God to type Int (Model.swift:258)

Here’s the snaps of Rested for those:
https://drive.google.com/open?id=19kkKHGSloOJmibPp-ppOLbuE-bfk7Xjx
https://drive.google.com/open?id=1R_pAR3lxpDGwRUuQD09STdueDnrpShLj

@amyerson there’s a bug with URL queries at the moment, issue is here Query container should not have .wait() on it's decoder · Issue #1567 · vapor/vapor · GitHub

This will need to be fixed before it works. Having said that, the bug you are getting is weird! Could you post your code and your Package.resolved file?

Hello @amyerson, just a quick comment:

return Acronym.query(on: req).filter(.short == searchTerm).all()

Please ignore this if your code is using the “long” for the search.

The search is based on “short” not the “long” and seems the URL you have post is to search the long one. Perhaps you can try with the short instead and see if it works?.

Cheers

Hello @amyerson and @0xtim I am getting the same error now :stuck_out_tongue:

The error points to Model.swift line 258

    guard let id = idType.decode(from: parameter) as? ID else {
        throw FluentError(
            identifier: "invalidID",
            reason: "Could not convert parameter \(parameter) to type `\(ID.self)`",
            source: .capture()
        )
    }

The code I am using is:

func searchHandler(_ req: Request) throws -> Future<[Acronym]> {
	guard let searchTerm = req.query[String.self, at: "term"] else {
		throw Abort(.badRequest, reason: "Missing search term in request")
	}
	return try Acronym.query(on: req).group(.or) { or in
		try or.filter(\.short == searchTerm)
		try or.filter(\.long == searchTerm)
	}.all()
}

Thank you very much in advanced

@fjrivash few things to try!

  1. Try running vapor update to get the latest changes and see if that helps
  2. What request are you sending it?
  3. How are you registering the searchHandler(_:)?

Hello @0xtim,

1.- Done
2.- I have been testing with these 2:
http://localhost:8080/api/acronyms/search?term=Oh My God
http://localhost:8080/api/acronyms/search?term=OMG
3.- acronymRoute.get(“search”, use: searchHandler)

It works now with Rested both URLs. Response
[
{
“id”: 1,
“short”: “OMG”,
“long”: “Oh My God”,
“creatorID”: “CC067DB0-8BD4-42DD-9B9E-0322B2C529A3”
}
]

Thank you very much Tim :smiley: I am loving this course. I really appreciate the time and effort you are putting on bringing Vapor to us and thank you for replying our questions.

Honestly I did not think I could do it. I am on a journey of learning Vapor to write my own backend for my iOS app, so I took RW’s course on iOS App Development and now this one on Vapor.

1 Like

I get an error when running http://localhost:8080/api/acronyms/search?term=OMG

“NIO-ELT-#1 (3): Precondition failed: wait() must not be called when on the EventLoop”

The error is in:

~/Vapor/TILApp/.build/checkouts/swift-nio.git-3108475404973543938/Sources/NIO/EventLoopFuture.swift, line 753

extension EventLoopFuture {
    /// instructions etc.
    public func wait() throws -> T {
            if !(self.eventLoop is EmbeddedEventLoop) {
                precondition(!eventLoop.inEventLoop, "wait() must not be called when on the EventLoop")
        }

    // etc.
}

the offending line of code is in:

~/Vapor/TILApp/.build/checkouts/vapor.git-5492988889259800272/Sources/Vapor/HTTP/QueryContainer.swift, line 57

/// Convenience for accessing a single value from the content
    public func get<D>(_ type: D.Type = D.self, at keyPath: [BasicKeyRepresentable]) throws -> D
        where D: Decodable
    {
        return try requireDecoder().get(at: keyPath.makeBasicKeys(), from: HTTPBody(string: query), on: container).wait()
    }

Here’s the stack above AcronymsController…

21%20PM

Any idea what’s happening?

@geosymani wait() can’t be called on the main loop (a request thread) - that was a bug that should have been fixed. Run vapor update and you should get the latest code that fixes it

1 Like

Hi Tim,

Suppose I wan’t to retrieve multiple Acronyms, but no all. I wan’t to provide a list of acronyms (short field) and expect to have only those returned where

acronym.short in [list_of_acronyms]

For example, I was thinking about something like

http://localhost:8080/api/acronyms/search?term=OMG&term=WTF

which should return all acronyms with short in [OMG, WTF]

Can’t figure out how to do it.

Thanks,

@epinaud it depends on the decoding of the search terms, but you should be able to decode it to [String] I think. Once you’ve done that you can just loop through the array and add the filters to your .or group

@0xtim Can you explain how to do multi-level queries? For example, an Order has multiple OrderPoint elements, and an OrderPoint has a single Point element. When I do my

Order.query(on: req).filter(\Order.month == month).all().flatMap(to: View.self) {

I then need to find all the points related to it. In essence I’d do this directly in the SQL database

SELECT p.abbrev, o.amount 
FROM order_points o
INNER JOIN points p ON o.points_id = p.id
WHERE o.order_id = 1;

You can see a visual of the three tables at this StackOverflow question

@gargoyle has this been answered for you on StackOverflow?

No, I haven’t asked this one there.

It looks like you OrderPoint is a pivot between Orders and Points? If so, can you just use Fluent’s sibling stuff to work?

Otherwise take a look in Fluent at joins - those should help you achieve what you need.