Skip to content

Commit

Permalink
Merge pull request #5762 from emberjs/release-port-5703
Browse files Browse the repository at this point in the history
[BUGFIX unloadRecord] bfs compatibility for custom RecordData (#5703)
  • Loading branch information
runspired authored Nov 29, 2018
2 parents ef6100c + e782ba8 commit 4a3c4d2
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 4 deletions.
14 changes: 10 additions & 4 deletions addon/-private/system/model/record-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -402,16 +402,22 @@ export default class RecordData {
while (queue.length > 0) {
let node = queue.shift();
array.push(node);

let related = node._directlyRelatedRecordDatas();

for (let i = 0; i < related.length; ++i) {
let recordData = related[i];
assert('Internal Error: seen a future bfs iteration', recordData._bfsId <= bfsId);
if (recordData._bfsId < bfsId) {
queue.push(recordData);
recordData._bfsId = bfsId;

if (recordData instanceof RecordData) {
assert('Internal Error: seen a future bfs iteration', recordData._bfsId <= bfsId);
if (recordData._bfsId < bfsId) {
queue.push(recordData);
recordData._bfsId = bfsId;
}
}
}
}

return array;
}

Expand Down
233 changes: 233 additions & 0 deletions tests/integration/records/record-data-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import Model from 'ember-data/model';
import { run } from '@ember/runloop';
import { attr, belongsTo, hasMany } from '@ember-decorators/data';
import { assign } from '@ember/polyfills';
import { RecordData } from 'ember-data/-private';

class Person extends Model {
@hasMany('pet', { inverse: null, async: false })
pets;
@attr
name;
}

class Pet extends Model {
@belongsTo('person', { inverse: null, async: false })
owner;
@attr
name;
}

function recordDataForRecord(record) {
return record._internalModel._recordData;
}

module('RecordData Compatibility', function(hooks) {
let store;
setupTest(hooks);

hooks.beforeEach(function() {
let { owner } = this;
owner.register('model:person', Person);
owner.register('model:pet', Pet);
store = owner.lookup('service:store');
});

class CustomRecordData {
constructor(modelName, id, clientId, storeWrapper) {
this.type = modelName;
this.id = id || null;
this.clientId = clientId;
this.storeWrapper = storeWrapper;
this.attributes = null;
this.relationships = null;
}

pushData(jsonApiResource, shouldCalculateChanges) {
let oldAttrs = this.attributes;
let changedKeys;

this.attributes = jsonApiResource.attributes || null;

if (shouldCalculateChanges) {
changedKeys = Object.keys(assign({}, oldAttrs, this.attributes));
}

return changedKeys || [];
}

getAttr(member) {
return this.attributes !== null ? this.attributes[member] : undefined;
}

hasAttr(key) {
return key in this.attributes;
}

// TODO missing from RFC but required to implement
_initRecordCreateOptions(options) {
return options !== undefined ? options : {};
}
// TODO missing from RFC but required to implement
getResourceIdentifier() {
return {
id: this.id,
type: this.type,
clientId: this.clientId,
};
}
// TODO missing from RFC but required to implement
unloadRecord() {
this.attributes = null;
this.relationships = null;
}
// TODO missing from RFC but required to implement
isNew() {
return this.id === null;
}

adapterDidCommit() {}
didCreateLocally() {}
adapterWillCommit() {}
saveWasRejected() {}
adapterDidDelete() {}
recordUnloaded() {}
rollbackAttributes() {}
rollbackAttribute() {}
changedAttributes() {}
hasChangedAttributes() {}
setAttr() {}
setHasMany() {}
getHasMany() {}
addToHasMany() {}
removeFromHasMany() {}
setBelongsTo() {}
getBelongsTo() {}
}

test(`store.unloadRecord on a record with default RecordData with relationship to a record with custom RecordData does not error`, async function(assert) {
const originalCreateRecordDataFor = store.createRecordDataFor;
store.createRecordDataFor = function provideCustomRecordData(
modelName,
id,
clientId,
storeWrapper
) {
if (modelName === 'pet') {
return new CustomRecordData(modelName, id, clientId, storeWrapper);
} else {
return originalCreateRecordDataFor.call(this, modelName, id, clientId, storeWrapper);
}
};

let chris = store.push({
data: {
type: 'person',
id: '1',
attributes: { name: 'Chris' },
relationships: {
pets: {
data: [{ type: 'pet', id: '1' }, { type: 'pet', id: '2' }],
},
},
},
included: [
{
type: 'pet',
id: '1',
attributes: { name: 'Shen' },
relationships: {
owner: { data: { type: 'person', id: '1' } },
},
},
{
type: 'pet',
id: '2',
attributes: { name: 'Prince' },
relationships: {
owner: { data: { type: 'person', id: '1' } },
},
},
],
});
let pets = chris.get('pets');
let shen = pets.objectAt(0);

assert.equal(shen.get('name'), 'Shen', 'We found Shen');
assert.ok(
recordDataForRecord(chris) instanceof RecordData,
'We used the default record-data for person'
);
assert.ok(
recordDataForRecord(shen) instanceof CustomRecordData,
'We used the custom record-data for pets'
);

try {
run(() => chris.unloadRecord());
assert.ok(true, 'expected `unloadRecord()` not to throw');
} catch (e) {
assert.ok(false, 'expected `unloadRecord()` not to throw');
}
});

test(`store.unloadRecord on a record with custom RecordData with relationship to a record with default RecordData does not error`, async function(assert) {
const originalCreateRecordDataFor = store.createModelDataFor;
store.createModelDataFor = function provideCustomRecordData(
modelName,
id,
clientId,
storeWrapper
) {
if (modelName === 'pet') {
return new CustomRecordData(modelName, id, clientId, storeWrapper);
} else {
return originalCreateRecordDataFor.call(this, modelName, id, clientId, storeWrapper);
}
};

let chris = store.push({
data: {
type: 'person',
id: '1',
attributes: { name: 'Chris' },
relationships: {
pets: {
data: [{ type: 'pet', id: '1' }, { type: 'pet', id: '2' }],
},
},
},
included: [
{
type: 'pet',
id: '1',
attributes: { name: 'Shen' },
relationships: {
owner: { data: { type: 'person', id: '1' } },
},
},
{
type: 'pet',
id: '2',
attributes: { name: 'Prince' },
relationships: {
owner: { data: { type: 'person', id: '1' } },
},
},
],
});
let pets = chris.get('pets');
let shen = pets.objectAt(0);

assert.equal(shen.get('name'), 'Shen', 'We found Shen');

try {
run(() => shen.unloadRecord());
assert.ok(true, 'expected `unloadRecord()` not to throw');
} catch (e) {
assert.ok(false, 'expected `unloadRecord()` not to throw');
}
});
});

0 comments on commit 4a3c4d2

Please sign in to comment.