-
Notifications
You must be signed in to change notification settings - Fork 76
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
Cannot call .save()
multiple times
#239
Comments
Looking a little deeper into this, it seems to be because calling
Without much knowledge of PouchDB/CouchDB, it seems to me that there is some kind of coupling between revisions or IDs in PouchDB/CouchDB and those in ember-data. If that is the case, isn't that bad? For one, it means that doing this will be problematic: const createSpecialPersonRecord = () => store.createRecord('person', { id: 'special_id' });
let record = createSpecialPersonRecord();
await record.save();
await record.destroyRecord();
// ...
record = createSpecialPersonRecord(); // will throw error about ID already having been used
// ... See also https://github.com/jacobq/ember-pouch-back-to-back-save-2x |
I started trying One thing that stood out to me was that
This helped me understand why calling const rec = store.createRecord('post', { title: 'foo' });
// rec.isNew = true, rec.isSaving=false, rec.hasDirtyAttributes=true
rec.save(); // begins creating a new persistent entry in the DB
// rec.isNew = true, rec.isSaving=true, rec.hasDirtyAttributes=true
rec.save(); // looks like it should "update" the same record but since
// it hasn't persisted yet and the current implementation of
// `ember-pouch` doesn't check `rec.isSaving`, this actually
// attempts to create another record in the DB. After the second item is created in the DB, an error gets thrown by ember data because when the first
|
One way to work around this "back-to-back" problem is to modify our implementation of createRecord: function(store, type, snapshot) {
const record = snapshot.record;
if (record._emberPouchSavePromise) {
// `createRecord` has been called with this record before,
// so we don't want to actually create another entry in the DB this time
return record._emberPouchSavePromise;
}
this._init(store, type);
var data = this._recordToData(store, type, snapshot);
let rel = this.get('db').rel;
let id = data.id;
if (!id) {
id = data.id = rel.uuid();
}
this.createdRecords[id] = true;
// Save a reference to this promise in case duplicate calls are made on this record
record._emberPouchSavePromise = rel.save(this.getRecordTypeName(type), data).catch((e) => {
delete this.createdRecords[id];
throw e;
});
return record._emberPouchSavePromise;
}, |
I'm not sure if just dropping the second save would work. Because the record could have changed between the two calls, so we still should persist any changes. I'm not really sure but I think this should be fixed in ember-data itself. That layer is responsible for all the state tracking and should know whether |
I think you're right, but we need to make sure that they get persisted to the correct (same) record (rather than a copy).
I was asking about this in the ember-data Discord channel and got this answer: |
Wow, I'm not really sure how I feel about that. But if that is really their final answer we indeed have to do something ourselves. |
Yeah, I suspect that this is because there isn't a "one size fits all" solution that would make sense for every conceivable adapter (though I would think that this could be done by overriding a default instead...). Plus, applications can avoid this situation manually by checking flags on |
You could always choose to queue and chain saves, or discard the save if it contained the same data as an already pending save request.
I don't recommend our state tracking, the proliferation of addons for additional state tracking makes it fairly clear that EmberData's is not good enough. It'll get better with time, we have some solid plans for it, but it's kinda far down the roadmap.
This is unknowable, and as such will always be an app code decision. Only the developer knows their API contract and the adapter is where the developer aligns the API contract with the requests EmberData is issuing. That said there are two potential solutions that allow you to continue issuing as many requests as you like.
|
P.S. once you have resolved this if you'd like to PR ember-pouch as an external-partner test to EmberData we're always happy to have more test coverage, especially around ensuring common patterns for extending the library continue to work as expected :) |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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, as indicated by Snapshot#changedAttributes, 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 the ID 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. Also, 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. Finally, the `engines` section of package.json has been updated to align with ember-auto-import's minimum version of 6.x BREAKING CHANGE: drop node 4.x support (and 6.x/7.x not tested by CI)
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, as indicated by Snapshot#changedAttributes, 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 the ID 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. Also, 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. Finally, the `engines` section of package.json has been updated to align with ember-auto-import's minimum version of 6.x BREAKING CHANGE: drop node 4.x support (and 6.x/7.x not tested by CI)
It seems that calling
.save()
on a record multiple times without waiting for the previous promise(s) to resolve leads to an error being thrown. AFAIK, there is nothing in the ember-data contract that prohibits this (though it seems rather pointless in this example).Reproduction repo here: jacobq/ember-pouch-back-to-back-save
Perhaps this is another manifestation of whatever is causing #237
The text was updated successfully, but these errors were encountered: