Skip to main content

Example 2

import Foundation
import XCTest


class BaseQueryBuilder<Model: DomainModel> {

typealias Predicate = (Model) -> (Bool)

func limit(_ limit: Int) -> BaseQueryBuilder<Model> {
return self
}

func filter(_ predicate: @escaping Predicate) -> BaseQueryBuilder<Model> {
return self
}

func fetch() -> [Model] {
preconditionFailure("Should be overridden in subclasses.")
}
}

class RealmQueryBuilder<Model: DomainModel>: BaseQueryBuilder<Model> {

enum Query {
case filter(Predicate)
case limit(Int)
/// ...
}

fileprivate var operations = [Query]()

@discardableResult
override func limit(_ limit: Int) -> RealmQueryBuilder<Model> {
operations.append(Query.limit(limit))
return self
}

@discardableResult
override func filter(_ predicate: @escaping Predicate) -> RealmQueryBuilder<Model> {
operations.append(Query.filter(predicate))
return self
}

override func fetch() -> [Model] {
print("RealmQueryBuilder: Initializing CoreDataProvider with \(operations.count) operations:")
return RealmProvider().fetch(operations)
}
}

class CoreDataQueryBuilder<Model: DomainModel>: BaseQueryBuilder<Model> {

enum Query {
case filter(Predicate)
case limit(Int)
case includesPropertyValues(Bool)
/// ...
}

fileprivate var operations = [Query]()

override func limit(_ limit: Int) -> CoreDataQueryBuilder<Model> {
operations.append(Query.limit(limit))
return self
}

override func filter(_ predicate: @escaping Predicate) -> CoreDataQueryBuilder<Model> {
operations.append(Query.filter(predicate))
return self
}

func includesPropertyValues(_ toggle: Bool) -> CoreDataQueryBuilder<Model> {
operations.append(Query.includesPropertyValues(toggle))
return self
}

override func fetch() -> [Model] {
print("CoreDataQueryBuilder: Initializing CoreDataProvider with \(operations.count) operations.")
return CoreDataProvider().fetch(operations)
}
}


/// Data Providers contain a logic how to fetch models. Builders accumulate
/// operations and then update providers to fetch the data.

class RealmProvider {

func fetch<Model: DomainModel>(_ operations: [RealmQueryBuilder<Model>.Query]) -> [Model] {

print("RealmProvider: Retrieving data from Realm...")

for item in operations {
switch item {
case .filter(_):
print("RealmProvider: executing the 'filter' operation.")
/// Use Realm instance to filter results.
break
case .limit(_):
print("RealmProvider: executing the 'limit' operation.")
/// Use Realm instance to limit results.
break
}
}

/// Return results from Realm
return []
}
}

class CoreDataProvider {

func fetch<Model: DomainModel>(_ operations: [CoreDataQueryBuilder<Model>.Query]) -> [Model] {

/// Create a NSFetchRequest

print("CoreDataProvider: Retrieving data from CoreData...")

for item in operations {
switch item {
case .filter(_):
print("CoreDataProvider: executing the 'filter' operation.")
/// Set a 'predicate' for a NSFetchRequest.
break
case .limit(_):
print("CoreDataProvider: executing the 'limit' operation.")
/// Set a 'fetchLimit' for a NSFetchRequest.
break
case .includesPropertyValues(_):
print("CoreDataProvider: executing the 'includesPropertyValues' operation.")
/// Set an 'includesPropertyValues' for a NSFetchRequest.
break
}
}

/// Execute a NSFetchRequest and return results.
return []
}
}


protocol DomainModel {
/// The protocol groups domain models to the common interface
}

private struct User: DomainModel {
let id: Int
let age: Int
let email: String
}


class BuilderRealWorld: XCTestCase {

func testBuilderRealWorld() {
print("Client: Start fetching data from Realm")
clientCode(builder: RealmQueryBuilder<User>())

print()

print("Client: Start fetching data from CoreData")
clientCode(builder: CoreDataQueryBuilder<User>())
}

fileprivate func clientCode(builder: BaseQueryBuilder<User>) {

let results = builder.filter({ $0.age < 20 })
.limit(1)
.fetch()

print("Client: I have fetched: " + String(results.count) + " records.")
}
}
Client: Start fetching data from Realm
RealmQueryBuilder: Initializing CoreDataProvider with 2 operations:
RealmProvider: Retrieving data from Realm...
RealmProvider: executing the 'filter' operation.
RealmProvider: executing the 'limit' operation.
Client: I have fetched: 0 records.

Client: Start fetching data from CoreData
CoreDataQueryBuilder: Initializing CoreDataProvider with 2 operations.
CoreDataProvider: Retrieving data from CoreData...
CoreDataProvider: executing the 'filter' operation.
CoreDataProvider: executing the 'limit' operation.
Client: I have fetched: 0 records.