Example Snippets

snej edited this page Oct 6, 2012 · 11 revisions
Clone this wiki locally

CouchCocoa Example Snippets

Connecting To A Database

TouchDB

TouchDB is a lightweight, embeddable CouchDB-compatible database for mobile and desktop apps. It's the successor to Couchbase Mobile. If you link the TouchDB framework into your app as well as CouchCocoa, you can start it up like so:

    CouchTouchDBServer* server = [CouchTouchDBServer sharedInstance];
    if (server.error) [self failed: server.error];
    self.database = [server databaseNamed: @"mydatabase"];
    NSError* error;
    if (![self.database ensureCreated: &error])
        [self failed: error];

CouchDB

You can use CouchCocoa to talk to an Apache CouchDB server (or compatible server like BigCouch) on any accessible Internet host. In this example we assume we don't have admin access to the remote server, so the database must already exist. To verify the connection we do a GET operation on the database URL; we ignore the result, just check for an error.

    NSURL* serverURL = [NSURL URLWithString: @"http://example.com:8080"];
    CouchServer *server = [[CouchServer alloc] initWithURL: serverURL];
    CouchDatabase *database = [server databaseNamed: @"theirdatabase"];
    RESTOperation* op = [database GET];
    if (![op wait])
        [self failed: op.error]

Note 1: There is an optional listener framework for TouchDB that lets it accept connections on TCP ports. You can connect to a remote TouchDB instance running that listener using this exact same code.

Note 2: Couchbase Server is not compatible with CouchDB. You can't use it directly with CouchCocoa.

CRUD Operations

Listing All The Documents In A Database

    CouchQuery* allDocs = database.allDocuments;
    for (CouchQueryRow* row in allDocs.rows) {
        CouchDocument* doc = row.document;
        NSString* message = [doc.properties objectForKey: @"message"];
        NSLog(@"Doc ID %@ has message: %@", row.documentID, message);
    }

Querying A View (TouchDB)

This example creates a TouchDB view (comparable to a stored SQL query) called "emailByName" that indexes all documents with a "name" property and associates the "email" property as the value. It then enumerates all the rows of the view/query (sorted by name) and writes the name and email.

The view's map function is defined as an Objective-C block. This differs from CouchDB: TouchDB uses native code for functions, both for performance and to avoid having to embed a JavaScript interpreter.

    // Define the view: needs to be done once after opening the database but not before every query:
    CouchDesignDocument* design = [database designDocumentWithName: @"mydesign"];
    [design defineViewNamed: @"emailByName" mapBlock: MAPBLOCK({
        NSString* name = [doc objectForKey: @"name"];
        if (name) emit(name, [doc objectForKey: @"email"]);
    }) version: @"1.0"];

    // Later on, we can query the view:
    CouchQuery* query = [design queryViewNamed: @"emailByName"];
    for (CouchQueryRow* row in query.rows) {
        NSLog(@"%@'s email is <%@>", row.key, row.value);
    }

Querying A View (CouchDB)

CouchDB views are defined in JavaScript, not native code, and are passed as strings. The querying is exactly the same.

    // Define the view: only needs to be done once after creating the database:
    CouchDesignDocument* design = [database designDocumentWithName: @"mydesign"];
    [design defineViewNamed: @"emailByName" map: @"function(doc){if (doc.name) emit(doc.name,doc.email);};"];

    // Later on, we can query the view:
    CouchQuery* query = [design queryViewNamed: @"emailByName"];
    for (CouchQueryRow* row in query.rows) {
        NSLog(@"%@'s email is <%@>", row.key, row.value);
    }

Note: The -defineViewNamed: method is smart enough to do nothing if the design document already contains a a view with the same name and function value, so it's OK to do the view creation right before you use the view; it won't cause extra database writes.

Updating A Document Asynchronously

Here we get a document's current properties (contents), make a mutable copy of them and update that, then store the changed properties back to the database. Instead of calling -wait on the operation, we add an onCompletion handler block that will run in the future when it finishes. That allows the code here to finish immediately without blocking (sic) the user interface.

    CouchRevision* latest = document.currentRevision;
    NSMutableDictionary* props = [[latest.properties mutableCopy] autorelease];
    int count = [[props objectForKey: @"count"] intValue];
    count++;
    [props setObject: [NSNumber numberWithInt: count] forKey: @"count"];

    RESTOperation* op = [latest putProperties: props];
    [op onCompletion: ^{
        if (op.isSuccessful)
            NSLog(@"Successfully updated document!");
        else
            NSLog(@"Failed to update document: %@", op.error);
    }];

Complete Simplistic Replication Example

Courtesy of Paul Okstad.

(Note that for simplicity this code runs synchronously, including spinning the runloop while it waits for replication to finish. This is not a good idea for a real app since it will block user input and make the app non-responsive. A real GUI app should use KVO to monitor replication state and let control return to the runloop. The Grocery Sync example app shows how to do this.)

#import <Foundation/Foundation.h>
#import <CouchCocoa/CouchCocoa.h>
#import <CouchCocoa/CouchTouchDBServer.h>
#import <TouchDB/TouchDB.h>

#define SERVER_URL @"http://10.0.0.123:5984"
#define DB_NAME @"testdb"

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {
        
        // Connect to local Linux VM hosting CouchDB server
        NSURL* testServerURL = [NSURL URLWithString: SERVER_URL];
        CouchServer *testServer = [[CouchServer alloc] initWithURL:testServerURL];
        CouchDatabase *testDB = [testServer databaseNamed: DB_NAME];

        // Show all docs from production server
        CouchQuery* allDocs = [testDB getAllDocuments];
        NSLog(@"Test server has %ld docs. Printing all of their ID's:", allDocs.rows.count);
        for (CouchQueryRow* row in allDocs.rows) {
            NSLog(@"test server doc: %@", row.documentID);
        }

        // Create a TouchDB server
        CouchTouchDBServer* tdbserver = [CouchTouchDBServer sharedInstance];
        if (tdbserver.error) {
            NSLog(@"ERROR: %@", [tdbserver.error localizedDescription]);
        }

        // Create TouchDB database
        CouchDatabase *localDB = [tdbserver databaseNamed:DB_NAME];
        NSError *error = nil;
        if (![localDB ensureCreated:&error]) {
            NSLog(@"ERROR: %@", [error localizedDescription]);
        }

        // Now replicate: pull from test server to touchdb
        NSLog(@"Pulling from test server to touchdb");
        CouchReplication *pull = [localDB pullFromDatabaseAtURL:testDB.URL];
        pull.continuous = FALSE;
        RESTOperation *op = [pull start];
        [op wait];

        // start an explicit run loop to turn this async call to a sync call
        NSTimeInterval timeout = 0.1;
        while(timeout < 15.0 && pull.running){
            // NSLog(@"Waiting for touchdb to hurry the hell up!");
            timeout += 0.1;
            [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
        }
        NSLog(@"Replication has finished.");

        // Finally print all the docs we received:
        allDocs = [localDB getAllDocuments];
        NSLog(@"Printing all %ld docs in touchdb database", allDocs.rows.count);
        for (CouchQueryRow* row in allDocs.rows) {
            NSLog(@"Test server doc: %@", row.documentID);
        }
        NSLog(@"Finished printing all docs in touchdb");
    }
    return 0;
}