From 72239523cc1e5de0fcd3ed607f549c7bcfa13888 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Tue, 20 Aug 2019 16:48:59 -0700 Subject: [PATCH] [FEAT] Adds support for parenless attr, belongsTo, and hasMany (#6339) (cherry picked from commit aa05d928737a2832051c8ffd1882d44db399794c) --- packages/-ember-data/tests/unit/model-test.js | 37 +++++++++++++++++++ .../tests/unit/model/relationships-test.js | 32 ++++++++++++++++ packages/model/addon/-private/attr.js | 5 ++- packages/model/addon/-private/belongs-to.js | 5 ++- packages/model/addon/-private/has-many.js | 5 ++- packages/model/addon/-private/util.ts | 31 ++++++++++++++++ packages/model/package.json | 1 + 7 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 packages/model/addon/-private/util.ts diff --git a/packages/-ember-data/tests/unit/model-test.js b/packages/-ember-data/tests/unit/model-test.js index 9a8a21a3f86..e93125a4d36 100644 --- a/packages/-ember-data/tests/unit/model-test.js +++ b/packages/-ember-data/tests/unit/model-test.js @@ -14,6 +14,7 @@ import JSONSerializer from '@ember-data/serializer/json'; import { attr as DSattr } from '@ember-data/model'; import { recordDataFor } from 'ember-data/-private'; import { attr, hasMany, belongsTo } from '@ember-data/model'; +import { gte } from 'ember-compatibility-helpers'; module('unit/model - Model', function(hooks) { setupTest(hooks); @@ -1470,6 +1471,42 @@ module('unit/model - Model', function(hooks) { await cat.save(); }); + + if (gte('3.10.0')) { + test('@attr decorator works without parens', async function(assert) { + assert.expect(1); + let cat; + + class Mascot extends Model { + @attr name; + } + + this.owner.register('model:mascot', Mascot); + adapter.updateRecord = function() { + assert.deepEqual(cat.changedAttributes(), { + name: ['Argon', 'Helia'], + }); + + return { data: { id: '1', type: 'mascot' } }; + }; + + cat = store.push({ + data: { + type: 'mascot', + id: '1', + attributes: { + name: 'Argon', + }, + }, + }); + + cat.setProperties({ + name: 'Helia', + }); + + await cat.save(); + }); + } }); module('Misc', function() { diff --git a/packages/-ember-data/tests/unit/model/relationships-test.js b/packages/-ember-data/tests/unit/model/relationships-test.js index 62496064ac1..860c1f18d03 100644 --- a/packages/-ember-data/tests/unit/model/relationships-test.js +++ b/packages/-ember-data/tests/unit/model/relationships-test.js @@ -3,6 +3,7 @@ import { module, test } from 'qunit'; import Model from '@ember-data/model'; import { hasMany, belongsTo } from '@ember-data/model'; import { get } from '@ember/object'; +import { gte } from 'ember-compatibility-helpers'; class Person extends Model { @hasMany('occupation', { async: false }) occupations; @@ -125,4 +126,35 @@ module('[@ember-data/model] unit - relationships', function(hooks) { assert.equal(relationship.meta.name, 'streamItems', 'relationship name has not been changed'); }); + + if (gte('3.10.0')) { + test('decorators works without parens', function(assert) { + let store; + let { owner } = this; + + class StreamItem extends Model { + @belongsTo user; + } + + class User extends Model { + @hasMany streamItems; + } + + owner.unregister('model:user'); + owner.register('model:stream-item', StreamItem); + owner.register('model:user', User); + + store = owner.lookup('service:store'); + + let user = store.modelFor('user'); + + const relationships = get(user, 'relationships'); + + assert.ok(relationships.has('stream-item'), 'relationship key has been normalized'); + + const relationship = relationships.get('stream-item')[0]; + + assert.equal(relationship.meta.name, 'streamItems', 'relationship name has not been changed'); + }); + } }); diff --git a/packages/model/addon/-private/attr.js b/packages/model/addon/-private/attr.js index 3598d0a165c..0330f2b49a2 100644 --- a/packages/model/addon/-private/attr.js +++ b/packages/model/addon/-private/attr.js @@ -3,6 +3,7 @@ import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import { recordDataFor } from '@ember-data/store/-private'; import { RECORD_DATA_ERRORS } from '@ember-data/canary-features'; +import { computedMacroWithOptionalParams } from './util'; /** @module @ember-data/model @@ -107,7 +108,7 @@ function hasValue(internalModel, key) { @param {Object} options a hash of options @return {Attribute} */ -export default function attr(type, options) { +function attr(type, options) { if (typeof type === 'object') { options = type; type = undefined; @@ -160,3 +161,5 @@ export default function attr(type, options) { }, }).meta(meta); } + +export default computedMacroWithOptionalParams(attr); diff --git a/packages/model/addon/-private/belongs-to.js b/packages/model/addon/-private/belongs-to.js index 24b965a0f86..f696031626d 100644 --- a/packages/model/addon/-private/belongs-to.js +++ b/packages/model/addon/-private/belongs-to.js @@ -2,6 +2,7 @@ import { computed } from '@ember/object'; import { assert, warn, inspect } from '@ember/debug'; import { normalizeModelName } from '@ember-data/store'; import { DEBUG } from '@glimmer/env'; +import { computedMacroWithOptionalParams } from './util'; /** @module @ember-data/model @@ -103,7 +104,7 @@ import { DEBUG } from '@glimmer/env'; @param {Object} options (optional) a hash of options @return {Ember.computed} relationship */ -export default function belongsTo(modelName, options) { +function belongsTo(modelName, options) { let opts, userEnteredModelName; if (typeof modelName === 'object') { opts = modelName; @@ -180,3 +181,5 @@ export default function belongsTo(modelName, options) { }, }).meta(meta); } + +export default computedMacroWithOptionalParams(belongsTo); diff --git a/packages/model/addon/-private/has-many.js b/packages/model/addon/-private/has-many.js index 9d7439b2ba2..671eec0b742 100644 --- a/packages/model/addon/-private/has-many.js +++ b/packages/model/addon/-private/has-many.js @@ -5,6 +5,7 @@ import { computed } from '@ember/object'; import { assert, inspect } from '@ember/debug'; import { normalizeModelName } from '@ember-data/store'; import { DEBUG } from '@glimmer/env'; +import { computedMacroWithOptionalParams } from './util'; /** `hasMany` is used to define One-To-Many and Many-To-Many @@ -141,7 +142,7 @@ import { DEBUG } from '@glimmer/env'; @param {Object} options (optional) a hash of options @return {Ember.computed} relationship */ -export default function hasMany(type, options) { +function hasMany(type, options) { if (typeof type === 'object') { options = type; type = undefined; @@ -199,3 +200,5 @@ export default function hasMany(type, options) { }, }).meta(meta); } + +export default computedMacroWithOptionalParams(hasMany); diff --git a/packages/model/addon/-private/util.ts b/packages/model/addon/-private/util.ts new file mode 100644 index 00000000000..d6cc8f1a512 --- /dev/null +++ b/packages/model/addon/-private/util.ts @@ -0,0 +1,31 @@ +import { gte } from 'ember-compatibility-helpers'; + +export type DecoratorPropertyDescriptor = PropertyDescriptor & { initializer?: any } | undefined; + +export function isElementDescriptor(args: any[]): args is [object, string, DecoratorPropertyDescriptor] { + let [maybeTarget, maybeKey, maybeDesc] = args; + + return ( + // Ensure we have the right number of args + args.length === 3 && + // Make sure the target is a class or object (prototype) + (typeof maybeTarget === 'function' || (typeof maybeTarget === 'object' && maybeTarget !== null)) && + // Make sure the key is a string + typeof maybeKey === 'string' && + // Make sure the descriptor is the right shape + ((typeof maybeDesc === 'object' && + maybeDesc !== null && + 'enumerable' in maybeDesc && + 'configurable' in maybeDesc) || + // TS compatibility + maybeDesc === undefined) + ); +} + +export function computedMacroWithOptionalParams(fn) { + if (gte('3.10.0')) { + return (...maybeDesc: any[]) => (isElementDescriptor(maybeDesc) ? fn()(...maybeDesc) : fn(...maybeDesc)); + } else { + return fn; + } +} diff --git a/packages/model/package.json b/packages/model/package.json index 24aadebcfb9..61684969212 100644 --- a/packages/model/package.json +++ b/packages/model/package.json @@ -24,6 +24,7 @@ "@ember-data/-build-infra": "3.13.0-beta.1", "@ember-data/store": "3.13.0-beta.1", "ember-cli-babel": "^7.8.0", + "ember-compatibility-helpers": "^1.2.0", "ember-cli-string-utils": "^1.1.0", "ember-cli-test-info": "^1.0.0", "ember-cli-typescript": "^2.0.2",