From d7bfcc2c2a842ac50971396dac79c51d00518777 Mon Sep 17 00:00:00 2001 From: Jacob Quant Date: Mon, 11 Nov 2019 17:34:31 -0600 Subject: [PATCH] fix: support multiple createRecord calls (fixes #239) This commit changes how ember-pouch implements `Adapter.createRecord`, which is invoked after calling `.save()` on a new record, so that it does not create multiple records if `.save()` is called more than once before the first operation has finished. If `.save()` is only invoked once before the record has finished persisting to the DB (i.e. the promise that it returns has resolved) then the behavior is unchanged. However, subsequent calls will wait for the previously returned promise to resolve and then, if changes have been made to the record, it will delegate the task to `updateRecord`. To avoid a problem caused by ember-data changing the ID associated with the internalModel/record when the record has finished persisting, the `Adapter.generateIdForRecord` method has been implemented so that it is available immediately. Previously ember-pouch had still been generating this id during `createRecord`, but ember-data was not being made aware of this until its returned promise resolved. Finally, rather than rely on `adapter.db.rel.uuid()` to generate an RFC4122 v4 UUID (requiring initialization to have completed), this has been replaced by the equivalent `uuid` module from npm, and the ember-auto-import addon has been installed to make it easy to access this from within ember-pouch. --- addon/adapters/pouch.js | 55 ++++++++++++++++++++++++++++------------- ember-cli-build.js | 10 +++++++- package.json | 4 ++- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/addon/adapters/pouch.js b/addon/adapters/pouch.js index 9aff528..22ed537 100644 --- a/addon/adapters/pouch.js +++ b/addon/adapters/pouch.js @@ -1,6 +1,7 @@ import Ember from 'ember'; import DS from 'ember-data'; import { pluralize } from 'ember-inflector'; +import { v4 } from 'uuid'; //import BelongsToRelationship from 'ember-data/-private/system/relationships/state/belongs-to'; import { @@ -153,7 +154,7 @@ export default DS.RESTAdapter.extend({ willDestroy: function() { this._stopChangesListener(); }, - + _indexPromises: [], _init: function (store, type) { @@ -206,8 +207,9 @@ export default DS.RESTAdapter.extend({ relModel = (typeof rel.type === 'string' ? store.modelFor(rel.type) : rel.type); if (relModel) { let includeRel = true; - if (!('options' in rel)) rel.options = {}; - + if (!('options' in rel)) { + rel.options = {}; + } if (typeof(rel.options.async) === "undefined") { rel.options.async = config.emberPouch && !Ember.isEmpty(config.emberPouch.async) ? config.emberPouch.async : true;//default true from https://github.com/emberjs/data/pull/3366 } @@ -464,27 +466,46 @@ export default DS.RESTAdapter.extend({ }); }, + generateIdForRecord: function(/* store, type, inputProperties */) { + return v4(); + }, + createdRecords: {}, - createRecord: function(store, type, record) { - this._init(store, type); - var data = this._recordToData(store, type, record); - let rel = this.get('db').rel; - - let id = data.id; - if (!id) { - id = data.id = rel.uuid(); + createRecord: function(store, type, snapshot) { + const record = snapshot.record; + if (record._emberPouchSavePromise) { + const changes = record.changedAttributes(); + record._emberPouchSavePromise = record._emberPouchSavePromise.then(records => { + // If there have been changes since the document was created then we should update the record now + if (Object.keys(changes).length > 0) { + const rev = records[Object.keys(records)[0]][0].rev; + (snapshot.__attributes || snapshot._attributes).rev = rev; // FIXME: it should be possible to do this elsewhere + return this.updateRecord(store, type, snapshot); + } + return records; + }); + return record._emberPouchSavePromise; } + + this._init(store, type); + var data = this._recordToData(store, type, snapshot); + const rel = this.get('db').rel; + const id = data.id; this.createdRecords[id] = true; - - return rel.save(this.getRecordTypeName(type), data).catch((e) => { - delete this.createdRecords[id]; - throw e; + Object.defineProperty(record, '_emberPouchSavePromise', { + enumerable: false, + writable: true, + value: rel.save(this.getRecordTypeName(type), data).catch((e) => { + delete this.createdRecords[id]; + throw e; + }), }); + return record._emberPouchSavePromise; }, - updateRecord: function (store, type, record) { + updateRecord: function (store, type, snapshot) { this._init(store, type); - var data = this._recordToData(store, type, record); + var data = this._recordToData(store, type, snapshot); return this.get('db').rel.save(this.getRecordTypeName(type), data); }, diff --git a/ember-cli-build.js b/ember-cli-build.js index 156e23f..03dfc9d 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -5,7 +5,15 @@ const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); module.exports = function(defaults) { let app = new EmberAddon(defaults, { - // Add options here + autoImport: { + webpack: { + node: { + global: true + } + }, + // We could use ember-auto-import for these, but index.js is already handling them + exclude: ['pouchdb', 'pouchdb-find', 'relational-pouch'] + } }); /* diff --git a/package.json b/package.json index cf44b4f..0c7df9d 100644 --- a/package.json +++ b/package.json @@ -62,9 +62,11 @@ "dependencies": { "broccoli-file-creator": "^2.1.1", "broccoli-stew": "^2.1.0", + "ember-auto-import": "^1.5.3", + "ember-cli-babel": "^7.7.3", "pouchdb": "^7.1.1", "relational-pouch": "^3.1.0", - "ember-cli-babel": "^7.7.3" + "uuid": "^3.3.3" }, "ember-addon": { "configPath": "tests/dummy/config"