-
Notifications
You must be signed in to change notification settings - Fork 66
CouchModel Relationships
CouchModel exposes document properties as Objective-C properties, making it easy to read and write them. In addition to scalar types like numbers and strings, these properties can be pointers to other CouchModel objects. This creates what are generally called "relationships" between objects. For example, a blog comment would probably have a relationship to the post that it refers to.
In the database, a relationship is expressed by a property whose value is the ID of the target document. CouchModel knows this convention, so if you simply declare an Objective-C dynamic property whose type is a pointer to a CouchModel subclass, then at runtime the property value is looked up like this:
Objective-C property --> document property --> database (by ID) --> document --> model
Let's say you have documents for blog comments, and each has a "post" property whose value is the document ID of the blog post it refers to. You can model that like this:
@class BlogPost;
@interface BlogComment : CouchModel
@property (assign) BlogPost* post;
@end
And in the implementation of BlogComment you simply declare the property as @dynamic
, like any other model property.
Note that the declaration uses (assign)
instead of the more typical (retain)
. This is because a relationship to another model doesn't retain it, to avoid creating reference-loops that can lead to memory leaks.
There's a slight gotcha with this. To save a model object that has a relationship, the document property has to be updated with the ID of the target document. But by default, documents are created "untitled", with no ID until the first time they're saved. In that case, the target document has to be saved first before you can save the source document that refers to it. This is awkward, and in the worst case leads to chicken-and-egg problems if you have two new documents that each refer to the other. Oops!
The solution to this is to create new documents with an ID. It needs to be unique, though. There are three ways to ensure this:
- Get an ID from the server by calling -[CouchServer generateUUIDs: 1]
- Create an ID locally by calling
CFUUIDCreate()
- Use a pre-existing ID that you know will be unique to the database (for example, an ISBN or employee number)
Once you have the ID, you can create the model like this:
[MyModel modelForDocument: [db documentWithID: someUniqueID]]
If you instantiate your models like this, you won't have any trouble with saving relationships.
So far, if you declare a property's type as being BlogPost*
, the instantiated object will be a BlogPost. But what if BlogPost has subclasses? In a tumblr-style app, maybe there are different types of posts, like text / image / video, differentiated by the value of a type
property, and you want these to be instantiated as subclasses like TextPost
, ImagePost
and VideoPost
. How do you tell the property which class to instantiate for which document when the property type doesn't narrow it down to one class?
Enter the CouchModelFactory
. This singleton object keeps a registry that maps document type
property values to classes. If at launch time you register the type strings and the corresponding BlogPost
subclasses, then CouchModel will consult this when instantiating model-reference properties. So the value of the post
property of a comment will be a TextPost
, ImagePost
or VideoPost
depending on the document's type.
Once you've started using the CouchModelFactory
, you'll probably want to start instantiating models for existing documents by calling +modelForDocument:
on CouchModel itself, rather than a subclass. This is because
[CouchModel modelForDocument: doc]
uses the factory to decide at runtime which class to instantiate based on the document's contents, while
[BlogPost modelForDocument: doc]
will always create a BlogPost
object, even if the document's type
indicates that it should get an ImagePost
or VideoPost
, which is probably not what you want.
If this sounds a bit confusing, that's because it is. It's a very useful feature, but new and not entirely polished yet. There's more that can be added in the future to make relationships work more intuitively. Stay tuned.