diff --git a/_includes/backend-list.html b/_includes/backend-list.html index 54fe15c2..04867a21 100644 --- a/_includes/backend-list.html +++ b/_includes/backend-list.html @@ -5,6 +5,6 @@
To use this demo you will need a CouchDB instance running and accessible over HTTP. You should then pass the following 2 query parameters to this page:
- -url: url-to-your-couchdb-instance -view_url: url-to-your-couchdb-view - -Example: -http://path-to-this-page/?url=/mycouchdb/&view_url=/mycouchdb/_design/yourdesigndoc/_view/yourview -- -
Note that if the CouchDB database is not running on the same domain as this page then the host it is on must support CORS – the simplest approach here is probably to set up a reverse proxy or proxy so your CouchDB database appears on the local domain at e.g. /mycouchdb/.
-Using CouchDB with Recline Multiview to provide an elegant powerful browser for a CouchDB database and view.
backend.couchdb.js | |
|---|---|
this.recline = this.recline || {};
-this.recline.Backend = this.recline.Backend || {};
-this.recline.Backend.CouchDB = this.recline.Backend.CouchDB || {};
-
-(function($, my) {
-my.__type__ = 'couchdb'; | |
CouchDB Wrapper- -Connecting to [CouchDB] (http://www.couchdb.apache.org/) endpoints. -@param {String} endpoint: url for CouchDB database, e.g. for Couchdb running -on localhost:5984 with database // ckan-std it would be: - -TODO Add user/password arguments for couchdb authentication support. - -See the example how to use this in: "demos/couchdb/" | my.CouchDBWrapper = function(db_url, view_url, options) {
- var self = this;
- self.endpoint = db_url;
- self.view_url = (view_url) ? view_url : db_url+'/'+'_all_docs';
- self.options = _.extend({
- dataType: 'json'
- },
- options);
-
- this._makeRequest = function(data, headers) {
- var extras = {};
- if (headers) {
- extras = {
- beforeSend: function(req) {
- _.each(headers, function(value, key) {
- req.setRequestHeader(key, value);
- });
- }
- };
- }
- data = _.extend(extras, data);
- return $.ajax(data);
- }; |
mapping- -Get mapping for this database. -Assume all docs in the view have the same schema so -limit query to single result. - -@return promise compatible deferred object. | this.mapping = function() {
- var schemaUrl = self.view_url + '?limit=1&include_docs=true';
- var jqxhr = self._makeRequest({
- url: schemaUrl,
- dataType: self.options.dataType
- });
- return jqxhr;
- }; |
get- -Get record corresponding to specified id - -@return promise compatible deferred object. | this.get = function(_id) {
- var base = self.endpoint + '/' + _id;
- return self._makeRequest({
- url: base,
- dataType: 'json'
- });
- }; |
upsert- -create / update a record to CouchDB backend - -@param {Object} doc an object to insert to the index. -@return deferred supporting promise API | this.upsert = function(doc) {
- var data = JSON.stringify(doc);
- url = self.endpoint;
- if (doc._id) {
- url += '/' + doc._id;
- } |
| use a PUT, not a POST to update the document: -http://wiki.apache.org/couchdb/HTTPDocumentAPI#POST | return self._makeRequest({
- url: url,
- type: 'PUT',
- data: data,
- dataType: 'json',
- contentType: 'application/json'
- });
- }; |
delete- -Delete a record from the CouchDB backend. - -@param {Object} id id of object to delete -@return deferred supporting promise API | this.remove = function(_id) {
- url = self.endpoint;
- url += '/' + _id;
- return self._makeRequest({
- url: url,
- type: 'DELETE',
- dataType: 'json'
- });
- }; |
_normalizeQuery- -Convert the query object from Elastic Search format to a -Couchdb View API compatible format. -See: http://wiki.apache.org/couchdb/HTTPviewAPI | this._normalizeQuery = function(queryObj) {
- var out = queryObj && queryObj.toJSON ? queryObj.toJSON() : _.extend({}, queryObj);
- delete out.sort;
- delete out.query;
- delete out.filters;
- delete out.fields;
- delete out.facets;
- out['skip'] = out.from || 0;
- out['limit'] = out.size || 100;
- delete out.from;
- delete out.size;
- out['include_docs'] = true;
- return out;
- }; |
query- -@param {Object} recline.Query instance. -@param {Object} additional couchdb view query options. -@return deferred supporting promise API | this.query = function(query_object, query_options) {
- var norm_q = self._normalizeQuery(query_object);
- var url = self.view_url;
- var q = _.extend(query_options, norm_q);
-
- var jqxhr = self._makeRequest({
- url: url,
- data: JSON.stringify(q),
- dataType: self.options.dataType,
- });
- return jqxhr;
- }
- }; |
CouchDB Backend- -Backbone connector for a CouchDB backend. - -
-
-Alternatively: - -
-
-Additionally, the Dataset instance may define three methods: - -function recordupdate (record, document) { ... } - function recorddelete (record, document) { ... } - function record_create (record, document) { ... } - -Where @param {string} url of couchdb database.
-@param {string} (optional) url of couchdb view. default: | my.couchOptions = {}; |
fetch- -@param {object} dataset json object with the dburl, viewurl, and query_options args. -@return promise object that resolves to the document mapping. | my.fetch = function (dataset) {
- var db_url = dataset.db_url;
- var view_url = dataset.view_url;
- var cdb = new my.CouchDBWrapper(db_url, view_url);
- var dfd = $.Deferred(); |
| if 'doc' attribute is present, return schema of that -else return schema of 'value' attribute which contains -the map-reduced document. | cdb.mapping().done(function(result) {
- var row = result.rows[0];
- var keys = [];
- if (view_url.search("_all_docs") !== -1) {
- keys = _.keys(row['doc']);
- keys = _.filter(keys, function (k) { return k.charAt(0) !== '_' });
- }
- else {
- keys = _.keys(row['value']);
- }
-
- var fieldData = _.map(keys, function(k) {
- return { 'id' : k };
- });
- dfd.resolve({
- fields: fieldData
- });
- })
- .fail(function(arguments) {
- dfd.reject(arguments);
- });
- return dfd.promise();
- }; |
save- -Iterate through all the changes and save them to the server. -N.B. This method is asynchronous and attempts to do multiple -operation concurrently. This can be problematic when more than -one operation is requested on the same document (as in the case -of bulk column transforms). - -@param {object} lists of create, update, delete. -@param {object} dataset json object. | my.save = function (changes, dataset) {
- var dfd = $.Deferred();
- var total = changes.creates.length + changes.updates.length + changes.deletes.length;
- var results = {'done': [], 'fail': [] };
-
- var decr_cb = function () { total -= 1; }
- var resolve_cb = function () { if (total == 0) dfd.resolve(results); }
-
- for (var i in changes.creates) {
- var new_doc = changes.creates[i];
- var succ_cb = function (msg) {results.done.push({'op': 'create', 'record': new_doc, 'reason': ''}); }
- var fail_cb = function (msg) {results.fail.push({'op': 'create', 'record': new_doc, 'reason': msg}); }
-
- _createDocument(new_doc, dataset).then([decr_cb, succ_cb, resolve_cb], [decr_cb, fail_cb, resolve_cb]);
- }
-
- for (var i in changes.updates) {
- var new_doc = changes.updates[i];
- var succ_cb = function (msg) {results.done.push({'op': 'update', 'record': new_doc, 'reason': ''}); }
- var fail_cb = function (msg) {results.fail.push({'op': 'update', 'record': new_doc, 'reason': msg}); }
-
- _updateDocument(new_doc, dataset).then([decr_cb, succ_cb, resolve_cb], [decr_cb, fail_cb, resolve_cb]);
- }
-
- for (var i in changes.deletes) {
- var old_doc = changes.deletes[i];
- var succ_cb = function (msg) {results.done.push({'op': 'delete', 'record': old_doc, 'reason': ''}); }
- var fail_cb = function (msg) {results.fail.push({'op': 'delete', 'record': old_doc, 'reason': msg}); }
-
- _deleteDocument(new_doc, dataset).then([decr_cb, succ_cb, resolve_cb], [decr_cb, fail_cb, resolve_cb]);
- }
-
- return dfd.promise();
-}; |
query- -fetch the data from the couchdb view and filter it. -@param {Object} recline.Dataset instance -@param {Object} recline.Query instance. | my.query = function(queryObj, dataset) {
- var dfd = $.Deferred();
- var db_url = dataset.db_url;
- var view_url = dataset.view_url;
- var query_options = dataset.query_options;
-
- var cdb = new my.CouchDBWrapper(db_url, view_url);
- var cdb_q = cdb._normalizeQuery(queryObj, query_options);
-
- cdb.query(queryObj, query_options).done(function(records){
-
- var query_result = { hits: [], total: 0 };
- _.each(records.rows, function(record) {
- var doc = {};
- if (record.hasOwnProperty('doc')) {
- doc = record['doc']; |
| couchdb uses _id to identify documents, Backbone models use id. -we add this fix so backbone.Model works correctly. | doc['id'] = doc['_id'];
- }
- else {
- doc = record['value']; |
| using dunder to create compound id. need something more robust. -couchdb uses _id to identify documents, Backbone models use id. -we add this fix so backbone.Model works correctly. | doc['_id'] = doc['id'] = record['id'] + '__' + record['key'];
- }
- query_result.total += 1;
- query_result.hits.push(doc);
- }); |
| the following block is borrowed verbatim from recline.backend.Memory -search (with filtering, faceting, and sorting) should be factored -out into a separate library. | query_result.hits = _applyFilters(query_result.hits, queryObj);
- query_result.hits = _applyFreeTextQuery(query_result.hits, queryObj); |
| not complete sorting! | _.each(queryObj.sort, function(sortObj) {
- var fieldName = _.keys(sortObj)[0];
- query_result.hits = _.sortBy(query_result.hits, function(doc) {
- var _out = doc[fieldName];
- return (sortObj[fieldName].order == 'asc') ? _out : -1*_out;
- });
- });
- query_result.total = query_result.hits.length;
- query_result.facets = _computeFacets(query_result.hits, queryObj);
- query_result.hits = query_result.hits.slice(cdb_q.skip, cdb_q.skip + cdb_q.limit+1);
- dfd.resolve(query_result);
-
- });
-
- return dfd.promise();
-}; |
| in place filtering | _applyFilters = function(results, queryObj) {
- _.each(queryObj.filters, function(filter) {
- results = _.filter(results, function(doc) {
- var fieldId = _.keys(filter.term)[0];
- return (doc[fieldId] == filter.term[fieldId]);
- });
- });
- return results;
-}; |
| we OR across fields but AND across terms in query string | _applyFreeTextQuery = function(results, queryObj) {
- if (queryObj.q) {
- var terms = queryObj.q.split(' ');
- results = _.filter(results, function(rawdoc) {
- var matches = true;
- _.each(terms, function(term) {
- var foundmatch = false;
- _.each(_.keys(rawdoc), function(field) {
- var value = rawdoc[field];
- if (value !== null) { value = value.toString(); } |
| TODO regexes? | foundmatch = foundmatch || (value === term); |
| TODO: early out (once we are true should break to spare unnecessary testing) -if (foundmatch) return true; | });
- matches = matches && foundmatch; |
| TODO: early out (once false should break to spare unnecessary testing) -if (!matches) return false; | });
- return matches;
- });
- }
- return results;
-};
-
-_computeFacets = function(records, queryObj) {
- var facetResults = {};
- if (!queryObj.facets) {
- return facetResults;
- }
- _.each(queryObj.facets, function(query, facetId) { |
| TODO: remove dependency on recline.Model | facetResults[facetId] = new recline.Model.Facet({id: facetId}).toJSON();
- facetResults[facetId].termsall = {};
- }); |
| faceting | _.each(records, function(doc) {
- _.each(queryObj.facets, function(query, facetId) {
- var fieldId = query.terms.field;
- var val = doc[fieldId];
- var tmp = facetResults[facetId];
- if (val) {
- tmp.termsall[val] = tmp.termsall[val] ? tmp.termsall[val] + 1 : 1;
- } else {
- tmp.missing = tmp.missing + 1;
- }
- });
- });
- _.each(queryObj.facets, function(query, facetId) {
- var tmp = facetResults[facetId];
- var terms = _.map(tmp.termsall, function(count, term) {
- return { term: term, count: count };
- });
- tmp.terms = _.sortBy(terms, function(item) { |
| want descending order | return -item.count;
- });
- tmp.terms = tmp.terms.slice(0, 10);
- });
- return facetResults;
-};
-
-_createDocument = function (new_doc, dataset) {
- var dfd = $.Deferred();
- var db_url = dataset.db_url;
- var view_url = dataset.view_url;
- var _id = new_doc['id'];
- var cdb = new my.CouchDBWrapper(db_url, view_url);
-
- delete new_doc['id'];
-
- if (view_url.search('_all_docs') !== -1) {
- jqxhr = cdb.get(_id);
- }
- else {
- _id = new_doc['_id'].split('__')[0];
- jqxhr = cdb.get(_id);
- }
-
- jqxhr.done(function(old_doc){
- if (dataset.record_create)
- new_doc = dataset.record_create(new_doc, old_doc);
- new_doc = _.extend(old_doc, new_doc);
- new_doc['_id'] = _id;
- dfd.resolve(cdb.upsert(new_doc));
- }).fail(function(args){
- dfd.reject(args);
- });
-
- return dfd.promise();
-};
-
-_updateDocument = function (new_doc, dataset) {
- var dfd = $.Deferred();
- var db_url = dataset.db_url;
- var view_url = dataset.view_url;
- var _id = new_doc['id'];
- var cdb = new my.CouchDBWrapper(db_url, view_url);
-
- delete new_doc['id'];
-
- if (view_url.search('_all_docs') !== -1) {
- jqxhr = cdb.get(_id);
- }
- else {
- _id = new_doc['_id'].split('__')[0];
- jqxhr = cdb.get(_id);
- }
-
- jqxhr.done(function(old_doc){
- if (dataset.record_update)
- new_doc = dataset.record_update(new_doc, old_doc);
- new_doc = _.extend(old_doc, new_doc);
- new_doc['_id'] = _id;
- dfd.resolve(cdb.upsert(new_doc));
- }).fail(function(args){
- dfd.reject(args);
- });
-
- return dfd.promise();
-};
-
-_deleteDocument = function (del_doc, dataset) {
- var dfd = $.Deferred();
- var db_url = dataset.db_url;
- var view_url = dataset.view_url;
- var _id = del_doc['id'];
- var cdb = new my.CouchDBWrapper(db_url, view_url);
-
- if (view_url.search('_all_docs') !== -1)
- return cdb.remove(_id);
- else {
- _id = model.get('_id').split('__')[0];
- var jqxhr = cdb.get(_id);
-
- jqxhr.done(function(old_doc){
- if (dataset.record_delete)
- old_doc = dataset.record_delete(del_doc, old_doc);
- if (_.isNull(del_doc))
- dfd.resolve(cdb.remove(_id)); // XXX is this the right thing to do?
- else { |
| couchdb uses _id to identify documents, Backbone models use id. -we should remove it before sending it to the server. | old_doc['_id'] = _id;
- delete old_doc['id'];
- dfd.resolve(cdb.upsert(old_doc));
- }
- }).fail(function(args){
- dfd.reject(args);
- });
- return dfd.promise();
- }
-};
-}(jQuery, this.recline.Backend.CouchDB));
-
- |