Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support calling save multiple times #249

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 53 additions & 17 deletions addon/adapters/pouch.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -9,6 +10,20 @@ import {
configFlagDisabled
} from '../utils';

function getRevFromSaveResult(records) {
let rev = null;
try {
rev = records[Object.keys(records)[0]][0].rev;
if (!rev || Object.keys(records).length > 1) {
// eslint-disable-next-line no-console
console.warn(`getRevFromSaveResult going to return ${rev}, but that may not be correct`);
}
} catch(e) {
throw Error(`Could not determine rev`);
}
return rev;
}

const {
getOwner,
run: {
Expand Down Expand Up @@ -153,7 +168,7 @@ export default DS.RESTAdapter.extend({
willDestroy: function() {
this._stopChangesListener();
},

_indexPromises: [],

_init: function (store, type) {
Expand Down Expand Up @@ -206,8 +221,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
}
Expand Down Expand Up @@ -464,27 +480,47 @@ 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) {
// Include latest rev to indicate that we're aware that data has changed since original request
// (otherwise a document update conflict error would be thrown by the DB)
snapshot._attributes.rev = getRevFromSaveResult(records);
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);
},

Expand Down
10 changes: 9 additions & 1 deletion ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']
}
});

/*
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"CouchDB"
],
"engines": {
"node": "^4.5 || 6.* || >= 7.*"
"node": ">= 6.*"
},
"author": "Nolan Lawson",
"license": "Apache-2.0",
Expand Down Expand Up @@ -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"
Expand Down
45 changes: 33 additions & 12 deletions tests/integration/adapters/pouch-basics-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,27 @@ test('create a new record', function (assert) {
}).finally(done);
});

test('update a newly created record before it has finished saving', function (assert) {
assert.expect(2);

var done = assert.async();
Ember.RSVP.Promise.resolve().then(() => {
var newSoup = this.store().createRecord('taco-soup', { id: 'E', flavor: 'oops-wrong-flavor' });
newSoup.save();
newSoup.set('flavor', 'balsamic');
return newSoup.save();
}).then(() => {
return this.db().get('tacoSoup_2_E');
}).then((newDoc) => {
assert.equal(newDoc.data.flavor, 'balsamic', 'should have saved the attribute');

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');

}).finally(done);
});

test('creating an associated record stores a reference to it in the parent', function (assert) {
assert.expect(1);

Expand Down Expand Up @@ -367,14 +388,14 @@ test('eventually consistency - success', function (assert) {
let result = [
foodItem.get('soup')
.then(soup => assert.equal(soup.id, 'C')),

promiseToRunLater(0)
.then(() => {
return this.db().bulkDocs([
{_id: 'tacoSoup_2_C', data: { flavor: 'test' } }
]);}),
];

return Ember.RSVP.all(result);
})
.finally(done);
Expand All @@ -395,13 +416,13 @@ test('eventually consistency - deleted', function (assert) {
foodItem.get('soup')
.then((soup) => assert.ok(soup === null, 'isDeleted'))
.catch(() => assert.ok(true, 'isDeleted')),

promiseToRunLater(100)
.then(() => this.db().bulkDocs([
{_id: 'tacoSoup_2_C', _deleted: true }
])),
];

return Ember.RSVP.all(result);
})
.finally(done);
Expand Down Expand Up @@ -450,9 +471,9 @@ test('remote delete removes belongsTo relationship', function (assert) {
.then((found) => {
let id = "tacoSoup_2_" + found.id;
let promise = this.adapter().waitForChangeWithID(id);

this.db().remove(id, found.get('rev'));

return promise;
}).then(() => {
return this.store().findRecord('food-item', 'Z');//Z should be updated now
Expand All @@ -472,7 +493,7 @@ test('remote delete removes belongsTo relationship', function (assert) {
test('remote delete removes hasMany relationship', function (assert) {
assert.timeout(5000);
assert.expect(3);

let liveIngredients = null;

var done = assert.async();
Expand All @@ -483,15 +504,15 @@ test('remote delete removes hasMany relationship', function (assert) {
.then(found => found.get('ingredients'))//prime hasMany
.then((ingredients) => {
liveIngredients = ingredients;//save for later

assert.equal(ingredients.length, 2, "should be 2 food items initially");

let itemToDelete = ingredients.toArray()[0];
let id = "foodItem_2_" + itemToDelete.id;
let promise = this.adapter().waitForChangeWithID(id);

this.db().remove(id, itemToDelete.get('rev'));

return promise;
}).then(() => {
return this.store().findRecord('taco-soup', 'C');//get updated soup.ingredients
Expand All @@ -514,7 +535,7 @@ module('not eventually consistent', { beforeEach: function() {
assert.expect(2);
assert.ok(config.emberPouch.eventuallyConsistent == false, 'eventuallyConsistent is false');
let done = assert.async();

Ember.RSVP.Promise.resolve().then(() => this.store().findRecord('food-item', 'non-existent')
.then(() => assert.ok(false))
.catch(() => {
Expand Down