UNPKG

@ember-data/record-data

Version:

Provides the default resource cache (RecordData) implementation for ember-data

138 lines (114 loc) 4.82 kB
import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import { assertPolymorphicType } from '../../debug/assert-polymorphic-type'; import type { ReplaceRelatedRecordOperation } from '../-operations'; import { isBelongsTo, isNew, notifyChange } from '../-utils'; import type { Graph } from '../graph'; import { addToInverse, notifyInverseOfPotentialMaterialization, removeFromInverse } from './replace-related-records'; export default function replaceRelatedRecord(graph: Graph, op: ReplaceRelatedRecordOperation, isRemote = false) { const relationship = graph.get(op.record, op.field); assert( `You can only '${op.op}' on a belongsTo relationship. ${op.record.type}.${op.field} is a ${relationship.definition.kind}`, isBelongsTo(relationship) ); if (isRemote) { graph._addToTransaction(relationship); } const { definition, state } = relationship; const prop = isRemote ? 'remoteState' : 'localState'; const existingState: StableRecordIdentifier | null = relationship[prop]; /* case 1:1 ======== In a bi-directional graph with 1:1 edges, replacing a value results in up-to 4 discrete value transitions. If: A <-> B, C <-> D is the initial state, and: A <-> C, B, D is the final state then we would undergo the following 4 transitions. remove A from B add C to A remove C from D add A to C case 1:many =========== In a bi-directional graph with 1:Many edges, replacing a value results in up-to 3 discrete value transitions. If: A<->>B<<->D, C<<->D is the initial state (double arrows representing the many side) And: A<->>C<<->D, B<<->D is the final state Then we would undergo three transitions. remove A from B add C to A. add A to C case 1:? ======== In a uni-directional graph with 1:? edges (modeled in EmberData with `inverse:null`) with artificial (implicit) inverses, replacing a value results in up-to 3 discrete value transitions. This is because a 1:? relationship is effectively 1:many. If: A->B, C->B is the initial state And: A->C, C->B is the final state Then we would undergo three transitions. Remove A from B Add C to A Add A to C */ // nothing for us to do if (op.value === existingState) { // if we were empty before but now know we are empty this needs to be true state.hasReceivedData = true; // if this is a remote update we still sync if (isRemote) { const { localState } = relationship; // don't sync if localState is a new record and our remoteState is null if (localState && isNew(localState) && !existingState) { return; } if (existingState && localState === existingState) { notifyInverseOfPotentialMaterialization(graph, existingState, definition.inverseKey, op.record, isRemote); } else { relationship.localState = existingState; notifyChange(graph, relationship.identifier, relationship.definition.key); } } return; } // remove this value from the inverse if required if (existingState) { removeFromInverse(graph, existingState, definition.inverseKey, op.record, isRemote); } // update value to the new value relationship[prop] = op.value; state.hasReceivedData = true; state.isEmpty = op.value === null; state.isStale = false; state.hasFailedLoadAttempt = false; if (op.value) { if (definition.type !== op.value.type) { assert( `The '<${definition.inverseType}>.${op.field}' relationship expects only '${definition.type}' records since it is not polymorphic. Received a Record of type '${op.value.type}'`, definition.isPolymorphic ); // TODO this should now handle the deprecation warning if isPolymorphic is not set // but the record does turn out to be polymorphic // this should still assert if the user is relying on legacy inheritance/mixins to // provide polymorphic behavior and has not yet added the polymorphic flags if (DEBUG) { assertPolymorphicType(relationship.identifier, definition, op.value, graph.store); } graph.registerPolymorphicType(definition.type, op.value.type); } addToInverse(graph, op.value, definition.inverseKey, op.record, isRemote); } if (isRemote) { const { localState, remoteState } = relationship; if (localState && isNew(localState) && !remoteState) { return; } if (localState !== remoteState) { relationship.localState = remoteState; notifyChange(graph, relationship.identifier, relationship.definition.key); } } else { notifyChange(graph, relationship.identifier, relationship.definition.key); } }