From b8e393c3f4882249b7454fe00744435478d8ac14 Mon Sep 17 00:00:00 2001 From: bmac Date: Mon, 18 Jan 2016 15:00:55 -0500 Subject: [PATCH] [BUGFIX release] Refactor Model.reopen to use mixins This fixes bugs where users import Model form 'ember-data/model' and it was missing required methods --- addon/-private/system/debug.js | 1 - addon/-private/system/debug/debug-info.js | 6 +- addon/-private/system/model/attr.js | 226 ++++++++++++++++++ addon/-private/system/model/model.js | 16 +- .../system/relationships/belongs-to.js | 3 +- addon/-private/system/relationships/ext.js | 7 +- .../-private/system/relationships/has-many.js | 2 +- addon/attr.js | 221 +---------------- addon/relationships.js | 2 - 9 files changed, 249 insertions(+), 235 deletions(-) create mode 100644 addon/-private/system/model/attr.js diff --git a/addon/-private/system/debug.js b/addon/-private/system/debug.js index 44886d0d8d4..8e605cb4b9a 100644 --- a/addon/-private/system/debug.js +++ b/addon/-private/system/debug.js @@ -2,7 +2,6 @@ @module ember-data */ -import "ember-data/-private/system/debug/debug-info"; import DebugAdapter from "ember-data/-private/system/debug/debug-adapter"; export default DebugAdapter; diff --git a/addon/-private/system/debug/debug-info.js b/addon/-private/system/debug/debug-info.js index 6d9a0e791af..f570f261de0 100644 --- a/addon/-private/system/debug/debug-info.js +++ b/addon/-private/system/debug/debug-info.js @@ -1,6 +1,6 @@ -import Model from "ember-data/model"; +import Ember from "ember"; -Model.reopen({ +export default Ember.Mixin.create({ /** Provides info about the model for debugging purposes @@ -63,5 +63,3 @@ Model.reopen({ }; } }); - -export default Model; diff --git a/addon/-private/system/model/attr.js b/addon/-private/system/model/attr.js new file mode 100644 index 00000000000..e111424e977 --- /dev/null +++ b/addon/-private/system/model/attr.js @@ -0,0 +1,226 @@ +import Ember from 'ember'; +import { assert } from "ember-data/-private/debug"; + + +var get = Ember.get; +var Map = Ember.Map; + +/** + @module ember-data +*/ + +/** + @class Model + @namespace DS +*/ + +export const AttrClassMethodsMixin = Ember.Mixin.create({ + /** + A map whose keys are the attributes of the model (properties + described by DS.attr) and whose values are the meta object for the + property. + + Example + + ```app/models/person.js + import DS from 'ember-data'; + + export default DS.Model.extend({ + firstName: attr('string'), + lastName: attr('string'), + birthday: attr('date') + }); + ``` + + ```javascript + import Ember from 'ember'; + import Person from 'app/models/person'; + + var attributes = Ember.get(Person, 'attributes') + + attributes.forEach(function(meta, name) { + console.log(name, meta); + }); + + // prints: + // firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"} + // lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"} + // birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"} + ``` + + @property attributes + @static + @type {Ember.Map} + @readOnly + */ + attributes: Ember.computed(function() { + var map = Map.create(); + + this.eachComputedProperty((name, meta) => { + if (meta.isAttribute) { + assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('')` from " + this.toString(), name !== 'id'); + + meta.name = name; + map.set(name, meta); + } + }); + + return map; + }).readOnly(), + + /** + A map whose keys are the attributes of the model (properties + described by DS.attr) and whose values are type of transformation + applied to each attribute. This map does not include any + attributes that do not have an transformation type. + + Example + + ```app/models/person.js + import DS from 'ember-data'; + + export default DS.Model.extend({ + firstName: attr(), + lastName: attr('string'), + birthday: attr('date') + }); + ``` + + ```javascript + import Ember from 'ember'; + import Person from 'app/models/person'; + + var transformedAttributes = Ember.get(Person, 'transformedAttributes') + + transformedAttributes.forEach(function(field, type) { + console.log(field, type); + }); + + // prints: + // lastName string + // birthday date + ``` + + @property transformedAttributes + @static + @type {Ember.Map} + @readOnly + */ + transformedAttributes: Ember.computed(function() { + var map = Map.create(); + + this.eachAttribute((key, meta) => { + if (meta.type) { + map.set(key, meta.type); + } + }); + + return map; + }).readOnly(), + + /** + Iterates through the attributes of the model, calling the passed function on each + attribute. + + The callback method you provide should have the following signature (all + parameters are optional): + + ```javascript + function(name, meta); + ``` + + - `name` the name of the current property in the iteration + - `meta` the meta object for the attribute property in the iteration + + Note that in addition to a callback, you can also pass an optional target + object that will be set as `this` on the context. + + Example + + ```javascript + import DS from 'ember-data'; + + var Person = DS.Model.extend({ + firstName: attr('string'), + lastName: attr('string'), + birthday: attr('date') + }); + + Person.eachAttribute(function(name, meta) { + console.log(name, meta); + }); + + // prints: + // firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"} + // lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"} + // birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"} + ``` + + @method eachAttribute + @param {Function} callback The callback to execute + @param {Object} [binding] the value to which the callback's `this` should be bound + @static + */ + eachAttribute(callback, binding) { + get(this, 'attributes').forEach((meta, name) => { + callback.call(binding, name, meta); + }); + }, + + /** + Iterates through the transformedAttributes of the model, calling + the passed function on each attribute. Note the callback will not be + called for any attributes that do not have an transformation type. + + The callback method you provide should have the following signature (all + parameters are optional): + + ```javascript + function(name, type); + ``` + + - `name` the name of the current property in the iteration + - `type` a string containing the name of the type of transformed + applied to the attribute + + Note that in addition to a callback, you can also pass an optional target + object that will be set as `this` on the context. + + Example + + ```javascript + import DS from 'ember-data'; + + var Person = DS.Model.extend({ + firstName: attr(), + lastName: attr('string'), + birthday: attr('date') + }); + + Person.eachTransformedAttribute(function(name, type) { + console.log(name, type); + }); + + // prints: + // lastName string + // birthday date + ``` + + @method eachTransformedAttribute + @param {Function} callback The callback to execute + @param {Object} [binding] the value to which the callback's `this` should be bound + @static + */ + eachTransformedAttribute(callback, binding) { + get(this, 'transformedAttributes').forEach((type, name) => { + callback.call(binding, name, type); + }); + } +}); + + +export const AttrInstanceMethodsMixin = Ember.Mixin.create({ + eachAttribute(callback, binding) { + this.constructor.eachAttribute(callback, binding); + } +}); diff --git a/addon/-private/system/model/model.js b/addon/-private/system/model/model.js index f89d802b569..f854d16166d 100644 --- a/addon/-private/system/model/model.js +++ b/addon/-private/system/model/model.js @@ -3,6 +3,11 @@ import { assert, deprecate } from "ember-data/-private/debug"; import { PromiseObject } from "ember-data/-private/system/promise-proxies"; import Errors from "ember-data/-private/system/model/errors"; import isEnabled from 'ember-data/-private/features'; +import DebuggerInfoMixin from 'ember-data/-private/system/debug/debug-info'; +import { BelongsToMixin } from 'ember-data/-private/system/relationships/belongs-to'; +import { HasManyMixin } from 'ember-data/-private/system/relationships/has-many'; +import { DidDefinePropertyMixin, RelationshipsClassMethodsMixin, RelationshipsInstanceMethodsMixin } from 'ember-data/-private/system/relationships/ext'; +import { AttrClassMethodsMixin, AttrInstanceMethodsMixin } from 'ember-data/-private/system/model/attr'; /** @module ember-data @@ -972,4 +977,13 @@ if (isEnabled("ds-references")) { } -export default Model; +Model.reopenClass(RelationshipsClassMethodsMixin); +Model.reopenClass(AttrClassMethodsMixin); + +export default Model.extend( + DebuggerInfoMixin, + BelongsToMixin, + DidDefinePropertyMixin, + RelationshipsInstanceMethodsMixin, + HasManyMixin, + AttrInstanceMethodsMixin); diff --git a/addon/-private/system/relationships/belongs-to.js b/addon/-private/system/relationships/belongs-to.js index e8c0c7e5de5..59d9ca468f4 100644 --- a/addon/-private/system/relationships/belongs-to.js +++ b/addon/-private/system/relationships/belongs-to.js @@ -1,6 +1,5 @@ import Ember from 'ember'; import { assert, warn } from "ember-data/-private/debug"; -import Model from 'ember-data/model'; import normalizeModelName from "ember-data/-private/system/normalize-model-name"; /** @@ -138,7 +137,7 @@ export default function belongsTo(modelName, options) { These observers observe all `belongsTo` relationships on the record. See `relationships/ext` to see how these observers get their dependencies. */ -Model.reopen({ +export const BelongsToMixin = Ember.Mixin.create({ notifyBelongsToChanged(key) { this.notifyPropertyChange(key); } diff --git a/addon/-private/system/relationships/ext.js b/addon/-private/system/relationships/ext.js index b91c24a887f..7741feba944 100644 --- a/addon/-private/system/relationships/ext.js +++ b/addon/-private/system/relationships/ext.js @@ -4,7 +4,6 @@ import { typeForRelationshipMeta, relationshipFromMeta } from "ember-data/-private/system/relationship-meta"; -import Model from "ember-data/model"; import EmptyObject from "ember-data/-private/system/empty-object"; var get = Ember.get; @@ -98,7 +97,7 @@ var relationshipsByNameDescriptor = Ember.computed(function() { @class Model @namespace DS */ -Model.reopen({ +export const DidDefinePropertyMixin = Ember.Mixin.create({ /** This Ember.js hook allows an object to be notified when a property @@ -157,7 +156,7 @@ Model.reopen({ extensively. */ -Model.reopenClass({ +export const RelationshipsClassMethodsMixin = Ember.Mixin.create({ /** For a given relationship name, returns the model type of the relationship. @@ -599,7 +598,7 @@ Model.reopenClass({ }); -Model.reopen({ +export const RelationshipsInstanceMethodsMixin = Ember.Mixin.create({ /** Given a callback, iterates over each of the relationships in the model, invoking the callback with the name of each relationship and its relationship diff --git a/addon/-private/system/relationships/has-many.js b/addon/-private/system/relationships/has-many.js index 86f2234ea21..7241fbf7142 100644 --- a/addon/-private/system/relationships/has-many.js +++ b/addon/-private/system/relationships/has-many.js @@ -159,7 +159,7 @@ export default function hasMany(type, options) { }).meta(meta); } -Model.reopen({ +export const HasManyMixin = Ember.Mixin.create({ notifyHasManyAdded(key) { //We need to notifyPropertyChange in the adding case because we need to make sure //we fetch the newly added record in case it is unloaded diff --git a/addon/attr.js b/addon/attr.js index 9a101508a16..bf628e3f316 100644 --- a/addon/attr.js +++ b/addon/attr.js @@ -1,229 +1,10 @@ import Ember from 'ember'; -import Model from "ember-data/-private/system/model/model"; -import { assert, deprecate } from "ember-data/-private/debug"; +import { deprecate } from "ember-data/-private/debug"; /** @module ember-data */ -var get = Ember.get; -var Map = Ember.Map; - -/** - @class Model - @namespace DS -*/ -Model.reopenClass({ - /** - A map whose keys are the attributes of the model (properties - described by DS.attr) and whose values are the meta object for the - property. - - Example - - ```app/models/person.js - import DS from 'ember-data'; - - export default DS.Model.extend({ - firstName: attr('string'), - lastName: attr('string'), - birthday: attr('date') - }); - ``` - - ```javascript - import Ember from 'ember'; - import Person from 'app/models/person'; - - var attributes = Ember.get(Person, 'attributes') - - attributes.forEach(function(meta, name) { - console.log(name, meta); - }); - - // prints: - // firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"} - // lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"} - // birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"} - ``` - - @property attributes - @static - @type {Ember.Map} - @readOnly - */ - attributes: Ember.computed(function() { - var map = Map.create(); - - this.eachComputedProperty((name, meta) => { - if (meta.isAttribute) { - assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('')` from " + this.toString(), name !== 'id'); - - meta.name = name; - map.set(name, meta); - } - }); - - return map; - }).readOnly(), - - /** - A map whose keys are the attributes of the model (properties - described by DS.attr) and whose values are type of transformation - applied to each attribute. This map does not include any - attributes that do not have an transformation type. - - Example - - ```app/models/person.js - import DS from 'ember-data'; - - export default DS.Model.extend({ - firstName: attr(), - lastName: attr('string'), - birthday: attr('date') - }); - ``` - - ```javascript - import Ember from 'ember'; - import Person from 'app/models/person'; - - var transformedAttributes = Ember.get(Person, 'transformedAttributes') - - transformedAttributes.forEach(function(field, type) { - console.log(field, type); - }); - - // prints: - // lastName string - // birthday date - ``` - - @property transformedAttributes - @static - @type {Ember.Map} - @readOnly - */ - transformedAttributes: Ember.computed(function() { - var map = Map.create(); - - this.eachAttribute((key, meta) => { - if (meta.type) { - map.set(key, meta.type); - } - }); - - return map; - }).readOnly(), - - /** - Iterates through the attributes of the model, calling the passed function on each - attribute. - - The callback method you provide should have the following signature (all - parameters are optional): - - ```javascript - function(name, meta); - ``` - - - `name` the name of the current property in the iteration - - `meta` the meta object for the attribute property in the iteration - - Note that in addition to a callback, you can also pass an optional target - object that will be set as `this` on the context. - - Example - - ```javascript - import DS from 'ember-data'; - - var Person = DS.Model.extend({ - firstName: attr('string'), - lastName: attr('string'), - birthday: attr('date') - }); - - Person.eachAttribute(function(name, meta) { - console.log(name, meta); - }); - - // prints: - // firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"} - // lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"} - // birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"} - ``` - - @method eachAttribute - @param {Function} callback The callback to execute - @param {Object} [binding] the value to which the callback's `this` should be bound - @static - */ - eachAttribute(callback, binding) { - get(this, 'attributes').forEach((meta, name) => { - callback.call(binding, name, meta); - }); - }, - - /** - Iterates through the transformedAttributes of the model, calling - the passed function on each attribute. Note the callback will not be - called for any attributes that do not have an transformation type. - - The callback method you provide should have the following signature (all - parameters are optional): - - ```javascript - function(name, type); - ``` - - - `name` the name of the current property in the iteration - - `type` a string containing the name of the type of transformed - applied to the attribute - - Note that in addition to a callback, you can also pass an optional target - object that will be set as `this` on the context. - - Example - - ```javascript - import DS from 'ember-data'; - - var Person = DS.Model.extend({ - firstName: attr(), - lastName: attr('string'), - birthday: attr('date') - }); - - Person.eachTransformedAttribute(function(name, type) { - console.log(name, type); - }); - - // prints: - // lastName string - // birthday date - ``` - - @method eachTransformedAttribute - @param {Function} callback The callback to execute - @param {Object} [binding] the value to which the callback's `this` should be bound - @static - */ - eachTransformedAttribute(callback, binding) { - get(this, 'transformedAttributes').forEach((type, name) => { - callback.call(binding, name, type); - }); - } -}); - - -Model.reopen({ - eachAttribute(callback, binding) { - this.constructor.eachAttribute(callback, binding); - } -}); - function getDefaultValue(record, options, key) { if (typeof options.defaultValue === "function") { return options.defaultValue.apply(null, arguments); diff --git a/addon/relationships.js b/addon/relationships.js index 23c1f6f7e55..e642287c6d8 100644 --- a/addon/relationships.js +++ b/addon/relationships.js @@ -5,8 +5,6 @@ import belongsTo from "ember-data/-private/system/relationships/belongs-to"; import hasMany from "ember-data/-private/system/relationships/has-many"; -import "ember-data/-private/system/relationships/ext"; - export { belongsTo, hasMany