swiftfluentvaporvapor-fluentserver-side-swift

How to make a optional query filter in Vapor


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.


Solution

  • 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.