diff --git a/PredicateKit/CoreData/NSFetchRequestBuilder.swift b/PredicateKit/CoreData/NSFetchRequestBuilder.swift index aa9b979..a191136 100644 --- a/PredicateKit/CoreData/NSFetchRequestBuilder.swift +++ b/PredicateKit/CoreData/NSFetchRequestBuilder.swift @@ -300,7 +300,7 @@ extension Query: NSExpressionConvertible { // MARK: - KeyPath -internal extension AnyKeyPath { +extension AnyKeyPath { var stringValue: String { // https://github.com/apple/swift/blob/main/stdlib/public/core/KeyPath.swift#L124 guard let value = _kvcKeyPathString else { diff --git a/PredicateKit/CoreData/NSManagedObjectContextExtensions.swift b/PredicateKit/CoreData/NSManagedObjectContextExtensions.swift index 309b0e3..84987ed 100644 --- a/PredicateKit/CoreData/NSManagedObjectContextExtensions.swift +++ b/PredicateKit/CoreData/NSManagedObjectContextExtensions.swift @@ -368,6 +368,9 @@ public struct FetchRequest { /// public func count() throws -> Int { let request: NSFetchRequest = requestBuilder.makeRequest(from: self) + #if DEBUG + debugInspector?.inspect(request.copy() as! NSFetchRequest) + #endif return try context.count(for: request) } @@ -395,7 +398,7 @@ public struct FetchRequest { /// - sectionNameKeyPath - A key path on resulting objects that returns the section name. This will be used to pre-compute the section information. /// - cacheName - Pre-computed section info is cached persistently to a private file under this name. Cached sections are checked to see if the time stamp matches the store, but not if you have illegally mutated the readonly fetch request, predicate, or sort descriptor. Defaults to `nil`. /// - /// - Returns: A fetchedResultsController with objects of type `Entity` matching the criteria specified by the fetch request. + /// - Returns: An NSFetchedResultsController with objects of type `Entity` matching the criteria specified by the fetch request. /// /// ## Example /// @@ -404,17 +407,24 @@ public struct FetchRequest { /// .sorted(by: \.creationDate, .descending) /// .fetchedResultsController(sectionNameKeyPath: \.creationDate) /// - public func fetchedResultsController(sectionNameKeyPath: KeyPath, cacheName: String? = nil) -> NSFetchedResultsController { + public func fetchedResultsController( + sectionNameKeyPath: KeyPath, + cacheName: String? = nil + ) -> NSFetchedResultsController { fetchedResultsController(sectionNameKeyPath: sectionNameKeyPath.stringValue, cacheName: cacheName) } - private func fetchedResultsController(sectionNameKeyPath: String?, cacheName: String? = nil) -> NSFetchedResultsController { + private func fetchedResultsController( + sectionNameKeyPath: String?, + cacheName: String? = nil + ) -> NSFetchedResultsController { let request: NSFetchRequest = requestBuilder.makeRequest(from: self) request.resultType = .managedObjectResultType - return NSFetchedResultsController(fetchRequest: request, - managedObjectContext: context, - sectionNameKeyPath: sectionNameKeyPath, - cacheName: cacheName + return NSFetchedResultsController( + fetchRequest: request, + managedObjectContext: context, + sectionNameKeyPath: sectionNameKeyPath, + cacheName: cacheName ) } diff --git a/PredicateKitTests/CoreDataTests/NSFetchRequestBuilderTests.swift b/PredicateKitTests/CoreDataTests/NSFetchRequestBuilderTests.swift index d7d92ed..711e9de 100644 --- a/PredicateKitTests/CoreDataTests/NSFetchRequestBuilderTests.swift +++ b/PredicateKitTests/CoreDataTests/NSFetchRequestBuilderTests.swift @@ -31,9 +31,9 @@ final class NSFetchRequestBuilderTests: XCTestCase { func testLessThanPredicate() throws { let request = makeRequest(\Data.count < 42) let builder = makeRequestBuilder() - + let result: NSFetchRequest = builder.makeRequest(from: request) - + let comparison = try XCTUnwrap(result.predicate as? NSComparisonPredicate) XCTAssertEqual(comparison.leftExpression, NSExpression(forKeyPath: "count")) XCTAssertEqual(comparison.rightExpression, NSExpression(forConstantValue: 42)) @@ -50,13 +50,13 @@ final class NSFetchRequestBuilderTests: XCTestCase { let comparison = try XCTUnwrap(result.predicate as? NSComparisonPredicate) XCTAssertEqual(comparison.predicateFormat, NSPredicate(format: "relationships[FIRST].count <[c] 42").predicateFormat) } - + func testLessThanOrEqualPredicate() throws { let request = makeRequest(\Data.count <= 42) let builder = makeRequestBuilder() - + let result: NSFetchRequest = builder.makeRequest(from: request) - + let comparison = try XCTUnwrap(result.predicate as? NSComparisonPredicate) XCTAssertEqual(comparison.leftExpression, NSExpression(forKeyPath: "count")) XCTAssertEqual(comparison.rightExpression, NSExpression(forConstantValue: 42)) @@ -96,7 +96,7 @@ final class NSFetchRequestBuilderTests: XCTestCase { let comparison = try XCTUnwrap(result.predicate as? NSComparisonPredicate) XCTAssertEqual(comparison.predicateFormat, NSPredicate(format: "relationships[LAST].count ==[c] 42").predicateFormat) } - + func testNotEqualPredicate() throws { let request = makeRequest(\Data.text != "Hello, World!") let builder = makeRequestBuilder() @@ -109,7 +109,7 @@ final class NSFetchRequestBuilderTests: XCTestCase { XCTAssertEqual(comparison.predicateOperatorType, .notEqualTo) XCTAssertEqual(comparison.comparisonPredicateModifier, .direct) } - + func testArrayElementNotEqualPredicate() throws { let request = makeRequest((\Data.relationships).last(\.count) != 42) let builder = makeRequestBuilder() @@ -119,7 +119,7 @@ final class NSFetchRequestBuilderTests: XCTestCase { let comparison = try XCTUnwrap(result.predicate as? NSComparisonPredicate) XCTAssertEqual(comparison.predicateFormat, NSPredicate(format: "relationships[LAST].count !=[c] 42").predicateFormat) } - + func testGreaterThanOrEqualPredicate() throws { let request = makeRequest(\Data.count >= 42) let builder = makeRequestBuilder() @@ -142,7 +142,7 @@ final class NSFetchRequestBuilderTests: XCTestCase { let comparison = try XCTUnwrap(result.predicate as? NSComparisonPredicate) XCTAssertEqual(comparison.predicateFormat, NSPredicate(format: "relationships[3].count >=[c] 42").predicateFormat) } - + func testGreaterThanPredicate() throws { let request = makeRequest(\Data.count > 42) let builder = makeRequestBuilder() diff --git a/PredicateKitTests/CoreDataTests/NSManagedObjectContextExtensionsTests.swift b/PredicateKitTests/CoreDataTests/NSManagedObjectContextExtensionsTests.swift index 987f67e..8c2b1fd 100644 --- a/PredicateKitTests/CoreDataTests/NSManagedObjectContextExtensionsTests.swift +++ b/PredicateKitTests/CoreDataTests/NSManagedObjectContextExtensionsTests.swift @@ -39,6 +39,10 @@ final class NSManagedObjectContextExtensionsTests: XCTestCase { override func tearDown() { super.tearDown() container.viewContext.deleteAll(Note.self) + container.viewContext.deleteAll(User.self) + container.viewContext.deleteAll(UserAccount.self) + container.viewContext.deleteAll(Profile.self) + container.viewContext.deleteAll(BillingInfo.self) container = nil } @@ -260,7 +264,7 @@ final class NSManagedObjectContextExtensionsTests: XCTestCase { XCTAssertEqual(notes.first?.numberOfViews, 3) } - func testFetchWithPredicateOnAirstArrayElement() throws { + func testFetchWithPredicateOnFirstArrayElement() throws { try container.viewContext.insertNotes( (text: "Hello, World!", creationDate: Date(), numberOfViews: 42, tags: ["greeting", "casual"]), (text: "Goodbye!", creationDate: Date(), numberOfViews: 3, tags: ["casual", "greeting"]) @@ -585,11 +589,9 @@ final class NSManagedObjectContextExtensionsTests: XCTestCase { .fetchedResultsController() try controller.performFetch() - let sectionCount = controller.sections?.count - let itemCount = controller.sections?.first?.numberOfObjects - - XCTAssertEqual(sectionCount, 1) - XCTAssertEqual(itemCount, 2) + XCTAssertNil(controller.sectionNameKeyPath) + XCTAssertEqual(controller.sections?.count, 1) + XCTAssertEqual(controller.sections?.first?.numberOfObjects, 2) } func testFetchedResultsControllerSections() throws { @@ -610,14 +612,10 @@ final class NSManagedObjectContextExtensionsTests: XCTestCase { let sectionCount = controller.sections?.count XCTAssertEqual(sectionCount, 3) - - let itemCount0 = controller.sections?[0].numberOfObjects - let itemCount1 = controller.sections?[1].numberOfObjects - let itemCount2 = controller.sections?[2].numberOfObjects - - XCTAssertEqual(itemCount0, 1) - XCTAssertEqual(itemCount1, 2) - XCTAssertEqual(itemCount2, 1) + XCTAssertEqual(controller.sections?[0].numberOfObjects, 1) + XCTAssertEqual(controller.sections?[1].numberOfObjects, 2) + XCTAssertEqual(controller.sections?[2].numberOfObjects, 1) + XCTAssertEqual(controller.sectionNameKeyPath, "creationDate") } func testNSFetchRequestsAreForwardedToInspector() throws { diff --git a/README.md b/README.md index 26f39f8..b6693a7 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ comparisons and logical operators, literal values, and functions. - [Quick start](#quick-start) - [Fetching objects](#fetching-objects) - [Configuring the fetch](#configuring-the-fetch) + - [Fetching objects with an NSFetchedResultsController](#fetching-objects-with-an-nsfetchedresultscontroller) - [Counting objects](#counting-objects) - [Documentation](#documentation) - [Writing predicates](#writing-predicates) @@ -135,19 +136,6 @@ let notes: [[String: Any]] = try managedObjectContext .result() ``` -### Fetching objects with an NSFetchedResultsController - -Instead of directly fetching results, you can use `fetchedResultsController()` to instantiate an NSFetchedResultsController with the configured fetch. `fetchedResultsController` has two optional parameters: `sectionNameKeyPath` is a KeyPath on the returned objects used to compute section info and `cacheName` is the name of a file to store pre-computed section info. - -###### Example - -```swift -let controller: NSFetchedResultsController = managedObjectContext - .fetch(where: \Note.text == "Hello, World!" && \Note.creationDate < Date()) - .sorted(by: \Note.creationDate, .descending) - .fetchedResultsController(sectionNameKeyPath: \Note.creationDate) -``` - ## Configuring the fetch `fetch(where:)` returns an object of type `FetchRequest`. You can apply a series of modifiers on this object to further configure how the objects should be matched and returned. @@ -167,6 +155,19 @@ let notes: [Note] = try managedObjectContext See [Request modifiers](#request-modifiers) for more about modifiers. +## Fetching objects with an NSFetchedResultsController + +Instead of directly fetching results, you can use `fetchedResultsController()` to instantiate an `NSFetchedResultsController` with the configured fetch. `fetchedResultsController` has two optional parameters: `sectionNameKeyPath` is a [key-path](https://developer.apple.com/documentation/swift/keypath) on the returned objects used to compute section info and `cacheName` is the name of a file to store pre-computed section info. + +###### Example + +```swift +let controller: NSFetchedResultsController = managedObjectContext +.fetch(where: \Note.text == "Hello, World!" && \Note.creationDate < Date()) +.sorted(by: \Note.creationDate, .descending) +.fetchedResultsController(sectionNameKeyPath: \Note.creationDate) +``` + ## Counting objects To count the number of objects matching a predicate, use the function `count(where:)` on an instance of `NSManagedObjectContext`.