Skip to content

Commit

Permalink
feat: Native Model
Browse files Browse the repository at this point in the history
Co-Authored-By: Scott Newcomer <[email protected]>

fix docs tests

getWithDefault

bad rebase

fix doc tests

add computeds

dont invalidate prop within isError

dont install tracking within our meta object

more prop cleanup

more get cleanup

cycle less on id

cycle less on id cleanup

update test

Revert "cycle less on id cleanup"

This reverts commit 173c42b.

Revert "cycle less on id"

This reverts commit 38f76aa.

less cycling but without whatever weird build issue

remove mixin if deprecation is resolved and strip deprecations for perf tests

new tests for refactoring

refactor to simpler definitions
  • Loading branch information
runspired committed Apr 13, 2021
1 parent 7c13f66 commit dadb84e
Show file tree
Hide file tree
Showing 15 changed files with 837 additions and 573 deletions.
2 changes: 1 addition & 1 deletion packages/-ember-data/node-tests/docs/test-coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ QUnit.module('Docs coverage', function(hooks) {

QUnit.module('modules', function() {
test('We have all expected modules', function(assert) {
assert.deepEqual(Object.keys(docs.modules), expected.modules, 'We have all modules');
assert.deepEqual(Object.keys(docs.modules).sort(), expected.modules, 'We have all modules');
});
});

Expand Down
7 changes: 3 additions & 4 deletions packages/-ember-data/node-tests/fixtures/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ module.exports = {
'@ember-data/adapter',
'@ember-data/canary-features',
'@ember-data/debug',
'@ember-data/model',
'@ember-data/store',
'@ember-data/deprecations',
'@ember-data/model',
'@ember-data/record-data',
'@ember-data/serializer'
'@ember-data/serializer',
'@ember-data/store',
],
classitems: [
'(private) @ember-data/adapter BuildURLMixin#_buildURL',
Expand All @@ -32,7 +32,6 @@ module.exports = {
'(private) @ember-data/model Model#_notifyProperties',
'(private) @ember-data/model Model#create',
'(private) @ember-data/model Model#currentState',
'(private) @ember-data/model Model#recordData',
'(private) @ember-data/model Model#send',
'(private) @ember-data/model Model#transitionTo',
'(private) @ember-data/model Model#trigger',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ module('integration/adapter/store-adapter - DS.Store and DS.Adapter integration
});
});

test('if a created record is marked as invalid by the server, it enters an error state', function(assert) {
test('if a created record is marked as invalid by the server, it enters an error state', async function(assert) {
let store = this.owner.lookup('service:store');
let adapter = store.adapterFor('application');
let Person = store.modelFor('person');
Expand Down Expand Up @@ -560,32 +560,30 @@ module('integration/adapter/store-adapter - DS.Store and DS.Adapter integration
let yehuda = store.createRecord('person', { id: 1, name: 'Yehuda Katz' });
// Wrap this in an Ember.run so that all chained async behavior is set up
// before flushing any scheduled behavior.
return run(function() {
return yehuda
.save()
.catch(error => {
assert.equal(get(yehuda, 'isValid'), false, 'the record is invalid');
assert.ok(get(yehuda, 'errors.name'), 'The errors.name property exists');

set(yehuda, 'updatedAt', true);
assert.equal(get(yehuda, 'isValid'), false, 'the record is still invalid');
try {
await yehuda.save();
} catch (e) {
assert.equal(get(yehuda, 'isValid'), false, 'the record is invalid');
assert.ok(get(yehuda, 'errors.name'), 'The errors.name property exists');

set(yehuda, 'name', 'Brohuda Brokatz');
set(yehuda, 'updatedAt', true);
assert.equal(get(yehuda, 'isValid'), false, 'the record is still invalid');

assert.equal(get(yehuda, 'isValid'), true, 'the record is no longer invalid after changing');
assert.equal(get(yehuda, 'hasDirtyAttributes'), true, 'the record has outstanding changes');
set(yehuda, 'name', 'Brohuda Brokatz');

assert.equal(get(yehuda, 'isNew'), true, 'precond - record is still new');
assert.equal(get(yehuda, 'isValid'), true, 'the record is no longer invalid after changing');
assert.equal(get(yehuda, 'hasDirtyAttributes'), true, 'the record has outstanding changes');

return yehuda.save();
})
.then(person => {
assert.strictEqual(person, yehuda, 'The promise resolves with the saved record');
assert.equal(get(yehuda, 'isNew'), true, 'precond - record is still new');

assert.equal(get(yehuda, 'isValid'), true, 'record remains valid after committing');
assert.equal(get(yehuda, 'isNew'), false, 'record is no longer new');
});
});
let person = await yehuda.save();

assert.strictEqual(person, yehuda, 'The promise resolves with the saved record');

assert.equal(get(yehuda, 'isValid'), true, 'record remains valid after committing');
assert.equal(get(yehuda, 'isNew'), false, 'record is no longer new');
}
});

test('allows errors on arbitrary properties on create', function(assert) {
Expand Down
26 changes: 18 additions & 8 deletions packages/-ember-data/tests/unit/model-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,21 +373,31 @@ module('unit/model - Model', function(hooks) {

test('ID mutation (complicated)', async function(assert) {
let idChange = 0;
let compChange = 0;
const OddPerson = Model.extend({
name: DSattr('string'),
idComputed: computed('id', function() {}),
idDidChange: observer('id', () => idChange++),
idComputed: computed('id', function() {
// we intentionally don't access the id here
return 'not-the-id:' + compChange++;
}),
idDidChange: observer('id', function() {
idChange++;
}),
});
this.owner.register('model:odd-person', OddPerson);

let person = store.createRecord('odd-person');
person.get('idComputed');
assert.equal(idChange, 0);
assert.strictEqual(person.get('idComputed'), 'not-the-id:0');
assert.equal(idChange, 0, 'we have had no changes initially');

assert.equal(person.get('id'), null, 'initial created model id should be null');
assert.equal(idChange, 0);
let personId = person.get('id');
assert.strictEqual(personId, null, 'initial created model id should be null');
assert.equal(idChange, 0, 'we should still have no id changes');

// simulate an update from the store or RecordData that doesn't
// go through the internalModelFactory
person._internalModel.setId('john');
assert.equal(idChange, 1);
assert.equal(idChange, 1, 'we should have one change after updating id');
let recordData = recordDataFor(person);
assert.equal(
recordData.getResourceIdentifier().id,
Expand Down Expand Up @@ -729,7 +739,7 @@ module('unit/model - Model', function(hooks) {

assert.expectAssertion(() => {
record.set('isLoaded', true);
}, /Cannot set read-only property "isLoaded"/);
}, /Cannot set property isLoaded of \[object Object\] which has only a getter/);
});

class NativePostWithInternalModel extends Model {
Expand Down
184 changes: 184 additions & 0 deletions packages/-ember-data/tests/unit/model/attr-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { module, skip, test } from 'qunit';

import { setupTest } from 'ember-qunit';

import Model, { attr } from '@ember-data/model';

module('unit/model/attr | attr syntax', function(hooks) {
setupTest(hooks);

let store;
let owner;
hooks.beforeEach(function() {
owner = this.owner;
store = owner.lookup('service:store');
});

test('attr can be used with classic syntax', async function(assert) {
const User = Model.extend({
name: attr(),
nameWithTransform: attr('string'),
nameWithOptions: attr({}),
nameWithTransformAndOptions: attr('string', {}),
});

owner.register('model:user', User);

let UserModel = store.modelFor('user');
let attrs = UserModel.attributes;
assert.true(attrs.has('name'), 'We have the attr: name');
assert.true(attrs.has('nameWithTransform'), 'We have the attr: nameWithTransform');
assert.true(attrs.has('nameWithOptions'), 'We have the attr: nameWithOptions');
assert.true(attrs.has('nameWithTransformAndOptions'), 'We have the attr: nameWithTransformAndOptions');

let userRecord = store.push({
data: {
type: 'user',
id: '1',
attributes: {
name: 'Chris',
nameWithTransform: '@runspired',
nameWithOptions: 'Contributor',
nameWithTransformAndOptions: '@runspired contribution',
},
},
});

assert.strictEqual(userRecord.name, 'Chris', 'attr is correctly set: name');
assert.strictEqual(userRecord.nameWithTransform, '@runspired', 'attr is correctly set: nameWithTransform');
assert.strictEqual(userRecord.nameWithOptions, 'Contributor', 'attr is correctly set: nameWithOptions');
assert.strictEqual(
userRecord.nameWithTransformAndOptions,
'@runspired contribution',
'attr is correctly set: nameWithTransformAndOptions'
);
});

test('attr can be used with native syntax decorator style', async function(assert) {
class User extends Model {
@attr() name;
@attr('string') nameWithTransform;
@attr({}) nameWithOptions;
@attr('string', {}) nameWithTransformAndOptions;
}

owner.register('model:user', User);

let UserModel = store.modelFor('user');
let attrs = UserModel.attributes;
assert.true(attrs.has('name'), 'We have the attr: name');
assert.true(attrs.has('nameWithTransform'), 'We have the attr: nameWithTransform');
assert.true(attrs.has('nameWithOptions'), 'We have the attr: nameWithOptions');
assert.true(attrs.has('nameWithTransformAndOptions'), 'We have the attr: nameWithTransformAndOptions');

let userRecord = store.push({
data: {
type: 'user',
id: '1',
attributes: {
name: 'Chris',
nameWithTransform: '@runspired',
nameWithOptions: 'Contributor',
nameWithTransformAndOptions: '@runspired contribution',
},
},
});

assert.strictEqual(userRecord.name, 'Chris', 'attr is correctly set: name');
assert.strictEqual(userRecord.nameWithTransform, '@runspired', 'attr is correctly set: nameWithTransform');
assert.strictEqual(userRecord.nameWithOptions, 'Contributor', 'attr is correctly set: nameWithOptions');
assert.strictEqual(
userRecord.nameWithTransformAndOptions,
'@runspired contribution',
'attr is correctly set: nameWithTransformAndOptions'
);
});

skip('attr cannot be used with native syntax prop style', async function(assert) {
class User extends Model {
name = attr();
nameWithTransform = attr('string');
nameWithOptions = attr({});
nameWithTransformAndOptions = attr('string', {});
}

owner.register('model:user', User);

let UserModel = store.modelFor('user');
let attrs = UserModel.attributes;
assert.true(attrs.has('name'), 'We have the attr: name');
assert.true(attrs.has('nameWithTransform'), 'We have the attr: nameWithTransform');
assert.true(attrs.has('nameWithOptions'), 'We have the attr: nameWithOptions');
assert.true(attrs.has('nameWithTransformAndOptions'), 'We have the attr: nameWithTransformAndOptions');

let userRecord = store.push({
data: {
type: 'user',
id: '1',
attributes: {
name: 'Chris',
nameWithTransform: '@runspired',
nameWithOptions: 'Contributor',
nameWithTransformAndOptions: '@runspired contribution',
},
},
});

assert.strictEqual(userRecord.name, 'Chris', 'attr is correctly set: name');
assert.strictEqual(userRecord.nameWithTransform, '@runspired', 'attr is correctly set: nameWithTransform');
assert.strictEqual(userRecord.nameWithOptions, 'Contributor', 'attr is correctly set: nameWithOptions');
assert.strictEqual(
userRecord.nameWithTransformAndOptions,
'@runspired contribution',
'attr is correctly set: nameWithTransformAndOptions'
);
});

skip('attr can be used with native syntax decorator style without parens', async function(assert) {
class User extends Model {
@attr name;
}

owner.register('model:user', User);

let UserModel = store.modelFor('user');
let attrs = UserModel.attributes;
assert.true(attrs.has('name'), 'We have the attr: name');

let userRecord = store.push({
data: {
type: 'user',
id: '1',
attributes: {
name: 'Chris',
},
},
});

assert.strictEqual(userRecord.name, 'Chris', 'attr is correctly set: name');
});

skip('attr can not be used classic syntax without parens', async function(assert) {
const User = Model.extend({
name: attr,
});

owner.register('model:user', User);

let UserModel = store.modelFor('user');
let attrs = UserModel.attributes;
assert.true(attrs.has('name'), 'We have the attr: name');

let userRecord = store.push({
data: {
type: 'user',
id: '1',
attributes: {
name: 'Chris',
},
},
});

assert.strictEqual(userRecord.name, 'Chris', 'attr is correctly set: name');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,17 @@ module('unit/model/rollbackAttributes - model.rollbackAttributes()', function(ho
return person;
});

assert.equal(person.get('firstName'), 'Thomas');
assert.equal(person.get('firstName'), 'Thomas', 'PreCond: we mutated firstName');
if (DEPRECATE_RECORD_LIFECYCLE_EVENT_METHODS) {
assert.equal(person.get('rolledBackCount'), 0);
assert.equal(person.get('rolledBackCount'), 0, 'PreCond: we have not yet rolled back');
}

run(() => person.rollbackAttributes());

assert.equal(person.get('firstName'), 'Tom');
assert.equal(person.get('firstName'), 'Tom', 'We rolled back firstName');
assert.equal(person.get('hasDirtyAttributes'), false);
if (DEPRECATE_RECORD_LIFECYCLE_EVENT_METHODS) {
assert.equal(person.get('rolledBackCount'), 1);
assert.equal(person.get('rolledBackCount'), 1, 'We rolled back once');
}
});

Expand Down
4 changes: 2 additions & 2 deletions packages/model/addon/-private/attr.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function attr(type, options) {
return computed({
get(key) {
if (DEBUG) {
if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) {
if (['_internalModel', 'currentState'].indexOf(key) !== -1) {
throw new Error(
`'${key}' is a reserved property name on instances of classes extending Model. Please choose a different property name for your attr on ${this.constructor.toString()}`
);
Expand All @@ -149,7 +149,7 @@ function attr(type, options) {
},
set(key, value) {
if (DEBUG) {
if (['_internalModel', 'recordData', 'currentState'].indexOf(key) !== -1) {
if (['_internalModel', 'currentState'].indexOf(key) !== -1) {
throw new Error(
`'${key}' is a reserved property name on instances of classes extending Model. Please choose a different property name for your attr on ${this.constructor.toString()}`
);
Expand Down
Loading

0 comments on commit dadb84e

Please sign in to comment.