updated README, added more utilities for updating and deleting objects from transactions

This commit is contained in:
John Rommel Estropia
2015-05-30 17:26:06 +09:00
parent 6b8d5df049
commit 869660d41d
9 changed files with 257 additions and 11 deletions

View File

@@ -22,7 +22,7 @@
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key> <key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
<string>../..</string> <string>../..</string>
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key> <key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
<string>../..Libraries/GCDKit/</string> <string>../..Libraries/GCDKit</string>
</dict> </dict>
<key>IDESourceControlProjectURL</key> <key>IDESourceControlProjectURL</key>
<string>github.com:JohnEstropia/CoreStore.git</string> <string>github.com:JohnEstropia/CoreStore.git</string>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.1.0</string> <string>0.1.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>

View File

@@ -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? { internal func inContext(context: NSManagedObjectContext) -> Self? {
return self.typedObjectInContext(context) return self.typedObjectInContext(context)
@@ -54,6 +59,20 @@ internal extension NSManagedObject {
// MARK: Private // MARK: Private
private class func typedObjectInContext<T: NSManagedObject>(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<T: NSManagedObject>(context: NSManagedObjectContext) -> T? { private func typedObjectInContext<T: NSManagedObject>(context: NSManagedObjectContext) -> T? {
let objectID = self.objectID let objectID = self.objectID

View File

@@ -109,11 +109,25 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
:param: object the `NSManagedObject` type to be edited :param: object the `NSManagedObject` type to be edited
:returns: an editable proxy for the specified `NSManagedObject`. :returns: an editable proxy for the specified `NSManagedObject`.
*/ */
public override func fetch<T: NSManagedObject>(object: T?) -> T? { public override func edit<T: NSManagedObject>(object: T?) -> T? {
CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self)).") 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<T: NSManagedObject>(into: Into<T>, _ 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) 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. 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.
*/ */

View File

@@ -45,6 +45,11 @@ public struct Into<T: NSManagedObject> {
// MARK: Public // MARK: Public
internal static var defaultConfigurationName: String {
return "PF_DEFAULT_CONFIGURATION_NAME"
}
/** /**
Initializes an `Into` clause. Initializes an `Into` clause.
Sample Usage: Sample Usage:
@@ -180,13 +185,28 @@ public /*abstract*/ class BaseDataTransaction {
:param: object the `NSManagedObject` type to be edited :param: object the `NSManagedObject` type to be edited
:returns: an editable proxy for the specified `NSManagedObject`. :returns: an editable proxy for the specified `NSManagedObject`.
*/ */
public func fetch<T: NSManagedObject>(object: T?) -> T? { public func edit<T: NSManagedObject>(object: T?) -> T? {
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type \(typeName(object)) outside its designated queue.") CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type \(typeName(object)) outside its designated queue.")
return object?.inContext(self.context) 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<T: NSManagedObject>(into: Into<T>, _ 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`. Deletes a specified `NSManagedObject`.
@@ -194,11 +214,38 @@ public /*abstract*/ class BaseDataTransaction {
*/ */
public func delete(object: NSManagedObject?) { 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() 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 // MARK: Saving changes
/** /**

View File

@@ -88,11 +88,25 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
:param: object the `NSManagedObject` type to be edited :param: object the `NSManagedObject` type to be edited
:returns: an editable proxy for the specified `NSManagedObject`. :returns: an editable proxy for the specified `NSManagedObject`.
*/ */
public override func fetch<T: NSManagedObject>(object: T?) -> T? { public override func edit<T: NSManagedObject>(object: T?) -> T? {
CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self)).") 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<T: NSManagedObject>(into: Into<T>, _ 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) 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. 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.
*/ */

View File

@@ -28,8 +28,6 @@ import CoreData
import GCDKit import GCDKit
private let defaultConfigurationName = "PF_DEFAULT_CONFIGURATION_NAME"
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")
@@ -185,7 +183,7 @@ public final class DataStack {
if store.type == NSSQLiteStoreType if store.type == NSSQLiteStoreType
&& isExistingStoreAutomigrating == automigrating && isExistingStoreAutomigrating == automigrating
&& store.configurationName == (configuration ?? defaultConfigurationName) { && store.configurationName == (configuration ?? Into.defaultConfigurationName) {
return PersistentStoreResult(store) return PersistentStoreResult(store)
} }

View File

@@ -253,6 +253,17 @@ class CoreStoreTests: XCTestCase {
case .Success(let hasChanges): case .Success(let hasChanges):
XCTAssertTrue(hasChanges, "hasChanges == true") 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( let count: Int? = CoreStore.queryValue(
From(TestEntity1), From(TestEntity1),
Select(.Count("testNumber")) Select(.Count("testNumber"))
@@ -279,6 +290,22 @@ class CoreStoreTests: XCTestCase {
) )
XCTAssertTrue(count == 2, "count == 2 (actual: \(count))") 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<Int>(.Count("testNumber"))
)
XCTAssertTrue(count2 == 0, "count == 0 (actual: \(count2))")
detachedExpectation.fulfill() detachedExpectation.fulfill()
case .Failure(let error): case .Failure(let error):

View File

@@ -254,9 +254,86 @@ Note that if you do explicitly specify the configuration name, CoreStore will on
### Updating objects ### 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 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()
}
## <a name="fetch_query"></a>Fetching and querying ## <a name="fetch_query"></a>Fetching and querying
(implemented; README pending) (implemented; README pending)