From f78895b812a88ed30aa6afbabc1f0af9f3d66f5c Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Fri, 13 Feb 2015 01:37:59 +0900 Subject: [PATCH] querying --- HardcoreData.xcodeproj/project.pbxproj | 68 ++++-- .../xcshareddata/HardcoreData.xccheckout | 53 +++++ HardcoreData/CustomizeQuery.swift | 51 +++++ HardcoreData/DataStack.swift | 62 +++++- HardcoreData/DataTransaction+Querying.swift | 61 ++++++ HardcoreData/DataTransaction.swift | 63 +----- HardcoreData/DefaultLogger.swift | 51 +++++ HardcoreData/FetchClause.swift | 35 +++ HardcoreData/HardcoreData.swift | 203 +++++++++--------- HardcoreData/HardcoreDataLogger.swift | 45 ++++ HardcoreData/NSError+HardcoreData.swift | 6 + .../NSManagedObject+HardcoreData.swift | 67 ++---- .../NSManagedObjectContext+HardcoreData.swift | 153 +++++-------- .../NSManagedObjectContext+Querying.swift | 157 ++++++++++++++ HardcoreData/NSObject+HardcoreData.swift | 3 + ...sistentStoreCoordinator+HardcoreData.swift | 3 + HardcoreData/NotificationObserver.swift | 3 + HardcoreData/ObjectQuery.swift | 134 ------------ HardcoreData/PersistentStoreResult.swift | 6 + ...bjectQueryable.swift => QueryClause.swift} | 24 +-- HardcoreData/SaveResult.swift | 6 + HardcoreData/SortedBy.swift | 104 +++++++++ HardcoreData/Where.swift | 97 +++++++++ HardcoreDataTests/HardcoreDataTests.swift | 63 ++++-- .../Model.xcdatamodel/contents | 13 ++ .../TestEntity2.swift | 16 +- 26 files changed, 1044 insertions(+), 503 deletions(-) create mode 100644 HardcoreData.xcodeproj/project.xcworkspace/xcshareddata/HardcoreData.xccheckout create mode 100644 HardcoreData/CustomizeQuery.swift create mode 100644 HardcoreData/DataTransaction+Querying.swift create mode 100644 HardcoreData/DefaultLogger.swift create mode 100644 HardcoreData/FetchClause.swift create mode 100644 HardcoreData/HardcoreDataLogger.swift create mode 100644 HardcoreData/NSManagedObjectContext+Querying.swift delete mode 100644 HardcoreData/ObjectQuery.swift rename HardcoreData/{ObjectQueryable.swift => QueryClause.swift} (54%) create mode 100644 HardcoreData/SortedBy.swift create mode 100644 HardcoreData/Where.swift rename HardcoreData/ValueQueryable.swift => HardcoreDataTests/TestEntity2.swift (72%) diff --git a/HardcoreData.xcodeproj/project.pbxproj b/HardcoreData.xcodeproj/project.pbxproj index 454b0bb..d7e2c00 100644 --- a/HardcoreData.xcodeproj/project.pbxproj +++ b/HardcoreData.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 2F03A54019C5C6DA005002A5 /* HardcoreDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F03A53F19C5C6DA005002A5 /* HardcoreDataTests.swift */; }; 2F03A54D19C5C872005002A5 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A54C19C5C872005002A5 /* CoreData.framework */; }; 2F291E2719C6D3CF007AF63F /* HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* HardcoreData.swift */; }; + B57078B01A50392D007E33F2 /* FetchClause.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57078AF1A50392D007E33F2 /* FetchClause.swift */; }; B5CFD36E1A0775F000B7885F /* SaveResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFD36D1A0775F000B7885F /* SaveResult.swift */; }; B5CFF23E19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */; }; B5CFF24019FD383100D6DFC4 /* DataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */; }; @@ -22,13 +23,19 @@ B5D399F519FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D399F419FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift */; }; B5D39A0219FD00C9000E91BB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0119FD00C9000E91BB /* Foundation.framework */; }; B5D39A0419FD00DE000E91BB /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0319FD00DE000E91BB /* UIKit.framework */; }; + B5D5E0CF1A4D6AAB006468AF /* TestEntity2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D5E0CE1A4D6AAB006468AF /* TestEntity2.swift */; }; B5D8080E1A3471A500A44484 /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D808021A34715700A44484 /* GCDKit.framework */; }; B5D808161A34947300A44484 /* NotificationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D808151A34947300A44484 /* NotificationObserver.swift */; }; B5D8081A1A3495BD00A44484 /* NSObject+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D808191A3495BD00A44484 /* NSObject+HardcoreData.swift */; }; - B5E186351A3C5CDE002171F0 /* ValueQueryable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E186341A3C5CDE002171F0 /* ValueQueryable.swift */; }; + B5D853791A8CF6930055EB32 /* QueryClause.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D853781A8CF6930055EB32 /* QueryClause.swift */; }; + B5E126551A7DCE1400AD8B39 /* Where.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E126541A7DCE1400AD8B39 /* Where.swift */; }; + B5E126571A7DCE5900AD8B39 /* SortedBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E126561A7DCE5900AD8B39 /* SortedBy.swift */; }; B5E209E01A0726460089C9D4 /* NSManagedObject+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E209DF1A0726460089C9D4 /* NSManagedObject+HardcoreData.swift */; }; - B5E472271A35E84700804CE1 /* ObjectQueryable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E472261A35E84700804CE1 /* ObjectQueryable.swift */; }; - B5F539901A17A6FC00EC763B /* ObjectQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F5398F1A17A6FC00EC763B /* ObjectQuery.swift */; }; + B5F409E91A8B11CE00A228EA /* HardcoreDataLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F409E81A8B11CE00A228EA /* HardcoreDataLogger.swift */; }; + B5F409EB1A8B199600A228EA /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F409EA1A8B199600A228EA /* DefaultLogger.swift */; }; + B5F409ED1A8B200700A228EA /* NSManagedObjectContext+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F409EC1A8B200700A228EA /* NSManagedObjectContext+Querying.swift */; }; + B5F409EF1A8B243D00A228EA /* DataTransaction+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F409EE1A8B243D00A228EA /* DataTransaction+Querying.swift */; }; + B5F409F11A8B27A600A228EA /* CustomizeQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F409F01A8B27A600A228EA /* CustomizeQuery.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -64,6 +71,7 @@ 2F03A53F19C5C6DA005002A5 /* HardcoreDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardcoreDataTests.swift; sourceTree = ""; }; 2F03A54C19C5C872005002A5 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 2F291E2619C6D3CF007AF63F /* HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HardcoreData.swift; sourceTree = ""; }; + B57078AF1A50392D007E33F2 /* FetchClause.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchClause.swift; sourceTree = ""; }; B5CFD36D1A0775F000B7885F /* SaveResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveResult.swift; sourceTree = ""; }; B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+HardcoreData.swift"; sourceTree = ""; }; B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataTransaction.swift; sourceTree = ""; }; @@ -75,14 +83,20 @@ B5D399F419FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSPersistentStoreCoordinator+HardcoreData.swift"; sourceTree = ""; }; B5D39A0119FD00C9000E91BB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; B5D39A0319FD00DE000E91BB /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + B5D5E0CE1A4D6AAB006468AF /* TestEntity2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity2.swift; sourceTree = ""; }; B5D806C51A34715700A44484 /* GCDKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = GCDKit.xcodeproj; sourceTree = ""; }; B5D808151A34947300A44484 /* NotificationObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationObserver.swift; sourceTree = ""; }; B5D808191A3495BD00A44484 /* NSObject+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSObject+HardcoreData.swift"; sourceTree = ""; }; - B5E186341A3C5CDE002171F0 /* ValueQueryable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueQueryable.swift; sourceTree = ""; }; + B5D853781A8CF6930055EB32 /* QueryClause.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryClause.swift; sourceTree = ""; }; + B5E126541A7DCE1400AD8B39 /* Where.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Where.swift; sourceTree = ""; }; + B5E126561A7DCE5900AD8B39 /* SortedBy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortedBy.swift; sourceTree = ""; }; B5E209DF1A0726460089C9D4 /* NSManagedObject+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+HardcoreData.swift"; sourceTree = ""; }; - B5E472261A35E84700804CE1 /* ObjectQueryable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectQueryable.swift; sourceTree = ""; }; B5F3D98419F3EB8E009690A6 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; - B5F5398F1A17A6FC00EC763B /* ObjectQuery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectQuery.swift; sourceTree = ""; }; + B5F409E81A8B11CE00A228EA /* HardcoreDataLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HardcoreDataLogger.swift; sourceTree = ""; }; + B5F409EA1A8B199600A228EA /* DefaultLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultLogger.swift; sourceTree = ""; }; + B5F409EC1A8B200700A228EA /* NSManagedObjectContext+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Querying.swift"; sourceTree = ""; }; + B5F409EE1A8B243D00A228EA /* DataTransaction+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataTransaction+Querying.swift"; sourceTree = ""; }; + B5F409F01A8B27A600A228EA /* CustomizeQuery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeQuery.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -138,11 +152,10 @@ B5E209DF1A0726460089C9D4 /* NSManagedObject+HardcoreData.swift */, B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */, B5D399F419FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift */, - B5F5398F1A17A6FC00EC763B /* ObjectQuery.swift */, - B5E472261A35E84700804CE1 /* ObjectQueryable.swift */, - B5E186341A3C5CDE002171F0 /* ValueQueryable.swift */, B5D1E22919FA9E63003B2874 /* PersistentStoreResult.swift */, B5CFD36D1A0775F000B7885F /* SaveResult.swift */, + B5F409E51A8B11B600A228EA /* Logging */, + B5E126531A7DCCE400AD8B39 /* Fetching and Querying */, B5D808141A34945A00A44484 /* Internal */, 2F03A53319C5C6DA005002A5 /* Supporting Files */, ); @@ -163,6 +176,7 @@ children = ( 2F03A53F19C5C6DA005002A5 /* HardcoreDataTests.swift */, B5D372851A39CDDB00F583D9 /* TestEntity1.swift */, + B5D5E0CE1A4D6AAB006468AF /* TestEntity2.swift */, 2F03A53D19C5C6DA005002A5 /* Supporting Files */, ); path = HardcoreDataTests; @@ -221,6 +235,29 @@ name = Internal; sourceTree = ""; }; + B5E126531A7DCCE400AD8B39 /* Fetching and Querying */ = { + isa = PBXGroup; + children = ( + B57078AF1A50392D007E33F2 /* FetchClause.swift */, + B5D853781A8CF6930055EB32 /* QueryClause.swift */, + B5E126541A7DCE1400AD8B39 /* Where.swift */, + B5E126561A7DCE5900AD8B39 /* SortedBy.swift */, + B5F409F01A8B27A600A228EA /* CustomizeQuery.swift */, + B5F409EC1A8B200700A228EA /* NSManagedObjectContext+Querying.swift */, + B5F409EE1A8B243D00A228EA /* DataTransaction+Querying.swift */, + ); + name = "Fetching and Querying"; + sourceTree = ""; + }; + B5F409E51A8B11B600A228EA /* Logging */ = { + isa = PBXGroup; + children = ( + B5F409E81A8B11CE00A228EA /* HardcoreDataLogger.swift */, + B5F409EA1A8B199600A228EA /* DefaultLogger.swift */, + ); + name = Logging; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -352,20 +389,26 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B5F539901A17A6FC00EC763B /* ObjectQuery.swift in Sources */, B5CFF24019FD383100D6DFC4 /* DataTransaction.swift in Sources */, B5D399F519FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift in Sources */, B5CFD36E1A0775F000B7885F /* SaveResult.swift in Sources */, B5D1E22C19FA9FBC003B2874 /* NSError+HardcoreData.swift in Sources */, B5CFF23E19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift in Sources */, - B5E186351A3C5CDE002171F0 /* ValueQueryable.swift in Sources */, + B5E126571A7DCE5900AD8B39 /* SortedBy.swift in Sources */, + B5F409EF1A8B243D00A228EA /* DataTransaction+Querying.swift in Sources */, + B5D853791A8CF6930055EB32 /* QueryClause.swift in Sources */, 2F291E2719C6D3CF007AF63F /* HardcoreData.swift in Sources */, + B5F409E91A8B11CE00A228EA /* HardcoreDataLogger.swift in Sources */, B5D8081A1A3495BD00A44484 /* NSObject+HardcoreData.swift in Sources */, B5E209E01A0726460089C9D4 /* NSManagedObject+HardcoreData.swift in Sources */, B5D1E22A19FA9E63003B2874 /* PersistentStoreResult.swift in Sources */, - B5E472271A35E84700804CE1 /* ObjectQueryable.swift in Sources */, + B5F409F11A8B27A600A228EA /* CustomizeQuery.swift in Sources */, + B5F409EB1A8B199600A228EA /* DefaultLogger.swift in Sources */, B5D399F119FC818E000E91BB /* DataStack.swift in Sources */, B5D808161A34947300A44484 /* NotificationObserver.swift in Sources */, + B57078B01A50392D007E33F2 /* FetchClause.swift in Sources */, + B5F409ED1A8B200700A228EA /* NSManagedObjectContext+Querying.swift in Sources */, + B5E126551A7DCE1400AD8B39 /* Where.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -376,6 +419,7 @@ 2F03A54019C5C6DA005002A5 /* HardcoreDataTests.swift in Sources */, B5D372861A39CDDB00F583D9 /* TestEntity1.swift in Sources */, B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */, + B5D5E0CF1A4D6AAB006468AF /* TestEntity2.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/HardcoreData.xcodeproj/project.xcworkspace/xcshareddata/HardcoreData.xccheckout b/HardcoreData.xcodeproj/project.xcworkspace/xcshareddata/HardcoreData.xccheckout new file mode 100644 index 0000000..b34317e --- /dev/null +++ b/HardcoreData.xcodeproj/project.xcworkspace/xcshareddata/HardcoreData.xccheckout @@ -0,0 +1,53 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + FE56D6CC-DFA6-4581-B49D-F2486374A545 + IDESourceControlProjectName + HardcoreData + IDESourceControlProjectOriginsDictionary + + 4B60F1BCB491FF717C56441AE7783C74F417BE48 + github.com:JohnEstropia/HardcoreData.git + 8B2E522D57154DFA93A06982C36315ECBEA4FA97 + github.com:JohnEstropia/GCDKit.git + + IDESourceControlProjectPath + HardcoreData.xcodeproj + IDESourceControlProjectRelativeInstallPathDictionary + + 4B60F1BCB491FF717C56441AE7783C74F417BE48 + ../.. + 8B2E522D57154DFA93A06982C36315ECBEA4FA97 + ../..Libraries/GCDKit + + IDESourceControlProjectURL + github.com:JohnEstropia/HardcoreData.git + IDESourceControlProjectVersion + 111 + IDESourceControlProjectWCCIdentifier + 4B60F1BCB491FF717C56441AE7783C74F417BE48 + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + 8B2E522D57154DFA93A06982C36315ECBEA4FA97 + IDESourceControlWCCName + GCDKit + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + 4B60F1BCB491FF717C56441AE7783C74F417BE48 + IDESourceControlWCCName + HardcoreData + + + + diff --git a/HardcoreData/CustomizeQuery.swift b/HardcoreData/CustomizeQuery.swift new file mode 100644 index 0000000..73e9ba3 --- /dev/null +++ b/HardcoreData/CustomizeQuery.swift @@ -0,0 +1,51 @@ +// +// CustomizeQuery.swift +// HardcoreData +// +// Copyright (c) 2015 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation +import CoreData + + +// MARK: - CustomizeQuery + +public struct CustomizeQuery: FetchClause { + + // MARK: Public + + public init(_ customization: (fetchRequest: NSFetchRequest) -> Void) { + + self.customization = customization + } + + + // MARK: QueryClause + + public func applyToFetchRequest(fetchRequest: NSFetchRequest) { + + self.customization(fetchRequest: fetchRequest) + } + + + private let customization: (fetchRequest: NSFetchRequest) -> Void +} diff --git a/HardcoreData/DataStack.swift b/HardcoreData/DataStack.swift index c8adb55..aad6c12 100644 --- a/HardcoreData/DataStack.swift +++ b/HardcoreData/DataStack.swift @@ -28,11 +28,13 @@ import CoreData import GCDKit -private let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first as NSURL +private let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first as! NSURL -private let applicationName = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String ?? "CoreData" +private let applicationName = ((NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData") +// MARK: DataStack + /** The DataStack encapsulates the data model for the Core Data stack. Each DataStack can have multiple data stores, usually specified as a "Configuration" in the model editor. Behind the scenes, the DataStack manages its own NSPersistentStoreCoordinator, a root NSManagedObjectContext for disk saves, and a shared NSManagedObjectContext acting as a model interface for NSManagedObjects. */ @@ -283,3 +285,59 @@ public class DataStack: NSObject { private let mainContext: NSManagedObjectContext private let transactionQueue: GCDQueue; } + + +// MARK: - DataStack+DataContextProvider + +//extension DataStack: ObjectQueryable { +// +// public func firstObject(entity: T.Type) -> T? { +// +// return self.mainContext.firstObject(entity) +// } +// +// public func firstObject(entity: T.Type, customizeFetch: FetchRequestCustomization?) -> T? { +// +// return self.mainContext.firstObject(entity, customizeFetch: customizeFetch) +// } +// +// public func firstObject(query: ObjectQuery) -> T? { +// +// return self.mainContext.firstObject(query) +// } +// +// public func firstObject(query: ObjectQuery, customizeFetch: FetchRequestCustomization?) -> T? { +// +// return self.mainContext.firstObject(query, customizeFetch: customizeFetch) +// } +// +// public func allObjects(entity: T.Type) -> [T]? { +// +// return self.mainContext.allObjects(entity) +// } +// +// public func allObjects(entity: T.Type, customizeFetch: FetchRequestCustomization?) -> [T]? { +// +// return self.mainContext.allObjects(entity, customizeFetch: customizeFetch) +// } +// +// public func allObjects(query: ObjectQuery) -> [T]? { +// +// return self.mainContext.allObjects(query) +// } +// +// public func allObjects(query: ObjectQuery, customizeFetch: FetchRequestCustomization?) -> [T]? { +// +// return self.mainContext.allObjects(query, customizeFetch: customizeFetch) +// } +// +// public func countObjects(entity: T.Type) -> Int { +// +// return self.mainContext.countObjects(entity) +// } +// +// public func countObjects(query: ObjectQuery) -> Int { +// +// return self.mainContext.countObjects(query) +// } +//} diff --git a/HardcoreData/DataTransaction+Querying.swift b/HardcoreData/DataTransaction+Querying.swift new file mode 100644 index 0000000..c7ea244 --- /dev/null +++ b/HardcoreData/DataTransaction+Querying.swift @@ -0,0 +1,61 @@ +// +// DataTransaction+Querying.swift +// HardcoreData +// +// Copyright (c) 2015 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation +import CoreData + + +extension DataTransaction { + + public func fetchOne(entity: T.Type, _ queryClauses: FetchClause...) -> T? { + + return self.context.fetchOne(entity, queryClauses) + } + + public func fetchOne(entity: T.Type, _ queryClauses: [FetchClause]) -> T? { + + return self.context.fetchOne(entity, queryClauses) + } + + public func fetchAll(entity: T.Type, _ queryClauses: FetchClause...) -> [T]? { + + return self.context.fetchAll(entity, queryClauses) + } + + public func fetchAll(entity: T.Type, _ queryClauses: [FetchClause]) -> [T]? { + + return self.context.fetchAll(entity, queryClauses) + } + + public func queryCount(entity: T.Type, _ queryClauses: FetchClause...) -> Int { + + return self.context.queryCount(entity, queryClauses) + } + + public func queryCount(entity: T.Type, _ queryClauses: [FetchClause]) -> Int { + + return self.context.queryCount(entity, queryClauses) + } +} \ No newline at end of file diff --git a/HardcoreData/DataTransaction.swift b/HardcoreData/DataTransaction.swift index a0f5d4d..f1566a4 100644 --- a/HardcoreData/DataTransaction.swift +++ b/HardcoreData/DataTransaction.swift @@ -27,6 +27,9 @@ import Foundation import CoreData import GCDKit + +// MARK: - DataTransaction + /** The DataTransaction provides an interface for NSManagedObject creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from DataStack.performTransaction(_:), or from HardcoreData.performTransaction(_:). */ @@ -60,7 +63,7 @@ public final class DataTransaction { :param: object the NSManagedObject type to be edited :returns: an editable proxy for the specified NSManagedObject. */ - public func update(object: T) -> T? { + public func fetch(object: T) -> T? { HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to update an NSManagedObject outside a transaction queue.") HardcoreData.assert(!self.isCommitted, "Attempted to update an NSManagedObject from an already committed DataTransaction.") @@ -132,6 +135,8 @@ public final class DataTransaction { self.transactionQueue = queue self.context = mainContext.temporaryContext() self.closure = closure + + self.context.parentTransaction = self } internal func perform() { @@ -160,59 +165,3 @@ public final class DataTransaction { private let transactionQueue: GCDQueue private let closure: (transaction: DataTransaction) -> () } - - -// MARK: - DataContextProvider - -extension DataTransaction: ObjectQueryable { - - public func findFirst(entity: T.Type) -> T? { - - return self.context.findFirst(entity) - } - - public func findFirst(entity: T.Type, customizeFetch: FetchRequestCustomization?) -> T? { - - return self.context.findFirst(entity, customizeFetch: customizeFetch) - } - - public func findFirst(query: ObjectQuery) -> T? { - - return self.context.findFirst(query) - } - - public func findFirst(query: ObjectQuery, customizeFetch: FetchRequestCustomization?) -> T? { - - return self.context.findFirst(query, customizeFetch: customizeFetch) - } - - public func findAll(entity: T.Type) -> [T]? { - - return self.context.findAll(entity) - } - - public func findAll(entity: T.Type, customizeFetch: FetchRequestCustomization?) -> [T]? { - - return self.context.findAll(entity, customizeFetch: customizeFetch) - } - - public func findAll(query: ObjectQuery) -> [T]? { - - return self.context.findAll(query) - } - - public func findAll(query: ObjectQuery, customizeFetch: FetchRequestCustomization?) -> [T]? { - - return self.context.findAll(query, customizeFetch: customizeFetch) - } - - public func count(entity: T.Type) -> Int { - - return self.context.count(entity) - } - - public func count(query: ObjectQuery) -> Int { - - return self.context.count(query) - } -} \ No newline at end of file diff --git a/HardcoreData/DefaultLogger.swift b/HardcoreData/DefaultLogger.swift new file mode 100644 index 0000000..0f84137 --- /dev/null +++ b/HardcoreData/DefaultLogger.swift @@ -0,0 +1,51 @@ +// +// DefaultLogger.swift +// HardcoreData +// +// Copyright (c) 2015 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + + +public final class DefaultLogger: HardcoreDataLogger { + + public func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) { + + #if DEBUG + Swift.println("[HardcoreData] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n") + #endif + } + + public func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) { + + #if DEBUG + Swift.println("[HardcoreData] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message): \(error)\n") + #endif + } + + public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) { + + #if DEBUG + Swift.assert(condition, message, file: fileName, line: lineNumber) + #endif + } +} diff --git a/HardcoreData/FetchClause.swift b/HardcoreData/FetchClause.swift new file mode 100644 index 0000000..d21f684 --- /dev/null +++ b/HardcoreData/FetchClause.swift @@ -0,0 +1,35 @@ +// +// FetchClause.swift +// HardcoreData +// +// Copyright (c) 2014 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation +import CoreData + + +// MARK: - FetchClause + +public protocol FetchClause { + + func applyToFetchRequest(fetchRequest: NSFetchRequest) +} diff --git a/HardcoreData/HardcoreData.swift b/HardcoreData/HardcoreData.swift index 0bfe3ce..ff1a548 100644 --- a/HardcoreData/HardcoreData.swift +++ b/HardcoreData/HardcoreData.swift @@ -27,6 +27,11 @@ import CoreData import GCDKit +typealias HCD = HardcoreData + + +// MARK: HardcoreData + /** The HardcoreData struct is the main entry point for all other APIs. */ @@ -59,6 +64,55 @@ public struct HardcoreData { } } + + /** + The HardcoreDataLogger instance to be used. The default logger is an instance of a DefaultLogger. + */ + public static var logger: HardcoreDataLogger = DefaultLogger() + + + internal static func log(level: LogLevel, message: String, fileName: StaticString = __FILE__, lineNumber: UWord = __LINE__, functionName: StaticString = __FUNCTION__) { + + self.logger.log( + level: level, + message: message, + fileName: fileName, + lineNumber: + lineNumber, + functionName: + functionName + ) + } + + internal static func handleError(error: NSError, _ message: String, fileName: StaticString = __FILE__, lineNumber: UWord = __LINE__, functionName: StaticString = __FUNCTION__) { + + self.logger.handleError( + error: error, + message: message, + fileName: fileName, + lineNumber: lineNumber, + functionName: functionName) + } + + internal static func assert(@autoclosure condition: () -> Bool, _ message: String, fileName: StaticString = __FILE__, lineNumber: UWord = __LINE__, functionName: StaticString = __FUNCTION__) { + + self.logger.assert( + condition, + message: message, + fileName: fileName, + lineNumber: lineNumber, + functionName: functionName) + } + + + private static let defaultStackBarrierQueue = GCDQueue.createConcurrent("com.hardcoreData.defaultStackBarrierQueue") + + private static var defaultStackInstance: DataStack? +} + + +extension HardcoreData { + /** Using the defaultStack, begins a transaction asynchronously where NSManagedObject creates, updates, and deletes can be made. @@ -79,102 +133,59 @@ public struct HardcoreData { return self.defaultStack.performTransactionAndWait(closure) } - - - public enum LogLevel { - - case Trace - case Notice - case Warning - case Fatal - } - - public typealias LogHandlerType = (level: LogLevel, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) -> () - - public typealias ErrorHandlerType = (error: NSError, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) -> () - - public typealias AssertionHandlerType = (condition: @autoclosure() -> Bool, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) -> () - - - /** - Sets the closure that handles all logging that occur within HardcoreData. The default logHandler logs via println() only when DEBUG is defined. - */ - public static func setLogHandler(logHandler: LogHandlerType) { - - self.logHandler = logHandler - } - - /** - Sets the closure that handles all errors that occur within HardcoreData. The default errorHandler logs via println() only when DEBUG is defined. - */ - public static func setErrorHandler(errorHandler: ErrorHandlerType) { - - self.errorHandler = errorHandler - } - - /** - Sets the closure that handles all assertions that occur within HardcoreData. The default assertHandler calls assert(). - */ - public static func setAssertionHandler(assertionHandler: AssertionHandlerType) { - - self.assertionHandler = assertionHandler - } - - internal static func log(level: LogLevel, message: String, fileName: StaticString = __FILE__, lineNumber: UWord = __LINE__, functionName: StaticString = __FUNCTION__) { - - self.logHandler( - level: level, - message: message, - fileName: fileName, - lineNumber: lineNumber, - functionName: functionName) - } - - internal static func handleError(error: NSError, _ message: String, fileName: StaticString = __FILE__, lineNumber: UWord = __LINE__, functionName: StaticString = __FUNCTION__) { - - self.errorHandler( - error: error, - message: message, - fileName: fileName, - lineNumber: lineNumber, - functionName: functionName) - } - - internal static func assert(condition: @autoclosure() -> Bool, _ message: String, fileName: StaticString = __FILE__, lineNumber: UWord = __LINE__, functionName: StaticString = __FUNCTION__) { - - self.assertionHandler( - condition: condition, - message: message, - fileName: fileName, - lineNumber: lineNumber, - functionName: functionName) - } - - - private static let defaultStackBarrierQueue = GCDQueue.createConcurrent("com.hardcoredata.defaultstackbarrierqueue") - - private static var defaultStackInstance: DataStack? - - private static var logHandler: LogHandlerType = { (level: LogLevel, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) -> () in - - #if DEBUG - println("[HardcoreData] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n") - #endif - } - - private static var errorHandler: ErrorHandlerType = { (error: NSError, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) -> () in - - #if DEBUG - println("[HardcoreData] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message): \(error)\n") - #endif - } - - private static var assertionHandler: AssertionHandlerType = { (condition: @autoclosure() -> Bool, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) -> () in - - #if DEBUG - assert(condition, message, file: fileName, line: lineNumber) - #endif - } } +//extension HardcoreData { +// +// public static func firstObject(entity: T.Type) -> T? { +// +// return self.defaultStack.firstObject(entity) +// } +// +// public static func firstObject(entity: T.Type, customizeFetch: FetchRequestCustomization?) -> T? { +// +// return self.defaultStack.firstObject(entity, customizeFetch: customizeFetch) +// } +// +// public static func firstObject(query: ObjectQuery) -> T? { +// +// return self.defaultStack.firstObject(query) +// } +// +// public static func firstObject(query: ObjectQuery, customizeFetch: FetchRequestCustomization?) -> T? { +// +// return self.defaultStack.firstObject(query, customizeFetch: customizeFetch) +// } +// +// public static func allObjects(entity: T.Type) -> [T]? { +// +// return self.defaultStack.allObjects(entity) +// } +// +// public static func allObjects(entity: T.Type, customizeFetch: FetchRequestCustomization?) -> [T]? { +// +// return self.defaultStack.allObjects(entity, customizeFetch: customizeFetch) +// } +// +// public static func allObjects(query: ObjectQuery) -> [T]? { +// +// return self.defaultStack.allObjects(query) +// } +// +// public static func allObjects(query: ObjectQuery, customizeFetch: FetchRequestCustomization?) -> [T]? { +// +// return self.defaultStack.allObjects(query, customizeFetch: customizeFetch) +// } +// +// public static func countObjects(entity: T.Type) -> Int { +// +// return self.defaultStack.countObjects(entity) +// } +// +// public static func countObjects(query: ObjectQuery) -> Int { +// +// return self.defaultStack.countObjects(query) +// } +//} + diff --git a/HardcoreData/HardcoreDataLogger.swift b/HardcoreData/HardcoreDataLogger.swift new file mode 100644 index 0000000..e2bb84b --- /dev/null +++ b/HardcoreData/HardcoreDataLogger.swift @@ -0,0 +1,45 @@ +// +// HardcoreDataLogger.swift +// HardcoreData +// +// Copyright (c) 2015 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + + +public enum LogLevel { + + case Trace + case Notice + case Warning + case Fatal +} + + +public protocol HardcoreDataLogger { + + func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) + + func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) + + func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) +} \ No newline at end of file diff --git a/HardcoreData/NSError+HardcoreData.swift b/HardcoreData/NSError+HardcoreData.swift index 2c59f68..d0535c6 100644 --- a/HardcoreData/NSError+HardcoreData.swift +++ b/HardcoreData/NSError+HardcoreData.swift @@ -46,6 +46,9 @@ public enum HardcoreDataErrorCode: Int { case DifferentPersistentStoreExistsAtURL } + +// MARK: - NSError+HardcoreData + public extension NSError { /** @@ -58,6 +61,9 @@ public extension NSError { : nil) } + + // MARK: Internal + internal convenience init(hardcoreDataErrorCode: HardcoreDataErrorCode) { self.init(hardcoreDataErrorCode: hardcoreDataErrorCode, userInfo: nil) diff --git a/HardcoreData/NSManagedObject+HardcoreData.swift b/HardcoreData/NSManagedObject+HardcoreData.swift index 9ce421f..fab0118 100644 --- a/HardcoreData/NSManagedObject+HardcoreData.swift +++ b/HardcoreData/NSManagedObject+HardcoreData.swift @@ -26,64 +26,19 @@ import Foundation import CoreData -public extension NSManagedObject { + +// MARK: - NSManagedObject+HardcoreData + +extension NSManagedObject { // MARK: - Entity Utilities public class var entityName: String { + // TODO: map from model file return NSStringFromClass(self).componentsSeparatedByString(".").last! } - public func inContext(context: NSManagedObjectContext) -> Self? { - - return self.typedObjectInContext(context) - } - - public func deleteFromContext() { - - self.managedObjectContext?.deleteObject(self) - } - - - // MARK: Querying - - public class func WHERE(predicate: NSPredicate) -> ObjectQuery { - - return ObjectQuery(entity: self).WHERE(predicate) - } - - public class func WHERE(value: Bool) -> ObjectQuery { - - return self.WHERE(NSPredicate(value: value)) - } - - public class func WHERE(format: String, _ args: CVarArgType...) -> ObjectQuery { - - return self.WHERE(NSPredicate(format: format, arguments: getVaList(args))) - } - - public class func WHERE(format: String, argumentArray: [AnyObject]?) -> ObjectQuery { - - return self.WHERE(NSPredicate(format: format, argumentArray: argumentArray)) - } - - public class func WHERE(attributeName: AttributeName, isEqualTo value: NSObject?) -> ObjectQuery { - - return ObjectQuery(entity: self).WHERE(attributeName, isEqualTo: value) - } - - public class func SORTEDBY(order: [SortOrder]) -> ObjectQuery { - - return ObjectQuery(entity: self).SORTEDBY(order) - } - - public class func SORTEDBY(order: SortOrder, _ subOrder: SortOrder...) -> ObjectQuery { - - return self.SORTEDBY([order] + subOrder) - } - - // MARK: - Internal @@ -93,6 +48,16 @@ public extension NSManagedObject { insertIntoManagedObjectContext: context) } + internal func inContext(context: NSManagedObjectContext) -> Self? { + + return self.typedObjectInContext(context) + } + + internal func deleteFromContext() { + + self.managedObjectContext?.deleteObject(self) + } + private func typedObjectInContext(context: NSManagedObjectContext) -> T? { let objectID = self.objectID @@ -111,7 +76,7 @@ public extension NSManagedObject { var existingObjectError: NSError? if let existingObject = context.existingObjectWithID(objectID, error: &existingObjectError) { - return (existingObject as T) + return (existingObject as! T) } HardcoreData.handleError( diff --git a/HardcoreData/NSManagedObjectContext+HardcoreData.swift b/HardcoreData/NSManagedObjectContext+HardcoreData.swift index 5e05e62..ae7f76c 100644 --- a/HardcoreData/NSManagedObjectContext+HardcoreData.swift +++ b/HardcoreData/NSManagedObjectContext+HardcoreData.swift @@ -27,6 +27,9 @@ import Foundation import CoreData import GCDKit + +// MARK: - NSManagedObjectContext+HardcoreData + public extension NSManagedObjectContext { // MARK: - Public @@ -39,6 +42,8 @@ public extension NSManagedObjectContext { context.parentContext = self context.setupForHardcoreDataWithContextName("com.hardcoredata.temporarycontext") context.shouldCascadeSavesToParent = true + context.parentStack = self.parentStack + context.parentTransaction = self.parentTransaction return context } @@ -46,6 +51,44 @@ public extension NSManagedObjectContext { // MARK: - Internal + internal var parentStack: DataStack? { + + get { + + return self.getAssociatedObjectForKey(&PropertyKeys.parentStack) + } + set { + + self.setAssociatedAssignedObject( + newValue, + forKey: &PropertyKeys.parentStack) + } + } + + internal var parentTransaction: DataTransaction? { + + get { + + return self.getAssociatedObjectForKey(&PropertyKeys.parentTransaction) + } + set { + + self.setAssociatedAssignedObject( + newValue, + forKey: &PropertyKeys.parentTransaction) + } + } + + internal func temporaryContextInTransaction(transaction: DataTransaction?) -> NSManagedObjectContext { + + let context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType) + context.parentContext = self + context.setupForHardcoreDataWithContextName("com.hardcoredata.temporarycontext") + context.shouldCascadeSavesToParent = true + + return context + } + internal func saveSynchronously() -> SaveResult { var result: SaveResult = SaveResult(hasChanges: false) @@ -109,8 +152,7 @@ public extension NSManagedObjectContext { return } - self.performBlock { - [unowned self] () -> () in + self.performBlock { () -> () in var saveError: NSError? if self.save(&saveError) { @@ -191,6 +233,8 @@ public extension NSManagedObjectContext { static var observerForWillSaveNotification: Void? static var observerForDidSaveNotification: Void? static var shouldCascadeSavesToParent: Void? + static var parentStack: Void? + static var parentTransaction: Void? } private var observerForWillSaveNotification: NotificationObserver? { @@ -248,7 +292,7 @@ public extension NSManagedObjectContext { object: self, closure: { (note) -> () in - let context = note.object as NSManagedObjectContext + let context = note.object as! NSManagedObjectContext let insertedObjects = context.insertedObjects if insertedObjects.count <= 0 { @@ -256,7 +300,7 @@ public extension NSManagedObjectContext { } var permanentIDError: NSError? - if context.obtainPermanentIDsForObjects(insertedObjects.allObjects, error: &permanentIDError) { + if context.obtainPermanentIDsForObjects(Array(insertedObjects), error: &permanentIDError) { return } @@ -271,104 +315,3 @@ public extension NSManagedObjectContext { } } - -// MARK: - DataContextProvider - -extension NSManagedObjectContext: ObjectQueryable { - - public func findFirst(entity: T.Type) -> T? { - - return self.findFirst(entity, customizeFetch: nil) - } - - public func findFirst(entity: T.Type, customizeFetch: FetchRequestCustomization?) -> T? { - - return self.findFirst(ObjectQuery(entity: entity), customizeFetch: customizeFetch) - } - - public func findFirst(query: ObjectQuery) -> T? { - - return self.findFirst(query, customizeFetch: nil) - } - - public func findFirst(query: ObjectQuery, customizeFetch: FetchRequestCustomization?) -> T? { - - let fetchRequest = query.createFetchRequestForContext(self) - customizeFetch?(fetchRequest: fetchRequest) - fetchRequest.fetchLimit = 1 - fetchRequest.resultType = .ManagedObjectResultType - - var fetchResults: [T]? - var error: NSError? - self.performBlockAndWait { - - fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [T] - } - if fetchResults == nil { - - HardcoreData.handleError(error!, "Failed executing fetch request.") - return nil - } - - return fetchResults?.first - } - - public func findAll(entity: T.Type) -> [T]? { - - return self.findAll(entity, customizeFetch: nil) - } - - public func findAll(entity: T.Type, customizeFetch: FetchRequestCustomization?) -> [T]? { - - return self.findAll(ObjectQuery(entity: entity), customizeFetch: customizeFetch) - } - - public func findAll(query: ObjectQuery) -> [T]? { - - return self.findAll(query, customizeFetch: nil) - } - - public func findAll(query: ObjectQuery, customizeFetch: FetchRequestCustomization?) -> [T]? { - - let fetchRequest = query.createFetchRequestForContext(self) - fetchRequest.fetchLimit = 0 - - var fetchResults: [T]? - var error: NSError? - self.performBlockAndWait { - - fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [T] - } - if fetchResults == nil { - - HardcoreData.handleError(error!, "Failed executing fetch request.") - return nil - } - - return fetchResults - } - - public func count(entity: T.Type) -> Int { - - return self.count(ObjectQuery(entity: entity)) - } - - public func count(query: ObjectQuery) -> Int { - - let fetchRequest = query.createFetchRequestForContext(self) - - var count = 0 - var error: NSError? - self.performBlockAndWait { - - count = self.countForFetchRequest(fetchRequest, error: &error) - } - if count == NSNotFound { - - HardcoreData.handleError( error!, "Failed executing fetch request.") - return 0 - } - - return count - } -} diff --git a/HardcoreData/NSManagedObjectContext+Querying.swift b/HardcoreData/NSManagedObjectContext+Querying.swift new file mode 100644 index 0000000..1771706 --- /dev/null +++ b/HardcoreData/NSManagedObjectContext+Querying.swift @@ -0,0 +1,157 @@ +// +// NSManagedObjectContext+Querying.swift +// HardcoreData +// +// Copyright (c) 2015 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation +import CoreData + + +extension NSManagedObjectContext { + + public func fetchOne(entity: T.Type, _ queryClauses: FetchClause...) -> T? { + + return self.fetchOne(entity, queryClauses) + } + + public func fetchOne(entity: T.Type, _ queryClauses: [FetchClause]) -> T? { + + let fetchRequest = NSFetchRequest() + fetchRequest.entity = NSEntityDescription.entityForName( + entity.entityName, + inManagedObjectContext: self) + fetchRequest.fetchLimit = 1 + fetchRequest.resultType = .ManagedObjectResultType + + for clause in queryClauses { + + clause.applyToFetchRequest(fetchRequest) + } + + var fetchResults: [T]? + var error: NSError? + self.performBlockAndWait { + + fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [T] + } + if fetchResults == nil { + + HardcoreData.handleError(error!, "Failed executing fetch request.") + return nil + } + + return fetchResults?.first + } + + public func fetchAll(entity: T.Type, _ queryClauses: FetchClause...) -> [T]? { + + return self.fetchAll(entity, queryClauses) + } + + public func fetchAll(entity: T.Type, _ queryClauses: [FetchClause]) -> [T]? { + + let fetchRequest = NSFetchRequest() + fetchRequest.entity = NSEntityDescription.entityForName( + entity.entityName, + inManagedObjectContext: self) + fetchRequest.fetchLimit = 0 + fetchRequest.resultType = .ManagedObjectResultType + + for clause in queryClauses { + + clause.applyToFetchRequest(fetchRequest) + } + + var fetchResults: [T]? + var error: NSError? + self.performBlockAndWait { + + fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [T] + } + if fetchResults == nil { + + HardcoreData.handleError(error!, "Failed executing fetch request.") + return nil + } + + return fetchResults + } + + public func queryCount(entity: T.Type, _ queryClauses: FetchClause...) -> Int { + + return self.queryCount(entity, queryClauses) + } + + public func queryCount(entity: T.Type, _ queryClauses: [FetchClause]) -> Int { + + let fetchRequest = NSFetchRequest() + fetchRequest.entity = NSEntityDescription.entityForName( + entity.entityName, + inManagedObjectContext: self) + + for clause in queryClauses { + + clause.applyToFetchRequest(fetchRequest) + } + + var count = 0 + var error: NSError? + self.performBlockAndWait { + + count = self.countForFetchRequest(fetchRequest, error: &error) + } + if count == NSNotFound { + + HardcoreData.handleError(error!, "Failed executing fetch request.") + return 0 + } + + return count + } + + +// public func queryCount(entity: T.Type, _ queryClauses: [FetchClause]) -> U? { +// +//// let expressionDescription = NSExpressionDescription() +//// expressionDescription.name = "queryCount" +//// expressionDescription.expressionResultType = .Integer32AttributeType +//// expressionDescription.expression = NSExpression( +//// forFunction: "min:", +//// arguments: [NSExpression(forKeyPath: attribute)]) +// +// let request = NSFetchRequest(entityName: entity.entityName) +// request.resultType = .DictionaryResultType +// request.predicate = predicate +// request.propertiesToFetch = [expressionDescription] +// +// var error: NSError? +// let results = NSManagedObjectContext.context()?.executeFetchRequest(request, error: &error) +// if results == nil { +// +// JEDumpAlert(error, "error") +// return nil +// } +// +// return (results?.first as? [String: NSDate])?[expressionDescription.name] +// } +} \ No newline at end of file diff --git a/HardcoreData/NSObject+HardcoreData.swift b/HardcoreData/NSObject+HardcoreData.swift index d5f436c..16a6e19 100644 --- a/HardcoreData/NSObject+HardcoreData.swift +++ b/HardcoreData/NSObject+HardcoreData.swift @@ -25,6 +25,9 @@ import Foundation + +// MARK: - NSObject+HardcoreData + internal extension NSObject { internal func getAssociatedObjectForKey(key: UnsafePointer) -> T? { diff --git a/HardcoreData/NSPersistentStoreCoordinator+HardcoreData.swift b/HardcoreData/NSPersistentStoreCoordinator+HardcoreData.swift index 21b1c2c..67f16b1 100644 --- a/HardcoreData/NSPersistentStoreCoordinator+HardcoreData.swift +++ b/HardcoreData/NSPersistentStoreCoordinator+HardcoreData.swift @@ -26,6 +26,9 @@ import Foundation import CoreData + +// MARK: - NSPersistentStoreCoordinator+HardcoreData + public extension NSPersistentStoreCoordinator { public func performSynchronously(closure: () -> ()) { diff --git a/HardcoreData/NotificationObserver.swift b/HardcoreData/NotificationObserver.swift index e79c863..f3db0e5 100644 --- a/HardcoreData/NotificationObserver.swift +++ b/HardcoreData/NotificationObserver.swift @@ -25,6 +25,9 @@ import Foundation + +// MARK: - NotificationObserver + internal final class NotificationObserver { let notificationName: String diff --git a/HardcoreData/ObjectQuery.swift b/HardcoreData/ObjectQuery.swift deleted file mode 100644 index 8111bb4..0000000 --- a/HardcoreData/ObjectQuery.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// ObjectQuery.swift -// HardcoreData -// -// Copyright (c) 2014 John Rommel Estropia -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation -import CoreData - - -public typealias AttributeName = Selector - -public enum SortOrder { - - case Ascending(AttributeName) - case Descending(AttributeName) -} - -public final class ObjectQuery { - - public var fetchLimit: Int = 0 - public var fetchOffset: Int = 0 - public var fetchBatchSize: Int = 0 - - public var entityName: String { - - return self.entity.entityName - } - - public func WHERE(predicate: NSPredicate) -> ObjectQuery { - - if self.predicate != nil { - - HardcoreData.log(.Warning, message: "Attempted to set a Query's WHERE clause more than once. The last predicate set will be used.") - } - self.predicate = predicate - return self - } - - public func WHERE(value: Bool) -> ObjectQuery { - - return self.WHERE(NSPredicate(value: value)) - } - - public func WHERE(format: String, _ args: CVarArgType...) -> ObjectQuery { - - return self.WHERE(NSPredicate(format: format, arguments: getVaList(args))) - } - - public func WHERE(format: String, argumentArray: [AnyObject]?) -> ObjectQuery { - - return self.WHERE(NSPredicate(format: format, argumentArray: argumentArray)) - } - - public func WHERE(attributeName: AttributeName, isEqualTo value: NSObject?) -> ObjectQuery { - - return self.WHERE(value == nil - ? NSPredicate(format: "\(attributeName) == nil")! - : NSPredicate(format: "\(attributeName) == %@", value!)!) - } - - public func SORTEDBY(order: [SortOrder]) -> ObjectQuery { - - if self.sortDescriptors != nil { - - HardcoreData.log(.Warning, message: "Attempted to set a Query's SORTEDBY clause more than once. The last sort order set will be used.") - } - self.sortDescriptors = order.map { sortOrder in - - switch sortOrder { - - case .Ascending(let attributeName): - return NSSortDescriptor( - key: NSStringFromSelector(attributeName), - ascending: true) - - case .Descending(let attributeName): - return NSSortDescriptor( - key: NSStringFromSelector(attributeName), - ascending: false) - } - } - return self - } - - public func SORTEDBY(order: SortOrder, _ subOrder: SortOrder...) -> ObjectQuery { - - return self.SORTEDBY([order] + subOrder) - } - - // MARK: Internal - - internal init(entity: T.Type) { - - self.entity = entity - } - - internal func createFetchRequestForContext(context: NSManagedObjectContext) -> NSFetchRequest { - - let fetchRequest = NSFetchRequest() - fetchRequest.entity = NSEntityDescription.entityForName( - self.entityName, - inManagedObjectContext: context) - fetchRequest.predicate = self.predicate - fetchRequest.sortDescriptors = self.sortDescriptors - - return fetchRequest - } - - - // MARK: Private - private let entity: T.Type - private var predicate: NSPredicate? - private var sortDescriptors: [NSSortDescriptor]? -} diff --git a/HardcoreData/PersistentStoreResult.swift b/HardcoreData/PersistentStoreResult.swift index ca7eda3..c5ba0fb 100644 --- a/HardcoreData/PersistentStoreResult.swift +++ b/HardcoreData/PersistentStoreResult.swift @@ -26,6 +26,9 @@ import Foundation import CoreData + +// MARK: - PersistentStoreResult + public enum PersistentStoreResult { case Success(NSPersistentStore) @@ -54,6 +57,9 @@ public enum PersistentStoreResult { } } + +// MARK: - PersistentStoreResult+BooleanType + extension PersistentStoreResult: BooleanType { public var boolValue: Bool { diff --git a/HardcoreData/ObjectQueryable.swift b/HardcoreData/QueryClause.swift similarity index 54% rename from HardcoreData/ObjectQueryable.swift rename to HardcoreData/QueryClause.swift index 06030c6..013f740 100644 --- a/HardcoreData/ObjectQueryable.swift +++ b/HardcoreData/QueryClause.swift @@ -1,5 +1,5 @@ // -// ObjectQueryable.swift +// FetchClause.swift // HardcoreData // // Copyright (c) 2014 John Rommel Estropia @@ -23,25 +23,13 @@ // SOFTWARE. // -import UIKit +import Foundation +import CoreData -public typealias FetchRequestCustomization = (fetchRequest: NSFetchRequest) -> () +// MARK: - QueryClause -public protocol ObjectQueryable { +public protocol QueryClause { - func findFirst(entity: T.Type) -> T? - func findFirst(entity: T.Type, customizeFetch: FetchRequestCustomization?) -> T? - - func findFirst(query: ObjectQuery) -> T? - func findFirst(query: ObjectQuery, customizeFetch: FetchRequestCustomization?) -> T? - - func findAll(entity: T.Type) -> [T]? - func findAll(entity: T.Type, customizeFetch: FetchRequestCustomization?) -> [T]? - - func findAll(query: ObjectQuery) -> [T]? - func findAll(query: ObjectQuery, customizeFetch: FetchRequestCustomization?) -> [T]? - - func count(entity: T.Type) -> Int - func count(query: ObjectQuery) -> Int + func applyToFetchRequest(fetchRequest: NSFetchRequest) } diff --git a/HardcoreData/SaveResult.swift b/HardcoreData/SaveResult.swift index f75b45b..4281e22 100644 --- a/HardcoreData/SaveResult.swift +++ b/HardcoreData/SaveResult.swift @@ -25,6 +25,9 @@ import Foundation + +// MARK: - SaveResult + public enum SaveResult { case Success(hasChanges: Bool) @@ -53,6 +56,9 @@ public enum SaveResult { } } + +// MARK: - SaveResult+BooleanType + extension SaveResult: BooleanType { public var boolValue: Bool { diff --git a/HardcoreData/SortedBy.swift b/HardcoreData/SortedBy.swift new file mode 100644 index 0000000..2e2cf53 --- /dev/null +++ b/HardcoreData/SortedBy.swift @@ -0,0 +1,104 @@ +// +// SortedBy.swift +// HardcoreData +// +// Copyright (c) 2015 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + + +public func +(left: SortedBy, right: SortedBy) -> SortedBy { + + return SortedBy(left.sortDescriptors + right.sortDescriptors) +} + + +public typealias AttributeName = Selector + + +public enum SortOrder { + + case Ascending(AttributeName) + case Descending(AttributeName) +} + + +// MARK: - SortedBy + +public struct SortedBy: FetchClause { + + // MARK: Public + + public init(_ sortDescriptors: [NSSortDescriptor]) { + + self.sortDescriptors = sortDescriptors + } + + public init() { + + self.init([NSSortDescriptor]()) + } + + public init(_ sortDescriptor: NSSortDescriptor) { + + self.init([sortDescriptor]) + } + + public init(_ order: [SortOrder]) { + + self.init(order.map { sortOrder -> NSSortDescriptor in + + switch sortOrder { + + case .Ascending(let attributeName): + return NSSortDescriptor( + key: NSStringFromSelector(attributeName), + ascending: true) + + case .Descending(let attributeName): + return NSSortDescriptor( + key: NSStringFromSelector(attributeName), + ascending: false) + } + }) + } + + public init(_ order: SortOrder, _ subOrder: SortOrder...) { + + self.init([order] + subOrder) + } + + public let sortDescriptors: [NSSortDescriptor] + + + // MARK: QueryClause + + public func applyToFetchRequest(fetchRequest: NSFetchRequest) { + + if fetchRequest.sortDescriptors != nil { + + HardcoreData.log(.Warning, message: "Existing sortDescriptors for the NSFetchRequest was overwritten by SortedBy query clause.") + } + + fetchRequest.sortDescriptors = self.sortDescriptors + } +} diff --git a/HardcoreData/Where.swift b/HardcoreData/Where.swift new file mode 100644 index 0000000..c957e99 --- /dev/null +++ b/HardcoreData/Where.swift @@ -0,0 +1,97 @@ +// +// Where.swift +// HardcoreData +// +// Copyright (c) 2015 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + + +public func &&(left: Where, right: Where) -> Where { + + return Where(NSCompoundPredicate(type: .AndPredicateType, subpredicates: [left.predicate, right.predicate])) +} + +public func ||(left: Where, right: Where) -> Where { + + return Where(NSCompoundPredicate(type: .OrPredicateType, subpredicates: [left.predicate, right.predicate])) +} + +public prefix func !(clause: Where) -> Where { + + return Where(NSCompoundPredicate(type: .NotPredicateType, subpredicates: [clause.predicate])) +} + + +// MARK: - Where + +public struct Where: FetchClause { + + // MARK: Public + + public init(_ predicate: NSPredicate) { + + self.predicate = predicate + } + + public init() { + + self.init(true) + } + + public init(_ value: Bool) { + + self.init(NSPredicate(value: value)) + } + + public init(_ format: String, _ args: CVarArgType...) { + + self.init(NSPredicate(format: format, arguments: getVaList(args))) + } + + public init(_ format: String, argumentArray: [AnyObject]?) { + + self.init(NSPredicate(format: format, argumentArray: argumentArray)) + } + + public init(_ attributeName: AttributeName, isEqualTo value: NSObject?) { + + self.init(value == nil + ? NSPredicate(format: "\(attributeName) == nil") + : NSPredicate(format: "\(attributeName) == %@", value!)) + } + + public let predicate: NSPredicate + + + // MARK: QueryClause + + public func applyToFetchRequest(fetchRequest: NSFetchRequest) { + + if fetchRequest.predicate != nil { + + HardcoreData.log(.Warning, message: "An existing predicate for the NSFetchRequest was overwritten by Where query clause.") + } + + fetchRequest.predicate = self.predicate + } +} diff --git a/HardcoreDataTests/HardcoreDataTests.swift b/HardcoreDataTests/HardcoreDataTests.swift index f943549..c9fc4e9 100644 --- a/HardcoreDataTests/HardcoreDataTests.swift +++ b/HardcoreDataTests/HardcoreDataTests.swift @@ -41,13 +41,22 @@ class HardcoreDataTests: XCTestCase { func testExample() { - #if DEBUG - let resetStoreOnMigrationFailure = true - #else - let resetStoreOnMigrationFailure = false - #endif + let stack = DataStack() + HardcoreData.defaultStack = stack + XCTAssertEqual(HardcoreData.defaultStack, stack, "HardcoreData.defaultStack == stack") - switch HardcoreData.defaultStack.addSQLiteStore(resetStoreOnMigrationFailure: resetStoreOnMigrationFailure) { + switch stack.addSQLiteStore("Config1Store", configuration: "Config1", resetStoreOnMigrationFailure: true){ + + case .Failure(let error): + NSException( + name: "CoreDataMigrationException", + reason: error.localizedDescription, + userInfo: error.userInfo).raise() + + default: + break + } + switch stack.addSQLiteStore("Config2Store", configuration: "Config2", resetStoreOnMigrationFailure: true){ case .Failure(let error): NSException( @@ -61,27 +70,43 @@ class HardcoreDataTests: XCTestCase { HardcoreData.performTransactionAndWait({ (transaction) -> () in - let obj = transaction.create(TestEntity1) - obj.testEntityID = 1 - obj.testString = "lololol" - obj.testNumber = 42 - obj.testDate = NSDate() - + let obj1 = transaction.create(TestEntity1) + obj1.testEntityID = 1 + obj1.testString = "lololol" + obj1.testNumber = 42 + obj1.testDate = NSDate() + + let obj2 = transaction.create(TestEntity2) + obj2.testEntityID = 2 + obj2.testString = "hahaha" + obj2.testNumber = 7 + obj2.testDate = NSDate() + transaction.commitAndWait() }) - HardcoreData.performTransactionAndWait({ (transaction) -> () in - let obj = transaction.findAll( - TestEntity1 - .WHERE("testEntityID", isEqualTo: 1) - .SORTEDBY(.Ascending("testEntityID"), .Descending("testString")), - customizeFetch: { (fetchRequest) -> () in + let obj1 = transaction.fetchOne( + TestEntity1.self, + Where("testEntityID", isEqualTo: 1), + SortedBy(.Ascending("testEntityID"), .Descending("testString")), + CustomizeQuery { (fetchRequest) -> Void in fetchRequest.includesPendingChanges = true } ) - NSLog("%@", obj ?? []) + NSLog(">>>>> %@", obj1 ?? "nil") + + let objs2 = transaction.fetchAll( + TestEntity2.self, + Where("testEntityID", isEqualTo: 2) && Where("testNumber", isEqualTo: 7), + SortedBy(.Ascending("testEntityID"), .Descending("testString")), + CustomizeQuery { (fetchRequest) -> () in + + fetchRequest.includesPendingChanges = true + } + ) + NSLog(">>>>> %@", objs2 ?? "nil") }) } } diff --git a/HardcoreDataTests/Model.xcdatamodeld/Model.xcdatamodel/contents b/HardcoreDataTests/Model.xcdatamodeld/Model.xcdatamodel/contents index a120a62..d7109a4 100644 --- a/HardcoreDataTests/Model.xcdatamodeld/Model.xcdatamodel/contents +++ b/HardcoreDataTests/Model.xcdatamodeld/Model.xcdatamodel/contents @@ -6,7 +6,20 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/HardcoreData/ValueQueryable.swift b/HardcoreDataTests/TestEntity2.swift similarity index 72% rename from HardcoreData/ValueQueryable.swift rename to HardcoreDataTests/TestEntity2.swift index d07cffa..0392d5b 100644 --- a/HardcoreData/ValueQueryable.swift +++ b/HardcoreDataTests/TestEntity2.swift @@ -1,5 +1,5 @@ // -// ValueQueryable.swift +// TestEntity1.swift // HardcoreData // // Copyright (c) 2014 John Rommel Estropia @@ -24,15 +24,13 @@ // import Foundation +import CoreData -public protocol ValueQueryable { +class TestEntity2: NSManagedObject { - func findFirst(entity: T.Type) -> T? - func findFirst(query: ObjectQuery) -> T? + @NSManaged var testEntityID: NSNumber? + @NSManaged var testString: String? + @NSManaged var testNumber: NSNumber? + @NSManaged var testDate: NSDate? - func findAll(entity: T.Type) -> [T]? - func findAll(query: ObjectQuery) -> [T]? - - func count(entity: T.Type) -> Int - func count(query: ObjectQuery) -> Int }