diff --git a/CoreStore.xcodeproj/project.xcworkspace/xcshareddata/CoreStore.xccheckout b/CoreStore.xcodeproj/project.xcworkspace/xcshareddata/CoreStore.xccheckout index 867b6eb..7a53aef 100644 --- a/CoreStore.xcodeproj/project.xcworkspace/xcshareddata/CoreStore.xccheckout +++ b/CoreStore.xcodeproj/project.xcworkspace/xcshareddata/CoreStore.xccheckout @@ -22,7 +22,7 @@ 4B60F1BCB491FF717C56441AE7783C74F417BE48 ../.. 8B2E522D57154DFA93A06982C36315ECBEA4FA97 - ../..Libraries/GCDKit/ + ../..Libraries/GCDKit IDESourceControlProjectURL github.com:JohnEstropia/CoreStore.git diff --git a/CoreStore/Info.plist b/CoreStore/Info.plist index 738d37e..4f315e0 100644 --- a/CoreStore/Info.plist +++ b/CoreStore/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.1.0 + 0.1.1 CFBundleSignature ???? CFBundleVersion diff --git a/CoreStore/Internal/NSManagedObject+Transaction.swift b/CoreStore/Internal/NSManagedObject+Transaction.swift index 1d9d49c..c574773 100644 --- a/CoreStore/Internal/NSManagedObject+Transaction.swift +++ b/CoreStore/Internal/NSManagedObject+Transaction.swift @@ -41,6 +41,11 @@ internal extension NSManagedObject { ) } + internal class func inContext(context: NSManagedObjectContext, withObjectID objectID: NSManagedObjectID) -> Self? { + + return self.typedObjectInContext(context, objectID: objectID) + } + internal func inContext(context: NSManagedObjectContext) -> Self? { return self.typedObjectInContext(context) @@ -54,6 +59,20 @@ internal extension NSManagedObject { // MARK: Private + private class func typedObjectInContext(context: NSManagedObjectContext, objectID: NSManagedObjectID) -> T? { + + var error: NSError? + if let existingObject = context.existingObjectWithID(objectID, error: &error) { + + return (existingObject as! T) + } + + CoreStore.handleError( + error ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed to load existing \(typeName(self)) in context.") + return nil; + } + private func typedObjectInContext(context: NSManagedObjectContext) -> T? { let objectID = self.objectID diff --git a/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift b/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift index 0e2e9cb..7099d86 100644 --- a/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift +++ b/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift @@ -109,11 +109,25 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { :param: object the `NSManagedObject` type to be edited :returns: an editable proxy for the specified `NSManagedObject`. */ - public override func fetch(object: T?) -> T? { + public override func edit(object: T?) -> T? { CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self)).") - return super.fetch(object) + return super.edit(object) + } + + /** + Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once. + + :param: into an `Into` clause specifying the entity type + :param: objectID the `NSManagedObjectID` for the object to be edited + :returns: an editable proxy for the specified `NSManagedObject`. + */ + public override func edit(into: Into, _ objectID: NSManagedObjectID) -> T? { + + CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type <\(T.self)> from an already committed \(typeName(self)).") + + return super.edit(into, objectID) } /** @@ -128,6 +142,31 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { super.delete(object) } + /** + Deletes the specified `NSManagedObject`'s. + + :param: object the `NSManagedObject` type to be deleted + :param: objects other `NSManagedObject`'s type to be deleted + */ + public override func delete(object: NSManagedObject?, _ objects: NSManagedObject?...) { + + CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).") + + super.delete([object] + objects) + } + + /** + Deletes the specified `NSManagedObject`'s. + + :param: objects the `NSManagedObject`'s type to be deleted + */ + public override func delete(objects: [NSManagedObject?]) { + + CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).") + + super.delete(objects) + } + /** Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid. This method should not be used after the `commit()` method was already called once. */ diff --git a/CoreStore/Saving and Processing/BaseDataTransaction.swift b/CoreStore/Saving and Processing/BaseDataTransaction.swift index e98a08b..f995db3 100644 --- a/CoreStore/Saving and Processing/BaseDataTransaction.swift +++ b/CoreStore/Saving and Processing/BaseDataTransaction.swift @@ -45,6 +45,11 @@ public struct Into { // MARK: Public + internal static var defaultConfigurationName: String { + + return "PF_DEFAULT_CONFIGURATION_NAME" + } + /** Initializes an `Into` clause. Sample Usage: @@ -180,13 +185,28 @@ public /*abstract*/ class BaseDataTransaction { :param: object the `NSManagedObject` type to be edited :returns: an editable proxy for the specified `NSManagedObject`. */ - public func fetch(object: T?) -> T? { + public func edit(object: T?) -> T? { CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type \(typeName(object)) outside its designated queue.") return object?.inContext(self.context) } + /** + Returns an editable proxy of the object with the specified `NSManagedObjectID`. + + :param: into an `Into` clause specifying the entity type + :param: objectID the `NSManagedObjectID` for the object to be edited + :returns: an editable proxy for the specified `NSManagedObject`. + */ + public func edit(into: Into, _ objectID: NSManagedObjectID) -> T? { + + CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type <\(T.self)> outside its designated queue.") + CoreStore.assert(into.inferStoreIfPossible || (into.configuration ?? Into.defaultConfigurationName) == objectID.persistentStore?.configurationName, "Attempted to update an entity of type <\(T.self)> but the specified persistent store do not match the `NSManagedObjectID`.") + + return T.inContext(self.context, withObjectID: objectID) + } + /** Deletes a specified `NSManagedObject`. @@ -194,11 +214,38 @@ public /*abstract*/ class BaseDataTransaction { */ public func delete(object: NSManagedObject?) { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete an entity of type \(typeName(object)) outside its designated queue.") + CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete an entity outside its designated queue.") object?.inContext(self.context)?.deleteFromContext() } + /** + Deletes the specified `NSManagedObject`'s. + + :param: object the `NSManagedObject` type to be deleted + :param: objects other `NSManagedObject`'s type to be deleted + */ + public func delete(object: NSManagedObject?, _ objects: NSManagedObject?...) { + + self.delete([object] + objects) + } + + /** + Deletes the specified `NSManagedObject`'s. + + :param: objects the `NSManagedObject`'s type to be deleted + */ + public func delete(objects: [NSManagedObject?]) { + + CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete entities outside their designated queue.") + + let context = self.context + for object in objects { + + object?.inContext(context)?.deleteFromContext() + } + } + // MARK: Saving changes /** diff --git a/CoreStore/Saving and Processing/SynchronousDataTransaction.swift b/CoreStore/Saving and Processing/SynchronousDataTransaction.swift index a54edef..aff2d6f 100644 --- a/CoreStore/Saving and Processing/SynchronousDataTransaction.swift +++ b/CoreStore/Saving and Processing/SynchronousDataTransaction.swift @@ -88,11 +88,25 @@ public final class SynchronousDataTransaction: BaseDataTransaction { :param: object the `NSManagedObject` type to be edited :returns: an editable proxy for the specified `NSManagedObject`. */ - public override func fetch(object: T?) -> T? { + public override func edit(object: T?) -> T? { CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self)).") - return super.fetch(object) + return super.edit(object) + } + + /** + Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once. + + :param: into an `Into` clause specifying the entity type + :param: objectID the `NSManagedObjectID` for the object to be edited + :returns: an editable proxy for the specified `NSManagedObject`. + */ + public override func edit(into: Into, _ objectID: NSManagedObjectID) -> T? { + + CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type <\(T.self)> from an already committed \(typeName(self)).") + + return super.edit(into, objectID) } /** @@ -107,6 +121,31 @@ public final class SynchronousDataTransaction: BaseDataTransaction { super.delete(object) } + /** + Deletes the specified `NSManagedObject`'s. + + :param: object the `NSManagedObject` type to be deleted + :param: objects other `NSManagedObject`'s type to be deleted + */ + public override func delete(object: NSManagedObject?, _ objects: NSManagedObject?...) { + + CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).") + + super.delete([object] + objects) + } + + /** + Deletes the specified `NSManagedObject`'s. + + :param: objects the `NSManagedObject`'s type to be deleted + */ + public override func delete(objects: [NSManagedObject?]) { + + CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).") + + super.delete(objects) + } + /** Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid. This method should not be used after the `commit()` method was already called once. */ diff --git a/CoreStore/Setting Up/DataStack.swift b/CoreStore/Setting Up/DataStack.swift index 4dcd52b..b4f89fc 100644 --- a/CoreStore/Setting Up/DataStack.swift +++ b/CoreStore/Setting Up/DataStack.swift @@ -28,8 +28,6 @@ import CoreData import GCDKit -private let defaultConfigurationName = "PF_DEFAULT_CONFIGURATION_NAME" - private let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first as! NSURL private let applicationName = ((NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData") @@ -185,7 +183,7 @@ public final class DataStack { if store.type == NSSQLiteStoreType && isExistingStoreAutomigrating == automigrating - && store.configurationName == (configuration ?? defaultConfigurationName) { + && store.configurationName == (configuration ?? Into.defaultConfigurationName) { return PersistentStoreResult(store) } diff --git a/CoreStoreTests/CoreStoreTests.swift b/CoreStoreTests/CoreStoreTests.swift index e8dddb1..a63918a 100644 --- a/CoreStoreTests/CoreStoreTests.swift +++ b/CoreStoreTests/CoreStoreTests.swift @@ -253,6 +253,17 @@ class CoreStoreTests: XCTestCase { case .Success(let hasChanges): XCTAssertTrue(hasChanges, "hasChanges == true") + CoreStore.beginSynchronous { (transaction) -> Void in + + let obj5Copy1 = transaction.edit(obj5) + XCTAssertTrue(obj5.objectID == obj5Copy1?.objectID, "obj5.objectID == obj5Copy1?.objectID") + XCTAssertFalse(obj5 == obj5Copy1, "obj5 == obj5Copy1") + + let obj5Copy2 = transaction.edit(Into(TestEntity1), obj5.objectID) + XCTAssertTrue(obj5.objectID == obj5Copy2?.objectID, "obj5.objectID == obj5Copy2?.objectID") + XCTAssertFalse(obj5 == obj5Copy2, "obj5 == obj5Copy2") + } + let count: Int? = CoreStore.queryValue( From(TestEntity1), Select(.Count("testNumber")) @@ -279,6 +290,22 @@ class CoreStoreTests: XCTestCase { ) XCTAssertTrue(count == 2, "count == 2 (actual: \(count))") + + CoreStore.beginSynchronous { (transaction) -> Void in + + let obj6 = transaction.edit(obj6) + let obj5 = transaction.edit(obj5) + transaction.delete(obj5, obj6) + + transaction.commit() + } + + let count2 = CoreStore.queryValue( + From(TestEntity1), + Select(.Count("testNumber")) + ) + XCTAssertTrue(count2 == 0, "count == 0 (actual: \(count2))") + detachedExpectation.fulfill() case .Failure(let error): diff --git a/README.md b/README.md index d56f2dd..440b02d 100644 --- a/README.md +++ b/README.md @@ -254,9 +254,86 @@ Note that if you do explicitly specify the configuration name, CoreStore will on ### Updating objects +After creating an object from the transaction, you can simply update it's properties as normal: + + CoreStore.beginAsynchronous { (transaction) -> Void in + let person = transaction.create(Into(MyPersonEntity)) + person.name = "John Smith" + person.age = 30 + transaction.commit() + } + +To update an existing object, fetch the object's instance from the transaction: + + CoreStore.beginAsynchronous { (transaction) -> Void in + let person = transaction.fetchOne( + From(MyPersonEntity), + Where("name", isEqualTo: "Jane Smith") + ) + person.age = person.age + 1 + transaction.commit() + } + +*(For more about fetching, read [Fetching and querying](#fetch_query))* + +**Do not update an instance that was not created/fetched from the transaction.** If you have a reference to the object already, use the transaction's `edit(...)` method to get an editable proxy instance for that object: + + let jane: MyPersonEntity = // ... + + CoreStore.beginAsynchronous { (transaction) -> Void in + // WRONG: jane.age = jane.age + 1 + // RIGHT: + let jane = transaction.edit(jane) // using the same variable name protects us from misusing the non-transaction instance + jane.age = jane.age + 1 + transaction.commit() + } + +This is also true when updating an object's relationships. Make sure that the object assigned to the relationship is also created/fetched from the transaction: + + let jane: MyPersonEntity = // ... + let john: MyPersonEntity = // ... + + CoreStore.beginAsynchronous { (transaction) -> Void in + // WRONG: jane.friends = [john] + // RIGHT: + let jane = transaction.edit(jane) + let john = transaction.edit(john) + jane.friends = [john] + transaction.commit() + } ### Deleting objects +Deleting an object is simpler as you can tell a transaction to delete an object directly without fetching an editable proxy (CoreStore does that for you): + + let john: MyPersonEntity = // ... + + CoreStore.beginAsynchronous { (transaction) -> Void in + transaction.delete(john) + transaction.commit() + } + +or several objects at once: + + let john: MyPersonEntity = // ... + let jane: MyPersonEntity = // ... + + CoreStore.beginAsynchronous { (transaction) -> Void in + transaction.delete(john, jane) + // transaction.delete([john, jane]) is also allowed + transaction.commit() + } + +If you do not have references yet to the objects to be deleted, transactions have a `deleteAll(...)` method you can pass a query to: + + CoreStore.beginAsynchronous { (transaction) -> Void in + transaction.deleteAll( + From(MyPersonEntity) + Where("age > 30") + ) + transaction.commit() + } + ## Fetching and querying (implemented; README pending)