Skip to content
Browse files

Fix adding attachment to untitled doc's CouchModel

CouchAttachment's parent could only be a CouchRevision. This meant an attachment couldn't be created on an
unsaved/untitled document, because it has no revision until it's saved. Fixed this by allowing the parent of
CouchAttachment to be either a CouchDocument or CouchRevision.
Added unit tests to Test_Model.m to test adding attachments before or after the first save.

Change-Id: I84896a9ab1fda520c475e609ee0e193a838555a7
  • Loading branch information...
1 parent afbe883 commit 1c0c646d8d52972cb244605e93c69fbbe301233a @snej snej committed Nov 15, 2011
View
2 Couch/CouchAttachment.h
@@ -18,7 +18,7 @@
/** A binary attachment to a document.
- Actually a CouchAttachment is a child of a CouchRevision, since attachments (like all document contents) are versioned. This means that each instance represents an attachment immutably as it appeared in one revision of its document. So if you PUT a change to an attachment, the updated attachment will have a new CouchAttachment object. */
+ Actually a CouchAttachment may be a child of either a CouchDocument or a CouchRevision. The latter represents an attachment immutably as it appeared in one revision of its document. So if you PUT a change to an attachment, the updated attachment will have a new CouchAttachment object. */
@interface CouchAttachment : CouchResource
{
@private
View
19 Couch/CouchAttachment.m
@@ -20,12 +20,12 @@
@implementation CouchAttachment
-- (id) initWithRevision: (CouchRevision*)revision
- name: (NSString*)name
- metadata: (NSDictionary*)metadata
+- (id) initWithParent: (CouchResource*)parent
+ name: (NSString*)name
+ metadata: (NSDictionary*)metadata
{
NSParameterAssert(metadata);
- self = [super initWithParent: revision relativePath: name];
+ self = [super initWithParent: parent relativePath: name];
if (self) {
_metadata = [metadata copy];
}
@@ -48,12 +48,19 @@ - (NSString*) name {
- (CouchRevision*) revision {
- return (CouchRevision*)self.parent;
+ id parent = self.parent;
+ if ([parent isKindOfClass: [CouchRevision class]])
+ return parent;
+ else
+ return nil;
}
- (CouchDocument*) document {
- return (CouchDocument*)self.parent.parent;
+ RESTResource* parent = self.parent;
+ if ([parent isKindOfClass: [CouchRevision class]])
+ parent = [parent parent];
+ return (CouchDocument*)parent;
}
View
6 Couch/CouchInternal.h
@@ -27,9 +27,9 @@ typedef void (^OnDatabaseChangeBlock)(CouchDocument*, BOOL externalChange);
@interface CouchAttachment ()
-- (id) initWithRevision: (CouchRevision*)revision
- name: (NSString*)name
- metadata: (NSDictionary*)metadata;
+- (id) initWithParent: (CouchResource*)parent // must be CouchDocument or CouchRevision
+ name: (NSString*)name
+ metadata: (NSDictionary*)metadata;
@end
View
4 Couch/CouchRevision.m
@@ -240,14 +240,14 @@ - (CouchAttachment*) attachmentNamed: (NSString*)name {
NSDictionary* metadata = [self attachmentMetadataFor: name];
if (!metadata)
return nil;
- return [[[CouchAttachment alloc] initWithRevision: self name: name metadata: metadata] autorelease];
+ return [[[CouchAttachment alloc] initWithParent: self name: name metadata: metadata] autorelease];
}
- (CouchAttachment*) createAttachmentWithName: (NSString*)name type: (NSString*)contentType {
NSDictionary* metadata = [NSDictionary dictionaryWithObject: contentType
forKey: @"content_type"];
- return [[[CouchAttachment alloc] initWithRevision: self name: name metadata: metadata]
+ return [[[CouchAttachment alloc] initWithParent: self name: name metadata: metadata]
autorelease];
}
View
6 CouchCocoa.xcodeproj/project.pbxproj
@@ -37,6 +37,8 @@
274EB8A714479D72001B7DD0 /* CouchEmbeddedServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 274EB8A314479D71001B7DD0 /* CouchEmbeddedServer.m */; };
274EB8D014490145001B7DD0 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27CDEBF413C67C9B00C979BB /* AppKit.framework */; };
2759A0B713E0907000866098 /* libcrypto-iphonesimulator.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2759A0B613E0907000866098 /* libcrypto-iphonesimulator.a */; };
+ 2771C7C21472ECF70012DF57 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 2771C7C11472ECF70012DF57 /* logo.png */; };
+ 2771C7C31472ECF70012DF57 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 2771C7C11472ECF70012DF57 /* logo.png */; };
2784E1B613CE5249009CC5C8 /* CouchCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27CDEBF113C67C9B00C979BB /* CouchCocoa.framework */; };
2784E1B713CE5249009CC5C8 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27A5792913974B65002776DB /* Cocoa.framework */; };
2784E1C313CE52FE009CC5C8 /* ShoppingDemo.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2784E1C113CE52FE009CC5C8 /* ShoppingDemo.xib */; };
@@ -208,6 +210,7 @@
274EB8A314479D71001B7DD0 /* CouchEmbeddedServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CouchEmbeddedServer.m; sourceTree = "<group>"; };
274EB8AA14479E7B001B7DD0 /* CouchbaseMobile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CouchbaseMobile.h; sourceTree = "<group>"; };
2759A0B613E0907000866098 /* libcrypto-iphonesimulator.a */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.objfile"; name = "libcrypto-iphonesimulator.a"; path = "Test/libcrypto-iphonesimulator.a"; sourceTree = "<group>"; };
+ 2771C7C11472ECF70012DF57 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = "<group>"; };
2781242213AC1B2F0051A99D /* JSONKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JSONKit.h; path = Vendor/JSONKit/JSONKit.h; sourceTree = SOURCE_ROOT; };
2781242313AC1B2F0051A99D /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JSONKit.m; path = Vendor/JSONKit/JSONKit.m; sourceTree = SOURCE_ROOT; };
2781242B13AC265A0051A99D /* RESTCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RESTCache.h; sourceTree = "<group>"; };
@@ -444,6 +447,7 @@
2795994E140A02DB001C168A /* Test_Model.m */,
27911B731411A8C700ABD31B /* CouchTestCase.h */,
27911B741411A8C700ABD31B /* CouchTestCase.m */,
+ 2771C7C11472ECF70012DF57 /* logo.png */,
);
path = Test;
sourceTree = "<group>";
@@ -834,6 +838,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 2771C7C31472ECF70012DF57 /* logo.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -856,6 +861,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 2771C7C21472ECF70012DF57 /* logo.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
6 Model/CouchModel.m
@@ -385,9 +385,9 @@ - (CouchAttachment*) createAttachmentWithName: (NSString*)name
[NSNumber numberWithUnsignedLong: body.length], @"length",
contentType, @"content_type",
nil];
- attach = [[[CouchAttachment alloc] initWithRevision: _document.currentRevision
- name: name
- metadata: metadata] autorelease];
+ attach = [[[CouchAttachment alloc] initWithParent: (_document.currentRevision ?: _document)
+ name: name
+ metadata: metadata] autorelease];
} else if (![self attachmentNamed: name]) {
return nil;
}
View
28 Test/Resources/CouchCocoa-Info.plist
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundleIdentifier</key>
+ <string>org.couchbase.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>FMWK</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>${CURRENT_PROJECT_VERSION}</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright © 2011 Couchbase, Inc.</string>
+ <key>NSPrincipalClass</key>
+ <string></string>
+</dict>
+</plist>
View
BIN Test/Resources/logo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
108 Test/Test_Model.m
@@ -24,6 +24,8 @@ @implementation TestModel
@interface Test_Model : CouchTestCase
+- (TestModel*) createModelWithName: (NSString*)name grade: (int)grade;
+- (NSData*) attachmentData;
@end
@@ -55,24 +57,14 @@ - (void) test1_read {
- (void) test2_write {
- CouchDocument* doc = [_db untitledDocument];
- TestModel* student = [TestModel modelForDocument: doc];
- STAssertNil(student.name, nil);
- STAssertEquals(student.grade, 0, nil);
- STAssertNil(student.permanentRecord, nil, nil);
- STAssertNil(student.birthday, nil, nil);
+ TestModel* student = [self createModelWithName: @"Bobby Tables" grade: 6];
+ CouchDocument* doc = student.document;
NSData* permanentRecord = [@"ACK PTHBBBT" dataUsingEncoding: NSUTF8StringEncoding];
CFAbsoluteTime time = floor(CFAbsoluteTimeGetCurrent()); // no fractional seconds
NSDate* birthday = [NSDate dateWithTimeIntervalSinceReferenceDate: time];
-
- student.name = @"Bobby Tables";
- student.grade = 6;
student.permanentRecord = permanentRecord;
student.birthday = birthday;
-
- STAssertEqualObjects(student.name, @"Bobby Tables", nil);
- STAssertEquals(student.grade, 6, nil);
STAssertEqualObjects(student.permanentRecord, permanentRecord, nil);
STAssertEqualObjects(student.birthday, birthday, nil);
@@ -95,4 +87,96 @@ - (void) test2_write {
}
+// Tests adding an attachment to a new document before saving.
+- (void) test3_newAttachment {
+ TestModel* student = [self createModelWithName: @"Pippi Langstrumpf" grade: 4];
+ CouchDocument* doc = student.document;
+
+ [student createAttachmentWithName: @"mugshot" type: @"image/png" body: self.attachmentData];
+
+ AssertWait([student save]);
+ NSString* docID = student.document.documentID;
+ STAssertNotNil(docID, nil);
+
+ // Forget all CouchDocuments!
+ [_db clearDocumentCache];
+
+ CouchDocument *doc2 = [_db documentWithID: docID];
+ STAssertFalse(doc2 == doc, @"Doc was cached when it shouldn't have been");
+ TestModel* student2 = [TestModel modelForDocument: doc2];
+ STAssertFalse(student2 == student, @"Model was cached when it shouldn't have been");
+
+ STAssertEqualObjects(student2.attachmentNames, [NSArray arrayWithObject: @"mugshot"], nil);
+ CouchAttachment* attach = [student2 attachmentNamed: @"mugshot"];
+ STAssertEqualObjects(attach.name, @"mugshot", nil);
+ STAssertEqualObjects(attach.contentType, @"image/png", nil);
+ STAssertEqualObjects(attach.body, self.attachmentData, nil);
+}
+
+
+// Tests adding an attachment to an existing already-saved document.
+- (void) test4_addAttachment {
+ TestModel* student = [self createModelWithName: @"Pippi Langstrumpf" grade: 4];
+ CouchDocument* doc = student.document;
+
+ AssertWait([student save]);
+
+ [student createAttachmentWithName: @"mugshot" type: @"image/png" body: self.attachmentData];
+
+ STAssertEqualObjects(student.attachmentNames, [NSArray arrayWithObject: @"mugshot"], nil);
+ CouchAttachment* attach = [student attachmentNamed: @"mugshot"];
+ STAssertEqualObjects(attach.name, @"mugshot", nil);
+ STAssertEqualObjects(attach.contentType, @"image/png", nil);
+ STAssertEqualObjects(attach.body, self.attachmentData, nil);
+
+ AssertWait([student save]);
+
+ STAssertEqualObjects(student.attachmentNames, [NSArray arrayWithObject: @"mugshot"], nil);
+ attach = [student attachmentNamed: @"mugshot"];
+ STAssertEqualObjects(attach.name, @"mugshot", nil);
+ STAssertEqualObjects(attach.contentType, @"image/png", nil);
+ STAssertEqualObjects(attach.body, self.attachmentData, nil);
+
+ // Forget all CouchDocuments!
+ NSString* docID = student.document.documentID;
+ STAssertNotNil(docID, nil);
+ [_db clearDocumentCache];
+
+ CouchDocument *doc2 = [_db documentWithID: docID];
+ STAssertFalse(doc2 == doc, @"Doc was cached when it shouldn't have been");
+ TestModel* student2 = [TestModel modelForDocument: doc2];
+ STAssertFalse(student2 == student, @"Model was cached when it shouldn't have been");
+
+ STAssertEqualObjects(student2.attachmentNames, [NSArray arrayWithObject: @"mugshot"], nil);
+ attach = [student2 attachmentNamed: @"mugshot"];
+ STAssertEqualObjects(attach.name, @"mugshot", nil);
+ STAssertEqualObjects(attach.contentType, @"image/png", nil);
+ STAssertEqualObjects(attach.body, self.attachmentData, nil);
+}
+
+
+#pragma mark - UTILITIES:
+
+- (TestModel*) createModelWithName: (NSString*)name grade: (int)grade {
+ CouchDocument* doc = [_db untitledDocument];
+ TestModel* student = [TestModel modelForDocument: doc];
+ STAssertNil(student.name, nil);
+ STAssertEquals(student.grade, 0, nil);
+ STAssertNil(student.permanentRecord, nil, nil);
+ STAssertNil(student.birthday, nil, nil);
+ student.name = name;
+ student.grade = grade;
+ STAssertEqualObjects(student.name, name, nil);
+ STAssertEquals(student.grade, grade, nil);
+ return student;
+}
+
+
+- (NSData*) attachmentData {
+ NSString* path = [[NSBundle bundleForClass: [self class]] pathForResource: @"logo" ofType:@"png"];
+ STAssertNotNil(path, @"Couldn't get Logo.png resource for attachment test");
+ return [NSData dataWithContentsOfFile: path];
+}
+
+
@end
View
BIN Test/logo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 1c0c646

Please sign in to comment.
Something went wrong with that request. Please try again.