Additional actions when NSManagedObject is deleted - objective-c

I have a core data 'ShoppingList' which contains 'Item' objects. I store a display order as an attribute of each item.
I would like to update the display order of all other items in the shopping list whenever an item is deleted. The code to do this is working fine when I use it in my view controller (from where the item is deleted), but since it is really related to the business objects and not the view, it would be better placed in either ShoppingList or Item.
Ideally, I would like it incorporated into the deletion of the item. So far I have tried the following:
1) Customize the standard Core Data generated ShoppingList.RemoveItemsObject (making sure to observe KVO before.after). What's strange about this way is that the item passed is stripped of its relationships to other core data entities before it gets to my code, which I need to process display orders correctly.
2) Customize Item.didTurnIntoFault. Same applies - but even attributes of the item are gone by this stage.
One answer would be to simply define a new method on ShoppingList that does my processing and then calls the original removeItemsObject. But I would prefer to know that whenever an item is removed, from anywhere, this is taken care of. This works nicely when I customize awakeFromInsert, for example - I know that whenever an item is created certain things are setup for me. But I'm surprised there's no equivalent for deletion.

Did you try to implement prepareForDeletion? Sounds like it's exactly what you're looking for.
The doc says:
You can implement this method to perform any operations required before the object is deleted, such as custom propagation before relationships are torn down, or reconfiguration of objects using key-value observing.

Related

One Core Data Entity Per NSPersistentDocument?

What's the best way to use Core Data if each document on disk corresponds to one Entity instance?
I have a data model file with one entity, and that entity has one attribute of name text and of type Text.
I have a Document.xib that has an NSObjectController that is set to 'Entity' mode and gets the managedObjectContext from the File's Owner. I have an NSTextField that is bound to the Object Controller for the Controller Key 'selection' and the Key Path 'text.' (This is just a test so I can figure out how Core Data works, but my eventual app will also only have one Entity instance per Document)
When I create a new document the textfield says 'No Selection' and is disabled.
I imagine that if I had a Table View or some other kind of way to select from among entity instances the selection would work but I don't nor do I want to. How can I hook up the NSObjectController to only have one Entity instance and to automatically 'select' it?
The intended behaviour is that I type something into the NSTextField, hit Save, close the document, re-open the document and the string in the textfield persists.
This is probably a really basic question but I can't find any tutorials or documents that would address this seemingly simple use case.
Ok, well I haven't figured it all out but my particular issue was being caused by the fact that nothing was being created. I switched out the NSObjectController for an NSArrayController, created an outlet for it in Document.m and added this to windowControllerDidLoadNib:
if (![self.arrayController selectedObjects]) {
[self.arrayController add:#""];
};
Now it seems to just manage the one Entity object.

Core Data and bindings - binding checkbox to result of many-to-many relationship

Core Data model
I have a many-to-many relationship between two of the principal entities; call them Item and Tag. There will be a large number of Documents. Each may have 0 to an arbitrary number of tags.
Each Item entity relevantly has an attribute called name, and a to-many relationship to Tag called tags. Each Tag entity relevantly has an attribute called name, and a to-many relationship to Item called items.
To display them, within the same window, I have: (i) an NSTableView (itemTableView), fed by an NSArrayController (itemArrayController), showing all Items; and (ii) an NSTableView (tagTableView), fed by a different NSArrayController (tagArrayController) showing all Lists.
tagTableView
In the tagTableView, the Table View is bound to tagArrayController, with controllerKey arrangedObjects.
There is only a single table column. The textfield in it is bound to Table Cell View, to model key path objectValue.name. That works so far; it displays all of the lists as expected, and sorts properly when I add a sort descriptor.
Everything has been set up using interface builder in Xcode.
The problem
I have added a checkbox into the tagTableView, in the same table column as the textfield. I am trying to implement two things:
The checkbox should be checked if the user has previously associated the Item with the relevant Tag. If not, the checkbox should be unchecked.
If the user checks an unchecked checkbox, I want to establish a relationship between the two; if the user unchecked a checked checkbox, I want to break that relationship.
The underlying behavior pattern is that the user will not necessarily have control over the tags and may not be able to create them. They are to choose from existing tags, and therefore should be able to see which ones exist, and be able to check/uncheck those that apply.
However, I can't see how to implement this.
Part solutions so far
I can see a possible way to do at least the first task programmatically, roughly along these lines:
Monitor tableViewSelectionDidChange for itemTableView
For a change, update the data source for tagTableView manually, and work out checkbox state by checking them for those Tags which relate to the Item entity that has just been selected, and otherwise unchecking them
However, this looks likely to add complexity, and ideally I would like to do this with bindings if possible.
I have reviewed the Apple Core Data and Bindings references, all the Cocoa books I have, stack overflow and I've also done extensive googling. I have found lots of similar questions (e.g. http://lists.apple.com/archives/cocoa-dev/2011/Mar/msg00164.html) but no answers.
I've also found a way that might work programmatically, but which seems to be like my idea above, at the expense of being able to use bindings (e.g. http://www.raywenderlich.com/14742/core-data-on-ios-5-tutorial-how-to-work-with-relations-and-predicates)
The only relevant question on this site -- Core-Data Check Box Cell with many-to-many data -- is not answered to a level that I can make use of.
It would seem to me that this should be a prime candidate for bindings. I should be able to ask the itemArrayController what Tags (if any) have a relation to its selected item, and then set the checkbox to ticked if it matches the relevant Tag, and unset it if it doesn't. I would expect I should be able to do this within the bindings for the checkbox itself, in interface builder. But I can't work out what model key path or binding to use, or what to set the cocoa bindings for the checkbox to. Am I missing something obvious? Thanks

Get ancestor object of un-saved object in Hobo

I'm working on a Hobo app trying to tie together a few models properly.
Activity objects have many Page children. They also have many DataSet children.
Page objects have several different kinds of children. We'll talk about Widget children, but there are several types with the same issue. An instance of a Widget belongs to a Page but also has a belongs_to relationship with a DataSet. Here's the important point: the DataSet must belong to the containing Activity. So for any given #widget:
#widget.page.activity === #widget.data_set.activity
It's easy enough to enforce this constraint in the model with a validation on save. The trick is presenting, within the Widget's form, a select menu of available DataSets which only contains DataSets for the current Activity
I was able to get this working for existing objects using a tag like this:
<data_set-tag: options="&DataSet.activity_is(&this.page.activity)" />
However, for a new Widget, this fails messily, because either &this or &this.page is not yet set. Even for a route which contains the page ID, like /pages/:page_id/widgets/new, I'm not really able to get an Activity to scope the list of DataSets with.
If this was proper Rails, I'd get in to the relevant controller method and make the Activity available to the view as #activity or something of the sort, but in Hobo the controllers seems to be 95% Magicâ„¢ and I don't know where to start. The knowledge of which Activity is current must be in there somewhere; how do I get it out?
This is Hobo 1.3.x on Rails 3.0.x.
ETA: The code producing the errors is in the form tag for Widget, like so:
<extend tag="form" for="Widget">
<old-form merge>
<field-list: fields="&this.field_order">
<data_set-tag: options="&DataSet.activity_is(&this.page.activity)" />
</field-list>
</old-form>
</extend>
As I said above, this works for editing existing Widgets, but not new Widgets; the error is undefined method 'page' for nil:NilClass. Bryan Larsen's answer seems to suggest that &this.page should not be null.
it looks like you tried to post this question to the Hobo Users mailing list -- I got a moderation message, but it doesn't appear that your post got posted, nor can I find it to let it through. Please try reposting it, there are several helpful people on the list that don't monitor the Hobo tag here.
In Hobo 1.3, the new action doesn't support part AJAX, so there really isn't much magic. You can just replace the action with your own:
def new_for_page
#activity = Activity.find(...)
#page = Page.find(params[:page_id])
#widget = #page.widgets.new
end
There is a little bit of magic referenced above: if you're in WidgetsController, assigning to #widget will also assign to this.
But as you said, the knowledge is obviously in there somewhere, and your custom controller action shouldn't be necessary.
This statement seems wrong: However, for a new Widget, this fails messily, because either &this or &this.page is not yet set.
It looks like you're properly using owner actions. /pages/:page_id/widgets/new is the route. In widgets_controller it's the new_for_page action. In a new or new_for action, this is set to an unsaved version of the object. In your action, it should have been created with the equivalent of Page.find(params[:page]).widgets.new. In other words, both this and this.page should be populated.
I'm sure you didn't make your statement up out of thin air, so there's probably something else going on.
In the end, it turned out to be syntax. Instead of
<data_set-tag: options="&DataSet.activity_is(&this.page.activity)" />
I needed
<data_set-tag: options="&DataSet.activity_is(#this.page.activity)" />
(note the #).
We actually made this into a helper method, so the final code is
<data_set-tag: options="&DataSet.activity_is(activity_for(#this))" />

Using NSArrayController without Core Data

I'm using Core Data in my OS X application where I have some TableViews bound to NSArrayControllers. The problem I'm having is when I'm trying to populate a tableview in a sheet using an array controller where I don't want the contents to persist.
Here's how the app hangs together;
Window 1 - Shows a list of users in a table view and allows adding and removing users. Contents persist via Core Data bindings.
Window 2 - Shows a list of groups in a table view. A second table view shows a list of users that belong to the selected group. Contents persist via Core Data bindings. An 'add users' button invokes a sheet for adding users to the group.
Add Users sheet - This sheet shows a table view of users that are not already members of the selected group. Pressing the close button on the sheet adds the selected users to the selected group.
Ok, so the problem I'm having is with the array controller for the Add Users sheet. When I invoke the sheet I iterate through all users and add any to the array controller if they don't already exists in the group. When I close the sheet I try to clear down the array controller using removeObject: but this causes a "can't use this method with a ModelObjectContect."
Why do I need a MOC to remove items from the array controller? It's only for display purposes so I don't need it to persist. If I set the array controllers MOC to that of my app delegate, it physically deletes the users, which I obviously don't want. I just want to remove them from the table view of the sheet.
I thought the answer might be to create another MOC to use as a scratch-pad and not tie it to a persistent store, however this just gave me a different error when using removeObject:, something along the lines of "can't remove objects that exists in another MOC."
Why am I allowed to add object to an array controller but not remove them? In cases where you don't actually want the items physically removed are you supposed to access the the underlying "content", e.g. [arraycontroller content]? I've played with this but get strange display results as it seem to be playing with the array controller's content behind it's back. If I do this, is there a way to tell the array controller "by the way, I've been tinkering with you're content and you may need to get yourself together"?
It looks to me like you shouldn't be using array controllers without Core Data, but there is numerous comments in the documentation that suggests that it works with and without core data.
Yes, you can use an array controller without a Core Data Managed Object Context. But as you're storing NSManagedObject instances inside it, I think it tries to mark them for deletion when you removed them.
If you work with managed objects and don't want the contents of the array controller to be deleted on removal, you have to bind the array controller's content to another object's property with Cocoa Bindings.
But there is a simpler solution. I suggest you to set the managed object context of the array controller to your main MOC and use a predicate to filter its content.
[arrayController setPredicate:[NSPredicate predicateWithFormat:#"NONE groups == %#", group]];
Thus, there is not need to add or remove users from the array controller as all users that are already in the group will be hidden.
You can use them with and without core data, but an array controller either uses core data (entity backed), or it doesn't. I don't think you can use one with managed objects and not have a context.
I'm not clear why you are creating objects instead of just using a fetch request?
You don't say how you are adding the "missing" users but If this is just a basic list, you could consider creating an array of proxy objects (so you aren't touching the MOC) which you can junk when the sheet is done. You could use a non-core data array controller for this, or just (gasp!) not use bindings at all and do it the old fashioned way.
Why not use [arrayController setContent:nil]

Updating NSTableView when enitiy is added to core data

I have a Cocoa App that I have manually added core data to. I setup the table in Interface Builder to list the entities from the data (with NSArrayController), and this is working just fine. The problem is when I insert a new entity (via code) the table does not update until I restart the app.
What do I have to do after inserting an entity and saving the context to get the table to automatically pick up the changes?
I'll assume you mean you want to update your array controller's contents, allowing the table to update as a result.
Short answer: Send your array controller a -fetch: message.
Longer answer: Only entity instances added through an array controller automatically show up in its contents array when it gets its contents via a straight fetch request (ie, when its contents array isn't bound to anything, but rather you set an entity name and a MOC, possibly a predicate, and nothing else).