From b52ec039d78bb2d63e3044566be70c1444e5930b Mon Sep 17 00:00:00 2001 From: Aaron Hamid Date: Sun, 6 Nov 2011 02:16:37 -0500 Subject: [PATCH 1/4] determine collection from model more conservatively - supports nested collections / names with paths - https://github.com/janmonschke/backbone-couchdb/issues/17 --- backbone-couchdb.coffee | 15 ++++++++++++--- backbone-couchdb.js | 25 +++++++++++++++++-------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/backbone-couchdb.coffee b/backbone-couchdb.coffee index b5c8545..d8d8c33 100644 --- a/backbone-couchdb.coffee +++ b/backbone-couchdb.coffee @@ -31,8 +31,17 @@ Backbone.couch_connector = con = # jquery.couch.js adds the id itself, so we delete the id if it is in the url. # "collection/:id" -> "collection" _splitted = _name.split "/" - _name = if _splitted.length > 0 then _splitted[0] else _name - _name = _name.replace "/", "" + + # only pop off the last component if it is the id + if (_splitted.length > 0) + if (model.id == _splitted[_splitted.length - 1]) + _splitted.pop() + _name = _splitted.join('/') + + # remove any leading slash + if (_name.indexOf("/") == 0) + _name = _name.replace("/", "") + _name # creates a database instance from the @@ -172,4 +181,4 @@ class Backbone.Collection extends Backbone.Collection class Backbone.Model extends Backbone.Model # change the idAttribute since CouchDB uses _id - idAttribute : "_id" \ No newline at end of file + idAttribute : "_id" diff --git a/backbone-couchdb.js b/backbone-couchdb.js index 6a4cde8..b929137 100644 --- a/backbone-couchdb.js +++ b/backbone-couchdb.js @@ -1,9 +1,10 @@ (function() { /* (c) 2011 Jan Monschke - v1.0 + v1.1 backbone-couchdb.js is licensed under the MIT license. - */ var con; + */ + var con; var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } @@ -38,8 +39,15 @@ _name = _name.slice(1, _name.length); } _splitted = _name.split("/"); - _name = _splitted.length > 0 ? _splitted[0] : _name; - _name = _name.replace("/", ""); + if (_splitted.length > 0) { + if (model.id === _splitted[_splitted.length - 1]) { + _splitted.pop(); + } + _name = _splitted.join('/'); + } + if (_name.indexOf("/") === 0) { + _name = _name.replace("/", ""); + } return _name; }, make_db: function() { @@ -157,11 +165,12 @@ } }; Backbone.Collection = (function() { + __extends(Collection, Backbone.Collection); function Collection() { - this._db_on_change = __bind(this._db_on_change, this);; - this._db_prepared_for_changes = __bind(this._db_prepared_for_changes, this);; Collection.__super__.constructor.apply(this, arguments); + this._db_on_change = __bind(this._db_on_change, this); + this._db_prepared_for_changes = __bind(this._db_prepared_for_changes, this); + Collection.__super__.constructor.apply(this, arguments); } - __extends(Collection, Backbone.Collection); Collection.prototype.initialize = function() { if (!this._db_changes_enabled && ((this.db && this.db.changes) || con.config.global_changes)) { return this.listen_to_changes(); @@ -213,10 +222,10 @@ return Collection; })(); Backbone.Model = (function() { + __extends(Model, Backbone.Model); function Model() { Model.__super__.constructor.apply(this, arguments); } - __extends(Model, Backbone.Model); Model.prototype.idAttribute = "_id"; return Model; })(); From de023494fb3079e82de16dd6ab05cfa538943971 Mon Sep 17 00:00:00 2001 From: Aaron Hamid Date: Sun, 13 Nov 2011 22:02:17 -0500 Subject: [PATCH 2/4] added support for single global handler coupled with local collection filter functions --- backbone-couchdb.coffee | 57 ++++++++++++-- backbone-couchdb.js | 170 +++++++++++++++++++++++++++++----------- 2 files changed, 177 insertions(+), 50 deletions(-) diff --git a/backbone-couchdb.coffee b/backbone-couchdb.coffee index d8d8c33..2a37cb2 100644 --- a/backbone-couchdb.coffee +++ b/backbone-couchdb.coffee @@ -12,9 +12,16 @@ Backbone.couch_connector = con = view_name : "byCollection" # if true, all Collections will have the _changes feed enabled global_changes : false + # if true, a single changes feed connection will be used + single_feed : false # change the databse base_url to be able to fetch from a remote couchdb base_url : null + # global changes feed for all collections + _global_db_inst: null + _global_changes_handler: null + _global_changes_callbacks: [] + # some helper methods for the connector helpers : # returns a string representing the collection (needed for the "collection"-field) @@ -44,6 +51,10 @@ Backbone.couch_connector = con = _name + # default local filter which selects documents of a given collection + filter_collection : (results, collection_name) -> + entry for entry in results when (entry.deleted == true) || (entry.doc?.collection == collection_name) + # creates a database instance from the make_db : -> db = $.couch.db con.config.db_name @@ -85,6 +96,27 @@ Backbone.couch_connector = con = @helpers.make_db().view "#{@config.ddoc_name}/#{_view}", _opts + # initializes the single global changes handler + init_global_changes_handler : (callback) -> + @_global_db_inst = con.helpers.make_db() + @_global_db_inst.info + "success" : (data) => + # initialize the global changes handler + opts = _.extend { include_docs : true }, con.config.global_changes_opts + @_global_changes_handler = @_global_db_inst.changes (data.update_seq || 0), opts + # register a callback which delegates to every registered collection + @_global_changes_handler.onChange (changes) => + cb(changes) for cb in @_global_changes_callbacks + callback() + + # registers a collection callback with the global changes feed + register_global_changes_callback : (callback) -> + return unless callback? + if !@_global_db_inst? + @init_global_changes_handler => + @_global_changes_callbacks.push callback + else + @_global_changes_callbacks.push callback # Reads a model from the couchdb by it's ID read_model : (model, opts) -> @@ -142,9 +174,14 @@ class Backbone.Collection extends Backbone.Collection # don't enable changes feed a second time unless @_db_changes_enabled @_db_changes_enabled = true - @_db_inst = con.helpers.make_db() unless @_db_inst - @_db_inst.info - "success" : @_db_prepared_for_changes + if con.config.single_feed + # if we are using a single feed, don't set up a separate connection for the collection + # register a callback with the global changes handler + @_db_prepared_for_global_changes() + else + @_db_inst = con.helpers.make_db() unless @_db_inst + @_db_inst.info + "success" : @_db_prepared_for_changes # Stop listening to real time updates stop_changes : -> @@ -153,6 +190,7 @@ class Backbone.Collection extends Backbone.Collection @_db_changes_handler.stop() @_db_changes_handler = null + # sets up a new changes feed for this collection _db_prepared_for_changes : (data) => @_db_update_seq = data.update_seq || 0 opts = @@ -163,9 +201,18 @@ class Backbone.Collection extends Backbone.Collection _.defer => @_db_changes_handler = @_db_inst.changes(@_db_update_seq, opts) @_db_changes_handler.onChange @._db_on_change - + + # registers this collection's change handler with the global change feed + _db_prepared_for_global_changes : => + con.register_global_changes_callback(@_db_on_change) + _db_on_change : (changes) => - for _doc in changes.results + results = changes.results + if @db and @db.local_filter # if a local filter has been defined on the collection, use it + results = @db.local_filter(results) + else if con.config.single_feed # otherwise, if we are using a single feed, use the default global changes collection filter + results = con.helpers.filter_collection(results, con.helpers.extract_collection_name(@)) + for _doc in results obj = @get _doc.id # test if collection contains the doc, if not, we add it to the collection if obj? diff --git a/backbone-couchdb.js b/backbone-couchdb.js index b929137..3be2f2a 100644 --- a/backbone-couchdb.js +++ b/backbone-couchdb.js @@ -1,32 +1,30 @@ (function() { + /* (c) 2011 Jan Monschke v1.1 backbone-couchdb.js is licensed under the MIT license. */ + var con; - var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { - for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } - function ctor() { this.constructor = child; } - ctor.prototype = parent.prototype; - child.prototype = new ctor; - child.__super__ = parent.prototype; - return child; - }; + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; + Backbone.couch_connector = con = { config: { db_name: "backbone_connect", ddoc_name: "backbone_example", view_name: "byCollection", global_changes: false, + single_feed: false, base_url: null }, + _global_db_inst: null, + _global_changes_handler: null, + _global_changes_callbacks: [], helpers: { extract_collection_name: function(model) { var _name, _splitted; - if (model == null) { - throw new Error("No model has been passed"); - } + if (model == null) throw new Error("No model has been passed"); if (!(((model.collection != null) && (model.collection.url != null)) || (model.url != null))) { return ""; } @@ -35,21 +33,26 @@ } else { _name = _.isFunction(model.collection.url) ? model.collection.url() : model.collection.url; } - if (_name[0] === "/") { - _name = _name.slice(1, _name.length); - } + if (_name[0] === "/") _name = _name.slice(1, _name.length); _splitted = _name.split("/"); if (_splitted.length > 0) { - if (model.id === _splitted[_splitted.length - 1]) { - _splitted.pop(); - } + if (model.id === _splitted[_splitted.length - 1]) _splitted.pop(); _name = _splitted.join('/'); } - if (_name.indexOf("/") === 0) { - _name = _name.replace("/", ""); - } + if (_name.indexOf("/") === 0) _name = _name.replace("/", ""); return _name; }, + filter_collection: function(results, collection_name) { + var entry, _i, _len, _ref, _results; + _results = []; + for (_i = 0, _len = results.length; _i < _len; _i++) { + entry = results[_i]; + if ((entry.deleted === true) || (((_ref = entry.doc) != null ? _ref.collection : void 0) === collection_name)) { + _results.push(entry); + } + } + return _results; + }, make_db: function() { var db; db = $.couch.db(con.config.db_name); @@ -68,22 +71,19 @@ }, read_collection: function(coll, opts) { var keys, _opts, _view; + var _this = this; _view = this.config.view_name; keys = [this.helpers.extract_collection_name(coll)]; if (coll.db != null) { if (coll.db.changes || this.config.global_changes) { coll.listen_to_changes(); } - if (coll.db.view != null) { - _view = coll.db.view; - } - if (coll.db.keys != null) { - keys = coll.db.keys; - } + if (coll.db.view != null) _view = coll.db.view; + if (coll.db.keys != null) keys = coll.db.keys; } _opts = { keys: keys, - success: __bind(function(data) { + success: function(data) { var doc, _i, _len, _ref, _temp; _temp = []; _ref = data.rows; @@ -92,7 +92,7 @@ _temp.push(doc.value); } return opts.success(_temp); - }, this), + }, error: function() { return opts.error(); } @@ -102,6 +102,41 @@ } return this.helpers.make_db().view("" + this.config.ddoc_name + "/" + _view, _opts); }, + init_global_changes_handler: function(callback) { + var _this = this; + this._global_db_inst = con.helpers.make_db(); + return this._global_db_inst.info({ + "success": function(data) { + var opts; + opts = _.extend({ + include_docs: true + }, con.config.global_changes_opts); + _this._global_changes_handler = _this._global_db_inst.changes(data.update_seq || 0, opts); + _this._global_changes_handler.onChange(function(changes) { + var cb, _i, _len, _ref, _results; + _ref = _this._global_changes_callbacks; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + cb = _ref[_i]; + _results.push(cb(changes)); + } + return _results; + }); + return callback(); + } + }); + }, + register_global_changes_callback: function(callback) { + var _this = this; + if (callback == null) return; + if (!(this._global_db_inst != null)) { + return this.init_global_changes_handler(function() { + return _this._global_changes_callbacks.push(callback); + }); + } else { + return this._global_changes_callbacks.push(callback); + } + }, read_model: function(model, opts) { if (!model.id) { throw new Error("The model has no id property, so it can't get fetched from the database"); @@ -119,9 +154,7 @@ var coll, vals; vals = model.toJSON(); coll = this.helpers.extract_collection_name(model); - if (coll.length > 0) { - vals.collection = coll; - } + if (coll.length > 0) vals.collection = coll; return this.helpers.make_db().saveDoc(vals, { success: function(doc) { return opts.success({ @@ -152,6 +185,7 @@ }); } }; + Backbone.sync = function(method, model, opts) { switch (method) { case "read": @@ -164,29 +198,38 @@ return con.del(model, opts); } }; + Backbone.Collection = (function() { + __extends(Collection, Backbone.Collection); + function Collection() { this._db_on_change = __bind(this._db_on_change, this); + this._db_prepared_for_global_changes = __bind(this._db_prepared_for_global_changes, this); this._db_prepared_for_changes = __bind(this._db_prepared_for_changes, this); Collection.__super__.constructor.apply(this, arguments); } + Collection.prototype.initialize = function() { if (!this._db_changes_enabled && ((this.db && this.db.changes) || con.config.global_changes)) { return this.listen_to_changes(); } }; + Collection.prototype.listen_to_changes = function() { if (!this._db_changes_enabled) { this._db_changes_enabled = true; - if (!this._db_inst) { - this._db_inst = con.helpers.make_db(); + if (con.config.single_feed) { + return this._db_prepared_for_global_changes(); + } else { + if (!this._db_inst) this._db_inst = con.helpers.make_db(); + return this._db_inst.info({ + "success": this._db_prepared_for_changes + }); } - return this._db_inst.info({ - "success": this._db_prepared_for_changes - }); } }; + Collection.prototype.stop_changes = function() { this._db_changes_enabled = false; if (this._db_changes_handler != null) { @@ -194,8 +237,10 @@ return this._db_changes_handler = null; } }; + Collection.prototype._db_prepared_for_changes = function(data) { var opts; + var _this = this; this._db_update_seq = data.update_seq || 0; opts = { include_docs: true, @@ -203,30 +248,65 @@ filter: "" + con.config.ddoc_name + "/by_collection" }; _.extend(opts, this.db); - return _.defer(__bind(function() { - this._db_changes_handler = this._db_inst.changes(this._db_update_seq, opts); - return this._db_changes_handler.onChange(this._db_on_change); - }, this)); + return _.defer(function() { + _this._db_changes_handler = _this._db_inst.changes(_this._db_update_seq, opts); + return _this._db_changes_handler.onChange(_this._db_on_change); + }); }; + + Collection.prototype._db_prepared_for_global_changes = function() { + return con.register_global_changes_callback(this._db_on_change); + }; + Collection.prototype._db_on_change = function(changes) { - var obj, _doc, _i, _len, _ref, _results; - _ref = changes.results; + var obj, results, _doc, _i, _len, _results; + results = changes.results; + if (this.db && this.db.local_filter) { + results = this.db.local_filter(results); + } else if (con.config.single_feed) { + results = con.helpers.filter_collection(results, con.helpers.extract_collection_name(this)); + } _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - _doc = _ref[_i]; + for (_i = 0, _len = results.length; _i < _len; _i++) { + _doc = results[_i]; obj = this.get(_doc.id); - _results.push(obj != null ? _doc.deleted ? this.remove(obj) : obj.get("_rev") !== _doc.doc._rev ? obj.set(_doc.doc) : void 0 : !_doc.deleted ? this.add(_doc.doc) : void 0); + if (obj != null) { + if (_doc.deleted) { + _results.push(this.remove(obj)); + } else { + if (obj.get("_rev") !== _doc.doc._rev) { + _results.push(obj.set(_doc.doc)); + } else { + _results.push(void 0); + } + } + } else { + if (!_doc.deleted) { + _results.push(this.add(_doc.doc)); + } else { + _results.push(void 0); + } + } } return _results; }; + return Collection; + })(); + Backbone.Model = (function() { + __extends(Model, Backbone.Model); + function Model() { Model.__super__.constructor.apply(this, arguments); } + Model.prototype.idAttribute = "_id"; + return Model; + })(); + }).call(this); From 749164235e06e02bd16bc9346050557eb86ea734 Mon Sep 17 00:00:00 2001 From: Tim Black Date: Tue, 2 Oct 2012 23:46:18 -0500 Subject: [PATCH 3/4] Merged backbone-couchdb.coffee 1.2.2 into 1.1 pull request 25's changes at https://github.com/janmonschke/backbone-couchdb/pull/25, updated README.md accordingly. Did not update generated .js files, docs or example app. --- README.md | 93 ++++++-------------------------- backbone-couchdb.coffee | 117 ++++++++++++++++++++++++++++++---------- 2 files changed, 107 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 627c15b..e3344d6 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ sync-behavior and connects your app to your [RELAX](http://vimeo.com/11852209) and don't need to worry about (real-time) server-side code. +For a more detailed description head to [http://janmonschke.com/projects/backbone-couchdb.html](http://janmonschke.com/projects/backbone-couchdb.html). + Demos ----- * [Real time chat](http://backbone.iriscouch.com/backbone-couchapp/_design/backbone_example/index.html) with support for private messages. (source in `/chat_example`) @@ -14,6 +16,20 @@ Demos Changelog --------- +* 1.3 + * Single global changes feed support to stay within the browser's concurrent connections per server limit [#25](https://github.com/janmonschke/backbone-couchdb/pull/25) + +* 1.2.2 + * Revamped view options [#51](https://github.com/janmonschke/backbone-couchdb/pull/51) + +* 1.2 + * CouchDB list support [#37](https://github.com/janmonschke/backbone-couchdb/pull/37) + * Support for custom design documents for collections [#38](https://github.com/janmonschke/backbone-couchdb/pull/38) + * Fix for views that emit `null` [#35](https://github.com/janmonschke/backbone-couchdb/pull/35) + * A better way to test the library [/test](https://github.com/janmonschke/backbone-couchdb/tree/master/test) + * more request information in error callbacks [#20](https://github.com/janmonschke/backbone-couchdb/issues/20#issuecomment-5461404) + * Support for more options when fetching a collection [#34](https://github.com/janmonschke/backbone-couchdb/pull/34) + * tested with Backbone 0.9.2 * 1.1 * Fixed a bug with empty key param @@ -24,79 +40,4 @@ Changelog * Chat example (including tests) * Backbone 5.1 support * Various bugfixes - * Started versioning ;) - -Dependencies (already included in the examples) ------------- - -* [Backbone.js](https://github.com/documentcloud/backbone) (>= 0.5.1) -* [Underscore.js](https://github.com/documentcloud/underscore) -* [jquery.couch.js](https://github.com/apache/couchdb/blob/trunk/share/www/script/jquery.couch.js) -* [jQuery](http://www.jquery.com/) - -Why a new connector? --------------------- - -I developed this connector because I didn't want to write a whole new -server that persists the models that Backbone.js creates. Instead of -writing a server you now only have to write a simple design document -containing one simple view and you're done with server-side code and can -fully concentrate on the Backbone App. - -Also I wanted to get real time updates when my models are changed on the -server (e.g. by a second user). The CouchDB _changes feed seemed like a -perfect match for this problem. - -Getting Started ---------------- - -All Backbone apps should work normally without any changes. Simply -include `backbone-couchdb.js` with its dependencies into your project -and configure the connector with your database infos. - - Backbone.couch_connector.config.db_name = "backbone-couchapp"; - Backbone.couch_connector.config.ddoc_name = "backbone-couchapp"; - Backbone.couch_connector.config.global_changes = false; - -As you can see you also need to create a new database in your CouchDB -and a new design document that contains the following view: - - function(doc) { - if (doc.collection) { - emit(doc.collection, doc); - } - } - -If you set `Backbone.couch_connector.config.global_changes` to true, the -connector will automatically update your models with remote changes in -near real time. - -Give your [couchapp](https://github.com/couchapp/couchapp) some backbone ------------------------------------------------------------------------- - -An easy way to host single-page apps is to enclose them in a couchapp. I -included a sample couchapp project (`/chat_example`) to show you how to -create couchapps with Backbone and this CouchDB connector. You can also -use it as a bare couchapp directory structure for new projects. - -There is an instance of this couchapp running on [iriscouch.com -(demo)](http://backbone.iriscouch.com/backbone-couchapp/_design/backbone_example/index.html) -and I uploaded a file with the [annotated -source](http://janmonschke.github.com/backbone-couchdb/app.html) of the -app. (Created with [docco](https://github.com/jashkenas/docco)) - -Erica support -------------- - -[Erica](http://github.com/benoitc/erica) templates are supported. Simply -link the backbone-couchdb folder to ~/.erica/templates. Then you can -create an application using these templates: - - $ erica create template=backbone appid=myappname - -Learn more ----------- - -To show how backbone-couchdb works under the hood I created an annotated -source file located -[here](http://janmonschke.github.com/backbone-couchdb/backbone-couchdb.html). + * Started versioning ;) \ No newline at end of file diff --git a/backbone-couchdb.coffee b/backbone-couchdb.coffee index 2a37cb2..6bcd40a 100644 --- a/backbone-couchdb.coffee +++ b/backbone-couchdb.coffee @@ -1,6 +1,6 @@ ### -(c) 2011 Jan Monschke -v1.1 +(c) 2012 Jan Monschke +v1.3 backbone-couchdb.js is licensed under the MIT license. ### @@ -10,6 +10,7 @@ Backbone.couch_connector = con = db_name : "backbone_connect" ddoc_name : "backbone_example" view_name : "byCollection" + list_name : null # if true, all Collections will have the _changes feed enabled global_changes : false # if true, a single changes feed connection will be used @@ -38,17 +39,8 @@ Backbone.couch_connector = con = # jquery.couch.js adds the id itself, so we delete the id if it is in the url. # "collection/:id" -> "collection" _splitted = _name.split "/" - - # only pop off the last component if it is the id - if (_splitted.length > 0) - if (model.id == _splitted[_splitted.length - 1]) - _splitted.pop() - _name = _splitted.join('/') - - # remove any leading slash - if (_name.indexOf("/") == 0) - _name = _name.replace("/", "") - + _name = if _splitted.length > 0 then _splitted[0] else _name + _name = _name.replace "/", "" _name # default local filter which selects documents of a given collection @@ -72,29 +64,69 @@ Backbone.couch_connector = con = # Reads all docs of a collection based on the byCollection view or a custom view specified by the collection read_collection : (coll, opts) -> _view = @config.view_name + _ddoc = @config.ddoc_name + _list = @config.list_name keys = [@helpers.extract_collection_name coll] if coll.db? coll.listen_to_changes() if coll.db.changes or @config.global_changes if coll.db.view? _view = coll.db.view + if coll.db.ddoc? + _ddoc = coll.db.ddoc if coll.db.keys? keys = coll.db.keys + if coll.db.list? + _list = coll.db.list _opts = keys : keys success : (data) => _temp = [] for doc in data.rows - _temp.push doc.value + if doc.value then _temp.push doc.value else _temp.push doc.doc opts.success _temp - error : -> - opts.error() - + opts.complete() + error : (status, error, reason) -> + res = + status: status + error: error + reason: reason + opts.error res + opts.complete res + + # support view querying opts http://wiki.apache.org/couchdb/HTTP_view_API + + view_options = [ + "key" + "keys" + "startkey" + "startkey_docid" + "endkey" + "endkey_docid" + "limit" + "stale" + "descending" + "skip" + "group" + "group_level" + "reduce" + "include_docs" + "inclusive_end" + "update_seq" + ] + + for option in view_options + if opts[option]? + _opts[option] = opts[option] + # delete keys if a custom view is requested but no custom keys if coll.db? and coll.db.view? and not coll.db.keys? delete _opts.keys - @helpers.make_db().view "#{@config.ddoc_name}/#{_view}", _opts + if _list + @helpers.make_db().list "#{_ddoc}/#{_list}", "#{_view}", _opts + else + @helpers.make_db().view "#{_ddoc}/#{_view}", _opts # initializes the single global changes handler init_global_changes_handler : (callback) -> @@ -124,8 +156,14 @@ Backbone.couch_connector = con = @helpers.make_db().openDoc model.id, success : (doc) -> opts.success(doc) - error : -> - opts.error() + opts.complete() + error : (status, error, reason) -> + res = + status: status + error: error + reason: reason + opts.error res + opts.complete res # Creates a model in the db create : (model, opts) -> @@ -137,8 +175,14 @@ Backbone.couch_connector = con = opts.success _id : doc.id _rev : doc.rev - error : -> - opts.error() + opts.complete() + error : (status, error, reason) -> + res = + status: status + error: error + reason: reason + opts.error res + opts.complete res # jquery.couch.js uses the same method for updating as it uses for creating a document, so we can use the `create` method here. ### update : (model, opts) -> @@ -153,19 +197,42 @@ Backbone.couch_connector = con = if e == "deleted" # The doc does no longer exist on the server opts.success() + opts.complete() else - opts.error() + res = + status: status + error: error + reason: reason + opts.error res + opts.complete res # Overriding the sync method here to make the connector work ### Backbone.sync = (method, model, opts) -> + opts.success ?= -> + opts.error ?= -> + opts.complete ?= -> + switch method when "read" then con.read model, opts when "create" then con.create model, opts when "update" then con.update model, opts when "delete" then con.del model, opts +class Backbone.Model extends Backbone.Model + # change the idAttribute since CouchDB uses _id + idAttribute : "_id" + clone : -> + new_model = new @constructor(@) + # remove _id and _rev attributes on the cloned model object to have a **really** new, unsaved model object. + # _id and _rev only exist on objects that have been saved, so check for existence is needed. + delete new_model.attributes._id if new_model.attributes._id + delete new_model.attributes._rev if new_model.attributes._rev + new_model + # Adds some more methods to Collections that are needed for the connector ### class Backbone.Collection extends Backbone.Collection + model : Backbone.Model + initialize : -> @listen_to_changes() if !@_db_changes_enabled && ((@db and @db.changes) or con.config.global_changes) @@ -224,8 +291,4 @@ class Backbone.Collection extends Backbone.Collection obj.set _doc.doc unless obj.get("_rev") == _doc.doc._rev else @add _doc.doc if !_doc.deleted - -class Backbone.Model extends Backbone.Model - # change the idAttribute since CouchDB uses _id - idAttribute : "_id" From 52d81b285dbe2c6f6e7305c5adcbd4ea51bb9bb2 Mon Sep 17 00:00:00 2001 From: Tim Black Date: Wed, 3 Oct 2012 00:16:13 -0500 Subject: [PATCH 4/4] Restore 'determine collection from model more conservatively...' - b52ec039d78bb2d63e3044566be70c1444e5930b --- backbone-couchdb.coffee | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/backbone-couchdb.coffee b/backbone-couchdb.coffee index 6bcd40a..36d89e5 100644 --- a/backbone-couchdb.coffee +++ b/backbone-couchdb.coffee @@ -39,8 +39,16 @@ Backbone.couch_connector = con = # jquery.couch.js adds the id itself, so we delete the id if it is in the url. # "collection/:id" -> "collection" _splitted = _name.split "/" - _name = if _splitted.length > 0 then _splitted[0] else _name - _name = _name.replace "/", "" + + # only pop off the last component if it is the id + if (_splitted.length > 0) + if (model.id == _splitted[_splitted.length - 1]) + _splitted.pop() + _name = _splitted.join('/') + + # remove any leading slash + if (_name.indexOf("/") == 0) + _name = _name.replace("/", "") _name # default local filter which selects documents of a given collection