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>
<string>../..</string>
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
<string>../..Libraries/GCDKit/</string>
<string>../..Libraries/GCDKit</string>
</dict>
<key>IDESourceControlProjectURL</key>
<string>github.com:JohnEstropia/CoreStore.git</string>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<string>0.1.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<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? {
return self.typedObjectInContext(context)
@@ -54,6 +59,20 @@ internal extension NSManagedObject {
// 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? {
let objectID = self.objectID

View File

@@ -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<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)).")
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)
}
/**
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.
*/

View File

@@ -45,6 +45,11 @@ public struct Into<T: NSManagedObject> {
// 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<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.")
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`.
@@ -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
/**

View File

@@ -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<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)).")
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)
}
/**
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.
*/

View File

@@ -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)
}

View File

@@ -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<Int>(.Count("testNumber"))
)
XCTAssertTrue(count2 == 0, "count == 0 (actual: \(count2))")
detachedExpectation.fulfill()
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
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()
}
## <a name="fetch_query"></a>Fetching and querying
(implemented; README pending)