Files

99 lines
3.7 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extension ValueObservation {
/// Notifies only values that dont match the previously observed value, as
/// evaluated by a provided closure.
///
/// - parameter predicate: A closure to evaluate whether two values are
/// equivalent, for purposes of filtering. Return true from this closure
/// to indicate that the second element is a duplicate of the first.
public func removeDuplicates(by predicate: @escaping (Reducer.Value, Reducer.Value) -> Bool)
-> ValueObservation<ValueReducers.RemoveDuplicates<Reducer>>
{
mapReducer { ValueReducers.RemoveDuplicates($0, predicate: predicate) }
}
}
extension ValueObservation where Reducer.Value: Equatable {
/// Notifies only values that dont match the previously observed value.
///
/// For example:
///
/// ```swift
/// // An observation of distinct Player?
/// let observation = ValueObservation
/// .tracking { db in try Player.fetchOne(db, id: 42) }
/// .removeDuplicates()
/// ```
///
/// > Tip: When the observed value does not adopt `Equatable`, and it is
/// > impractical to provide a custom comparison function, you can observe
/// > distinct raw database values such as ``Row`` or ``DatabaseValue``,
/// > before converting them to the desired type. For example, the previous
/// > observation can be rewritten as below:
/// >
/// > ```swift
/// > // An observation of distinct `Player?`
/// > let request = Player.filter(id: 42)
/// > let observation = ValueObservation
/// > .tracking { db in try Row.fetchOne(db, request) }
/// > .removeDuplicates()
/// > .map { row in try row.map(Player.init(row:)) }
/// > ```
/// >
/// > This technique is also available for requests that
/// > involve associations:
/// >
/// > ```swift
/// > struct TeamInfo: Decodable, FetchableRecord {
/// > var team: Team
/// > var players: [Player]
/// > }
/// >
/// > // An observation of distinct `[TeamInfo]`
/// > let request = Team.including(all: Team.players)
/// > let observation = ValueObservation
/// > .tracking { db in try Row.fetchAll(db, request) }
/// > .removeDuplicates() // Row adopts Equatable
/// > .map { rows in try rows.map(TeamInfo.init(row:)) }
/// > ```
public func removeDuplicates()
-> ValueObservation<ValueReducers.RemoveDuplicates<Reducer>>
{
mapReducer { ValueReducers.RemoveDuplicates($0, predicate: ==) }
}
}
extension ValueReducers {
/// A `ValueReducer` that notifies only values that dont match the
/// previously observed value.
///
/// See ``ValueObservation/removeDuplicates()``.
public struct RemoveDuplicates<Base: _ValueReducer>: _ValueReducer {
private var base: Base
private var previousValue: Base.Value?
private var predicate: (Base.Value, Base.Value) -> Bool
init(_ base: Base, predicate: @escaping (Base.Value, Base.Value) -> Bool) {
self.base = base
self.predicate = predicate
}
public mutating func _value(_ fetched: Base.Fetched) throws -> Base.Value? {
guard let value = try base._value(fetched) else {
return nil
}
if let previousValue, predicate(previousValue, value) {
// Don't notify consecutive identical values
return nil
}
self.previousValue = value
return value
}
}
}
extension ValueReducers.RemoveDuplicates: ValueReducer where Base: ValueReducer {
public func _fetch(_ db: Database) throws -> Base.Fetched {
try base._fetch(db)
}
}