From 529a5de7925df714bb53d37de8ad167418bd3a68 Mon Sep 17 00:00:00 2001 From: Olivier Chatry Date: Wed, 2 Sep 2015 10:27:39 +0200 Subject: [PATCH 01/32] db can now be switched dynamically on the adapter, allowing to have a dynamic adapter --- addon/adapters/pouch.js | 93 +++++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/addon/adapters/pouch.js b/addon/adapters/pouch.js index 9ee19cc..f13e78a 100644 --- a/addon/adapters/pouch.js +++ b/addon/adapters/pouch.js @@ -26,13 +26,19 @@ export default DS.RESTAdapter.extend({ shouldReloadRecord: function () { return false; }, shouldBackgroundReloadRecord: function () { return false; }, - _startChangesToStoreListener: on('init', function () { - this.changes = this.get('db').changes({ - since: 'now', - live: true, - returnDocs: false - }).on('change', bind(this, 'onChange')); + _hookObservers : on('init', function() { + this.addObserver('db', this, this._startChangesToStoreListener) }), + _startChangesToStoreListener: function () { + var db = this.get('db'); + if (db) { + this.changes = this.get('db').changes({ + since: 'now', + live: true, + returnDocs: false + }).on('change', bind(this, 'onChange')); + } + }, onChange: function (change) { // If relational_pouch isn't initialized yet, there can't be any records @@ -81,7 +87,8 @@ export default DS.RESTAdapter.extend({ _init: function (store, type) { var self = this, recordTypeName = this.getRecordTypeName(type); - if (!this.get('db') || typeof this.get('db') !== 'object') { + var db = this.get('db'); + if (!db || typeof db !== 'object') { throw new Error('Please set the `db` property on the adapter.'); } @@ -97,49 +104,55 @@ export default DS.RESTAdapter.extend({ var plural = pluralize(recordTypeName); // check that we haven't already registered this model + var isSchemaNew = true; for (var i = 0, len = this._schema.length; i < len; i++) { var currentSchemaDef = this._schema[i]; if (currentSchemaDef.singular === singular) { - return; + isSchemaNew = false; + break; } } - var schemaDef = { - singular: singular, - plural: plural - }; - - if (type.documentType) { - schemaDef['documentType'] = type.documentType; - } - // else it's new, so update - this._schema.push(schemaDef); - - // check all the subtypes - // We check the type of `rel.type`because with ember-data beta 19 - // `rel.type` switched from DS.Model to string - type.eachRelationship(function (_, rel) { - if (rel.kind !== 'belongsTo' && rel.kind !== 'hasMany') { - // TODO: support inverse as well - return; // skip + if (isSchemaNew) { + var schemaDef = { + singular: singular, + plural: plural + }; + + if (type.documentType) { + schemaDef['documentType'] = type.documentType; } - var relDef = {}, - relModel = (typeof rel.type === 'string' ? store.modelFor(rel.type) : rel.type); - if (relModel) { - relDef[rel.kind] = { - type: self.getRecordTypeName(relModel), - options: rel.options - }; - if (!schemaDef.relations) { - schemaDef.relations = {}; + + this._schema.push(schemaDef); + // check all the subtypes + // We check the type of `rel.type`because with ember-data beta 19 + // `rel.type` switched from DS.Model to string + type.eachRelationship(function (_, rel) { + if (rel.kind !== 'belongsTo' && rel.kind !== 'hasMany') { + // TODO: support inverse as well + return; // skip } - schemaDef.relations[rel.key] = relDef; - self._init(store, relModel); - } - }); + var relDef = {}, + relModel = (typeof rel.type === 'string' ? store.modelFor(rel.type) : rel.type); + if (relModel) { + relDef[rel.kind] = { + type: self.getRecordTypeName(relModel), + options: rel.options + }; + if (!schemaDef.relations) { + schemaDef.relations = {}; + } + schemaDef.relations[rel.key] = relDef; + self._init(store, relModel); + } + }); + } + + if (!db.rel) { + db.setSchema(this._schema); + } - this.get('db').setSchema(this._schema); }, _recordToData: function (store, type, record) { From 26b256786664527c8b536df397df3b8e76021b5d Mon Sep 17 00:00:00 2001 From: Olivier Chatry Date: Wed, 2 Sep 2015 11:25:47 +0200 Subject: [PATCH 02/32] small cosmetic fix --- addon/adapters/pouch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/adapters/pouch.js b/addon/adapters/pouch.js index f13e78a..0751233 100644 --- a/addon/adapters/pouch.js +++ b/addon/adapters/pouch.js @@ -32,7 +32,7 @@ export default DS.RESTAdapter.extend({ _startChangesToStoreListener: function () { var db = this.get('db'); if (db) { - this.changes = this.get('db').changes({ + this.changes = db.changes({ since: 'now', live: true, returnDocs: false From 71201e11a2b3dd4709fed6aaaa7efde1ccd18501 Mon Sep 17 00:00:00 2001 From: Olivier Chatry Date: Wed, 2 Sep 2015 11:48:00 +0200 Subject: [PATCH 03/32] build fix --- addon/adapters/pouch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/adapters/pouch.js b/addon/adapters/pouch.js index 0751233..31f2501 100644 --- a/addon/adapters/pouch.js +++ b/addon/adapters/pouch.js @@ -27,7 +27,7 @@ export default DS.RESTAdapter.extend({ shouldBackgroundReloadRecord: function () { return false; }, _hookObservers : on('init', function() { - this.addObserver('db', this, this._startChangesToStoreListener) + this.addObserver('db', this, this._startChangesToStoreListener); }), _startChangesToStoreListener: function () { var db = this.get('db'); From 1f7441c64b2db64a109773dc2f415adc89d04c2d Mon Sep 17 00:00:00 2001 From: olivier Date: Sat, 5 Sep 2015 10:06:17 +0200 Subject: [PATCH 04/32] changing database is now done through a call to changeDb --- README.md | 25 +++++++++++ addon/adapters/pouch.js | 95 ++++++++++++++++++++++------------------- 2 files changed, 76 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 496abb1..7fabe0d 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,31 @@ The value for `documentType` is the camelCase version of the primary model name. For best results, only create/update records using the full model definition. Treat the others as read-only. +## Multiple databases for the same model + +In some cases it might diserable (security related, where you want a given user to only have some informations stored on his computer) to have multiple databases for the same model of data. + +`Ember-Pouch` allows you to dynamically change the database a model is using by calling the function `changeDb` on the adapter. + +```javascript +function changeProjectDatabase(dbName, dbUser, dbPassword) { + // CouchDB is serving at http://localhost:5455 + let remote = new PouchDB('http://localhost:5455/' + dbName); + // here we are using pouchdb-authentication for credential supports + remote.login( dbUser, dbPassword).then( + function (user) { + let db = new PouchDB(dbName) + db.sync(remote, {live:true, retry:true}) + // grab the adapter, it can be any ember-pouch adapter. + let adapter = this.store.adapterFor('project'); + // this is where we told the adapter to change the current database. + adapter.changeDb(db); + } + ) +} +``` + + ## Installation * `git clone` this repository diff --git a/addon/adapters/pouch.js b/addon/adapters/pouch.js index 31f2501..90e9c7d 100644 --- a/addon/adapters/pouch.js +++ b/addon/adapters/pouch.js @@ -25,9 +25,8 @@ export default DS.RESTAdapter.extend({ // reloading redundant. shouldReloadRecord: function () { return false; }, shouldBackgroundReloadRecord: function () { return false; }, - - _hookObservers : on('init', function() { - this.addObserver('db', this, this._startChangesToStoreListener); + _onInit : on('init', function() { + this._startChangesToStoreListener(); }), _startChangesToStoreListener: function () { var db = this.get('db'); @@ -39,7 +38,22 @@ export default DS.RESTAdapter.extend({ }).on('change', bind(this, 'onChange')); } }, + changeDb: function(db) { + if (this.changes) { + this.changes.cancel(); + } + + var store = this.container.lookup('store:main'); + var schema = this._schema || []; + + for (var i = 0, len = schema.length; i < len; i++) { + store.unloadAll(schema[i].singular); + } + this._schema = null; + this.set('db', db); + this._startChangesToStoreListener(); + }, onChange: function (change) { // If relational_pouch isn't initialized yet, there can't be any records // in the store to update. @@ -87,8 +101,7 @@ export default DS.RESTAdapter.extend({ _init: function (store, type) { var self = this, recordTypeName = this.getRecordTypeName(type); - var db = this.get('db'); - if (!db || typeof db !== 'object') { + if (!this.get('db') || typeof this.get('db') !== 'object') { throw new Error('Please set the `db` property on the adapter.'); } @@ -104,55 +117,49 @@ export default DS.RESTAdapter.extend({ var plural = pluralize(recordTypeName); // check that we haven't already registered this model - var isSchemaNew = true; for (var i = 0, len = this._schema.length; i < len; i++) { var currentSchemaDef = this._schema[i]; if (currentSchemaDef.singular === singular) { - isSchemaNew = false; - break; + return; } } - // else it's new, so update - if (isSchemaNew) { - var schemaDef = { - singular: singular, - plural: plural - }; - - if (type.documentType) { - schemaDef['documentType'] = type.documentType; - } + var schemaDef = { + singular: singular, + plural: plural + }; - this._schema.push(schemaDef); - // check all the subtypes - // We check the type of `rel.type`because with ember-data beta 19 - // `rel.type` switched from DS.Model to string - type.eachRelationship(function (_, rel) { - if (rel.kind !== 'belongsTo' && rel.kind !== 'hasMany') { - // TODO: support inverse as well - return; // skip - } - var relDef = {}, - relModel = (typeof rel.type === 'string' ? store.modelFor(rel.type) : rel.type); - if (relModel) { - relDef[rel.kind] = { - type: self.getRecordTypeName(relModel), - options: rel.options - }; - if (!schemaDef.relations) { - schemaDef.relations = {}; - } - schemaDef.relations[rel.key] = relDef; - self._init(store, relModel); - } - }); + if (type.documentType) { + schemaDef['documentType'] = type.documentType; } - if (!db.rel) { - db.setSchema(this._schema); - } + // else it's new, so update + this._schema.push(schemaDef); + + // check all the subtypes + // We check the type of `rel.type`because with ember-data beta 19 + // `rel.type` switched from DS.Model to string + type.eachRelationship(function (_, rel) { + if (rel.kind !== 'belongsTo' && rel.kind !== 'hasMany') { + // TODO: support inverse as well + return; // skip + } + var relDef = {}, + relModel = (typeof rel.type === 'string' ? store.modelFor(rel.type) : rel.type); + if (relModel) { + relDef[rel.kind] = { + type: self.getRecordTypeName(relModel), + options: rel.options + }; + if (!schemaDef.relations) { + schemaDef.relations = {}; + } + schemaDef.relations[rel.key] = relDef; + self._init(store, relModel); + } + }); + this.get('db').setSchema(this._schema); }, _recordToData: function (store, type, record) { From 54254ce5e1e866656a2b08dd47bdc242f669f424 Mon Sep 17 00:00:00 2001 From: Olivier Chatry Date: Tue, 13 Oct 2015 10:54:47 +0200 Subject: [PATCH 05/32] fixing deprecation warnings --- addon/adapters/pouch.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addon/adapters/pouch.js b/addon/adapters/pouch.js index 90e9c7d..7cd5d99 100644 --- a/addon/adapters/pouch.js +++ b/addon/adapters/pouch.js @@ -43,7 +43,7 @@ export default DS.RESTAdapter.extend({ this.changes.cancel(); } - var store = this.container.lookup('store:main'); + var store = this.container.lookup('service:store'); var schema = this._schema || []; for (var i = 0, len = schema.length; i < len; i++) { @@ -73,12 +73,12 @@ export default DS.RESTAdapter.extend({ return; } - var recordInStore = store.getById(obj.type, obj.id); + var recordInStore = store.peekRecord(obj.type, obj.id); if (!recordInStore) { // The record hasn't been loaded into the store; no need to reload its data. return; } - if (!recordInStore.get('isLoaded') || recordInStore.get('isDirty')) { + if (!recordInStore.get('isLoaded') || recordInStore.get('hasDirtyAttributes')) { // The record either hasn't loaded yet or has unpersisted local changes. // In either case, we don't want to refresh it in the store // (and for some substates, attempting to do so will result in an error). From e270115da15ea23f5e8f37071ceba1c04f44feae Mon Sep 17 00:00:00 2001 From: Olivier Chatry Date: Fri, 20 Nov 2015 13:00:00 +0100 Subject: [PATCH 06/32] fixes livesync for ember data 2.x --- addon/adapters/pouch.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addon/adapters/pouch.js b/addon/adapters/pouch.js index 7cd5d99..e199f69 100644 --- a/addon/adapters/pouch.js +++ b/addon/adapters/pouch.js @@ -63,7 +63,7 @@ export default DS.RESTAdapter.extend({ // skip changes for non-relational_pouch docs. E.g., design docs. if (!obj.type || !obj.id || obj.type === '') { return; } - var store = this.container.lookup('store:main'); + var store = this.container.lookup('service:store'); try { store.modelFor(obj.type); @@ -78,7 +78,9 @@ export default DS.RESTAdapter.extend({ // The record hasn't been loaded into the store; no need to reload its data. return; } - if (!recordInStore.get('isLoaded') || recordInStore.get('hasDirtyAttributes')) { + // removed || recordInStore.get('hasDirtyAttributes') because it does not track + // relationships correctly. + if (!recordInStore.get('isLoaded')) { // The record either hasn't loaded yet or has unpersisted local changes. // In either case, we don't want to refresh it in the store // (and for some substates, attempting to do so will result in an error). From 962c0cfeb348e3b34944938b97ee3b4ecd4ed0fb Mon Sep 17 00:00:00 2001 From: Olivier Chatry Date: Fri, 20 Nov 2015 13:03:30 +0100 Subject: [PATCH 07/32] reverted stupid mistake --- addon/adapters/pouch.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/addon/adapters/pouch.js b/addon/adapters/pouch.js index e199f69..ac94922 100644 --- a/addon/adapters/pouch.js +++ b/addon/adapters/pouch.js @@ -78,9 +78,7 @@ export default DS.RESTAdapter.extend({ // The record hasn't been loaded into the store; no need to reload its data. return; } - // removed || recordInStore.get('hasDirtyAttributes') because it does not track - // relationships correctly. - if (!recordInStore.get('isLoaded')) { + if (!recordInStore.get('isLoaded') || recordInStore.get('hasDirtyAttributes')) { // The record either hasn't loaded yet or has unpersisted local changes. // In either case, we don't want to refresh it in the store // (and for some substates, attempting to do so will result in an error). From 0b5e8b8e3b39be2a92599b9b6b813c715e8220c8 Mon Sep 17 00:00:00 2001 From: Olivier Chatry Date: Tue, 15 Dec 2015 22:39:13 +0100 Subject: [PATCH 08/32] use this.store instead of container.lookup --- addon/adapters/pouch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/adapters/pouch.js b/addon/adapters/pouch.js index d8a2016..0712346 100644 --- a/addon/adapters/pouch.js +++ b/addon/adapters/pouch.js @@ -43,7 +43,7 @@ export default DS.RESTAdapter.extend({ this.changes.cancel(); } - var store = this.container.lookup('service:store'); + var store = this.store; var schema = this._schema || []; for (var i = 0, len = schema.length; i < len; i++) { From 00ff6bec8727a7cc0a55dc28c72dff539e0cfbc6 Mon Sep 17 00:00:00 2001 From: Martin Broerse Date: Wed, 16 Dec 2015 09:43:47 +0100 Subject: [PATCH 09/32] Bump ember-cli-version-checker --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 24520d0..a689921 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ }, "dependencies": { "ember-cli-babel": "^5.1.5", - "ember-cli-version-checker": "1.1.4" + "ember-cli-version-checker": "1.1.5" }, "ember-addon": { "configPath": "tests/dummy/config" From 9b5444b7d00ae76d26b77c586145eccaf63c20eb Mon Sep 17 00:00:00 2001 From: Rhett Sutphin Date: Fri, 18 Dec 2015 21:03:22 -0600 Subject: [PATCH 10/32] Add section about not automatically loading every record Suggested on #50. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 09e58c6..0f240b5 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,14 @@ From day one, CouchDB and its protocol have been designed to be always **A**vail To learn more about how CouchDB sync works, check out [the PouchDB guide to replication](http://pouchdb.com/guides/replication.html). +### Sync and the ember-data store + +Out of the box, ember-pouch includes a PouchDB [change listener](http://pouchdb.com/guides/changes.html) that automatically updates any records your app has loaded when they change due to a sync. It also unloads records that are removed due to a sync. + +However, ember-pouch *does not automatically load new records* that arrive during a sync. The records are saved in the local database, but ember-data is not automatically told to load them into memory. Automatically loading every new record works well with a small number of records and a limited number of models. As an app grows, automatically loading every record will negatively impact app responsiveness during syncs (especially the first sync). To avoid puzzling slowdowns, ember-pouch only works with records you have used ember-data to load. + +If you have a model or two that you know will always have a small number of records, you can write your own change listener to automatically load them as they arrive. + ### Plugins With PouchDB, you also get access to a whole host of [PouchDB plugins](http://pouchdb.com/external.html). From 6d0091b71cdd19f57f7fbfc7012af1dba23b1009 Mon Sep 17 00:00:00 2001 From: Rhett Sutphin Date: Fri, 18 Dec 2015 21:14:43 -0600 Subject: [PATCH 11/32] Clarify Continuation of section added due to discussion on #50. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0f240b5..3549ef3 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,9 @@ To learn more about how CouchDB sync works, check out [the PouchDB guide to repl Out of the box, ember-pouch includes a PouchDB [change listener](http://pouchdb.com/guides/changes.html) that automatically updates any records your app has loaded when they change due to a sync. It also unloads records that are removed due to a sync. -However, ember-pouch *does not automatically load new records* that arrive during a sync. The records are saved in the local database, but ember-data is not automatically told to load them into memory. Automatically loading every new record works well with a small number of records and a limited number of models. As an app grows, automatically loading every record will negatively impact app responsiveness during syncs (especially the first sync). To avoid puzzling slowdowns, ember-pouch only works with records you have used ember-data to load. +However, ember-pouch does not automatically load new records that arrive during a sync. The records are saved in the local database, but **ember-data is not told to load them into memory**. Automatically loading every new record works well with a small number of records and a limited number of models. As an app grows, automatically loading every record will negatively impact app responsiveness during syncs (especially the first sync). To avoid puzzling slowdowns, ember-pouch only automatically reloads records you have already used ember-data to load. -If you have a model or two that you know will always have a small number of records, you can write your own change listener to automatically load them as they arrive. +If you have a model or two that you know will always have a small number of records, you can write your own change listener to tell ember-data to automatically load them into memory as they arrive. ### Plugins From c94badfb816f518278c5fe08787fb91c016ecaf0 Mon Sep 17 00:00:00 2001 From: Matt Marcum Date: Fri, 18 Dec 2015 22:43:33 -0600 Subject: [PATCH 12/32] added blueprint for pouch-model --- README.md | 12 ++++++++++++ blueprints/.jshintrc | 6 ++++++ .../pouch-model/files/__root__/__path__/__name__.js | 12 ++++++++++++ blueprints/pouch-model/index.js | 3 +++ 4 files changed, 33 insertions(+) create mode 100644 blueprints/.jshintrc create mode 100644 blueprints/pouch-model/files/__root__/__path__/__name__.js create mode 100644 blueprints/pouch-model/index.js diff --git a/README.md b/README.md index 3549ef3..b14485a 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,18 @@ PouchDB.debug.enable('*'); See the [PouchDB sync API](http://pouchdb.com/api.html#sync) for full usage instructions. +## EmberPouch Blueprints + +### Model + +In order to create a model run the following command from the command line: + +``` +ember g pouch-model +``` + +Replace `` with the name of your model and the file will automatically be generated for you. + ## Sample app Tom Dale's blog example using Ember CLI and EmberPouch: [broerse/ember-cli-blog](https://github.com/broerse/ember-cli-blog) diff --git a/blueprints/.jshintrc b/blueprints/.jshintrc new file mode 100644 index 0000000..33f4f6f --- /dev/null +++ b/blueprints/.jshintrc @@ -0,0 +1,6 @@ +{ + "predef": [ + "console" + ], + "strict": false +} diff --git a/blueprints/pouch-model/files/__root__/__path__/__name__.js b/blueprints/pouch-model/files/__root__/__path__/__name__.js new file mode 100644 index 0000000..04750b6 --- /dev/null +++ b/blueprints/pouch-model/files/__root__/__path__/__name__.js @@ -0,0 +1,12 @@ +import Model from 'ember-pouch/model'; +import DS from 'ember-data'; + +const { + attr, + hasMany, + belongsTo +} = DS; + +export default Model.extend({ + <%= attrs %> +}); diff --git a/blueprints/pouch-model/index.js b/blueprints/pouch-model/index.js new file mode 100644 index 0000000..3a7d2cd --- /dev/null +++ b/blueprints/pouch-model/index.js @@ -0,0 +1,3 @@ +var EmberCliModelBlueprint = require('ember-cli/blueprints/model'); + +module.exports = EmberCliModelBlueprint; From ed59fbf587e8dd616b5f021d816ee1beea95ce53 Mon Sep 17 00:00:00 2001 From: Matt Marcum Date: Fri, 18 Dec 2015 22:52:10 -0600 Subject: [PATCH 13/32] created blueprint for pouch-adapter --- README.md | 15 +++++++++ .../files/__root__/__path__/__name__.js | 32 +++++++++++++++++++ blueprints/pouch-adapter/index.js | 14 ++++++++ tests/dummy/app/adapters/application.js | 21 +++++++++++- tests/dummy/config/environment.js | 6 ++-- tests/integration/adapters/pouch-test.js | 7 ++-- 6 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 blueprints/pouch-adapter/files/__root__/__path__/__name__.js create mode 100644 blueprints/pouch-adapter/index.js diff --git a/README.md b/README.md index b14485a..6eb2026 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,21 @@ ember g pouch-model Replace `` with the name of your model and the file will automatically be generated for you. +### Adapter + +You can now create an adapter using ember-cli's blueprint functionality. Once you've installed `ember-pouch` into your ember-cli app you can run the following command to automatically generate an application adapter. + +``` +ember g pouch-adapter application +``` + +Now you can store your localDb and remoteDb names in your ember-cli's config. Just add the following keys to the `ENV` object: + +```javascript +ENV.emberPouch.localDb = 'test'; +ENV.emberPouch.remoteDB = 'http://localhost:5984/my_couch'; +``` + ## Sample app Tom Dale's blog example using Ember CLI and EmberPouch: [broerse/ember-cli-blog](https://github.com/broerse/ember-cli-blog) diff --git a/blueprints/pouch-adapter/files/__root__/__path__/__name__.js b/blueprints/pouch-adapter/files/__root__/__path__/__name__.js new file mode 100644 index 0000000..e468366 --- /dev/null +++ b/blueprints/pouch-adapter/files/__root__/__path__/__name__.js @@ -0,0 +1,32 @@ +import { Adapter } from 'ember-pouch'; +import PouchDB from 'pouchdb'; +import config from '<%= dasherizedPackageName %>/config/environment'; +import Ember from 'ember'; + +const { assert, isEmpty } = Ember; + +function createDb() { + let localDb = config.emberPouch.localDb; + + assert('emberPouch.localDb must be set', !isEmpty(localDb)); + + let db = new PouchDB(localDb); + + if (config.emberPouch.remoteDb) { + let remoteDb = new PouchDB(config.emberPouch.remoteDb); + + db.sync(remoteDb, { + live: true, + retry: true + }); + } + + return db; +} + +export default Adapter.extend({ + init() { + this._super(...arguments); + this.set('db', createDb()); + } +}); diff --git a/blueprints/pouch-adapter/index.js b/blueprints/pouch-adapter/index.js new file mode 100644 index 0000000..41a516d --- /dev/null +++ b/blueprints/pouch-adapter/index.js @@ -0,0 +1,14 @@ +module.exports = { + description: '' + + // locals: function(options) { + // // Return custom template variables here. + // return { + // foo: options.entity.options.foo + // }; + // } + + // afterInstall: function(options) { + // // Perform extra work here. + // } +}; diff --git a/tests/dummy/app/adapters/application.js b/tests/dummy/app/adapters/application.js index b087629..740da8a 100644 --- a/tests/dummy/app/adapters/application.js +++ b/tests/dummy/app/adapters/application.js @@ -1,8 +1,27 @@ import { Adapter } from 'ember-pouch/index'; import PouchDB from 'pouchdb'; +import config from 'dummy/config/environment'; +import Ember from 'ember'; + +const { assert, isEmpty } = Ember; function createDb() { - return new PouchDB('ember-pouch-test'); + let localDb = config.emberpouch.localDb; + + assert('emberpouch.localDb must be set', !isEmpty(localDb)); + + let db = new PouchDB(localDb); + + if (config.emberpouch.remote) { + let remoteDb = new PouchDB(config.emberpouch.remoteDb); + + db.sync(remoteDb, { + live: true, + retry: true + }); + } + + return db; } export default Adapter.extend({ diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js index c59bcd5..f50bb9c 100644 --- a/tests/dummy/config/environment.js +++ b/tests/dummy/config/environment.js @@ -4,6 +4,7 @@ module.exports = function(environment) { var ENV = { modulePrefix: 'dummy', environment: environment, + emberpouch: { localDb: 'ember-pouch-test' }, baseURL: '/', locationType: 'auto', EmberENV: { @@ -16,7 +17,8 @@ module.exports = function(environment) { APP: { // Here you can pass flags/options to your application instance // when it is created - } + }, + }; if (environment === 'development') { @@ -37,10 +39,10 @@ module.exports = function(environment) { ENV.APP.LOG_VIEW_LOOKUPS = false; ENV.APP.rootElement = '#ember-testing'; + } if (environment === 'production') { - } return ENV; diff --git a/tests/integration/adapters/pouch-test.js b/tests/integration/adapters/pouch-test.js index 4f0b3f1..f85f5fb 100644 --- a/tests/integration/adapters/pouch-test.js +++ b/tests/integration/adapters/pouch-test.js @@ -1,5 +1,6 @@ import { module, test } from 'qunit'; import startApp from '../../helpers/start-app'; +import config from 'dummy/config/environment'; import Ember from 'ember'; /* globals PouchDB */ @@ -14,11 +15,7 @@ module('adapter:pouch [integration]', { beforeEach: function (assert) { var done = assert.async(); - // TODO: do this in a way that doesn't require duplicating the name of the - // test database here and in dummy/app/adapters/application.js. Importing - // the adapter directly doesn't work because of what seems like a resolver - // issue. - (new PouchDB('ember-pouch-test')).destroy().then(() => { + (new PouchDB(config.emberpouch.localDb)).destroy().then(() => { App = startApp(); var bootPromise; Ember.run(() => { From d6b511cac2a07a0fa246a61242d8d5f11693559a Mon Sep 17 00:00:00 2001 From: Rhett Sutphin Date: Fri, 18 Dec 2015 23:26:22 -0600 Subject: [PATCH 14/32] Changelog for #101 and #102. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6eb2026..191d7f2 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,8 @@ And of course thanks to all our wonderful contributors, [here](https://github.co ## Changelog +* **3.0.1** + - Add blueprints for model and adapter (see above for details). Thanks [@mattmarcum](https://github.com/mattmarcum). * **3.0.0** - Update for compatibility with Ember & Ember-Data 2.0+. The adapter now supports Ember & Ember-Data 1.13.x and 2.x only. * **2.0.3** From e90f672b12eb08561eba7066f51ddda0403a0fa4 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Sat, 19 Dec 2015 12:47:19 -0500 Subject: [PATCH 15/32] Move blueprint files into explicit directories Using __path__ meant the adapter file was generated at /app/pouch-adapters/__name__.js, for instance. Ember Data was not finding it, nor the models stored within pouch-models. --- .../files/__root__/{__path__ => adapters}/__name__.js | 0 .../pouch-model/files/__root__/{__path__ => models}/__name__.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename blueprints/pouch-adapter/files/__root__/{__path__ => adapters}/__name__.js (100%) rename blueprints/pouch-model/files/__root__/{__path__ => models}/__name__.js (100%) diff --git a/blueprints/pouch-adapter/files/__root__/__path__/__name__.js b/blueprints/pouch-adapter/files/__root__/adapters/__name__.js similarity index 100% rename from blueprints/pouch-adapter/files/__root__/__path__/__name__.js rename to blueprints/pouch-adapter/files/__root__/adapters/__name__.js diff --git a/blueprints/pouch-model/files/__root__/__path__/__name__.js b/blueprints/pouch-model/files/__root__/models/__name__.js similarity index 100% rename from blueprints/pouch-model/files/__root__/__path__/__name__.js rename to blueprints/pouch-model/files/__root__/models/__name__.js From 65a29e6beafd0dad666470821df9ebf96267b144 Mon Sep 17 00:00:00 2001 From: Rhett Sutphin Date: Sun, 20 Dec 2015 00:59:50 -0600 Subject: [PATCH 16/32] Factor out integration test setup into module helper --- tests/helpers/module-for-acceptance.js | 39 ++++++++++-- tests/integration/adapters/pouch-test.js | 79 ++++++------------------ 2 files changed, 53 insertions(+), 65 deletions(-) diff --git a/tests/helpers/module-for-acceptance.js b/tests/helpers/module-for-acceptance.js index ed23003..65c61ba 100644 --- a/tests/helpers/module-for-acceptance.js +++ b/tests/helpers/module-for-acceptance.js @@ -1,15 +1,44 @@ import { module } from 'qunit'; import startApp from '../helpers/start-app'; import destroyApp from '../helpers/destroy-app'; +import config from 'dummy/config/environment'; + +import Ember from 'ember'; +/* globals PouchDB */ export default function(name, options = {}) { module(name, { - beforeEach() { - this.application = startApp(); + beforeEach(assert) { + var done = assert.async(); - if (options.beforeEach) { - options.beforeEach.apply(this, arguments); - } + Ember.RSVP.Promise.resolve().then(() => { + return (new PouchDB(config.emberpouch.localDb)).destroy(); + }).then(() => { + this.application = startApp(); + + this.lookup = function (item) { + return this.application.__container__.lookup(item); + }; + + this.store = function store() { + return this.lookup('service:store'); + }; + + // At the container level, adapters are not singletons (ember-data + // manages them). To get the instance that the app is using, we have to + // go through the store. + this.adapter = function adapter() { + return this.store().adapterFor('taco-soup'); + }; + + this.db = function db() { + return this.adapter().get('db'); + }; + + if (options.beforeEach) { + options.beforeEach.apply(this, arguments); + } + }).finally(done); }, afterEach() { diff --git a/tests/integration/adapters/pouch-test.js b/tests/integration/adapters/pouch-test.js index f85f5fb..30c7aaf 100644 --- a/tests/integration/adapters/pouch-test.js +++ b/tests/integration/adapters/pouch-test.js @@ -1,67 +1,26 @@ -import { module, test } from 'qunit'; -import startApp from '../../helpers/start-app'; -import config from 'dummy/config/environment'; +import { test } from 'qunit'; +import moduleForIntegration from '../../helpers/module-for-acceptance'; import Ember from 'ember'; -/* globals PouchDB */ - -var App; /* * Tests basic CRUD behavior for an app using the ember-pouch adapter. */ -module('adapter:pouch [integration]', { - beforeEach: function (assert) { - var done = assert.async(); - - (new PouchDB(config.emberpouch.localDb)).destroy().then(() => { - App = startApp(); - var bootPromise; - Ember.run(() => { - if (App.boot) { - App.advanceReadiness(); - bootPromise = App.boot(); - } else { - bootPromise = Ember.RSVP.Promise.resolve(); - } - }); - return bootPromise; - }).then(() => { - done(); - }); - }, - - afterEach: function () { - Ember.run(App, 'destroy'); - } -}); - -function db() { - return adapter().get('db'); -} - -function adapter() { - // the default adapter in the dummy app is an ember-pouch adapter - return App.__container__.lookup('adapter:application'); -} - -function store() { - return App.__container__.lookup('service:store'); -} +moduleForIntegration('adapter:pouch [integration]'); test('can find all', function (assert) { assert.expect(3); var done = assert.async(); Ember.RSVP.Promise.resolve().then(() => { - return db().bulkDocs([ + return this.db().bulkDocs([ { _id: 'tacoSoup_2_A', data: { flavor: 'al pastor' } }, { _id: 'tacoSoup_2_B', data: { flavor: 'black bean' } }, { _id: 'burritoShake_2_X', data: { consistency: 'smooth' } } ]); }).then(() => { - return store().findAll('taco-soup'); + return this.store().findAll('taco-soup'); }).then((found) => { assert.equal(found.get('length'), 2, 'should have found the two taco soup items only'); assert.deepEqual(found.mapBy('id'), ['A', 'B'], @@ -81,12 +40,12 @@ test('can find one', function (assert) { var done = assert.async(); Ember.RSVP.Promise.resolve().then(() => { - return db().bulkDocs([ + return this.db().bulkDocs([ { _id: 'tacoSoup_2_C', data: { flavor: 'al pastor' } }, { _id: 'tacoSoup_2_D', data: { flavor: 'black bean' } }, ]); }).then(() => { - return store().find('taco-soup', 'D'); + return this.store().find('taco-soup', 'D'); }).then((found) => { assert.equal(found.get('id'), 'D', 'should have found the requested item'); @@ -105,7 +64,7 @@ test('can find associated records', function (assert) { var done = assert.async(); Ember.RSVP.Promise.resolve().then(() => { - return db().bulkDocs([ + return this.db().bulkDocs([ { _id: 'tacoSoup_2_C', data: { flavor: 'al pastor', ingredients: ['X', 'Y'] } }, { _id: 'tacoSoup_2_D', data: { flavor: 'black bean', ingredients: ['Z'] } }, { _id: 'foodItem_2_X', data: { name: 'pineapple' }}, @@ -113,7 +72,7 @@ test('can find associated records', function (assert) { { _id: 'foodItem_2_Z', data: { name: 'black beans' }} ]); }).then(() => { - return store().find('taco-soup', 'C'); + return this.store().find('taco-soup', 'C'); }).then((found) => { assert.equal(found.get('id'), 'C', 'should have found the requested item'); @@ -136,14 +95,14 @@ test('create a new record', function (assert) { var done = assert.async(); Ember.RSVP.Promise.resolve().then(() => { - var newSoup = store().createRecord('taco-soup', { id: 'E', flavor: 'balsamic' }); + var newSoup = this.store().createRecord('taco-soup', { id: 'E', flavor: 'balsamic' }); return newSoup.save(); }).then(() => { - return db().get('tacoSoup_2_E'); + return this.db().get('tacoSoup_2_E'); }).then((newDoc) => { assert.equal(newDoc.data.flavor, 'balsamic', 'should have saved the attribute'); - var recordInStore = store().peekRecord('tacoSoup', 'E'); + var recordInStore = this.store().peekRecord('tacoSoup', 'E'); assert.equal(newDoc._rev, recordInStore.get('rev'), 'should have associated the ember-data record with the rev for the new record'); @@ -164,21 +123,21 @@ if (!DS.VERSION.match(/^2\.0/)) { var done = assert.async(); Ember.RSVP.Promise.resolve().then(() => { - return db().bulkDocs([ + return this.db().bulkDocs([ { _id: 'tacoSoup_2_C', data: { flavor: 'al pastor' } }, { _id: 'tacoSoup_2_D', data: { flavor: 'black bean' } }, ]); }).then(() => { - return store().find('taco-soup', 'C'); + return this.store().find('taco-soup', 'C'); }).then((found) => { found.set('flavor', 'pork'); return found.save(); }).then(() => { - return db().get('tacoSoup_2_C'); + return this.db().get('tacoSoup_2_C'); }).then((updatedDoc) => { assert.equal(updatedDoc.data.flavor, 'pork', 'should have updated the attribute'); - var recordInStore = store().peekRecord('tacoSoup', 'C'); + var recordInStore = this.store().peekRecord('tacoSoup', 'C'); assert.equal(updatedDoc._rev, recordInStore.get('rev'), 'should have associated the ember-data record with the updated rev'); @@ -196,16 +155,16 @@ test('delete an existing record', function (assert) { var done = assert.async(); Ember.RSVP.Promise.resolve().then(() => { - return db().bulkDocs([ + return this.db().bulkDocs([ { _id: 'tacoSoup_2_C', data: { flavor: 'al pastor' } }, { _id: 'tacoSoup_2_D', data: { flavor: 'black bean' } }, ]); }).then(() => { - return store().find('taco-soup', 'C'); + return this.store().find('taco-soup', 'C'); }).then((found) => { return found.destroyRecord(); }).then(() => { - return db().get('tacoSoup_2_C'); + return this.db().get('tacoSoup_2_C'); }).then((doc) => { assert.ok(!doc, 'document should no longer exist'); }, (result) => { From 1efa7c831609ed01f0259da64f71e360c44c50ce Mon Sep 17 00:00:00 2001 From: Rhett Sutphin Date: Sun, 20 Dec 2015 02:31:30 -0600 Subject: [PATCH 17/32] Tests for existing change watcher behavior --- .../{pouch-test.js => pouch-basics-test.js} | 2 +- .../pouch-default-change-watcher-test.js | 144 ++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) rename tests/integration/adapters/{pouch-test.js => pouch-basics-test.js} (98%) create mode 100644 tests/integration/adapters/pouch-default-change-watcher-test.js diff --git a/tests/integration/adapters/pouch-test.js b/tests/integration/adapters/pouch-basics-test.js similarity index 98% rename from tests/integration/adapters/pouch-test.js rename to tests/integration/adapters/pouch-basics-test.js index 30c7aaf..8b8ee92 100644 --- a/tests/integration/adapters/pouch-test.js +++ b/tests/integration/adapters/pouch-basics-test.js @@ -7,7 +7,7 @@ import Ember from 'ember'; * Tests basic CRUD behavior for an app using the ember-pouch adapter. */ -moduleForIntegration('adapter:pouch [integration]'); +moduleForIntegration('Integration | Adapter | Basic CRUD Ops'); test('can find all', function (assert) { assert.expect(3); diff --git a/tests/integration/adapters/pouch-default-change-watcher-test.js b/tests/integration/adapters/pouch-default-change-watcher-test.js new file mode 100644 index 0000000..25dc73c --- /dev/null +++ b/tests/integration/adapters/pouch-default-change-watcher-test.js @@ -0,0 +1,144 @@ +import { test } from 'qunit'; +import moduleForIntegration from '../../helpers/module-for-acceptance'; + +import Ember from 'ember'; + +/* + * Tests for the default automatic change listener. + */ + +moduleForIntegration('Integration | Adapter | Default Change Watcher', { + beforeEach(assert) { + var done = assert.async(); + + Ember.RSVP.Promise.resolve().then(() => { + return this.db().bulkDocs([ + { _id: 'tacoSoup_2_A', data: { flavor: 'al pastor', ingredients: ['X', 'Y'] } }, + { _id: 'tacoSoup_2_B', data: { flavor: 'black bean', ingredients: ['Z'] } }, + { _id: 'foodItem_2_X', data: { name: 'pineapple' } }, + { _id: 'foodItem_2_Y', data: { name: 'pork loin' } }, + { _id: 'foodItem_2_Z', data: { name: 'black beans' } } + ]); + }).finally(done); + } +}); + +function promiseToRunLater(callback, timeout) { + return new Ember.RSVP.Promise((resolve) => { + Ember.run.later(() => { + callback(); + resolve(); + }, timeout); + }); +} + +test('a loaded instance automatically reflects directly-made database changes', function (assert) { + assert.expect(2); + var done = assert.async(); + + Ember.RSVP.resolve().then(() => { + return this.store().find('taco-soup', 'B'); + }).then((soupB) => { + assert.equal('black bean', soupB.get('flavor'), + 'the loaded instance should reflect the initial test data'); + + return this.db().get('tacoSoup_2_B'); + }).then((soupBRecord) => { + soupBRecord.data.flavor = 'carnitas'; + return this.db().put(soupBRecord); + }).then(() => { + return promiseToRunLater(() => { + var alreadyLoadedSoupB = this.store().peekRecord('taco-soup', 'B'); + assert.equal(alreadyLoadedSoupB.get('flavor'), 'carnitas', + 'the loaded instance should automatically reflect the change in the database'); + }, 15); + }).finally(done); +}); + +test('a record that is not loaded stays not loaded when it is changed', function (assert) { + assert.expect(2); + var done = assert.async(); + + Ember.RSVP.resolve().then(() => { + assert.equal(null, this.store().peekRecord('taco-soup', 'A'), + 'test setup: record should not be loaded already'); + + return this.db().get('tacoSoup_2_A'); + }).then((soupARecord) => { + soupARecord.data.flavor = 'barbacoa'; + return this.db().put(soupARecord); + }).then(() => { + return promiseToRunLater(() => { + assert.equal(null, this.store().peekRecord('taco-soup', 'A'), + 'the corresponding instance should still not be loaded'); + }, 15); + }).finally(done); +}); + +test('a new record is not automatically loaded', function (assert) { + assert.expect(2); + var done = assert.async(); + + Ember.RSVP.resolve().then(() => { + assert.equal(null, this.store().peekRecord('taco-soup', 'C'), + 'test setup: record should not be loaded already'); + + return this.db().put({ + _id: 'tacoSoup_2_C', data: { flavor: 'sofritas' } + }); + }).then(() => { + return promiseToRunLater(() => { + assert.equal(null, this.store().peekRecord('taco-soup', 'C'), + 'the corresponding instance should still not be loaded'); + }, 15); + }).finally(done); +}); + +test('a deleted record is automatically unloaded', function (assert) { + assert.expect(2); + var done = assert.async(); + + Ember.RSVP.resolve().then(() => { + return this.store().find('taco-soup', 'B'); + }).then((soupB) => { + assert.equal('black bean', soupB.get('flavor'), + 'the loaded instance should reflect the initial test data'); + + return this.db().get('tacoSoup_2_B'); + }).then((soupBRecord) => { + return this.db().remove(soupBRecord); + }).then(() => { + return promiseToRunLater(() => { + assert.equal(null, this.store().peekRecord('taco-soup', 'B'), + 'the corresponding instance should no longer be loaded'); + }, 15); + }).finally(done); +}); + +test('a change to a record with a non-relational-pouch ID does not cause an error', function (assert) { + assert.expect(0); + var done = assert.async(); + + Ember.RSVP.resolve().then(() => { + // do some op to cause relational-pouch to be initialized + return this.store().find('taco-soup', 'B'); + }).then(() => { + return this.db().put({ + _id: '_design/ingredient-use' + }); + }).finally(done); +}); + +test('a change to a record of an unknown type does not cause an error', function (assert) { + assert.expect(0); + var done = assert.async(); + + Ember.RSVP.resolve().then(() => { + // do some op to cause relational-pouch to be initialized + return this.store().find('taco-soup', 'B'); + }).then(() => { + return this.db().put({ + _id: 'burritoShake_2_X', data: { consistency: 'chunky' } + }); + }).finally(done); +}); From da9dbe43cbc7fca4c0a5834110d9b69e39c356dd Mon Sep 17 00:00:00 2001 From: Rhett Sutphin Date: Sun, 20 Dec 2015 02:45:38 -0600 Subject: [PATCH 18/32] Update changelog for #103 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 191d7f2..97af1a4 100644 --- a/README.md +++ b/README.md @@ -249,7 +249,7 @@ And of course thanks to all our wonderful contributors, [here](https://github.co ## Changelog * **3.0.1** - - Add blueprints for model and adapter (see above for details). Thanks [@mattmarcum](https://github.com/mattmarcum). + - Add blueprints for model and adapter (see above for details). Thanks [@mattmarcum](https://github.com/mattmarcum) ([#101](https://github.com/nolanlawson/ember-pouch/issues/101), [#102](https://github.com/nolanlawson/ember-pouch/issues/102)) and [@backspace](https://github.com/backspace) ([#103](https://github.com/nolanlawson/ember-pouch/issues/103)). * **3.0.0** - Update for compatibility with Ember & Ember-Data 2.0+. The adapter now supports Ember & Ember-Data 1.13.x and 2.x only. * **2.0.3** From 0ebccd5c3a273fdafb6f560627f67162aeec54c1 Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Fri, 15 Jan 2016 12:50:46 -0500 Subject: [PATCH 19/32] Fix version check for Ember Data 3.2.x --- .travis.yml | 1 + bower.json | 6 +++--- config/ember-try.js | 22 ++++++++++++++++++---- index.js | 4 ++-- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index d048991..c7a4ea5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ env: - EMBER_TRY_SCENARIO=ember-data-2.0 - EMBER_TRY_SCENARIO=ember-data-2.1 - EMBER_TRY_SCENARIO=ember-data-2.2 + - EMBER_TRY_SCENARIO=ember-data-2.3 - EMBER_TRY_SCENARIO=ember-release - EMBER_TRY_SCENARIO=ember-beta - EMBER_TRY_SCENARIO=ember-canary diff --git a/bower.json b/bower.json index f56480f..4da0589 100644 --- a/bower.json +++ b/bower.json @@ -23,17 +23,17 @@ ], "dependencies": { "ember": "~1.13.0 || 2.x", - "ember-cli-shims": "0.0.6", + "ember-cli-shims": "0.1.0", "ember-cli-test-loader": "0.2.1", "ember-data": "~1.13.0 || 2.x", "ember-load-initializers": "0.1.7", "ember-qunit": "0.4.16", "ember-qunit-notifications": "0.1.0", "ember-resolver": "~0.1.20", - "jquery": "^1.11.3", + "jquery": "1.11.3", "loader.js": "ember-cli/loader.js#3.4.0", "qunit": "~1.20.0", "pouchdb": "^3.5.0", "relational-pouch": "^1.3.2" } -} \ No newline at end of file +} diff --git a/config/ember-try.js b/config/ember-try.js index 6393d72..ff666d3 100644 --- a/config/ember-try.js +++ b/config/ember-try.js @@ -4,7 +4,8 @@ module.exports = { name: 'ember-data-1.13', dependencies: { 'ember': '1.13.11', - 'ember-data': '1.13.15' + 'ember-data': '1.13.15', + 'ember-cli-shims': '0.0.6' }, resolutions: { 'ember': '1.13.11' @@ -14,7 +15,8 @@ module.exports = { name: 'ember-data-2.0', dependencies: { 'ember': '2.0.2', - 'ember-data': '2.0.1' + 'ember-data': '2.0.1', + 'ember-cli-shims': '0.0.6' }, resolutions: { 'ember': '2.0.2' @@ -24,7 +26,8 @@ module.exports = { name: 'ember-data-2.1', dependencies: { 'ember': '2.1.1', - 'ember-data': '2.1.0' + 'ember-data': '2.1.0', + 'ember-cli-shims': '0.0.6' }, resolutions: { 'ember': '2.1.1' @@ -34,12 +37,23 @@ module.exports = { name: 'ember-data-2.2', dependencies: { 'ember': '2.2.0', - 'ember-data': '2.2.1' + 'ember-data': '2.2.1', + 'ember-cli-shims': '0.0.6' }, resolutions: { 'ember': '2.2.0' } }, + { + name: 'ember-data-2.3', + dependencies: { + 'ember': '2.2.1', + 'ember-data': '2.3.0', + }, + resolutions: { + 'ember': '2.2.1' + } + }, { name: 'ember-release', dependencies: { diff --git a/index.js b/index.js index fbb1ced..cff2ff5 100644 --- a/index.js +++ b/index.js @@ -5,13 +5,13 @@ var VersionChecker = require('ember-cli-version-checker'); // We support Ember-Data 1.13.x and 2.x. Checking for this is complicated // because the effective version of ember-data is controlled by bower for -// 1.13.0-2.3.x and npm for 2.4.x. This gets as close as we can get using +// 1.13.0-2.2.x and npm for 2.3.x. This gets as close as we can get using // ember-cli-version-checker. function satisfactoryEmberDataVersion(addon) { var checker = new VersionChecker(addon), bowerEmberData = checker.for('ember-data', 'bower'), npmEmberData = checker.for('ember-data', 'npm'); - return npmEmberData.isAbove('2.3.99') || bowerEmberData.isAbove('1.12.99'); + return npmEmberData.isAbove('2.2.99') || bowerEmberData.isAbove('1.12.99'); } module.exports = { From 80882ce06da2b52c0ecc7bf221150fc6cfee1642 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sat, 16 Jan 2016 14:04:40 -0500 Subject: [PATCH 20/32] 3.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a689921..cf67bc3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ember-pouch", - "version": "3.0.0", + "version": "3.1.0", "description": "PouchDB adapter for Ember Data", "directories": { "doc": "doc", From 25c3866dad9633e6805a72fd8a29bdef1978890d Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sat, 16 Jan 2016 14:15:41 -0500 Subject: [PATCH 21/32] update readme [skip ci] --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c332f52..e2bf7f1 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,9 @@ And of course thanks to all our wonderful contributors, [here](https://github.co ## Changelog +* **3.1.0** + - Database can now be dynamically switched on the adapter ([#89](https://github.com/nolanlawson/ember-pouch/pull/89)). Thanks to [@olivierchatry](https://github.com/olivierchatry) for this! + - Various bugfixes by [@backspace](https://github.com/backspace), [@jkleinsc](https://github.com/jkleinsc), [@rsutphin](https://github.com/rsutphin), [@mattmarcum](https://github.com/mattmarcum), [@broerse](https://github.com/broerse), and [@olivierchatry](https://github.com/olivierchatry). See [the full commit log](https://github.com/nolanlawson/ember-pouch/compare/7c216311ffacd2f08b57df4fe34d49f4e7c373f1...v3.1.0) for details. Thank you! * **3.0.1** - Add blueprints for model and adapter (see above for details). Thanks [@mattmarcum](https://github.com/mattmarcum) ([#101](https://github.com/nolanlawson/ember-pouch/issues/101), [#102](https://github.com/nolanlawson/ember-pouch/issues/102)) and [@backspace](https://github.com/backspace) ([#103](https://github.com/nolanlawson/ember-pouch/issues/103)). * **3.0.0** From 58e3e81a4427c28aedb0752395ca393319f9372c Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Fri, 22 Jan 2016 12:34:14 -0500 Subject: [PATCH 22/32] Pouch adapter now calls a hook method when encountering a change for a record that is not yet loaded. - The hook is no-op by default, to match present behavior and performance profile - Added a test demonstrating use of the hook to load the new data into the ember-data store --- README.md | 14 ++++- addon/adapters/pouch.js | 14 +++++ tests/dummy/app/adapters/taco-salad.js | 39 ++++++++++++++ tests/dummy/app/models/taco-salad.js | 8 +++ .../pouch-default-change-watcher-test.js | 52 +++++++++++++++++++ 5 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 tests/dummy/app/adapters/taco-salad.js create mode 100644 tests/dummy/app/models/taco-salad.js diff --git a/README.md b/README.md index e2bf7f1..ece6ec8 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,17 @@ Out of the box, ember-pouch includes a PouchDB [change listener](http://pouchdb. However, ember-pouch does not automatically load new records that arrive during a sync. The records are saved in the local database, but **ember-data is not told to load them into memory**. Automatically loading every new record works well with a small number of records and a limited number of models. As an app grows, automatically loading every record will negatively impact app responsiveness during syncs (especially the first sync). To avoid puzzling slowdowns, ember-pouch only automatically reloads records you have already used ember-data to load. -If you have a model or two that you know will always have a small number of records, you can write your own change listener to tell ember-data to automatically load them into memory as they arrive. +If you have a model or two that you know will always have a small number of records, you can tell ember-data to automatically load them into memory as they arrive. Your PouchAdapter subclass has a method `unloadedDocumentChanged`, which is called when a document is received during sync that has not been loaded into the ember-data store. In your subclass, you can implement the following to load it automatically: + +```js + unloadedDocumentChanged: function(obj) { + let store = this.get('store'); + let recordTypeName = this.getRecordTypeName(store.modelFor(obj.type)); + this.get('db').rel.find(recordTypeName, obj.id).then(function(doc) { + store.pushPayload(recordTypeName, doc); + }); + }, +``` ### Plugins @@ -238,7 +248,7 @@ function changeProjectDatabase(dbName, dbUser, dbPassword) { // this is where we told the adapter to change the current database. adapter.changeDb(db); } - ) + ) } ``` diff --git a/addon/adapters/pouch.js b/addon/adapters/pouch.js index 0712346..cff8735 100644 --- a/addon/adapters/pouch.js +++ b/addon/adapters/pouch.js @@ -76,6 +76,7 @@ export default DS.RESTAdapter.extend({ var recordInStore = store.peekRecord(obj.type, obj.id); if (!recordInStore) { // The record hasn't been loaded into the store; no need to reload its data. + this.unloadedDocumentChanged(obj); return; } if (!recordInStore.get('isLoaded') || recordInStore.get('hasDirtyAttributes')) { @@ -92,6 +93,19 @@ export default DS.RESTAdapter.extend({ } }, + unloadedDocumentChanged: function(/* obj */) { + /* + * For performance purposes, we don't load records into the store that haven't previously been loaded. + * If you want to change this, subclass this method, and push the data into the store. e.g. + * + * let store = this.get('store'); + * let recordTypeName = this.getRecordTypeName(store.modelFor(obj.type)); + * this.get('db').rel.find(recordTypeName, obj.id).then(function(doc){ + * store.pushPayload(recordTypeName, doc); + * }); + */ + }, + willDestroy: function() { if (this.changes) { this.changes.cancel(); diff --git a/tests/dummy/app/adapters/taco-salad.js b/tests/dummy/app/adapters/taco-salad.js new file mode 100644 index 0000000..9ec86b7 --- /dev/null +++ b/tests/dummy/app/adapters/taco-salad.js @@ -0,0 +1,39 @@ +import { Adapter } from 'ember-pouch/index'; +import PouchDB from 'pouchdb'; +import config from 'dummy/config/environment'; +import Ember from 'ember'; + +const { assert, isEmpty } = Ember; + +function createDb() { + let localDb = config.emberpouch.localDb; + + assert('emberpouch.localDb must be set', !isEmpty(localDb)); + + let db = new PouchDB(localDb); + + if (config.emberpouch.remote) { + let remoteDb = new PouchDB(config.emberpouch.remoteDb); + + db.sync(remoteDb, { + live: true, + retry: true + }); + } + + return db; +} + +export default Adapter.extend({ + init() { + this._super(...arguments); + this.set('db', createDb()); + }, + unloadedDocumentChanged(obj) { + let store = this.get('store'); + let recordTypeName = this.getRecordTypeName(store.modelFor(obj.type)); + this.get('db').rel.find(recordTypeName, obj.id).then(function(doc){ + store.pushPayload(recordTypeName, doc); + }); + } +}); diff --git a/tests/dummy/app/models/taco-salad.js b/tests/dummy/app/models/taco-salad.js new file mode 100644 index 0000000..8cc83c4 --- /dev/null +++ b/tests/dummy/app/models/taco-salad.js @@ -0,0 +1,8 @@ +import DS from 'ember-data'; + +export default DS.Model.extend({ + rev: DS.attr('string'), + + flavor: DS.attr('string'), + ingredients: DS.hasMany('food-item', { async: true }) +}); diff --git a/tests/integration/adapters/pouch-default-change-watcher-test.js b/tests/integration/adapters/pouch-default-change-watcher-test.js index 25dc73c..cf0285e 100644 --- a/tests/integration/adapters/pouch-default-change-watcher-test.js +++ b/tests/integration/adapters/pouch-default-change-watcher-test.js @@ -142,3 +142,55 @@ test('a change to a record of an unknown type does not cause an error', function }); }).finally(done); }); + +moduleForIntegration('Integration | Adapter | With unloadedDocumentChanged implementation to load new docs into store', { + beforeEach(assert) { + var done = assert.async(); + this.adapter = function adapter() { + return this.store().adapterFor('taco-salad'); + }; + this.db = function db() { + return this.adapter().get('db'); + }; + + Ember.RSVP.Promise.resolve().then(() => { + return this.db().bulkDocs([ + { _id: 'tacoSalad_2_A', data: { flavor: 'al pastor', ingredients: ['X', 'Y'] } }, + { _id: 'tacoSalad_2_B', data: { flavor: 'black bean', ingredients: ['Z'] } }, + { _id: 'foodItem_2_X', data: { name: 'pineapple' } }, + { _id: 'foodItem_2_Y', data: { name: 'pork loin' } }, + { _id: 'foodItem_2_Z', data: { name: 'black beans' } } + ]); + }).finally(done); + } +}); + +test('a new record is automatically loaded', function (assert) { + assert.expect(4); + var done = assert.async(); + + Ember.RSVP.resolve().then(() => { + return this.store().find('taco-salad', 'B'); + }).then((soupB) => { + assert.equal('black bean', soupB.get('flavor'), + 'the loaded instance should reflect the initial test data'); + }).then(() => { + assert.equal(null, this.store().peekRecord('taco-salad', 'C'), + 'test setup: record should not be loaded already'); + + return this.db().put({ + _id: 'tacoSalad_2_C', data: { flavor: 'sofritas' } + }); + }).then(() => { + return promiseToRunLater(() => { + var alreadyLoadedSaladC = this.store().peekRecord('taco-salad', 'C'); + assert.ok(alreadyLoadedSaladC, + 'the corresponding instance should now be loaded'); + if (alreadyLoadedSaladC) { + assert.equal(alreadyLoadedSaladC.get('flavor'), 'sofritas', + 'the corresponding instance should now be loaded with the right data'); + } + }, 15); + }).finally(done); +}); + From 3cb223173ca53c3a864ae23db86c79b43c020fa9 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Sun, 31 Jan 2016 16:56:32 -0500 Subject: [PATCH 23/32] (#16) - Add override so serialiser saves hasMany As suggested by @fsmanuel: https://github.com/nolanlawson/ember-pouch/issues/16#issuecomment-122651303 --- addon/serializers/pouch.js | 18 ++++++++++- tests/dummy/app/models/food-item.js | 3 +- tests/dummy/app/serializers/application.js | 3 ++ .../integration/adapters/pouch-basics-test.js | 30 +++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 tests/dummy/app/serializers/application.js diff --git a/addon/serializers/pouch.js b/addon/serializers/pouch.js index 6a892ae..ec5fdad 100644 --- a/addon/serializers/pouch.js +++ b/addon/serializers/pouch.js @@ -1,3 +1,19 @@ import DS from 'ember-data'; -export default DS.RESTSerializer.extend({}); \ No newline at end of file +export default DS.RESTSerializer.extend({ + _shouldSerializeHasMany: function() { + return true; + }, + + // This fixes a failure in Ember Data 1.13 where an empty hasMany + // was saving as undefined rather than []. + serializeHasMany(snapshot, json, relationship) { + this._super.apply(this, arguments); + + const key = relationship.key; + + if (!json[key]) { + json[key] = []; + } + } +}); diff --git a/tests/dummy/app/models/food-item.js b/tests/dummy/app/models/food-item.js index 5a441cb..702c0d3 100644 --- a/tests/dummy/app/models/food-item.js +++ b/tests/dummy/app/models/food-item.js @@ -5,5 +5,6 @@ import DS from 'ember-data'; export default DS.Model.extend({ rev: DS.attr('string'), - name: DS.attr('string') + name: DS.attr('string'), + soup: DS.belongsTo('taco-soup', { async: true }) }); diff --git a/tests/dummy/app/serializers/application.js b/tests/dummy/app/serializers/application.js new file mode 100644 index 0000000..060aa04 --- /dev/null +++ b/tests/dummy/app/serializers/application.js @@ -0,0 +1,3 @@ +import { Serializer } from 'ember-pouch'; + +export default Serializer; diff --git a/tests/integration/adapters/pouch-basics-test.js b/tests/integration/adapters/pouch-basics-test.js index 8b8ee92..b92973b 100644 --- a/tests/integration/adapters/pouch-basics-test.js +++ b/tests/integration/adapters/pouch-basics-test.js @@ -114,6 +114,36 @@ test('create a new record', function (assert) { }); }); +test('creating an associated record stores a reference to it in the parent', function (assert) { + assert.expect(1); + + var done = assert.async(); + Ember.RSVP.Promise.resolve().then(() => { + return this.db().bulkDocs([ + { _id: 'tacoSoup_2_C', data: { flavor: 'al pastor', ingredients: [] } } + ]); + }).then(() => { + return this.store().findRecord('taco-soup', 'C'); + }).then(tacoSoup => { + var newIngredient = this.store().createRecord('food-item', { + name: 'pineapple', + soup: tacoSoup + }); + + return newIngredient.save().then(() => tacoSoup.save()); + }).then(() => { + this.store().unloadAll(); + + return this.store().findRecord('taco-soup', 'C'); + }).then(tacoSoup => { + return tacoSoup.get('ingredients'); + }).then(foundIngredients => { + assert.deepEqual(foundIngredients.mapBy('name'), ['pineapple'], + 'should have fully loaded the associated items'); + done(); + }); +}); + // This test fails due to a bug in ember data // (https://github.com/emberjs/data/issues/3736) // starting with ED v2.0.0-beta.1. It works again with ED v2.1.0. From eafca0e48c3c4594ac4cc7ceb7e98025b2c21192 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Mon, 1 Feb 2016 08:34:10 -0500 Subject: [PATCH 24/32] 3.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cf67bc3..3d0312d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ember-pouch", - "version": "3.1.0", + "version": "3.1.1", "description": "PouchDB adapter for Ember Data", "directories": { "doc": "doc", From 7a9bd62f0d0cddf1e40334031193f08ba878d6d7 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Mon, 1 Feb 2016 08:35:57 -0500 Subject: [PATCH 25/32] update readme [skip ci] --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e2bf7f1..f04fc1e 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,8 @@ And of course thanks to all our wonderful contributors, [here](https://github.co ## Changelog +* **3.1.1** + - Bugfix for hasMany relations by [@backspace](https://github.com/backspace) ([#111](https://github.com/nolanlawson/ember-pouch/pull/111)). * **3.1.0** - Database can now be dynamically switched on the adapter ([#89](https://github.com/nolanlawson/ember-pouch/pull/89)). Thanks to [@olivierchatry](https://github.com/olivierchatry) for this! - Various bugfixes by [@backspace](https://github.com/backspace), [@jkleinsc](https://github.com/jkleinsc), [@rsutphin](https://github.com/rsutphin), [@mattmarcum](https://github.com/mattmarcum), [@broerse](https://github.com/broerse), and [@olivierchatry](https://github.com/olivierchatry). See [the full commit log](https://github.com/nolanlawson/ember-pouch/compare/7c216311ffacd2f08b57df4fe34d49f4e7c373f1...v3.1.0) for details. Thank you! From 0c65283743b2a893d6518b70509e300fae0d8a81 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Mon, 1 Feb 2016 08:36:22 -0500 Subject: [PATCH 26/32] update readme [skip ci] --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index f04fc1e..66efc4a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -# Ember Pouch - -[![Build Status](https://travis-ci.org/nolanlawson/ember-pouch.svg)](https://travis-ci.org/nolanlawson/ember-pouch) +# Ember Pouch [![Build Status](https://travis-ci.org/nolanlawson/ember-pouch.svg)](https://travis-ci.org/nolanlawson/ember-pouch) [**Changelog**](#changelog) From 81a16716b26345c1be5d9245ba6859121df70d67 Mon Sep 17 00:00:00 2001 From: Niels Dequeker Date: Mon, 7 Mar 2016 22:15:53 +0100 Subject: [PATCH 27/32] Update README.md Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 66efc4a..cd3759c 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ var db = new PouchDB('local_pouch'); db.sync(remote, { live: true, // do a live, ongoing sync - retry: true // retry if the conection is lost + retry: true // retry if the connection is lost }); export default Adapter.extend({ From df8a619e51126ecdc4d65c9ca637422028ff5599 Mon Sep 17 00:00:00 2001 From: Dylan Weremeichik Date: Tue, 8 Mar 2016 14:06:26 -0500 Subject: [PATCH 28/32] Add relationship documentation --- README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/README.md b/README.md index cd3759c..2b93543 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,77 @@ ENV.emberPouch.localDb = 'test'; ENV.emberPouch.remoteDB = 'http://localhost:5984/my_couch'; ``` +## Relationships + +EmberPouch supports both `hasMany` and `belongsTo` relationships. + +### Saving + +When saving a `hasMany` - `belongsTo` relationship, both sides of the relationship (the child and the parent) must be saved. Note that the parent needs to have been saved at least once prior to adding children to it. + +```javascript +// app/routes/post/index.js +import Ember from 'ember'; + +export default Ember.Route.extend({ + model(params){ + //We are getting a post that already exists + return this.store.findRecord('post', params.post_id); + }, + + actions:{ + addComment(comment, author){ + //Create the comment + const comment = this.store.createRecord('comment',{ + comment: comment, + author: author + }); + //Get our post + const post = this.controller.get('model'); + //Add our comment to our existing post + post.get('comments').pushObject(comment); + //Save the child then the parent + comment.save().then(() => post.save()); + } + } +}); + +``` + +### Removing + +When removing a `hasMany` - `belongsTo` relationship, the children must be removed prior to the parent being removed. + +```javascript +// app/routes/posts/admin/index.js +import Ember from 'ember'; + +export default Ember.Route.extend({ + model(){ + //We are getting all posts for some sort of list + return this.store.findAll('post'); + }, + + actions:{ + deletePost(post){ + //collect the promises for deletion + let deletedComments = []; + //get and destroy the posts comments + post.get('comments').then((comments) => { + comments.map((comment) => { + deletedComments.push(comment.destroyRecord()); + }); + }); + //Wait for comments to be destroyed then destroy the post + Ember.RSVP.all(deletedComments).then(() => { + post.destroyRecord(); + }); + } + } +}); + +``` + ## Sample app Tom Dale's blog example using Ember CLI and EmberPouch: [broerse/ember-cli-blog](https://github.com/broerse/ember-cli-blog) From bc495e590f8e0174ba25ecc6b3aa70801196899b Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sat, 19 Mar 2016 18:17:53 -0400 Subject: [PATCH 29/32] 3.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d0312d..16282da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ember-pouch", - "version": "3.1.1", + "version": "3.2.0", "description": "PouchDB adapter for Ember Data", "directories": { "doc": "doc", From 9382e62181a474dd9896ced88e0a7d97d110b0d4 Mon Sep 17 00:00:00 2001 From: Manuel Wiedenmann Date: Tue, 21 Jun 2016 00:01:14 +0200 Subject: [PATCH 30/32] Fix(Addon): Call super in init --- index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.js b/index.js index cff2ff5..c56da1f 100644 --- a/index.js +++ b/index.js @@ -18,6 +18,8 @@ module.exports = { name: 'ember-pouch', init: function () { + this._super.init && this._super.init.apply(this, arguments); + if (!satisfactoryEmberDataVersion(this)) { var error = new Error("ember-pouch requires ember-data 1.13.x or 2.x"); error.suppressStacktrace = true; From 306fe68ca4a56bf18e84428ef92a54754a4fa029 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Mon, 20 Jun 2016 18:02:00 -0700 Subject: [PATCH 31/32] 3.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 16282da..dd42981 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ember-pouch", - "version": "3.2.0", + "version": "3.2.1", "description": "PouchDB adapter for Ember Data", "directories": { "doc": "doc", From dd94847aaf0421ab76c18b860a836bfb293d1f7c Mon Sep 17 00:00:00 2001 From: Bruno Pedroso Date: Tue, 2 Aug 2016 19:05:44 -0300 Subject: [PATCH 32/32] fixing readme reference to config key, concerning adapter blueprint. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c4ea88..95e31a8 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ Now you can store your localDb and remoteDb names in your ember-cli's config. J ```javascript ENV.emberPouch.localDb = 'test'; -ENV.emberPouch.remoteDB = 'http://localhost:5984/my_couch'; +ENV.emberPouch.remoteDb = 'http://localhost:5984/my_couch'; ``` ## Relationships