How to make a optional query filter in Vapor

144 Views Asked by At

Here is my code:

var query = Post.query(on: req.db)

if needFilter == true {
     query = query.filter(\.$board.$id == boardId)
}
    
return try await query
                 .with(\.$profile).all()

The above code is a bit complicated. Is there a way to write it like this❓:

return try await query
                 .filter(\.$board.$id == boardId, enable: needFilter)
                 .with(\.$profile).all()

'enable:' means this filter on/off.

Or is there another way to do it? Some convenient way?

Thanks in advance.

2

There are 2 best solutions below

0
Nick On BEST ANSWER

Using queries directly in routes is not a good idea from the repetition, abstraction and testing viewpoints. Using repositories is a much better approach. In this case, it means you can pass both the filter value and the conditional value as parameters. This will give you neater use in routes. I think your original use of the filter inside the if is entirely readable and not complicated, just remove the redundant == true.

struct PostRepository: DatabaseRepository {
    let database: Database

    func filterIf(_ needFilter: Bool, _ boardId: Board.IDValue ) async throws -> [Post] {
        var query = try await Post.query(on: database)
        if needFilter {
            query = query.filter(\.$board.$id == boardId)
        }
        return try await query
            .with(\.$profile).all()
    }
}

extension Application.Repositories {
    var posts: PostRepository {
        guard let storage = storage.makePostRepository
        else { fatalError("PostRepository not configured") }

        return storage(app)
    }

    func use(_ make: @escaping (Application) -> (PostRepository)) {
        storage.makePostRepository = make
    }
}

The link above gives you the rest of the code to enable this and all other repositories in your application. Then you can use it in your route as:

let posts = try await request.posts.filterIf(needFilter, boardId)

This results in a more readable, testable and maintainable solution than the accepted answer.

1
Pincha On

You can achieve that like this:

return try await query
    .filter { needFilter ? $0.$board.$id == boardId : true }
    .with(\.$profile)
    .all() 

Edit: Or if you want a robust way of doing it:

You can create an extension for QueryBuilder that adds a custom filterIf method which takes a Bool parameter enableFilterIf and a closure for the filter condition. Here's an example of how you can do this:

import Vapor

extension QueryBuilder where Model: Fluent.Model {
    @discardableResult
    func filterIf(_ enableFilterIf: Bool, _ condition: (QueryBuilder<Model>) throws -> ()) rethrows -> Self {
        if enableFilterIf {
            try condition(self)
        }
        return self
    }
}

Here's how you can use this extension:

return try await query
    .filterIf(needFilter) { query in
        query.filter(\.$board.$id == boardId)
    }
    .with(\.$profile)
    .all()