-
-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add pagination * add range tests * add docs * run count/all at same time
- Loading branch information
1 parent
d3bb891
commit 7dc374e
Showing
4 changed files
with
228 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
extension QueryBuilder { | ||
/// Returns a single `Page` out of the complete result set according to the supplied `PageRequest`. | ||
/// | ||
/// This method will first `count()` the result set, then request a subset of the results using `range()` and `all()`. | ||
/// - Parameters: | ||
/// - request: Describes which page should be fetched. | ||
/// - Returns: A single `Page` of the result set containing the requested items and page metadata. | ||
public func paginate( | ||
_ request: PageRequest | ||
) -> EventLoopFuture<Page<Model>> { | ||
let count = self.count() | ||
let items = self.copy().range(request.start..<request.end).all() | ||
return items.and(count).map { (models, total) in | ||
Page( | ||
items: models, | ||
metadata: .init( | ||
page: request.page, | ||
per: request.per, | ||
total: total | ||
) | ||
) | ||
} | ||
} | ||
} | ||
|
||
/// A single section of a larger, traversable result set. | ||
public struct Page<T>: Codable where T: Codable { | ||
/// The page's items. Usually models. | ||
public let items: [T] | ||
|
||
/// Metadata containing information about current page, items per page, and total items. | ||
public let metadata: PageMetadata | ||
|
||
/// Creates a new `Page`. | ||
public init(items: [T], metadata: PageMetadata) { | ||
self.items = items | ||
self.metadata = metadata | ||
} | ||
|
||
/// Maps a page's items to a different type using the supplied closure. | ||
public func map<U>(_ transform: (T) throws -> (U)) rethrows -> Page<U> | ||
where U: Codable | ||
{ | ||
try .init( | ||
items: self.items.map(transform), | ||
metadata: self.metadata | ||
) | ||
} | ||
} | ||
|
||
/// Metadata for a given `Page`. | ||
public struct PageMetadata: Codable { | ||
/// Current page number. Starts at `1`. | ||
public let page: Int | ||
|
||
/// Max items per page. | ||
public let per: Int | ||
|
||
/// Total number of items available. | ||
public let total: Int | ||
} | ||
|
||
/// Represents information needed to generate a `Page` from the full result set. | ||
public struct PageRequest: Decodable { | ||
/// Page number to request. Starts at `1`. | ||
public let page: Int | ||
|
||
/// Max items per page. | ||
public let per: Int | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case page = "page" | ||
case per = "per" | ||
} | ||
|
||
/// `Decodable` conformance. | ||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
self.page = try container.decodeIfPresent(Int.self, forKey: .page) ?? 1 | ||
self.per = try container.decodeIfPresent(Int.self, forKey: .per) ?? 10 | ||
} | ||
|
||
/// Crates a new `PageRequest` | ||
/// - Parameters: | ||
/// - page: Page number to request. Starts at `1`. | ||
/// - per: Max items per page. | ||
public init(page: Int, per: Int) { | ||
self.page = page | ||
self.per = per | ||
} | ||
|
||
var start: Int { | ||
(self.page - 1) * self.per | ||
} | ||
|
||
var end: Int { | ||
self.page * self.per | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
extension QueryBuilder { | ||
// MARK: Range | ||
|
||
/// Limits the results of this query to the specified range. | ||
/// | ||
/// query.range(2..<5) // returns at most 3 results, offset by 2 | ||
/// | ||
/// - returns: Query builder for chaining. | ||
public func range(_ range: Range<Int>) -> Self { | ||
return self.range(lower: range.lowerBound, upper: range.upperBound - 1) | ||
} | ||
|
||
/// Limits the results of this query to the specified range. | ||
/// | ||
/// query.range(...5) // returns at most 6 results | ||
/// | ||
/// - returns: Query builder for chaining. | ||
public func range(_ range: PartialRangeThrough<Int>) -> Self { | ||
return self.range(upper: range.upperBound) | ||
} | ||
|
||
/// Limits the results of this query to the specified range. | ||
/// | ||
/// query.range(..<5) // returns at most 5 results | ||
/// | ||
/// - returns: Query builder for chaining. | ||
public func range(_ range: PartialRangeUpTo<Int>) -> Self { | ||
return self.range(upper: range.upperBound - 1) | ||
} | ||
|
||
/// Limits the results of this query to the specified range. | ||
/// | ||
/// query.range(5...) // offsets the result by 5 | ||
/// | ||
/// - returns: Query builder for chaining. | ||
public func range(_ range: PartialRangeFrom<Int>) -> Self { | ||
return self.range(lower: range.lowerBound) | ||
} | ||
|
||
/// Limits the results of this query to the specified range. | ||
/// | ||
/// query.range(2..<5) // returns at most 3 results, offset by 2 | ||
/// | ||
/// - returns: Query builder for chaining. | ||
public func range(_ range: ClosedRange<Int>) -> Self { | ||
return self.range(lower: range.lowerBound, upper: range.upperBound) | ||
} | ||
|
||
/// Limits the results of this query to the specified range. | ||
/// | ||
/// - parameters: | ||
/// - lower: Amount to offset the query by. | ||
/// - upper: `upper` - `lower` = maximum results. | ||
/// - returns: Query builder for chaining. | ||
public func range(lower: Int = 0, upper: Int? = nil) -> Self { | ||
self.query.offsets.append(.count(lower)) | ||
upper.flatMap { upper in | ||
self.query.limits.append(.count((upper - lower) + 1)) | ||
} | ||
return self | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters