13

I have just started learning how to implement Core Data Model on the iOS. After some basic tutorials on how to store and retrieve data with one to one relationship among entities, I am now trying to implement a one to many relationship. My data model consists of two entities with their respective classes defined as follows:

Restaurant:
@interface Restaurant :NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSMutableArray *user_reviews;   /* One to Many Relation to Review Entity*/
@end

Review:
@interface Review : NSObject
@property (nonatomic, strong) NSString *rating;
@property (nonatomic, strong) NSString *review_text;
@property (nonatomic, strong) User *user;
@end

I found similar questions that used NSMutableSet to insert, but I am unable to implement the same for an NSMutable Array.

Currently my insertion code looks like :

NSManagedObjectContext *context = [self managedObjectContext];
Restaurant *rest = [NSEntityDescription insertNewObjectForEntityForName:@"Restaurant" inManagedObjectContext:context];
rest.name = restaurant.name;

I retrieve the data for Restaurant and its Reviews via JSON and store them in temporary classes before saving them to the CORE Database. How do I insert such data which has One to Many Relationship ?

EDIT : Currently I receive the data in the form of a class which defines the user_review property as

NSMutableArray *user_reviews;

I am trying to implement this same class to insert into core data model. But the core data model uses NSSet instead of NSMutableArray. One brute approach is to duplicate all the classes with the same properties except instead of using NSMutableArray, i change it to NSSet. But this creates a huge amount of redundant code. Shouldn't there be a more efficient way to do this ?

Kyuubi
  • 1,228
  • 3
  • 18
  • 32

2 Answers2

22

if your Review shall be a relationship in your coreDataModel, please use instead of NSMutableArray the NSSet and connect it with the Restaurant Entity.

In Review: enter image description here

In Restaurant: enter image description here

If you let xcode generate your class, it will look like this:

Restaurant:
@interface Restaurant : NSManagedObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSSet *user_reviews;   /* One to Many Relation to Review Entity*/

@interface Restaurant(CoreDataGeneratedAccessors)

- (void)addUser_reviewsObject:(Review *)value;
- (void)removeUser_reviewsObject:(Review *)value;
- (void)addUser_reviews:(NSSet *)value;
- (void)removeUser_reviews:(NSSet *)value;

@end

Review:
@interface Review : NSManagedObject
@property (nonatomic, strong) NSString *rating;
@property (nonatomic, strong) NSString *review_text;
@property (nonatomic, strong) User *user;
@property (nonatomic, strong) Restaurant *restaurant;
@end

Your call will be:

NSManagedObjectContext *context = [self managedObjectContext];
Restaurant *rest = [NSEntityDescription insertNewObjectForEntityForName:@"Restaurant" inManagedObjectContext:context];
rest.name = restaurant.name;

Review *rev = [NSEntityDescription insertNewObjectForEntityForName:@"Review" inManagedObjectContext:context];
rev.rating = @"1";
rev.review_text = @"nomnomnom";

[rest addUser_reviewsObject:rev];
// or rev.restaurant = restaurant; one of both is enought as far as I remember

// save your context

edit

If it has to be a NSMutableArray, it cannot be a ralation.
Those are always NSSets (if x to n) or the destination classes. Using NSMutableArray takes the advantages of sets and the automatic handling.

But if you realy want to store in NSMutableArray, I recommand to expand your Review class at least by a reviewID attribute (unique) and store the NSMutableArray as Transformable.

//Review:

@interface Review : NSManagedObject
@property (nonatomic, strong) NSString *rating;
@property (nonatomic, strong) NSString *review_text;
@property (nonatomic, strong) User *user;
@property (nonatomic, strong) NSNumber *reviewID;
@end

//Restaurant.h:

@interface Restaurant : NSManagedObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSSet *user_reviews; // Set of NSNumbers

- (void)addUser_reviewsObject:(Review *)value;
- (void)addUser_reviewsID:(NSNumber *)value;
- (void)removeUser_reviewsObject:(Review *)value;
- (void)addUser_reviews:(NSMutableArray *)value;
- (void)removeUser_reviews:(NSMutableArray *)value;

@end

//Restaurant.m:

- (void)addUser_reviewsObject:(Review *)value
{
    [self addUser_reviewsID:value.reviewID];
}

- (void)addUser_reviewsID:(NSNumber *)value
{
    if(![self.user_reviews containsObject:value];
        [self.user_reviews addObject:value];
}

- (void)removeUser_reviewsObject:(NSNumber *)value
{
    // follow upper logic and implement yourself
}

- (void)addUser_reviews:(NSMutableArray *)value
{
    // follow upper logic and implement yourself
}

- (void)removeUser_reviews:(NSMutableArray *)value
{
    // follow upper logic and implement yourself
}
geo
  • 1,781
  • 1
  • 18
  • 30
  • Right, just copy/pasted your code at this part ^^ is there a problem with if they are not `NSObjects` but `NSManagedObjects`? If there is a reason speaking against this solution, please tell me :) – geo Jun 10 '13 at 07:47
  • *"... as far as I remember"* - that *is* correct if Restaurant.user_reviews and Review.restaurant are setup as **inverse relationships**, as the screenshot indicates. – Martin R Jun 10 '13 at 07:54
  • Thats the issue. I am first loading the entire data from an API online with the help of RestKit 0.20. Restkit loads all the Relations in an array instead of an NSSet. I have defined a class for the "Restaurant.h" for Restkit to load/map data into. Now I am trying to store the data by loading it into the Core Data Model , so that it is available for offline access. But to do that I need to create the classes for each entity. I am trying to reuse the classes I made for the RestKit API, since everything is the same, except for the NSMutableArray and NSSet. – Kyuubi Jun 10 '13 at 07:56
  • @Kyuubi: You should add to your question that you are using RestKit, as that is relevant information. - I have no experience with RestKit, but from what I have seen (here at SO) about it, it seems to me that you can tell RestKit about the relationship so that the connections are made automatically. – Martin R Jun 10 '13 at 08:03
  • @MartinR: It seemed irrelevant. Since I cannot change the RestKit portion of the code. Changing it would mean changeing more than half of the code base. The RestKit Controller sends me the data in the form of a class which uses NSMutableArray. And Core Data uses NSSet. So one brute option is to make a separate class just for core data model. But that is a lot of redundant code. Which is why my question is , how to store an NSMutable array in a Core Data Model. – Kyuubi Jun 10 '13 at 08:09
  • 1
    If it has to be a NSMutableArray, it cannot be a ralation. Those are always `NSSets` (if x to n) or the destination classes. Using `NSMutableArray` takes the advantages of sets and the automatic handling. But if you realy want to store in `NSMutableArray`, I recommand to expand your `Review` class at least by a "reviewID" attribute and store the `NSMutableArray` as `Transformable`. I will add this part to my answer – geo Jun 10 '13 at 08:24
1
Review *review = (Review *)[NSEntityDescription insertNewObjectForEntityForName:@"Review" inManagedObjectContext:[self managedObjectContext]];
review.review_text = @"test text";
//set other properties if needed

rest.user_reviews = [NSSet setWithObjects:review, nil];//just make NSSet for you reviews
p.balmasov
  • 357
  • 1
  • 16