Created
December 17, 2016 15:37
-
-
Save bennadel/5ac536bd32d6c9c9abb2827d834686c2 to your computer and use it in GitHub Desktop.
Creating A PouchDB Plugin For Bulk Document Updates
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<title> | |
Creating A PouchDB Plugin For Bulk Document Updates | |
</title> | |
</head> | |
<body> | |
<h1> | |
Creating A PouchDB Plugin For Bulk Document Updates | |
</h1> | |
<p> | |
<em>Look at console — things being logged, yo!</em> | |
</p> | |
<script type="text/javascript" src="../../vendor/pouchdb/6.0.7/pouchdb-6.0.7.min.js"></script> | |
<script type="text/javascript"> | |
// I provide an API for updating many documents (encapsulating the fetch and | |
// subsequent .bulkDocs() call). This method will use either the .allDocs() method | |
// or the .query() method for fetching, depending on the invocation signature: | |
// -- | |
// .updateMany( options, operator ) ==> Uses .allDocs() | |
// .updateMany( viewName, options, operator ) ==> Uses .query() | |
// -- | |
// In each case, the "options" object is passed to the underlying fetch method. | |
// Each document in the resultant collection is then passed to the given operator | |
// function - operator( doc ) - to perform the update transformation. | |
PouchDB.plugin({ | |
updateMany: function( /* [ viewName, ] options, operator */ ) { | |
var pouch = this; | |
// CAUTION: Top-level errors MAY NOT be caught in a Promise. | |
// .allDocs() invocation signature: ( options, operator ). | |
if ( arguments.length === 2 ) { | |
var options = arguments[ 0 ]; | |
var operator = arguments[ 1 ]; | |
var promise = pouch.allDocs( ensureIncludeDocs( options ) ); | |
// .query() invocation signature: ( viewName, options, operator ). | |
} else { | |
var viewName = arguments[ 0 ]; | |
var options = arguments[ 1 ]; | |
var operator = arguments[ 2 ]; | |
var promise = pouch.query( viewName, ensureIncludeDocs( options ) ); | |
} | |
// Even though the results are potentially coming back from two different | |
// search methods - .allDocs() or .query() - the result structure from | |
// both methods is the same. As such, we can count on the following keys | |
// to exist in the results: | |
// -- | |
// * offset | |
// * total_rows | |
// * rows : [{ doc }] | |
// -- | |
promise = promise.then( | |
function( results ) { | |
var docsToUpdate = results.rows.map( | |
function iterator( row, index, rows ) { | |
return( operator( row.doc, index, rows ) || row.doc ); | |
} | |
); | |
return( pouch.bulkDocs( docsToUpdate ) ); | |
} | |
); | |
return( promise ); | |
// -- Utility methods for my PouchDB plugin. Thar be hoistin'! -- // | |
// I ensure that the given search options has the "include_docs" set to | |
// true. Since we are working on updating documents, it is important | |
// that we actually fetch the docs being updated. Returns options. | |
function ensureIncludeDocs( options ) { | |
options.include_docs = true; | |
return( options ); | |
} | |
} | |
}); | |
// --------------------------------------------------------------------------- // | |
// --------------------------------------------------------------------------- // | |
getPristineDatabase( window, "db" ).then( | |
function() { | |
// To experiment with the bulk update PLUGIN, we need to have documents | |
// on which to experiment. Let's create some food products with names | |
// and prices that we'll update with the bulk update plugin. | |
var promise = db | |
.bulkDocs([ | |
{ | |
_id: "apple:fuji", | |
name: "Fuji", | |
price: 1.05 | |
}, | |
{ | |
_id: "apple:applecrisp", | |
name: "Apple Crisp", | |
price: 1.33 | |
}, | |
{ | |
_id: "pear:bosc", | |
name: "Bosc", | |
price: 1.95 | |
}, | |
{ | |
_id: "apple:goldendelicious", | |
name: "Golden Delicious", | |
price: 1.27 | |
}, | |
{ | |
_id: "pear:bartlett", | |
name: "Bartlett", | |
price: 1.02 | |
} | |
]) | |
.then( | |
function() { | |
// Since the bulk update plugin can also fetch documents | |
// using a VIEW, let's create a view that indexes the | |
// fruit products by type (ie, key prefix). | |
var promise = db.put({ | |
_id: "_design/by-type", | |
views: { | |
"by-type": { | |
map: function( doc ) { | |
// Emit the key prefix, example "apple". | |
emit( doc._id.split( ":", 1 )[ 0 ] ); | |
}.toString() | |
} | |
} | |
}); | |
// NOTE: View are not populated proactively - they are | |
// populated at query time. As such, one might ordinarily | |
// kick off a query to force indexing. However, to keep this | |
// demo simple, we won't care about pre-heating the index. | |
return( promise ); | |
} | |
) | |
; | |
return( promise ); | |
} | |
) | |
.then( | |
function() { | |
// Now that we've inserted the documents, let's fetch all the Apples | |
// and output them so we can see the pre-update values. | |
var promise = db | |
.allDocs({ | |
startkey: "apple:", | |
endkey: "apple:\uffff", | |
include_docs: true | |
}) | |
.then( renderResultsToConsole ) | |
; | |
return( promise ); | |
} | |
) | |
.then( | |
function() { | |
// Now, let's update the Apples, applying a 10% price increase. Since we | |
// invoking the .updateMany() plugin method with TWO ARGUMENTS, it will | |
// use the .allDocs() bulk fetch method under the hood. | |
var promise = db.updateMany( | |
{ | |
startkey: "apple:", | |
endkey: "apple:\uffff" | |
}, | |
function operator( doc ) { | |
// Apply the 10% price increase. | |
doc.price = +( doc.price * 1.1 ).toFixed( 2 ) | |
} | |
); | |
return( promise ); | |
} | |
) | |
.then( | |
function() { | |
// Now, let's update the Apples, upper-casing the name. Since we are | |
// invoking the .updateMany() plugin method with THREE ARGUMENTS, it will | |
// use the .query() bulk fetch method under the hood. In this case, the | |
// first argument is the View / secondary-index name. | |
var promise = db.updateMany( | |
"by-type", | |
{ | |
key: "apple" | |
}, | |
function operator( doc ) { | |
doc.name = doc.name.toUpperCase(); | |
} | |
); | |
return( promise ); | |
} | |
) | |
.then( | |
function() { | |
// Now that we've updated the Apples twice (once using .allDocs() and | |
// once using .query() under the hood), let's re-fetch the Apples to see | |
// how the values have changed. | |
var promise = db | |
.allDocs({ | |
startkey: "apple:", | |
endkey: "apple:\uffff", | |
include_docs: true | |
}) | |
.then( renderResultsToConsole ) | |
; | |
return( promise ); | |
} | |
) | |
.catch( | |
function( error ) { | |
console.warn( "An error occurred:" ); | |
console.error( error ); | |
} | |
); | |
// --------------------------------------------------------------------------- // | |
// --------------------------------------------------------------------------- // | |
// I ensure that a new database is created and stored in the given scope. | |
function getPristineDatabase( scope, handle ) { | |
var dbName = "javascript-demos-pouchdb-playground"; | |
var promise = new PouchDB( dbName ) | |
.destroy() | |
.then( | |
function() { | |
// Store new, pristine database in to the given scope. | |
return( scope[ handle ] = new PouchDB( dbName ) ); | |
} | |
) | |
; | |
return( promise ); | |
} | |
// I use the console.table() method to log the documents in the given results | |
// collection to the console. | |
function renderResultsToConsole( results ) { | |
var docs = results.rows.map( | |
function( row ) { | |
return( row.doc ) | |
} | |
); | |
console.table( docs ); | |
} | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Extracted from PouchDB source code. | |
Object.keys( obj ).forEach( | |
function ( id ) { | |
PouchDB.prototype[ id ] = obj[ id ]; | |
} | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// I provide an API for updating many documents (encapsulating the fetch and subsequent | |
// .bulkDocs() call). This method will use either the .allDocs() method or the .query() | |
// method for fetching, depending on the invocation signature: | |
// -- | |
// .updateMany( options, operator ) ==> Uses .allDocs() | |
// .updateMany( viewName, options, operator ) ==> Uses .query() | |
// -- | |
// In each case, the "options" object is passed to the underlying fetch method. Each | |
// document in the resultant collection is then passed to the given operator function - | |
// operator( doc ) - to perform the update transformation. | |
PouchDB.plugin({ | |
updateMany: function( /* [ viewName, ] options, operator */ ) { | |
var pouch = this; | |
// CAUTION: Top-level errors MAY NOT be caught in a Promise. | |
// .allDocs() invocation signature: ( options, operator ). | |
if ( arguments.length === 2 ) { | |
var options = arguments[ 0 ]; | |
var operator = arguments[ 1 ]; | |
var promise = pouch.allDocs( ensureIncludeDocs( options ) ); | |
// .query() invocation signature: ( viewName, options, operator ). | |
} else { | |
var viewName = arguments[ 0 ]; | |
var options = arguments[ 1 ]; | |
var operator = arguments[ 2 ]; | |
var promise = pouch.query( viewName, ensureIncludeDocs( options ) ); | |
} | |
// Even though the results are potentially coming back from two different search | |
// methods - .allDocs() or .query() - the result structure from both methods is | |
// the same. As such, we can count on the following keys to exist in the results: | |
// -- | |
// * offset | |
// * total_rows | |
// * rows : [{ doc }] | |
// -- | |
promise = promise.then( | |
function( results ) { | |
var docsToUpdate = results.rows.map( | |
function iterator( row, index, rows ) { | |
return( operator( row.doc, index, rows ) || row.doc ); | |
} | |
); | |
return( pouch.bulkDocs( docsToUpdate ) ); | |
} | |
); | |
return( promise ); | |
// -- Utility methods for my PouchDB plugin. Thar be hoistin'! -- // | |
// I ensure that the given search options has the "include_docs" set to true. | |
// Since we are working on updating documents, it is important that we actually | |
// fetch the docs being updated. Returns options. | |
function ensureIncludeDocs( options ) { | |
options.include_docs = true; | |
return( options ); | |
} | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment