question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Duplication in this.store when creating a record on a route using RealtimeRouteMixin

See original GitHub issue

Version info

index.js:185 DEBUG: Ember      : 3.10.2
index.js:185 DEBUG: Ember Data : 3.10.0
index.js:185 DEBUG: jQuery     : 3.4.1
index.js:185 DEBUG: EmberFire  : 3.0.0-rc.3
index.js:185 DEBUG: Firebase   : 6.2.2
index.js:185 DEBUG: -------------------------------

When creating and saving a Firestore Record on a route using RealtimeRouteMixin the following error occurs Uncaught (in promise) Error: Assertion Failed: 'nameOfModel' was saved to the server, but the response returned the new id 'HYplP17VdPrSjDwZd8Ig', which has already been used with another record.'

A single copy of the record is correctly saved to Firestore, but the local store contains two copies of it; One copy has the correct Firestore value for its id, the other copy has null for its id and the following unsaved indicators {isDirty:true, isSaving:true hasDirtyAttributes:true dirtyTYpe: "created"}

The error and duplication in the store do not occur if the RealtimeRouteMixin is removed form the route.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

4reactions
iofusioncommented, Jul 7, 2019

After some more digging around…

The realtime duplication issue that occurs when store.createRecord (+save) and realtime-listeners’ store.push manage the same record simultaneously, is due to the way ember-data manages identity of records without an id. When store.createRecord is not supplied an id, it begins by creating a clientId to manage identity. When a clientId is present, the callStack for both store.createRecord and store.push change; that change creates the identity problem that results in the duplication issue.

ember-data is preparing to update its internal Identifiers and it probably makes sense to participate in that rfc’s maturity. Without updates to ember-data the simplest way to avoid the realtime duplication issue is to avoid ember-data’s use of clientId by supplying an id for createRecord, or by disabling the realtime subscription during createRecord.

Disabling the routes realtime subscription during createRecord

In the route route.setupController is used to place an instance of the route on the controller. The realtime-listener uses that instance to identify the subscription.

import Route from '@ember/routing/route';
import RealtimeRouteMixin from 'emberfire/mixins/realtime-route';

export default Route.extend(RealtimeRouteMixin, {

  setupController(controller, model){
    this._super(controller, model);
    this.controllerFor('application').set('routeInstance', this);
  },

  model: function() {
    return this.store.query(...
  }
});

In the controller store.createRecord is insulated from the realtime-listener with an unsubscribe/subscribe pair.

import Controller from '@ember/controller';
import { subscribe, unsubscribe } from 'emberfire/services/realtime-listener';

export default Controller.extend({

  routeInstance,

  actions: {
    create(){
      unsubscribe(this.routeInstance, this.model);
      let newRecord = this.store.createRecord('modelName', {
          ...
      });
      newRecord.save().then(() => {
        subscribe(this.routeInstance, this.model);
        }, function(failure) {
          debug(failure);
        })
    },

Suppling a local uuid for createRecord

store.createRecord gives the adpater a chance to provide a local id for a new record with generateIdForRecord

import FirestoreAdapter from 'emberfire/adapters/firestore';
import { v4 } from 'ember-uuid';

export default FirestoreAdapter.extend({

  generateIdForRecord() {
    return v4();
  },

});

I tried using that function to await an id from Firebase but store.coerceId ended up stringifying the promise. Intefering with ember-data’s backburner queues seems dangerous… The docs for generateIdForRecord recommend using didCommit for a server generated id but is too late in the cycle to avoid the identity failure.

Supplying a firebase id for createRecord

Since adapter.generateIdForRecord is too late in the stack to request a firebase id, a service is used to generate the key and await that key in createRecord.

import Service, { inject as service } from '@ember/service';
import { pluralize } from 'ember-inflector';

export default Service.extend({
  firebaseApp: service(),

  async generateIdForModelName(modelName) {
    let db = await this.firebaseApp.firestore();
    let key = await db.collection(pluralize(modelName)).doc().id;
    return key;
  },

});

In the controller…

import Controller from '@ember/controller';
import { debug } from '@ember/debug';
import { inject as service } from '@ember/service';

export default Controller.extend({
  identity: service(),

  actions: {
    async create(){
      let newShift = this.store.createRecord('modelName', {
        id: await this.identity.generateIdForModelName('modelName'),
        ...
      });
      newShift.save().then(() => {
        }, function(failure) {
          debug(failure);
        })
    }
});

I would not count this as resolved, but perhaps these solutions will be helpful to others.

0reactions
jamesdanielscommented, Oct 8, 2019

rc.4 shipped.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Duplication in this.store when creating a record on a route ...
When creating and saving a Firestore Record on a route using RealtimeRouteMixin the following error occurs Uncaught (in promise) Error: ...
Read more >
Duplicate null-id records in ember-data - Stack Overflow
I'm using ember 1.0 and ember-data 1.0.0 beta 1. I have the following routes and controller to create and save simple notes ('AuthenticatedRoute'...
Read more >
Creating, Updating and Deleting - Ember Data
Creating Records You can create records by calling the createRecord() method ... The store object is available in controllers and routes using this.store...
Read more >
business rule creating duplicate record in original table
Solved: Ok so I have a business rule that does what it should, it creates a new song record in a 'set list'...
Read more >
NetSuite Applications Suite - Duplicate Detection Preferences
Set Up Duplicate Detection page. For each type of record, in the Fields to Match On box, select the fields from respective record...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found