Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

CouchModel improvements: defaults, property handling, load/reload/exi…

…sts.
  • Loading branch information...
commit dc3dea09a9c2c4d05f7a4eb2a6f90e6ee0d9334a 1 parent 6d331cd
@fabien authored
View
8 Couch/CouchDocument.m
@@ -75,6 +75,14 @@ - (NSString*) description {
@synthesize isDeleted=_isDeleted, modelObject=_modelObject;
+- (void) resetCurrentRevision {
+ [_currentRevisionID autorelease];
+ _currentRevisionID = nil;
+ [_currentRevision autorelease];
+ _currentRevision = nil;
+}
+
+
- (NSString*) currentRevisionID {
return _currentRevisionID;
}
View
1  Couch/CouchInternal.h
@@ -51,6 +51,7 @@ typedef void (^OnDatabaseChangeBlock)(CouchDocument*, BOOL externalChange);
- (void) loadCurrentRevisionFrom: (CouchQueryRow*)row;
- (void) bulkSaveCompleted: (NSDictionary*) result forProperties: (NSDictionary*)properties;
- (BOOL) notifyChanged: (NSDictionary*)change;
+- (void) resetCurrentRevision;
@end
View
12 CouchCocoa.xcodeproj/project.pbxproj
@@ -39,7 +39,6 @@
2784E1B713CE5249009CC5C8 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27A5792913974B65002776DB /* Cocoa.framework */; };
2784E1C313CE52FE009CC5C8 /* ShoppingDemo.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2784E1C113CE52FE009CC5C8 /* ShoppingDemo.xib */; };
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 */; };
27911B751411A8C700ABD31B /* CouchTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 27911B741411A8C700ABD31B /* CouchTestCase.m */; };
27911B761411A8C700ABD31B /* CouchTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 27911B741411A8C700ABD31B /* CouchTestCase.m */; };
@@ -117,8 +116,6 @@
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, ); }; };
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 */; };
27D083B8143FBEEA0067702F /* CouchbaseCallbacks.h in Headers */ = {isa = PBXBuildFile; fileRef = 27D083B7143FBEEA0067702F /* CouchbaseCallbacks.h */; };
27D083B9143FBEEA0067702F /* CouchbaseCallbacks.h in Headers */ = {isa = PBXBuildFile; fileRef = 27D083B7143FBEEA0067702F /* CouchbaseCallbacks.h */; };
@@ -159,6 +156,9 @@
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 */; };
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 */
/* Begin PBXContainerItemProxy section */
@@ -1075,11 +1075,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 27CDEC3713C67E1600C979BB /* Test_REST.m in Sources */,
- 27CDEC3813C67E1A00C979BB /* Test_Couch.m in Sources */,
2795994F140A02DB001C168A /* Test_Model.m in Sources */,
- 27911B711411A7C100ABD31B /* Test_DynamicObject.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;
};
View
3  Model/CouchDynamicObject.h
@@ -17,6 +17,9 @@
/** Returns the names of all properties defined in this class and superclasses up to CouchDynamicObject. */
+ (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.
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. */
View
50 Model/CouchDynamicObject.m
@@ -105,6 +105,24 @@ static void setDoubleProperty(CouchDynamicObject *self, SEL _cmd, double value)
#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 {
static NSMutableDictionary* classToNames;
@@ -117,20 +135,34 @@ + (NSSet*) propertyNames {
NSSet* cachedPropertyNames = [classToNames objectForKey:self];
if (cachedPropertyNames)
return cachedPropertyNames;
-
- NSMutableSet* propertyNames = [NSMutableSet set];
- objc_property_t* propertiesExcludingSuperclass = class_copyPropertyList(self, NULL);
- if (propertiesExcludingSuperclass) {
- objc_property_t* propertyPtr = propertiesExcludingSuperclass;
- while (*propertyPtr)
- [propertyNames addObject:[NSString stringWithUTF8String:property_getName(*propertyPtr++)]];
- free(propertiesExcludingSuperclass);
- }
+
+ NSMutableSet* propertyNames = [self propertyNamesPassingBlock:^BOOL(NSString *name, BOOL isSettable) {
+ return YES;
+ }];
[propertyNames unionSet:[[self superclass] propertyNames]];
[classToNames setObject:propertyNames forKey:self];
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
static const char* getPropertyType(objc_property_t property, BOOL *outIsSettable) {
View
25 Model/CouchModel.h
@@ -22,7 +22,6 @@
CouchDocument* _document;
CFAbsoluteTime _changedTime;
bool _autosaves :1;
- bool _isNew :1;
bool _needsSave :1;
NSMutableDictionary* _properties; // Cached property values, including changed values
@@ -45,6 +44,12 @@
(This method is mostly here so that NSController objects can create CouchModels.) */
- (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. */
@property (readonly, retain) CouchDocument* document;
@@ -92,6 +97,20 @@
#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.
You can use this for document properties that you haven't added @@property declarations for. */
- (id) getValueOfProperty: (NSString*)property;
@@ -133,6 +152,10 @@
Default is nil, which means to assign no ID (the server will assign one). */
- (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.
This happens both when initialized from a document, and after an external change. */
- (void) didLoadFromDocument;
View
84 Model/CouchModel.m
@@ -15,11 +15,20 @@ @interface CouchModel ()
@property (readwrite, retain) CouchDocument* document;
@property (readwrite) bool needsSave;
- (NSDictionary*) attachmentDataToSave;
+- (void) reset;
@end
@implementation CouchModel
+- (BOOL)ensureDefaults {
+ if (self.document.currentRevision == nil) {
+ [self setDefaultValues];
+ _needsSave = NO;
+ return YES;
+ }
+ return NO;
+}
- (id)init {
return [self initWithDocument: nil];
@@ -32,10 +41,11 @@ - (id) initWithDocument: (CouchDocument*)document
if (document) {
COUCHLOG2(@"%@ initWithDocument: %@ @%p", self, document, document);
self.document = document;
+ [self ensureDefaults];
[self didLoadFromDocument];
} else {
- _isNew = true;
COUCHLOG2(@"%@ init", self);
+ [self setDefaultValues];
}
}
return self;
@@ -89,6 +99,26 @@ - (NSString*) description {
#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 {
return _document;
@@ -146,6 +176,9 @@ - (RESTOperation*) deleteDocument {
return op;
}
+- (void) setDefaultValues {
+ // subclasses can override this
+}
- (void) didLoadFromDocument {
// subclasses can override this
@@ -191,8 +224,11 @@ - (void) markExternallyChanged {
#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 {
if (autosaves != _autosaves) {
@@ -217,7 +253,6 @@ - (void) saveCompleted: (RESTOperation*)op {
[self couchDocumentChanged: _document]; // reset to contents from server
//[NSApp presentError: op.error];
} else {
- _isNew = NO;
[_properties release];
_properties = nil;
[_changedNames release];
@@ -282,6 +317,49 @@ + (NSSet*) 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
- (id) externalizePropertyValue: (id)value {
if ([value isKindOfClass: [NSData class]])
View
11 Test/Test_DynamicObject.m
@@ -146,5 +146,16 @@ - (void) test3_intTypes {
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
View
283 Test/Test_Model.m
@@ -24,19 +24,34 @@ @implementation TestModel
@dynamic name, grade, permanentRecord, birthday, otherNames, buddy;
@end
+@interface TestModelSubclass : TestModel
+@property (readonly) NSString* type;
+@property (readwrite, retain) NSString* status;
+@end
+
+@implementation TestModelSubclass
+@dynamic type, status;
+- (void)setDefaultValues {
+ [self setValue:@"test-model" ofProperty:@"type"];
+ [self setValue:@"inactive" ofProperty:@"status"];
+ [self setValue:[NSArray array] ofProperty:@"otherNames"];
+}
+@end
@interface Test_Model : CouchTestCase
- (TestModel*) createModelWithName: (NSString*)name grade: (int)grade;
- (NSData*) attachmentData;
@end
-
@implementation Test_Model
- (void) test0_propertyNames {
NSSet* names = [NSSet setWithObjects: @"name", @"grade", @"permanentRecord", @"birthday", @"otherNames", @"buddy", nil];
STAssertEqualObjects([TestModel propertyNames], names, nil);
+ NSSet* allNames = [NSSet setWithObjects: @"name", @"grade", @"permanentRecord", @"birthday", @"otherNames", @"buddy", @"type", @"status", nil];
+ STAssertEqualObjects([TestModelSubclass propertyNames], allNames, nil);
+ STAssertEqualObjects([TestModelSubclass writablePropertyNames], [names setByAddingObject:@"status"], nil);
}
@@ -84,8 +99,11 @@ - (void) test2_write {
student.otherNames = otherNames;
STAssertEqualObjects(student.otherNames, otherNames, nil);
+ STAssertTrue(student.isNew, nil);
+
AssertWait([student save]);
NSString* docID = student.document.documentID;
+ STAssertFalse(student.isNew, nil);
STAssertNotNil(docID, nil);
// Forget all CouchDocuments!
@@ -235,11 +253,274 @@ - (void) test6_bulkSave {
}
+- (void) test7_setDefaultValues {
+ NSString* docID;
+ NSArray* otherNames = [NSArray arrayWithObjects:@"Alicia", @"Ali", nil];
+ {
+ CouchDocument* doc = [_db untitledDocument];
+ TestModelSubclass* student = [TestModelSubclass modelForDocument: doc];
+
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects([student.properties objectForKey:@"type"], @"test-model", nil);
+ STAssertEqualObjects([student.propertiesToSave objectForKey:@"type"], @"test-model", nil);
+
+ STAssertEqualObjects(student.status, @"inactive", nil);
+ STAssertEqualObjects([student.properties objectForKey:@"status"], @"inactive", nil);
+ STAssertEqualObjects([student.propertiesToSave objectForKey:@"status"], @"inactive", nil);
+
+ STAssertEqualObjects(student.otherNames, [NSArray array], nil);
+ STAssertEqualObjects([student.properties objectForKey:@"otherNames"], [NSArray array], nil);
+ STAssertEqualObjects([student.propertiesToSave objectForKey:@"otherNames"], [NSArray array], nil);
+
+ STAssertFalse(student.needsSave, nil); // stock object, with all the defaults
+
+ student.status = @"active";
+
+ STAssertTrue(student.needsSave, nil); // stock object, with all the defaults
+
+ student.otherNames = otherNames;
+ STAssertEqualObjects(student.otherNames, otherNames, nil);
+
+ STAssertEqualObjects(student.status, @"active", nil);
+ STAssertEqualObjects([student.propertiesToSave objectForKey:@"status"], @"active", nil);
+ STAssertEqualObjects([student.propertiesToSave objectForKey:@"otherNames"], otherNames, nil);
+
+ STAssertTrue(student.isNew, nil);
+
+ RESTOperation* op = [student save];
+ AssertWait(op);
+ docID = student.document.documentID;
+ }
+ [_db clearDocumentCache];
+
+ TestModelSubclass* student = [TestModelSubclass modelForDocument: [_db documentWithID: docID]];
+ STAssertFalse(student.isNew, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"active", nil);
+ STAssertEqualObjects([student.propertiesToSave objectForKey:@"status"], @"active", nil);
+ STAssertEqualObjects([student.propertiesToSave objectForKey:@"otherNames"], otherNames, nil);
+}
+
+- (void) test9_setDefaultValues {
+ {
+ CouchDocument* doc = [_db documentWithID: @"0001"];
+ TestModelSubclass* student = [TestModelSubclass modelForDocument: doc];
+ STAssertTrue(student.isNew, nil);
+ student.name = @"Tweedledum";
+ student.grade = 2;
+
+ STAssertTrue(student.isNew, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"inactive", nil);
+
+ student.status = @"suspended";
+
+ AssertWait([student save]);
+
+ STAssertEqualObjects(student.status, @"suspended", nil);
+ }
+ [_db clearDocumentCache];
+
+ TestModelSubclass* student = [TestModelSubclass modelForDocument: [_db documentWithID: @"0001"]];
+ STAssertFalse(student.isNew, nil);
+ STAssertEqualObjects(student.status, @"suspended", nil);
+}
+
+- (void) test10_Properties {
+ TestModel* student = [self createModelWithName: @"Pippi Langstrumpf" grade: 4];
+ [student setValue:@"example" ofProperty:@"extended"];
+ // propertiesToSave will include all properties
+ {
+ NSDictionary* expected = [NSDictionary dictionaryWithObjectsAndKeys:
+ @"Pippi Langstrumpf", @"name",
+ [NSNumber numberWithInt:4], @"grade",
+ @"example", @"extended", nil];
+ STAssertEqualObjects(student.propertiesToSave, expected, nil);
+ }
+ // properties will not include properties that have not been explicitly defined
+ {
+ NSDictionary* expected = [NSDictionary dictionaryWithObjectsAndKeys:
+ @"Pippi Langstrumpf", @"name",
+ [NSNumber numberWithInt:4], @"grade", nil];
+ STAssertEqualObjects(student.properties, expected, nil);
+ }
+}
+
+- (void) test11_setProperties {
+ CouchDocument* doc = [_db untitledDocument];
+ TestModelSubclass* student = [TestModelSubclass modelForDocument: doc];
+ STAssertEqualObjects(student.name, nil, nil);
+ STAssertEquals(student.grade, 0, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"inactive", nil);
+
+ NSArray* otherNames = [NSArray arrayWithObjects:@"Alicia", @"Ali", nil];
+ student.otherNames = otherNames;
+ STAssertEqualObjects(student.otherNames, otherNames, nil);
+
+ student.properties = [NSDictionary dictionaryWithObjectsAndKeys:@"Alice", @"name", [NSNumber numberWithInt:9], @"grade", nil];
+ STAssertEqualObjects(student.name, @"Alice", nil);
+ STAssertEquals(student.grade, 9, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"inactive", nil);
+ STAssertEqualObjects(student.otherNames, [NSArray array], nil);
+
+ student.properties = [NSDictionary dictionaryWithObjectsAndKeys:@"Bartholomew", @"name", nil];
+ STAssertEqualObjects(student.name, @"Bartholomew", nil);
+ STAssertEquals(student.grade, 0, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"inactive", nil);
+
+ student.properties = [NSDictionary dictionaryWithObjectsAndKeys:@"suspended", @"status", nil];
+ STAssertEqualObjects(student.name, nil, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"suspended", nil);
+
+ student.properties =[NSDictionary dictionaryWithObjectsAndKeys:@"tricky", @"type", @"example", @"extended", nil];
+ STAssertEqualObjects(student.type, @"tricky", nil); // see difference with updateProperties
+ STAssertEqualObjects([student getValueOfProperty:@"extended"], @"example", nil);
+}
+
+- (void) test12_clearProperties {
+ CouchDocument* doc = [_db untitledDocument];
+ TestModelSubclass* student = [TestModelSubclass modelForDocument: doc];
+ NSDictionary* blank = [NSDictionary dictionaryWithObjectsAndKeys:@"test-model", @"type", @"inactive", @"status", [NSArray array], @"otherNames", nil];
+ STAssertEqualObjects(student.properties, blank, nil);
+
+ student.properties = [NSDictionary dictionaryWithObjectsAndKeys:@"Alice", @"name", [NSNumber numberWithInt:9], @"grade", @"example", @"extended", nil];
+ STAssertEqualObjects(student.name, @"Alice", nil);
+ STAssertEquals(student.grade, 9, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"inactive", nil);
+ STAssertEqualObjects(student.otherNames, [NSArray array], nil);
+ STAssertEqualObjects([student getValueOfProperty:@"extended"], @"example", nil);
+
+ [student clearProperties];
+
+ STAssertEqualObjects(student.properties, blank, nil);
+}
+
+- (void) test13_updateProperties {
+ CouchDocument* doc = [_db untitledDocument];
+ TestModelSubclass* student = [TestModelSubclass modelForDocument: doc];
+ STAssertEqualObjects(student.name, nil, nil);
+ STAssertEquals(student.grade, 0, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"inactive", nil);
+
+ NSArray* otherNames = [NSArray arrayWithObjects:@"Alicia", @"Ali", nil];
+ student.otherNames = otherNames;
+ STAssertEqualObjects(student.otherNames, otherNames, nil);
+
+ [student updateProperties:[NSDictionary dictionaryWithObjectsAndKeys:@"Alice", @"name", [NSNumber numberWithInt:9], @"grade", nil]];
+ STAssertEqualObjects(student.name, @"Alice", nil);
+ STAssertEquals(student.grade, 9, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"inactive", nil);
+ STAssertEqualObjects(student.otherNames, otherNames, nil);
+
+ student.grade = 6;
+ STAssertEquals(student.grade, 6, nil);
+
+ [student updateProperties:[NSDictionary dictionaryWithObjectsAndKeys:@"Alicia", @"name", nil]];
+ STAssertEqualObjects(student.name, @"Alicia", nil);
+ STAssertEquals(student.grade, 6, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"inactive", nil);
+
+ [student updateProperties:[NSDictionary dictionaryWithObjectsAndKeys:@"suspended", @"status", nil]];
+ STAssertEqualObjects(student.name, @"Alicia", nil);
+ STAssertEquals(student.grade, 6, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"suspended", nil);
+
+ [student updateProperties:[NSDictionary dictionaryWithObjectsAndKeys:@"tricky", @"type", @"example", @"extended", nil]];
+ STAssertEqualObjects(student.type, @"test-model", nil); // compare with updateProperties
+ STAssertEqualObjects([student getValueOfProperty:@"extended"], nil, nil);
+}
+
+- (void) test14_resetProperties {
+ CouchDocument* doc = [_db untitledDocument];
+ TestModelSubclass* student = [TestModelSubclass modelForDocument: doc];
+ NSDictionary* blank = [NSDictionary dictionaryWithObjectsAndKeys:@"test-model", @"type", @"inactive", @"status", [NSArray array], @"otherNames", nil];
+ STAssertEqualObjects(student.properties, blank, nil);
+
+ student.properties = [NSDictionary dictionaryWithObjectsAndKeys:@"Alice", @"name", [NSNumber numberWithInt:9], @"grade", @"example", @"extended", nil];
+
+ NSArray* otherNames = [NSArray arrayWithObjects:@"Alicia", @"Ali", nil];
+ student.otherNames = otherNames;
+ STAssertEqualObjects(student.otherNames, otherNames, nil);
+
+ STAssertEqualObjects(student.name, @"Alice", nil);
+ STAssertEquals(student.grade, 9, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"inactive", nil);
+ STAssertEqualObjects(student.otherNames, otherNames, nil);
+ STAssertEqualObjects([student getValueOfProperty:@"extended"], @"example", nil);
+
+ [student resetProperties];
+
+ STAssertEqualObjects(student.name, nil, nil);
+ STAssertEquals(student.grade, 0, nil);
+ STAssertEqualObjects(student.type, @"test-model", nil);
+ STAssertEqualObjects(student.status, @"inactive", nil);
+ STAssertEqualObjects(student.otherNames, [NSArray array], nil);
+ STAssertEqualObjects([student getValueOfProperty:@"extended"], @"example", nil); // not cleared out
+}
+
+- (void) test15_reload {
+ NSArray* otherNames = [NSArray arrayWithObjects:@"Alicia", @"Ali", nil];
+ {
+ CouchDocument* doc = [_db documentWithID: @"0001"];
+ TestModelSubclass* student = [TestModelSubclass modelForDocument: doc];
+ student.name = @"Alice";
+ student.grade = 9;
+ student.status = @"active";
+ student.otherNames = otherNames;
+ [student setValue:@"example" ofProperty:@"extended"];
+ AssertWait([student save]);
+ }
+ [_db clearDocumentCache];
+
+ TestModelSubclass* student = [TestModelSubclass modelForDocument: [_db documentWithID: @"0001"]];
+ STAssertEqualObjects(student.name, @"Alice", nil);
+ STAssertEqualObjects(student.status, @"active", nil);
+ STAssertEqualObjects(student.otherNames, otherNames, nil);
+ STAssertEqualObjects([student getValueOfProperty:@"extended"], @"example", nil);
+
+ NSDictionary *originalProperties = [student propertiesToSave];
+
+ NSArray* names = [NSArray arrayWithObjects:@"Alice", @"Alicia", nil];
+
+ student.name = @"Ali";
+ student.status = @"suspended";
+ student.otherNames = names;
+ [student setValue:@"changed" ofProperty:@"extended"];
+ STAssertEqualObjects(student.name, @"Ali", nil);
+ STAssertEqualObjects(student.status, @"suspended", nil);
+ STAssertEqualObjects(student.otherNames, names, nil);
+ STAssertEqualObjects([student getValueOfProperty:@"extended"], @"changed", nil);
+
+ STAssertTrue(student.needsSave, nil);
+
+ [student reload]; // reload from db
+
+ STAssertEqualObjects(student.name, @"Alice", nil);
+ STAssertEqualObjects(student.status, @"active", nil);
+ STAssertEqualObjects(student.otherNames, otherNames, nil);
+ STAssertEqualObjects([student getValueOfProperty:@"extended"], @"example", nil);
+
+ STAssertEqualObjects([student propertiesToSave], originalProperties, nil);
+
+ STAssertFalse(student.needsSave, nil);
+}
+
#pragma mark - UTILITIES:
- (TestModel*) createModelWithName: (NSString*)name grade: (int)grade {
CouchDocument* doc = [_db untitledDocument];
TestModel* student = [TestModel modelForDocument: doc];
+ STAssertTrue(student.isNew, nil);
STAssertNil(student.name, nil);
STAssertEquals(student.grade, 0, nil);
STAssertNil(student.permanentRecord, nil, nil);
Please sign in to comment.
Something went wrong with that request. Please try again.