Firstly, the true purpose of Core Data is not persistence but rather to create the model layer of a Model-View-Controller design app. That means that Core Data is really a model/simulation API first and a persistence API second. A Core Data data model, therefore, should accurately represent the attributes of real-world objects, conditions or events and the relationships between them.
So, when you set down to build a data model, you should forget about the UI, the data source or any other implementation details and simply try to create a model that mirrors the real-world objects, conditions or events the app deals with.
Secondly, while a data model deals with how entities are related, it doesn't deal with the logic of why the entities are related. That logic is belongs in code somewhere often in the custom NSManagedObject subclasses for the entities. In this case, the how of the entities relationships is that the MeetingNote
entity is related to both Task
and Tags
. The why is that there should be a relationship between any particular MeetingNote
object and anyTask
object only if the MeetingNote
object has a relationship to a Tag
object with the name of task
.
So, your basic data model should look like this:
MeetingNote{
title:string
date:date
tags<<-->>Tag.meetingNotes
tasks<-->>Task.meetingNote
}
Task{
name:string
meetingNote<<-->MeetingNote.tasks
}
Tag{
name:string
meetingNotes<<-->>MeetingNote.tags
}
Now the question becomes one of where to stick the custom logic for the why. The most logically simple way would be to create a custom accessor for MeetingNote.tags
property that checks if name of a tag being added or removed to an MeetingNote instance
equals task
and if so, adding or removing a Task
object from the instance's MeetingNote.tasks
relationship.
However, that has an obvious performance penalty of having to check every tag added or removed. A better solution would be to add the custom to only one point that is called only when the exact condition of MeetingNote.tags.name' contains a value of
task`.
Let's assume you have the following constraints:
- A
MeetingNote
object cannot have a related Task
object without also having a Tag object with name=="task"
.
- If the
MeetingNote
object does have a Tag object with name=="task"
it must have at least one related Task
object.
- If a
MeetingNote
object looses its relationship to a Tag object with name=="task"
, then it loses all its task.
It is immediately obvious at this point that `Tag object with name=="task" is a special object with behaviors different from other tags. This justifies and requires that it have its own entity and subclass so we would add to the data model:
TaskTag:Tag{
}
Since the TaskTag
entity inherits from the Tag
entity it can automatically inherits the in the Tag.meetingNotes
relationship so it will behave as a Tag
object from the perspective of any MeetinNote
objects.
Then in the TaskTag
NSManagedObject subclass we would add the following code:
-(NSString *) name {
// the name of a TaskTag is always "task"
// you should set the defalut value in the data model to "task" as well.
return @"task";
}
-(void) setName:(NSString *)name{
return; // the name can never be changed
}
- (void)addMeetingNotesObject:(MeetingNote *)value {
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
[self willChangeValueForKey:@"meetingNotes" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
[[self primitiveValueForKey:@"meetingNotes"] addObject:value];
// If the meeting object does not an existing task, add one
if ([value.tasks count]==0 ) {
Task *t=[NSEntityDescription insertNewObjectForEntityForName:@"Task" inManagedObjectContext:self.managedObjectContext];
t.meetingNote=value;
}
[self didChangeValueForKey:@"meetingNotes" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
[changedObjects release];
}
- (void)removeMeetingNotesObject:(MeetingNote *)value {
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
[self willChangeValueForKey:@"meetingNotes" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
[[self primitiveValueForKey:@"meetingNotes"] removeObject:value];
// A MeetingNote object cannot have any task without a taskTag so remove all task objects
if ([value.tasks count]!=0 ) {
[value removeTasks:value.tasks]; // removes all tasks from meeting notes
}
[self didChangeValueForKey:@"meetingNotes" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
[changedObjects release];
}
- (void)addMeetingNotes:(NSSet *)value {
[self willChangeValueForKey:@"meetingNotes" withSetMutation:NSKeyValueUnionSetMutation usingObjects:value];
[[self primitiveValueForKey:@"meetingNotes"] unionSet:value];
Task *newTask;
// same as addMeetingNotesObject:
for (MeetingNote *meetNote in value) {
if ([meetNote.tasks count]==0 ) {
newTask=[NSEntityDescription insertNewObjectForEntityForName:@"Task" inManagedObjectContext:self.managedObjectContext];
newTask.meetingNote=value;
}
}
[self didChangeValueForKey:@"meetingNotes" withSetMutation:NSKeyValueUnionSetMutation usingObjects:value];
}
- (void)removeMeetingNotes:(NSSet *)value {
[self willChangeValueForKey:@"meetingNotes" withSetMutation:NSKeyValueMinusSetMutation usingObjects:value];
[[self primitiveValueForKey:@"meetingNotes"] minusSet:value];
//removeMeetingNotesObject:
for (MeetingNote *meetNote in value) {
[meetNote removeTasks:meetNote.tasks];
}
[self didChangeValueForKey:@"meetingNotes" withSetMutation:NSKeyValueMinusSetMutation usingObjects:value];
}
// Note: This code is not compiled and my contain errors.
This code will automatically enforce the constraints above without having to do anything else. You could also customize the Task
subclass to set its name automatically based on some attributes of the MeetingNote
object it is related to.
Now you have all the why logic in the data model and your constraints are automatically enforced. This may not be the exact solution you need but you get the idea.