Skip to content

Commit

Permalink
CouchModel improvements: defaults, property handling, load/reload/exi…
Browse files Browse the repository at this point in the history
…sts.
  • Loading branch information
fabien committed Jun 23, 2012
1 parent 6d331cd commit dc3dea0
Show file tree
Hide file tree
Showing 9 changed files with 457 additions and 20 deletions.
8 changes: 8 additions & 0 deletions Couch/CouchDocument.m
Expand Up @@ -75,6 +75,14 @@ - (NSString*) description {
@synthesize isDeleted=_isDeleted, modelObject=_modelObject; @synthesize isDeleted=_isDeleted, modelObject=_modelObject;




- (void) resetCurrentRevision {
[_currentRevisionID autorelease];
_currentRevisionID = nil;
[_currentRevision autorelease];
_currentRevision = nil;
}


- (NSString*) currentRevisionID { - (NSString*) currentRevisionID {
return _currentRevisionID; return _currentRevisionID;
} }
Expand Down
1 change: 1 addition & 0 deletions Couch/CouchInternal.h
Expand Up @@ -51,6 +51,7 @@ typedef void (^OnDatabaseChangeBlock)(CouchDocument*, BOOL externalChange);
- (void) loadCurrentRevisionFrom: (CouchQueryRow*)row; - (void) loadCurrentRevisionFrom: (CouchQueryRow*)row;
- (void) bulkSaveCompleted: (NSDictionary*) result forProperties: (NSDictionary*)properties; - (void) bulkSaveCompleted: (NSDictionary*) result forProperties: (NSDictionary*)properties;
- (BOOL) notifyChanged: (NSDictionary*)change; - (BOOL) notifyChanged: (NSDictionary*)change;
- (void) resetCurrentRevision;
@end @end




Expand Down
12 changes: 6 additions & 6 deletions CouchCocoa.xcodeproj/project.pbxproj
Expand Up @@ -39,7 +39,6 @@
2784E1B713CE5249009CC5C8 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27A5792913974B65002776DB /* Cocoa.framework */; }; 2784E1B713CE5249009CC5C8 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27A5792913974B65002776DB /* Cocoa.framework */; };
2784E1C313CE52FE009CC5C8 /* ShoppingDemo.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2784E1C113CE52FE009CC5C8 /* ShoppingDemo.xib */; }; 2784E1C313CE52FE009CC5C8 /* ShoppingDemo.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2784E1C113CE52FE009CC5C8 /* ShoppingDemo.xib */; };
2784E1C513CE5559009CC5C8 /* DemoQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A577A713970959002776DB /* DemoQuery.m */; }; 2784E1C513CE5559009CC5C8 /* DemoQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 27A577A713970959002776DB /* DemoQuery.m */; };
27911B711411A7C100ABD31B /* Test_DynamicObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 27911B701411A7C100ABD31B /* Test_DynamicObject.m */; };
27911B721411A7C100ABD31B /* Test_DynamicObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 27911B701411A7C100ABD31B /* Test_DynamicObject.m */; }; 27911B721411A7C100ABD31B /* Test_DynamicObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 27911B701411A7C100ABD31B /* Test_DynamicObject.m */; };
27911B751411A8C700ABD31B /* CouchTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 27911B741411A8C700ABD31B /* CouchTestCase.m */; }; 27911B751411A8C700ABD31B /* CouchTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 27911B741411A8C700ABD31B /* CouchTestCase.m */; };
27911B761411A8C700ABD31B /* CouchTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 27911B741411A8C700ABD31B /* CouchTestCase.m */; }; 27911B761411A8C700ABD31B /* CouchTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 27911B741411A8C700ABD31B /* CouchTestCase.m */; };
Expand Down Expand Up @@ -117,8 +116,6 @@
27CDEC3413C67D3500C979BB /* CouchQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 278B275E1394225600DDD950 /* CouchQuery.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27CDEC3413C67D3500C979BB /* CouchQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 278B275E1394225600DDD950 /* CouchQuery.h */; settings = {ATTRIBUTES = (Public, ); }; };
27CDEC3513C67D3500C979BB /* CouchResource.h in Headers */ = {isa = PBXBuildFile; fileRef = 278B24CA1392EE3600DDD950 /* CouchResource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27CDEC3513C67D3500C979BB /* CouchResource.h in Headers */ = {isa = PBXBuildFile; fileRef = 278B24CA1392EE3600DDD950 /* CouchResource.h */; settings = {ATTRIBUTES = (Public, ); }; };
27CDEC3613C67D3500C979BB /* CouchCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = 270A663C13A5B3DF00791F4A /* CouchCocoa.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27CDEC3613C67D3500C979BB /* CouchCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = 270A663C13A5B3DF00791F4A /* CouchCocoa.h */; settings = {ATTRIBUTES = (Public, ); }; };
27CDEC3713C67E1600C979BB /* Test_REST.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E9D9313A2EBE0009F18E9 /* Test_REST.m */; };
27CDEC3813C67E1A00C979BB /* Test_Couch.m in Sources */ = {isa = PBXBuildFile; fileRef = 270A663A13A5B36900791F4A /* Test_Couch.m */; };
27CDEC3A13C6841E00C979BB /* CouchCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27CDEBF113C67C9B00C979BB /* CouchCocoa.framework */; }; 27CDEC3A13C6841E00C979BB /* CouchCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27CDEBF113C67C9B00C979BB /* CouchCocoa.framework */; };
27D083B8143FBEEA0067702F /* CouchbaseCallbacks.h in Headers */ = {isa = PBXBuildFile; fileRef = 27D083B7143FBEEA0067702F /* CouchbaseCallbacks.h */; }; 27D083B8143FBEEA0067702F /* CouchbaseCallbacks.h in Headers */ = {isa = PBXBuildFile; fileRef = 27D083B7143FBEEA0067702F /* CouchbaseCallbacks.h */; };
27D083B9143FBEEA0067702F /* CouchbaseCallbacks.h in Headers */ = {isa = PBXBuildFile; fileRef = 27D083B7143FBEEA0067702F /* CouchbaseCallbacks.h */; }; 27D083B9143FBEEA0067702F /* CouchbaseCallbacks.h in Headers */ = {isa = PBXBuildFile; fileRef = 27D083B7143FBEEA0067702F /* CouchbaseCallbacks.h */; };
Expand Down Expand Up @@ -159,6 +156,9 @@
27EEA5B613D6052300D7ACA4 /* REST.h in Headers */ = {isa = PBXBuildFile; fileRef = 270A664413A5BA4600791F4A /* REST.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27EEA5B613D6052300D7ACA4 /* REST.h in Headers */ = {isa = PBXBuildFile; fileRef = 270A664413A5BA4600791F4A /* REST.h */; settings = {ATTRIBUTES = (Public, ); }; };
27EF148C1396D8CC0052913E /* DemoAppController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF148B1396D8CC0052913E /* DemoAppController.m */; }; 27EF148C1396D8CC0052913E /* DemoAppController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF148B1396D8CC0052913E /* DemoAppController.m */; };
27EF14B31396DD3B0052913E /* AddressesDemo.xib in Resources */ = {isa = PBXBuildFile; fileRef = 27EF14B21396DD3B0052913E /* AddressesDemo.xib */; }; 27EF14B31396DD3B0052913E /* AddressesDemo.xib in Resources */ = {isa = PBXBuildFile; fileRef = 27EF14B21396DD3B0052913E /* AddressesDemo.xib */; };
5FDC4FC015932E58004B0576 /* Test_REST.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E9D9313A2EBE0009F18E9 /* Test_REST.m */; };
5FDC4FC115932E5A004B0576 /* Test_Couch.m in Sources */ = {isa = PBXBuildFile; fileRef = 270A663A13A5B36900791F4A /* Test_Couch.m */; };
5FDC4FC215932E5D004B0576 /* Test_DynamicObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 27911B701411A7C100ABD31B /* Test_DynamicObject.m */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */


/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -1075,11 +1075,11 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
27CDEC3713C67E1600C979BB /* Test_REST.m in Sources */,
27CDEC3813C67E1A00C979BB /* Test_Couch.m in Sources */,
2795994F140A02DB001C168A /* Test_Model.m in Sources */, 2795994F140A02DB001C168A /* Test_Model.m in Sources */,
27911B711411A7C100ABD31B /* Test_DynamicObject.m in Sources */,
27911B751411A8C700ABD31B /* CouchTestCase.m in Sources */, 27911B751411A8C700ABD31B /* CouchTestCase.m in Sources */,
5FDC4FC015932E58004B0576 /* Test_REST.m in Sources */,
5FDC4FC115932E5A004B0576 /* Test_Couch.m in Sources */,
5FDC4FC215932E5D004B0576 /* Test_DynamicObject.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
Expand Down
3 changes: 3 additions & 0 deletions Model/CouchDynamicObject.h
Expand Up @@ -17,6 +17,9 @@
/** Returns the names of all properties defined in this class and superclasses up to CouchDynamicObject. */ /** Returns the names of all properties defined in this class and superclasses up to CouchDynamicObject. */
+ (NSSet*) propertyNames; + (NSSet*) propertyNames;


///** Returns the names of all writable properties defined in this class and superclasses up to CouchDynamicObject. */
+ (NSSet*) writablePropertyNames;

/** Returns the value of a named property. /** Returns the value of a named property.
This method will only be called for properties that have been declared in the class's @@interface using @@property. This method will only be called for properties that have been declared in the class's @@interface using @@property.
You must override this method -- the base implementation just raises an exception. */ You must override this method -- the base implementation just raises an exception. */
Expand Down
50 changes: 41 additions & 9 deletions Model/CouchDynamicObject.m
Expand Up @@ -105,6 +105,24 @@ static void setDoubleProperty(CouchDynamicObject *self, SEL _cmd, double value)


#pragma mark - PROPERTY INTROSPECTION: #pragma mark - PROPERTY INTROSPECTION:


+ (NSMutableSet*) propertyNamesPassingBlock:(BOOL (^)(NSString* name, BOOL isSettable))block {
NSMutableSet* propertyNames = [NSMutableSet set];
unsigned int count;
objc_property_t* propertiesExcludingSuperclass = class_copyPropertyList(self, &count);
if (propertiesExcludingSuperclass) {
BOOL isSettable = NO;
objc_property_t* propertyPtr = propertiesExcludingSuperclass;
for ( int i = 0; i < count; i++ ) {
objc_property_t property = propertyPtr[i];
const char *propName = property_getName(property);
getPropertyType(property, &isSettable);
NSString* name = [NSString stringWithUTF8String:propName];
if (block(name, isSettable)) [propertyNames addObject:name];
}
free(propertiesExcludingSuperclass);
}
return propertyNames;
}


+ (NSSet*) propertyNames { + (NSSet*) propertyNames {
static NSMutableDictionary* classToNames; static NSMutableDictionary* classToNames;
Expand All @@ -117,20 +135,34 @@ + (NSSet*) propertyNames {
NSSet* cachedPropertyNames = [classToNames objectForKey:self]; NSSet* cachedPropertyNames = [classToNames objectForKey:self];
if (cachedPropertyNames) if (cachedPropertyNames)
return cachedPropertyNames; return cachedPropertyNames;


NSMutableSet* propertyNames = [NSMutableSet set]; NSMutableSet* propertyNames = [self propertyNamesPassingBlock:^BOOL(NSString *name, BOOL isSettable) {
objc_property_t* propertiesExcludingSuperclass = class_copyPropertyList(self, NULL); return YES;
if (propertiesExcludingSuperclass) { }];
objc_property_t* propertyPtr = propertiesExcludingSuperclass;
while (*propertyPtr)
[propertyNames addObject:[NSString stringWithUTF8String:property_getName(*propertyPtr++)]];
free(propertiesExcludingSuperclass);
}
[propertyNames unionSet:[[self superclass] propertyNames]]; [propertyNames unionSet:[[self superclass] propertyNames]];
[classToNames setObject:propertyNames forKey:self]; [classToNames setObject:propertyNames forKey:self];
return propertyNames; return propertyNames;
} }


+ (NSSet*) writablePropertyNames {
static NSMutableDictionary* classToNames;
if (!classToNames)
classToNames = [[NSMutableDictionary alloc] init];

if (self == [CouchDynamicObject class])
return [NSSet set];

NSSet* cachedPropertyNames = [classToNames objectForKey:self];
if (cachedPropertyNames)
return cachedPropertyNames;

NSMutableSet* propertyNames = [self propertyNamesPassingBlock:^BOOL(NSString *name, BOOL isSettable) {
return isSettable;
}];
[propertyNames unionSet:[[self superclass] propertyNames]];
[classToNames setObject:propertyNames forKey:self];
return propertyNames;
}


// Look up the encoded type of a property, and whether it's settable or readonly // Look up the encoded type of a property, and whether it's settable or readonly
static const char* getPropertyType(objc_property_t property, BOOL *outIsSettable) { static const char* getPropertyType(objc_property_t property, BOOL *outIsSettable) {
Expand Down
25 changes: 24 additions & 1 deletion Model/CouchModel.h
Expand Up @@ -22,7 +22,6 @@
CouchDocument* _document; CouchDocument* _document;
CFAbsoluteTime _changedTime; CFAbsoluteTime _changedTime;
bool _autosaves :1; bool _autosaves :1;
bool _isNew :1;
bool _needsSave :1; bool _needsSave :1;


NSMutableDictionary* _properties; // Cached property values, including changed values NSMutableDictionary* _properties; // Cached property values, including changed values
Expand All @@ -45,6 +44,12 @@
(This method is mostly here so that NSController objects can create CouchModels.) */ (This method is mostly here so that NSController objects can create CouchModels.) */
- (id) init; - (id) init;


/** Attempts to load the document/revision (properties) if not already loaded **/
- (BOOL) load;

/** Force reloading of the document/revision (properties) **/
- (BOOL) reload;

/** The document this item is associated with. Will be nil if it's new and unsaved. */ /** The document this item is associated with. Will be nil if it's new and unsaved. */
@property (readonly, retain) CouchDocument* document; @property (readonly, retain) CouchDocument* document;


Expand Down Expand Up @@ -92,6 +97,20 @@


#pragma mark - PROPERTIES & ATTACHMENTS: #pragma mark - PROPERTIES & ATTACHMENTS:


- (NSDictionary*) properties;

/** Replace the current properties dictionary completely, taking default values into account. **/
- (void) setProperties:(NSDictionary*)properties;

/** Reset the current properties dictionary completely, while keeping default values. **/
- (void) clearProperties;

/** Merge the current properties dictionary; writable properties only. **/
- (void) updateProperties:(NSDictionary*)properties;

/** Reset known (writable) properties to default values. **/
- (void) resetProperties;

/** Gets a property by name. /** Gets a property by name.
You can use this for document properties that you haven't added @@property declarations for. */ You can use this for document properties that you haven't added @@property declarations for. */
- (id) getValueOfProperty: (NSString*)property; - (id) getValueOfProperty: (NSString*)property;
Expand Down Expand Up @@ -133,6 +152,10 @@
Default is nil, which means to assign no ID (the server will assign one). */ Default is nil, which means to assign no ID (the server will assign one). */
- (NSString*) idForNewDocumentInDatabase: (CouchDatabase*)db; - (NSString*) idForNewDocumentInDatabase: (CouchDatabase*)db;


/** Called when the model's properties are reset or the document is explicitly loaded, but missing.
If it's missing, it's a new document, ready to be initialized with the defaults. */
- (void) setDefaultValues;

/** Called when the model's properties are reloaded from the document. /** Called when the model's properties are reloaded from the document.
This happens both when initialized from a document, and after an external change. */ This happens both when initialized from a document, and after an external change. */
- (void) didLoadFromDocument; - (void) didLoadFromDocument;
Expand Down
84 changes: 81 additions & 3 deletions Model/CouchModel.m
Expand Up @@ -15,11 +15,20 @@ @interface CouchModel ()
@property (readwrite, retain) CouchDocument* document; @property (readwrite, retain) CouchDocument* document;
@property (readwrite) bool needsSave; @property (readwrite) bool needsSave;
- (NSDictionary*) attachmentDataToSave; - (NSDictionary*) attachmentDataToSave;
- (void) reset;
@end @end




@implementation CouchModel @implementation CouchModel


- (BOOL)ensureDefaults {
if (self.document.currentRevision == nil) {
[self setDefaultValues];
_needsSave = NO;
return YES;
}
return NO;
}


- (id)init { - (id)init {
return [self initWithDocument: nil]; return [self initWithDocument: nil];
Expand All @@ -32,10 +41,11 @@ - (id) initWithDocument: (CouchDocument*)document
if (document) { if (document) {
COUCHLOG2(@"%@ initWithDocument: %@ @%p", self, document, document); COUCHLOG2(@"%@ initWithDocument: %@ @%p", self, document, document);
self.document = document; self.document = document;
[self ensureDefaults];
[self didLoadFromDocument]; [self didLoadFromDocument];
} else { } else {
_isNew = true;
COUCHLOG2(@"%@ init", self); COUCHLOG2(@"%@ init", self);
[self setDefaultValues];
} }
} }
return self; return self;
Expand Down Expand Up @@ -89,6 +99,26 @@ - (NSString*) description {


#pragma mark - DOCUMENT / DATABASE: #pragma mark - DOCUMENT / DATABASE:


- (BOOL) load {
if (!self.document.currentRevisionID) { // not loaded yet
if (self.document.currentRevision) [self couchDocumentChanged: self.document];
return [self ensureDefaults];
}
return NO;
}

- (void) reset {
_properties = nil;
_changedNames = nil;
_changedAttachments = nil;
[self.document resetCurrentRevision];
_needsSave = NO;
}

- (BOOL) reload {
[self reset];
return [self load];
}


- (CouchDocument*) document { - (CouchDocument*) document {
return _document; return _document;
Expand Down Expand Up @@ -146,6 +176,9 @@ - (RESTOperation*) deleteDocument {
return op; return op;
} }


- (void) setDefaultValues {
// subclasses can override this
}


- (void) didLoadFromDocument { - (void) didLoadFromDocument {
// subclasses can override this // subclasses can override this
Expand Down Expand Up @@ -191,8 +224,11 @@ - (void) markExternallyChanged {
#pragma mark - SAVING: #pragma mark - SAVING:




@synthesize isNew=_isNew, autosaves=_autosaves, needsSave=_needsSave; @synthesize autosaves=_autosaves, needsSave=_needsSave;


- (bool) isNew {
return !(_document && _document.currentRevisionID);
}


- (void) setAutosaves: (bool) autosaves { - (void) setAutosaves: (bool) autosaves {
if (autosaves != _autosaves) { if (autosaves != _autosaves) {
Expand All @@ -217,7 +253,6 @@ - (void) saveCompleted: (RESTOperation*)op {
[self couchDocumentChanged: _document]; // reset to contents from server [self couchDocumentChanged: _document]; // reset to contents from server
//[NSApp presentError: op.error]; //[NSApp presentError: op.error];
} else { } else {
_isNew = NO;
[_properties release]; [_properties release];
_properties = nil; _properties = nil;
[_changedNames release]; [_changedNames release];
Expand Down Expand Up @@ -282,6 +317,49 @@ + (NSSet*) propertyNames {
return [super propertyNames]; return [super propertyNames];
} }


+ (NSSet*) writablePropertyNames {
if (self == [CouchModel class])
return [NSSet set]; // Ignore non-persisted properties declared on base CouchModel
return [super writablePropertyNames];
}

- (NSDictionary*) properties {
NSMutableDictionary* props = [NSMutableDictionary dictionary];
[[self.class propertyNames] enumerateObjectsUsingBlock:^(id key, BOOL *stop) {
id value = [self getValueOfProperty:key];
[props setValue:value forKey:key];
}];
return props;
}

- (void) setProperties:(NSDictionary*)properties {
[self resetProperties];
[self updateProperties:properties strict:NO];
}

- (void) updateProperties:(NSDictionary*)properties {
[self updateProperties:properties strict:YES];
}

- (void) updateProperties:(NSDictionary*)properties strict:(BOOL)strict {
NSSet *writableNames = [self.class writablePropertyNames];
[properties enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
if (!strict || [writableNames member:key]) [self setValue:value ofProperty:key];
}];
}

- (void) clearProperties {
_properties = nil;
[self setDefaultValues];
}

- (void) resetProperties {
[[self.class writablePropertyNames] enumerateObjectsUsingBlock:^(id key, BOOL *stop) {
[self setValue:nil ofProperty:key];
}];
[self setDefaultValues];
}

// Transforms cached property values back into JSON-compatible objects // Transforms cached property values back into JSON-compatible objects
- (id) externalizePropertyValue: (id)value { - (id) externalizePropertyValue: (id)value {
if ([value isKindOfClass: [NSData class]]) if ([value isKindOfClass: [NSData class]])
Expand Down
11 changes: 11 additions & 0 deletions Test/Test_DynamicObject.m
Expand Up @@ -146,5 +146,16 @@ - (void) test3_intTypes {
STAssertEqualObjects([test->_dict objectForKey: @"shorty"], [NSNumber numberWithShort: -32768], nil); STAssertEqualObjects([test->_dict objectForKey: @"shorty"], [NSNumber numberWithShort: -32768], nil);
} }


- (void) test4_propertyNames {
NSSet* names = [NSSet setWithObjects: @"stringy", @"intey", @"shorty", @"doubley", @"booley", nil];
STAssertEqualObjects([TestDynamicObject propertyNames], names, nil);
names = [NSSet setWithObjects: @"stringy", @"intey", @"shorty", @"doubley", @"booley", @"dataey", nil];
STAssertEqualObjects([TestDynamicSubclass propertyNames], names, nil);
}

- (void) test5_writablePropertyNames {
NSSet* names = [NSSet setWithObjects: @"stringy", @"shorty", @"doubley", @"booley", nil];
STAssertEqualObjects([TestDynamicObject writablePropertyNames], names, nil);
}


@end @end

0 comments on commit dc3dea0

Please sign in to comment.