Core data parent and child entity - ios

I have a small problem in Parent and child entity in core data model. My core data model has a Person entity which can have many googleplus account or twitter account linked to it. Since these two entities; GooglePlus and Twitter are somehow similar with the properties they have, I thought of creating an abstract entity called SocialConnection. Now, my Person entity has to-many relationship to SocialConnection entity which inturn is parent to both GooglePlus and Twitter.
Person <----->> SocialIdentifier ---- Child ---- GooglePlus
|
|
Child
|
|
Twitter
My graphical model is shown in the figure below.
Now, I have a situation here. Person has a property called socialConnections which will fetch all the connections regardless of if it is Twitter object or GooglePlus object. But, I would like to fetch just twitter sometime and GooglePlus sometimes. How would I do it.
I think one idea would be to have a backward relationship from SocialIdentifier pointing to the Person entity and let Person create forward relationship with each of the child.
It would be represented as;
Person <----------- SocialIdentifier -------- Child --------GooglePlus----->> Person
|
|
Child
|
|
Person <<------- Twitter
Graphical representation;
But, even with this representation, I am facing some problem. I could get googles and twitters from Person model but it has no reference to the SocialConnections ie. all the connections.
And, I feel this is redundant as the first one simplifies the design a lot. I am sure a little tweak to the first one would yield the desired one but I could not figure it out.
How could I fetch twitters or googles directly from the first model or is it possible to fetch all the connections from Person model without using predicate ofcourse. I am sure there are some pretty neat solution to this.
I would like to thank you for your help and suggestions beforehand.

I wouldn't set up new relationships for this. There are a couple of different approaches you could use.
Fetched property
Give Person a new fetched property named twitters. Set the destination entity to Twitter and the fetch predicate to person==$FETCH_SOURCE. Then just ask for the value of twitters when you need it. (Yeah, you said no predicate for some reason, but honestly, it's the right tool for the job here).
The advantage here is that you don't have to maintain a new relationship. If you add a new Twitter account to socialConnections, it automatically becomes part of twitters.
Filtering the set
If there won't be a large number of socialConnections then it might be easier to just filter it in code instead of getting Core Data to do it. Get every socialConnections object and filter the set, something like:
NSEntityDescription *twitterEntity = [NSEntityDescription entityForName:#"Twitter" inManagedObjectContext:[self managedObjectContext]];
NSSet *twitters = [[person valueForKey:#"socialConnections"] objectsPassingTest:^BOOL(id obj, BOOL *stop) {
return [[obj entity] isKindOfEntity:twitterEntity];
}];
That runs through every social connection and gets just the Twitter accounts.

Related

Realm Many to Many Query (Inverse too!)

My app's db has a many to many relationship between a Feed object and a Tweet object. This is to keep track of which feeds every tweet belongs in. If you're familiar with Twitter, imagine the main feed, a list feed, a user profile feed, etc.
How can I make a query using an NSPredicate to get a list of Tweets that exist in a specific Feed (and, inversely, get a list of Feeds that a Tweet exists in)? It seems that queries on inverse relationships does not work in Realm, so what are my options?
If I understand your question correctly this part of the documentation should be helpful:
Inverse Relationships Links are unidirectional. So if a to-many
property Person.dogs links to a Dog instance and a to-one property
Dog.owner links to Person, these links are independent from one
another. Appending a Dog to a Person instance’s dogs property, doesn’t
automatically set the dog’s owner property to this Person. Because
manually synchronizing pairs of relationships is error prone, complex
and duplicates information, Realm exposes an API to retrieve backlinks
described below.
With inverse relationships, you can obtain all objects linking to a
given object through a specific property. For example, calling
Object().linkingObjects(_:forProperty:) on a Dog instance will return
all objects of the specified class linking to the calling instance
with the specified property.
I guess you can do something like:
//assuming your Tweet object has a property like "let feeds = List<Feed>()"
someTweet.linkingObjects(Feed.self, forProperty: "feeds") //should return feeds your Tweet is in
But still I don't think I understand your question clearly. From my point of view your first requirement:
get a list of Tweets that exist in a specific Feed
should have a straightforward solution such as having a property in your Feed object like:
let tweets = List<Tweet>()
I wish you can clarify your situation further.
I wonder if it's possible to simplify the model a bit so many-to-many isn't necessary.
My understanding of Twitter is that tweets aren't 'owned' by any feeds. They simply exist on the platform, and are referenced by any number of feeds, but don't actually belong to any specific feed.
So a model setup like this should be appropriate:
class Tweet : Object {
}
class Feed : Object {
let tweets = List<Tweet>()
}
You can do a reverse lookup on a Tweet to see if there are any feeds in which it is currently visible, and you can simply use the tweets property of Feed objects to see which tweets they're displaying
Since the linkingObjects reverse lookup method of Realm simply returns a standard Swift Array, if you did want to filter that further, you could just use the system APIs (like filter or map) to refine it further.
Otherwise, if you really do want to be able to use Realm's NSPredicate filtering system both ways, then, as messy as it is, you would need to manually have each model linking to a list of the other:
class Tweet : Object {
let feeds = List<Feed>()
}
class Feed : Object {
let tweets = List<Tweet>()
}
While it's not recommended (Since it adds additional work), it's not disallowed.
Good luck!

Need assistance regarding core data

I am new to core data and trying to create a simple apps using core data. I am currently working on app to save data in to-many relationship, there are several questions and tutorials but I am still confused.
I have two entities person and contactNumbers, I am fetching person and its contact numbers from address book, person A has mobile number, iphone number, home, work, other... which creates to-many relationship.
In my code I didnt subclass any entity, Is it necessary to subclass entities to save data in relationship? I am asking this because I have read that its not necessary to subclass direct working with NSManagedObject class will do the job.
All I want is to save person A first name, last name in person entity and its contact numbers in contactNumbers entity. How to save data in to-many relationship using core data?
Please I request do not refer other stackoverflow questions, raywenderlich tutorial, app code blog tutorials etc.
Please provide a clearly understandable code with concepts to deal this situation. Thanks a lot.
This is my model
if I Do this
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:#"person" inManagedObjectContext:self.managedObjectContext];
[person setValue:#"first name A" forKey:#"firstName"];
[person setValue:#"last name B" forKey:#"lastName"];
[person setValue:#"123" forKey:#"mobile"];
I got this error
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSManagedObject 0x82abcc0> setValue:forUndefinedKey:]: the entity Person is not key value coding-compliant for the key "mobile".
If i do this
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.managedObjectContext];
[person setValue:#"first name A" forKey:#"firstName"];
[person setValue:#"last name B" forKey:#"lastName"];
NSManagedObject *contactNumber = [NSEntityDescription insertNewObjectForEntityForName:#"ContactNumber" inManagedObjectContext:self.managedObjectContext];
[contactNumber setValue:#"123" forKey:#"home"];
[contactNumber setValue:#"456" forKey:#"iPhone"];
[contactNumber setValue:#"789" forKey:#"mobile"];
[contactNumber setValue:#"111" forKey:#"work"];
[contactNumber setValue:#"112" forKey:#"other"];
its working fine, but how data got related to each other in two entities?
You don't need to subclass.
You just need relations.
Make a relation on Person named contactNumbers that points to the ContactNumber entity, and a matching relation on ContactNumber entity called person that points back to person.
The contactNumber relation should be one to many (because one person has many contacts), while the relation from ContactNumber going back to Person should be one to one (because each contact entry will appear in only one person).
When you get a Person entity then, you'll be able to get a set of ContactNumber objects from the contactNumbers property, and then look through elements in the set. Or you can search ContactNumber entities where the person ID matches the person relation going back to Person.
Followup: Your first code does not work because you cannot reference a single instance through a set.
Your second section of code is fine, just add the line
contactNumber.person = person;
Then you can access the set of contacts from a person at any time with:
NSSet *contacts = person.contactNumbers;
And get the contact number you want. You cannot go directly from a person to one contact without some very tricky keypath stuff.
I would EXTREMELY STRONGLY suggest using Mogenerator to generate data objects after you set up the model, it makes the data clearer as to how you can use it. Using the setKey mechanism with Core Data is UTTERLY INSANE.
You do not need to do any subclassing at all to read and write Core Data relationships (or any other values for that matter). In Core Data, a relationship is represented as an NSMutableSet. Here is some sample code to show you how to retrieve this set and modify it:
// Assuming myPerson is an NSManagedObject instance of the Person entity
NSMutableSet *contactNumbers = [myPerson mutableSetValueForKey:#"contactNumbers"];
// Add numbers normally by calling the set's addObject property
[contactNumbers addObject:myNewNumber];
//etc.
I would also highly recommend taking a look at the Sensible TableView framework if you haven't already done so. The framework will automatically generate the UI for you Core Data entities, including all relationships. It will also automatically handle adding/removing new relationship objects. I myself wouldn't imagine going back to doing stuff manually again without it. Good luck!

How to get ID instead of entity object from a relation field in Core Data?

I want to fetch a related entity's ID from current entity's field. How can I do this?
For example, assume I have two entity classes. Company and Employee. Company has Employee set. With Core Data, I will do this to get that from company.
TTCompany* company = <get company>
TTEmployee* employee = [[company employees] anyObject];
id employeeID = employee.objectID;
What I want to get is just only the employeeID. I don't need to load the employee object now, because I will resolve them later if required. And obviously, current company object has the employee's ID. I think there's a way to get this without fetching any data.
Is there any better way to get ID without loading the object?
If they have relationship, rather than going from Company to employee, you can build a fetch request on Employee that has a given company.
Then before execute set the return result type a NSManagedObjectID.
I wrote this without testing, and a little bit verbose to make the concept more clear.
NSFetchRequest *myFetchRequest =[[NSFetchRequest alloc] init];
myFetchRequest.entity=<your employee description>;
myFetchRequest.predicate=[NSPredicate predicateWithFormat:#"(company==%#)",yourCompany];
myFetchRequest.setResultType=NSManagedObjectIDResultType;
// execute fetch and the rest....

How can I save an object that's related to another object in core data?

I'm having difficulty with a one to one relationship. At the highest level, I have a one to many relationship. I'll use the typical manager, employee, example to explain what I'm trying to do. And to take it a step further, I'm trying to add a one to one House relationship to the employe.
I have the employees being added no problem with the addEmployeesToManagereObject method that was created for me when I subclassed NSManagedObject. When I select an Employee in my table view, I set the currentEmployee of type Employee - which is declared in my .h.
Now that I have that current employee I would like to save the Houses entities attributes in relation to the current employee.
The part that I'm really struggling with is setting the managedObjectContext and setting and saving the houses attributes in relation to the currentEmployee.
I've tried several things but here's my last attempt:
NOTE: employeeToHouse is a property of type House that was created for
me when I subclassed NSManagedObject
House *theHouse = [NSEntityDescription
insertNewObjectForEntityForName:#"House"
inManagedObjectContext:self.managedObjectContext];
// This is where I'm lost, trying to set the House
// object through the employeeToHouse relationship
self.currentEmployee.employeeToHouse
How can I access and save the houses attributes to the currentEmployee?
since House is setup as an Entity it can be considered a table within the data store. If that truly is the case, you need to setup a 1 to 1 relationship between Employee and House in your data model.
If you have already done so, then it is as simple as calling. Although I'm not as familiar with one to one relationships with Core Data as I am with to-many. In either case, try one of the following
[self.currentEmployee addHouseObject: theHouse];
or
self.currentEmployee.employeeToHouse=theHouse;
then to the save to the managedObjectContext:
NSError *error=nil;
if (![self.managedObjectContext save:&error]{
NSLog(#"Core Data Save Error: %#", error);
}
Also, I'm not sure about your particular situation, but your self.managedObjectContext should already be the same as the one pointed to by self.currentEmployee.managedObjectContext.
Good luck,
Tim

Cross-Store weak relationship with Fetched Properties?

I would like to separate my reference data from my user data in my Core Data model to simplify future updates of my app (and because, I plan to store the database on the cloud and there is no need to store reference data on the cloud as this is part of my application). Therefore, I've been looking for a while for a way to code a cross-store relationship using fetched properties. I have not found any example implementations of this.
I have a Core Data model using 2 configurations :
data model config 1 : UserData (entities relative to user)
data model config 2 : ReferenceData (entities relative to application itself)
I set up 2 different SQLite persistent stores for both config.
UserData config (and store) contains entity "User"
ReferenceData config (and store) contains entities "Type" and "Item".
I would like to create two single-way weak relationships as below :
A "User" has a unique "Type"
A "User" has many "Items"
Here are my questions :
How do I set up my properties?
Do I need 2 properties for each relation (one for storing Unique ID and another to access my fetched results)?
Could this weak relationship be ordered?
Could someone give me an example implementation of this?
As a follow-on to Marcus' answer:
Looking through the forums and docs, I read that I should use the URI Representation of my entity instance instead of objectID. What is the reason behind this?
// Get the URI of my object to reference
NSURL * uriObjectB [[myObjectB objectID] URIRepresentation];
Next, I wonder, how do I store my object B URI (NSURL) in my parent object A as a weak relationship? What attribute type should I use? How do I convert this? I heard about archive... ?
Then, later I should retrieve the managed object the same way (by unconvert/unarchive the URIRepresentation) and get Object from URI
// Get the Object ID from the URI
NSManagedObjectID* idObjectB = [storeCoordinator managedObjectIDForURIRepresentation:[[myManagedObject objectID] URIRepresentation]];
// Get the Managed Object for the idOjectB ...
And last but not least, shouId I declare two properties in my entity A, one for persisting of URI needs and another for retrieving direclty object B?
NSURL * uriObjectB [objectA uriObjectB];
ObjectB * myObjectB = [objectA objectB];
As you can read, I really miss some simple example to implement thes weak relationships ! I would really appreciate some help.
Splitting the data is the right answer by far. Reference data should not be synced with the cloud, especially since iCloud has soft caps on what it will allow an application to sync and store in documents.
To create soft references across to stores (they do not need to be SQLite but it is a good idea for general app performance) you will need to have some kind of unique key that can be referenced from the other side; a good old fashioned foreign key.
From there you can create a fetched property in the model to reference the entity.
While this relationship cannot be ordered directly you can create order via a sort index or if it has a logical sort then you can sort it once you retrieve the data (I use convenience methods for this that return a sorted array instead of a set).
I can build up an example but you really are on the right track. The only fun part is migration. When you detect a migration situation you will need to migrate each store independently before you build up your core data stack. It sounds tricky but it really is not that hard to accomplish.
Example
Imagine you have a UserBar entity in the user store and a RefBar entity in the reference store. The RefBar will then have a fetchedProperty "relationship" with a UserBar thereby creating a ToOne relationship.
UserBar
----------
refBarID : NSInteger
RefBar
--------
identifier : NSInteger
You can then create a fetched property on the RefBar entity in the modeler with a predicate of:
$FETCHED_PROPERTY.refBarID == identifier
Lets name that predicate "userBarFetched"
Now that will return an array so we want to add a convenience method to the RefBar
#class UserBar;
#interface RefBar : NSManagedObject
- (UserBar*)userBar;
#end
#implementation RefBar
- (UserBar*)userBar
{
NSArray *fetched = [self valueForKey:#"userBarFetched"];
return [fetched lastObject];
}
#end
To create a ToMany is the same except your convenience method would return an array and you would sort the array before returning it.
As Heath Borders mentioned, it is possible to add a sort to the NSFetchedProperty if you want but you must do it in code. Personally I have always found it wasteful and don't use that feature. It might be more useful if I could set the sort in the modeler.
Using the ObjectID
I do not recommend using the ObjectID or the URIRepresentation. The ObjectID (and therefore the URIRepresentation of that ObjectID) can and will change. Whenever you migrate a database that value will change. You are far better off creating a non-changing GUID.
The weak relationship
You only need a single value on the M side of the relationship and that stores the foreign identifier. In your object subclass you only need to implement accessors that retrieve the object (or objects).
I would go with just one store.
For storing stuff in the cloud, you will anyway have to serialize the data, either as JSON or SQL statements, or whatever scheme you prefer.
You will need a local copy of the data on the user's device, so he can access it quickly and offline. The cloud store can have only the user entity, while the local store (part of the app) can also have the reference entity.
I have a similar project with a huge reference store (20000 records) with geographic information, and user generated content ("posts"). I use a single store. When I ship the app, the "posts" entity is also defined but empty. When I update the data model I simply re-generate the whole reference store before shipping.
I see absolutely no reason to go for a cross store solution here.

Resources