Originally created by @Jeehut on GitHub (Jan 22, 2016).
Hi, I'm currently writing an app where I try to partition all of its different parts into modules which I can test much easier. One module is responsible for storing the data so I wrote a framework that does exactly that using CoreStore. In my tests everything seemed to work except for refetching the related set of objects of a NSManagedObject subclass.
To be more specific, here's the test that fails (I'm using Quick and Nimble for testing):
varwordPool:WordPool!beforeEach{CoreDataHelper.sharedInstance.setupCoreStore()wordPool=WordPool.create(name:"Test")}describe("destroy()"){varwordPoolItem:WordPoolItem!beforeEach{wordPoolItem=WordPoolItem.create(clue:"Some clue for 'Test'",answer:"TEST",wordPool:wordPool)}it("destroys entry"){expect(WordPoolItem.all().count).to(equal(1))wordPool=wordPool.reload()expect(wordPool.items.count).to(equal(1))wordPoolItem.destroy()expect(WordPoolItem.all().count).to(equal(0))wordPool=wordPool.reload()expect(wordPool.items.count).to(equal(0))// this line fails: expected to equal <0>, got <1>}}
Now my question is basically why the items property of the WordPool subclass of NSManagedObject still contains one object after deleting the only item existing (and even checking it is deleted successfully two lines right before the failing line) when it should be empty. Am I missing something here? Did I even use CoreStore the way you intended it to? I like to have methods as simple as all(), create(..), update(...) and destroy() in usage so things are kept as simple as possible when using the framework.
I first thought about asking this on SO but it somehow feels like a bug within CoreStore to me as everything works except for the .items property update. Thanks in advance for any help or clarification.
The delete rules are 'Cascade' on the side of WordPool and 'Nullify' on the side of WordPoolItem.
I'm also willing to invite you to the project so you can investigate the issue directly within the project if you like. Just send me an email in that case (see my profile).
@Dschee I believe this is because you are extracting objects out of their transaction scope:
publicstaticfunccreate(namename:String)->WordPool{varwordPool:WordPool!CoreStore.beginSynchronous{transactioninwordPool=transaction.create(Into(WordPool))wordPool.name=nametransaction.commit()}returnwordPool// dead instance}
The MOC for the wordPool you returned already expired, and the behavior from then on is undefined.
Also, I don't think this does what you expect it to:
This fetches an object from the main context, so you can't use this object instance inside transactions like so:
publicfuncupdate(clueclue:String,answer:String)->WordPoolItem{CoreStore.beginSynchronous{transactioninletwordPoolItem=self.reload()// Should be transaction.edit(self) or transaction.fetchExisting(self)wordPoolItem.clue=cluewordPoolItem.answer=answertransaction.commit()}returnself.reload()// It's actually safe to just return self, as long as you used the proper instance within the transaction}
These mistakes are easy to make when doing operations via the NSManagedObject instead of executing from the data stack/transactions directly, so CoreStore doesn't encourage the pattern. I suggest avoiding methods that are vague in read/write usage. Otherwise, just create a transaction with CoreStore.beginUnsafe() and use it globally (never use CoreStore.xxx methods).
@JohnEstropia commented on GitHub (Jan 22, 2016):
@Dschee I believe this is because you are extracting objects out of their transaction scope:
``` swift
public static func create(name name: String) -> WordPool {
var wordPool: WordPool!
CoreStore.beginSynchronous { transaction in
wordPool = transaction.create(Into(WordPool))
wordPool.name = name
transaction.commit()
}
return wordPool // dead instance
}
```
The MOC for the wordPool you returned already expired, and the behavior from then on is undefined.
Also, I don't think this does what you expect it to:
``` swift
public func reload() -> WordPoolItem {
return CoreStore.fetchExisting(self)!
}
```
This fetches an object from the main context, so you can't use this object instance inside transactions like so:
``` swift
public func update(clue clue: String, answer: String) -> WordPoolItem {
CoreStore.beginSynchronous { transaction in
let wordPoolItem = self.reload() // Should be transaction.edit(self) or transaction.fetchExisting(self)
wordPoolItem.clue = clue
wordPoolItem.answer = answer
transaction.commit()
}
return self.reload() // It's actually safe to just return self, as long as you used the proper instance within the transaction
}
```
These mistakes are easy to make when doing operations via the NSManagedObject instead of executing from the data stack/transactions directly, so CoreStore doesn't encourage the pattern. I suggest avoiding methods that are vague in read/write usage. Otherwise, just create a transaction with `CoreStore.beginUnsafe()` and use it globally (never use CoreStore.xxx methods).
Thank you for your answer, @JohnEstropia. I actually like how this library is designed (which is why I'm using it in favor of others) but my goal is to abstract away from the implementation of how my data is saved within my framework so that I can change the implementation at all times without the need to change the usage of it in apps. So that's why I opted for the above solution which is both easy to understand and use (e.g. WordPool.create(name: "NewWordPool")) but still abstracts away from the fact that CoreStore or even CoreData is used at all.
I'm not sure though what you were referring to when you said:
These mistakes are easy to make when doing operations via the NSManagedObject instead of executing from the data stack/transactions directly, so CoreStore doesn't encourage the pattern. I suggest avoiding methods that are vague in read/write usage.
Is there another way of abstracting away from the implementation + keeping usage simple and at the same time not use NSManagedObject instances and passing them around? If there is, I'd be glad to rethink my approach. But passing a stack / transaction to the app that is using my data storage framework and calling methods like commit from there is not an option as this would disclose unnecessary implementation details (in my opinion).
In any case, I just changed my code to fix the issues you mentioned, now not only my tests for the destroy method fail, but also the ones for update(...). Here's my new create and update methods:
Seems like I'm still not using this correctly. I thought the issue was that I called fetchExisting after the beginSynchronous closure was closed. Also I removed returning anything from update as you stated that returning self would be sufficient (and as returning self would be redundant, I just removed it completely). What is my misconception?
@Jeehut commented on GitHub (Jan 23, 2016):
Thank you for your answer, @JohnEstropia. I actually like how this library is designed (which is why I'm using it in favor of others) but my goal is to **abstract away from the implementation** of how my data is saved within my framework so that I can change the implementation at all times without the need to change the usage of it in apps. So that's why I opted for the above solution which is both **easy to understand and use** (e.g. `WordPool.create(name: "NewWordPool")`) but still abstracts away from the fact that CoreStore or even CoreData is used at all.
I'm not sure though what you were referring to when you said:
> These mistakes are easy to make when doing operations _via the NSManagedObject_ instead of executing from the data stack/transactions directly, so CoreStore doesn't encourage the pattern. I suggest avoiding methods that are _vague in read/write usage_.
Is there another way of abstracting away from the implementation + keeping usage simple and at the same time **not** use NSManagedObject instances and passing them around? If there is, I'd be glad to rethink my approach. But passing a stack / transaction to the app that is using my data storage framework and calling methods like `commit` from there is not an option as this would disclose unnecessary implementation details (in my opinion).
In any case, I just changed my code to fix the issues you mentioned, now not only my tests for the `destroy` method fail, but also the ones for `update(...)`. Here's my **new create and update methods**:
**WordPool**
``` Swift
public static func create(name name: String) -> WordPool {
var wordPool: WordPool!
CoreStore.beginSynchronous { transaction in
let transactionWordPool = transaction.create(Into(WordPool))
transactionWordPool.name = name
transaction.commit()
wordPool = CoreStore.fetchExisting(transactionWordPool)
}
return wordPool
}
public func update(name name: String) {
CoreStore.beginSynchronous { transaction in
let editableSelf = transaction.edit(self)!
editableSelf.name = name
transaction.commit()
}
}
```
**WordPoolItem**
``` Swift
public static func create(clue clue: String, answer: String, wordPool: WordPool) -> WordPoolItem {
var wordPoolItem: WordPoolItem!
CoreStore.beginSynchronous { transaction in
let transactionItem = transaction.create(Into(WordPoolItem))
transactionItem.clue = clue
transactionItem.answer = answer
transactionItem.wordPool = transaction.fetchExisting(wordPool)!
transaction.commit()
wordPoolItem = CoreStore.fetchExisting(transactionItem)
}
return wordPoolItem
}
public func update(clue clue: String, answer: String) {
CoreStore.beginSynchronous { transaction in
let editableSelf = transaction.edit(self)!
editableSelf.clue = clue
editableSelf.answer = answer
transaction.commit()
}
}
```
Seems like I'm still not using this correctly. I thought the issue was that I called fetchExisting after the `beginSynchronous` closure was closed. Also I removed returning anything from `update` as you stated that returning self would be sufficient (and as returning `self` would be redundant, I just removed it completely). What is my misconception?
Your WordPool and WordPoolItem's update() looks fine, but you might need to re-fault the object right after beginSynchronous(). This might also fix your destroy() method:
About the architecture, it was a conscious decision to avoid adding utility methods that abstract away the separation between DataStack and Transaction operations. I used to use MagicalRecord before, which implements such methods as your WordPool.create(), but I found it's too easy to make mistakes such as mixing objects from different MOCs, or using objects from the wrong threads.
Since you want to bypass transactions (you're just doing things synchronously, which will cause you deadlocks at some point), I suggest you just use a global UnsafeDataTransaction and do everything from there. I'll think about letting UnsafeDataTransactions create their own ListMonitors and ObjectMonitors.
@JohnEstropia commented on GitHub (Jan 26, 2016):
First, about your code:
_WordPool.create()_
``` swift
public static func create(name name: String) -> WordPool {
var wordPool: WordPool!
CoreStore.beginSynchronous { transaction in
wordPool = transaction.create(Into(WordPool))
wordPool.name = name
wordPool.commit()
}
return CoreStore.fetchExisting(wordPool)
}
```
I suggest you only call `fetchExisting()` on the same queue you'll use the instance on. Use the same pattern for your WordPoolItem.create()
_WordPool.update()_
``` swift
public func update(name name: String) {
CoreStore.beginSynchronous { transaction in
let editableSelf = transaction.edit(self)!
editableSelf.name = name
transaction.commit()
}
self.refreshAsFault()
}
```
Your WordPool and WordPoolItem's `update()` looks fine, but you might need to re-fault the object right after `beginSynchronous()`. This might also fix your `destroy()` method:
``` swift
public func destroy() {
var wordPool: WordPool?
CoreStore.beginSynchronous { transaction in
wordPool = transaction.edit(self)?.wordPool
transaction.delete(self)
transaction.commit()
}
wordPool?.refreshAsFault()
}
```
About the architecture, it was a conscious decision to avoid adding utility methods that _abstract_ away the separation between DataStack and Transaction operations. I used to use MagicalRecord before, which implements such methods as your `WordPool.create()`, but I found it's too easy to make mistakes such as mixing objects from different MOCs, or using objects from the wrong threads.
Since you want to bypass transactions (you're just doing things synchronously, which will cause you deadlocks at some point), I suggest you just use a global `UnsafeDataTransaction` and do everything from there. I'll think about letting `UnsafeDataTransaction`s create their own `ListMonitor`s and `ObjectMonitor`s.
I'm closing this as it's more of an architecture problem and not a bug in CoreStore (the unit test includes object deletion). If you have any other questions feel free to message anytime :)
@JohnEstropia commented on GitHub (Feb 2, 2016):
I'm closing this as it's more of an architecture problem and not a bug in CoreStore (the unit test includes object deletion). If you have any other questions feel free to message anytime :)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @Jeehut on GitHub (Jan 22, 2016).
Hi, I'm currently writing an app where I try to partition all of its different parts into modules which I can test much easier. One module is responsible for storing the data so I wrote a framework that does exactly that using CoreStore. In my tests everything seemed to work except for refetching the related set of objects of a
NSManagedObjectsubclass.To be more specific, here's the test that fails (I'm using Quick and Nimble for testing):
Now my question is basically why the
itemsproperty of theWordPoolsubclass ofNSManagedObjectstill contains one object after deleting the only item existing (and even checking it is deleted successfully two lines right before the failing line) when it should be empty. Am I missing something here? Did I even use CoreStore the way you intended it to? I like to have methods as simple asall(),create(..),update(...)anddestroy()in usage so things are kept as simple as possible when using the framework.I first thought about asking this on SO but it somehow feels like a bug within CoreStore to me as everything works except for the
.itemsproperty update. Thanks in advance for any help or clarification.For reference here are my classes:
WordPool
WordPoolItem
Here's a screenshot of my data model:
The delete rules are 'Cascade' on the side of
WordPooland 'Nullify' on the side ofWordPoolItem.I'm also willing to invite you to the project so you can investigate the issue directly within the project if you like. Just send me an email in that case (see my profile).
@JohnEstropia commented on GitHub (Jan 22, 2016):
@Dschee I believe this is because you are extracting objects out of their transaction scope:
The MOC for the wordPool you returned already expired, and the behavior from then on is undefined.
Also, I don't think this does what you expect it to:
This fetches an object from the main context, so you can't use this object instance inside transactions like so:
These mistakes are easy to make when doing operations via the NSManagedObject instead of executing from the data stack/transactions directly, so CoreStore doesn't encourage the pattern. I suggest avoiding methods that are vague in read/write usage. Otherwise, just create a transaction with
CoreStore.beginUnsafe()and use it globally (never use CoreStore.xxx methods).@Jeehut commented on GitHub (Jan 23, 2016):
Thank you for your answer, @JohnEstropia. I actually like how this library is designed (which is why I'm using it in favor of others) but my goal is to abstract away from the implementation of how my data is saved within my framework so that I can change the implementation at all times without the need to change the usage of it in apps. So that's why I opted for the above solution which is both easy to understand and use (e.g.
WordPool.create(name: "NewWordPool")) but still abstracts away from the fact that CoreStore or even CoreData is used at all.I'm not sure though what you were referring to when you said:
Is there another way of abstracting away from the implementation + keeping usage simple and at the same time not use NSManagedObject instances and passing them around? If there is, I'd be glad to rethink my approach. But passing a stack / transaction to the app that is using my data storage framework and calling methods like
commitfrom there is not an option as this would disclose unnecessary implementation details (in my opinion).In any case, I just changed my code to fix the issues you mentioned, now not only my tests for the
destroymethod fail, but also the ones forupdate(...). Here's my new create and update methods:WordPool
WordPoolItem
Seems like I'm still not using this correctly. I thought the issue was that I called fetchExisting after the
beginSynchronousclosure was closed. Also I removed returning anything fromupdateas you stated that returning self would be sufficient (and as returningselfwould be redundant, I just removed it completely). What is my misconception?@JohnEstropia commented on GitHub (Jan 26, 2016):
First, about your code:
WordPool.create()
I suggest you only call
fetchExisting()on the same queue you'll use the instance on. Use the same pattern for your WordPoolItem.create()WordPool.update()
Your WordPool and WordPoolItem's
update()looks fine, but you might need to re-fault the object right afterbeginSynchronous(). This might also fix yourdestroy()method:About the architecture, it was a conscious decision to avoid adding utility methods that abstract away the separation between DataStack and Transaction operations. I used to use MagicalRecord before, which implements such methods as your
WordPool.create(), but I found it's too easy to make mistakes such as mixing objects from different MOCs, or using objects from the wrong threads.Since you want to bypass transactions (you're just doing things synchronously, which will cause you deadlocks at some point), I suggest you just use a global
UnsafeDataTransactionand do everything from there. I'll think about lettingUnsafeDataTransactions create their ownListMonitors andObjectMonitors.@JohnEstropia commented on GitHub (Feb 2, 2016):
I'm closing this as it's more of an architecture problem and not a bug in CoreStore (the unit test includes object deletion). If you have any other questions feel free to message anytime :)