views:

55

answers:

3

Hi all,

I have a Rails background and am trying to muck through Core Data. How does Core Data handle complex :has_and_belongs_to_many =>, :through => relationships?

As an example, suppose you were modeling open source project contributions. You might set up a table structure such as:

Contributor            Contribution             Project
----------------     ------------------     ------------------
name                   typeOfContribution       name
address                amountOfContribution     urlForSourceCode
...                    startedOnDate            ...
...                    endedOnDate              ...
                       contributor              
                       project  

In this case you would say that both Contributor and Project :has_and_belong_to_many of each other, and that both relationships are :through the Contribution join table. This lets you store information about the relationship within the relationship itself (the Contribution table).

The Rails convenience methods are also really handy to have, so you can navigate through the relationships simply. For example:

contributor.projects   // this would return an array of project objects associated with the contributor
project.contributors   // this would return an array of contributors associated with a project

So my question boils do to this:

  1. Is it possible to model in a similar way using Core Data? and
  2. If so, can the relationships be traversed as easily as the code above (sample code would be great)?
A: 

Have a look

// this would return an array of project objects associated with the contributor
contributor.projects
// this would return an array of contributors associated with a project  
project.contributors


Class Contributor  < ActiveRecord::Base
   has_and_belongs_to_many :projects , :join_table => "contributions"
end

Class  Contribution  < ActiveRecord::Base 
end           

Class Project  < ActiveRecord::Base 
   has_and_belongs_to_many :contributors , :join_table => "contributions"
end
The question was about how to do this in Core Data in Onjective-C, not in Ruby on Rails.
Chris Hanson
A: 

Actually, they're both pretty much same because both are object graph systems. The vocabulary is different. Rails still maintains much of the relational database nomenclature whereas Core Data uses a purely object oriented nomenclature.

Instead of "tables" core data has "entities" that represent attributes and relationships of runtime objects. Core Data names relationships at each end of relationship using to-one(-->), to-many(-->>).

So, in your example you would have (pseudocode):

Contributor{
    name:string;
    address:string;
    contributions<-->>Contribution.contributor;
}

Contribution{
    typeOfContribution:string;
    ...
    contributor<<-->Contributor.contributions;

}

Project{
    name:string;
    ...
    contributions<-->Contribution.project;
}

To find a project starting with a Contributor, you would walk the relationship using a keypath in a predicate. Something like:

NSPredicate *aPred=[NSPredicate predicateWithFormat:@"project.name=%@",nameVariable];
NSSet *foundProjects=[aContributorObj.contributions filteredSetUsingPredicate:aPred];

This would take all the Contribution objects related to the specific Contributor object and then walk the keypath to the Contribution object's project relationship and then to the project objects name attribute. If the name attribute matches the value in nameVariable the project object is returned to the set held as foundProjects.

I'm not a big Rails guy but from what I have done it looks like Core Data and Rails are fairly close in functionality, its just the nomenclature that is different. Core Data has always had the graphical data modeler and predicates so it uses fewer textual shortcuts than does Rails.

TechZen
+1  A: 

In Core Data, you need to model the relationships "crossing" the intermediate entity yourself, the framework won't do this for you. It doesn't really have the concept of "has many through" like ActiveRecord in Rails or "flattened relationships" like in the Enterprise Objects Framework.

That said, you can implement properties and methods for them easily. For example:

@interface Contributor : NSManagedObject
@property (readonly, copy) NSSet *projects;
@end

@implementation Contributor
- (NSSet *)projects {
    // use aggregate KVC to do the iteration automatically
    return [self.contributions valueForKey:@"projects"]; 
}
@end

If you need to make your "projects" relationship modifiable it should be similar. Making it support Key-Value Observing, say if you need to bind to it, is more interesting - you essentially need to have your Contributor observe every one of its Contributions' projects relationships, and post similar KVO notifications for its own "projects" relationship. Not that hard, just a bit of boilerplate code to write.

Chris Hanson
Ok, this makes more sense to me. So I would model the relationship in the .xcdatamodel, then implement the accessor methods in the Contributor and Project models. That would give me the "missing" methods. Am I getting this right, and I understand the concept but could you give a bit more detail KVO? Thanks!
Neal L
You're getting it right. Sorry, I don't have the time to go into more detail on what you'd need to do with KVO. I suggest asking on the cocoa-dev list, or asking a separate question on Stack Overflow.
Chris Hanson