Reputation: 617
I'm trying to filter my results based on siblings relationship.
Customer Model
final class Customer: Model, Content {
static var schema: String = "customer"
@ID(custom: .id)
var id: ObjectId?
@Siblings(through: BranchCustomer.self, from: \.$customer, to: \.$branch)
var branches: [Branch]
}
Branch Model
final class Branch: Model, Content {
static let schema: String = "branch"
@ID(custom: .id)
var id: ObjectId?
@Siblings(through: BranchCustomer.self, from: \.$branch, to: \.$customer)
var customers: [Customer]
}
Branch+Customer Pivot
final class BranchCustomer: Model {
static var schema: String = "branch+customer"
@ID(custom: .id)
var id: ObjectId?
@Parent(key: "branch_id")
var branch: Branch
@Parent(key: "customer_id")
var customer: Customer
}
I want to create a query that will return me all the customers that are in one or more of the specified branches, something like the following (of course the following code doesn't compile):
let query = Customer
.query(on: request.db)
.filter(\.$branches ~~ [branch1, branch2])
As I red in the documentation I couldn't find any solution to fit my needs, I also tried to query pivot table instead like that:
let query = BranchCustomer
.query(on: request.db)
.group(.or) { builder in
authorizedUser.branches.forEach { branch in
guard let branchId = branch.id else { return }
builder.filter(\.$branch.$id == branchId)
}
}
.join(Customer.self, on: \BranchCustomer.$customer.$id == \Customer.$id)
.sort(Customer.self, \.$firstName)
.with(\.$customer)
But I'm not quite sure if this is efficient or the right way.
Thank you in advance!
Upvotes: 3
Views: 656
Reputation: 182
The easiest way to filter customers by their associated branches using Vapor 4 is to utilize the join(siblings:) method along with the filter method. Here's how you can achieve this:
let filteredCustomers = try await Customer.query(on: req.db)
.join(siblings: \.$branches)
.filter(Branch.self, \.$id ~~ branchIds)
.all()
Upvotes: 1
Reputation: 121
Or actually - you could be even more succinct... but note I haven't tested all possible ways of initialising a ModelValueFilter with an extension like this. I wouldn't be surprised if it introduced some unexpected behaviour.
extension QueryBuilder {
@discardableResult
public func filter<To: FluentKit.Model, Through: FluentKit.Model>(
siblings: KeyPath<Model, SiblingsProperty<Model, To, Through>>,
_ filter: ModelValueFilter<To>
) -> Self {
self.join(siblings: siblings).filter(To.self, filter)
}
}
It does make for a beautifully simple API though.
let filteredCustomers = try await Customer.query(on: request.db)
.filter(siblings: \.$branches, \.$id ~~ branchIds)
.all()
Upvotes: 1
Reputation: 121
If you wanted to make your query syntax a little cleaner / if you're making a lot of similar queries, you might consider adding a simple extension to Fluent's QueryBuilder class.
For example:
extension QueryBuilder {
@discardableResult
public func filter<
Field: QueryableProperty,
To: FluentKit.Model,
Through: FluentKit.Model,
Values: Collection<Field.Value>
>(
siblings: KeyPath<Model, SiblingsProperty<Model, To, Through>>,
_ fieldKeyPath: KeyPath<To, Field>,
subset: Values,
inverse: Bool = false
) -> Self
where Field.Model == To, Values.Element == Field.Value {
self
.join(siblings: siblings)
.filter(
.extendedPath(
To.path(for: fieldKeyPath),
schema: To.schemaOrAlias,
space: nil
),
.subset(inverse: inverse),
.array(subset.map { .bind($0) })
)
}
}
With your example (and assuming async/await), this would then be usable like so:
let filteredCustomers = try await Customer.query(on: request.db)
.filter(siblings: \.$branches, \.$id, subset: branchIds)
.all()
Upvotes: 2
Reputation: 5620
You need to use a join to bridge across from the customer table to the branch table which then allows you to return all the customers in a certain branch. See https://docs.vapor.codes/fluent/query/#join for more details
Upvotes: 1