dist/globals/ember-data.prod.js in ember-data-source-2.10.0 vs dist/globals/ember-data.prod.js in ember-data-source-2.11.0.beta.1

- old
+ new

@@ -4,11 +4,11 @@ /*! * @overview Ember Data * @copyright Copyright 2011-2016 Tilde Inc. and contributors. * Portions Copyright 2011 LivingSocial Inc. * @license Licensed under MIT license (see license.js) - * @version 2.10.0 + * @version 2.11.0-beta.1 */ var loader, define, requireModule, require, requirejs; (function(global) { @@ -640,15 +640,15 @@ urlForDeleteRecord(id, modelName, snapshot) { return this._super(...arguments) + '/destroy'; } }); ``` - * @method urlForDeleteRecord - * @param {String} id - * @param {String} modelName - * @param {DS.Snapshot} snapshot - * @return {String} url + @method urlForDeleteRecord + @param {String} id + @param {String} modelName + @param {DS.Snapshot} snapshot + @return {String} url */ urlForDeleteRecord: function (id, modelName, snapshot) { return this._buildURL(modelName, id); }, @@ -789,20 +789,20 @@ function debugSeal() { return _ember.default.debugSeal.apply(_ember.default, arguments); } - function checkPolymorphic(typeClass, addedRecord) { - if (typeClass.__isMixin) { + function checkPolymorphic(modelClass, addedModelClass) { + if (modelClass.__isMixin) { //TODO Need to do this in order to support mixins, should convert to public api //once it exists in Ember - return typeClass.__mixin.detect(addedRecord.type.PrototypeMixin); + return modelClass.__mixin.detect(addedModelClass.PrototypeMixin); } if (_ember.default.MODEL_FACTORY_INJECTIONS) { - typeClass = typeClass.superclass; + modelClass = modelClass.superclass; } - return typeClass.detect(addedRecord.type); + return modelClass.detect(addedModelClass); } /* Assert that `addedRecord` has a valid type so it can be added to the relationship of the `record`. @@ -814,26 +814,25 @@ be an InternalModel and the `relationshipMeta` needs to be the meta information about the relationship, retrieved via `record.relationshipFor(key)`. @method assertPolymorphicType - @param {InternalModel} record + @param {InternalModel} internalModel @param {RelationshipMeta} relationshipMeta retrieved via `record.relationshipFor(key)` @param {InternalModel} addedRecord record which should be added/set for the relationship */ - function assertPolymorphicType(record, relationshipMeta, addedRecord) { - var addedType = addedRecord.type.modelName; - var recordType = record.type.modelName; + function assertPolymorphicType(parentInternalModel, relationshipMeta, addedInternalModel) { + var addedModelName = addedInternalModel.modelName; + var parentModelName = parentInternalModel.modelName; var key = relationshipMeta.key; - var typeClass = record.store.modelFor(relationshipMeta.type); + var relationshipClass = parentInternalModel.store.modelFor(relationshipMeta.type); + var assertionMessage = 'You cannot add a record of modelClass \'' + addedModelName + '\' to the \'' + parentModelName + '.' + key + '\' relationship (only \'' + relationshipClass.modelName + '\' allowed)'; - var assertionMessage = 'You cannot add a record of type \'' + addedType + '\' to the \'' + recordType + '.' + key + '\' relationship (only \'' + typeClass.modelName + '\' allowed)'; - - assert(assertionMessage, checkPolymorphic(typeClass, addedRecord)); + assert(assertionMessage, checkPolymorphic(relationshipClass, addedInternalModel.modelClass)); } }); define('ember-data/-private/ext/date', ['exports', 'ember', 'ember-data/-private/debug'], function (exports, _ember, _emberDataPrivateDebug) { /** @@ -1225,38 +1224,42 @@ @for DS.Model @private */ _debugInfo: function () { var attributes = ['id']; - var relationships = { belongsTo: [], hasMany: [] }; + var relationships = {}; var expensiveProperties = []; this.eachAttribute(function (name, meta) { return attributes.push(name); }); - this.eachRelationship(function (name, relationship) { - relationships[relationship.kind].push(name); - expensiveProperties.push(name); - }); - var groups = [{ name: 'Attributes', properties: attributes, expand: true - }, { - name: 'Belongs To', - properties: relationships.belongsTo, - expand: true - }, { - name: 'Has Many', - properties: relationships.hasMany, - expand: true - }, { + }]; + + this.eachRelationship(function (name, relationship) { + var properties = relationships[relationship.kind]; + + if (properties === undefined) { + properties = relationships[relationship.kind] = []; + groups.push({ + name: relationship.name, + properties: properties, + expand: true + }); + } + properties.push(name); + expensiveProperties.push(name); + }); + + groups.push({ name: 'Flags', properties: ['isLoaded', 'hasDirtyAttributes', 'isSaving', 'isDeleted', 'isError', 'isNew', 'isValid'] - }]; + }); return { propertyInfo: { // include all other mixins / properties (not just the grouped ones) includeOtherProperties: true, @@ -1323,11 +1326,10 @@ } return false; } }); define("ember-data/-private/system/many-array", ["exports", "ember", "ember-data/-private/debug", "ember-data/-private/system/promise-proxies", "ember-data/-private/system/store/common"], function (exports, _ember, _emberDataPrivateDebug, _emberDataPrivateSystemPromiseProxies, _emberDataPrivateSystemStoreCommon) { - var get = _ember.default.get; var set = _ember.default.set; /** A `ManyArray` is a `MutableArray` that represents the contents of a has-many @@ -1373,34 +1375,90 @@ @uses Ember.MutableArray, Ember.Evented */ exports.default = _ember.default.Object.extend(_ember.default.MutableArray, _ember.default.Evented, { init: function () { this._super.apply(this, arguments); - this.currentState = _ember.default.A([]); - }, - record: null, + /** + The loading state of this array + @property {Boolean} isLoaded + */ + this.isLoaded = false; + this.length = 0; - canonicalState: null, - currentState: null, + /** + Used for async `hasMany` arrays + to keep track of when they will resolve. + @property {Ember.RSVP.Promise} promise + @private + */ + this.promise = null; - length: 0, + /** + Metadata associated with the request for async hasMany relationships. + Example + Given that the server returns the following JSON payload when fetching a + hasMany relationship: + ```js + { + "comments": [{ + "id": 1, + "comment": "This is the first comment", + }, { + // ... + }], + "meta": { + "page": 1, + "total": 5 + } + } + ``` + You can then access the metadata via the `meta` property: + ```js + post.get('comments').then(function(comments) { + var meta = comments.get('meta'); + // meta.page => 1 + // meta.total => 5 + }); + ``` + @property {Object} meta + @public + */ + this.meta = this.meta || null; + /** + `true` if the relationship is polymorphic, `false` otherwise. + @property {Boolean} isPolymorphic + @private + */ + this.isPolymorphic = this.isPolymorphic || false; + + /** + The relationship which manages this array. + @property {ManyRelationship} relationship + @private + */ + this.relationship = this.relationship || null; + + this.currentState = _ember.default.A([]); + this.flushCanonical(false); + }, + objectAt: function (index) { //Ember observers such as 'firstObject', 'lastObject' might do out of bounds accesses if (!this.currentState[index]) { return undefined; } + return this.currentState[index].getRecord(); }, flushCanonical: function () { - //TODO make this smarter, currently its plenty stupid - var toSet = this.canonicalState.filter(function (internalModel) { - return !internalModel.isDeleted(); - }); + var isInitialized = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; + var toSet = this.canonicalState; + //a hack for not removing new records //TODO remove once we have proper diffing var newRecords = this.currentState.filter( // only add new records which are not yet in the canonical state of this // relationship (a new record can be in the canonical state if it has @@ -1415,86 +1473,31 @@ if ((0, _emberDataPrivateSystemStoreCommon._objectIsAlive)(this)) { this.set('length', toSet.length); } this.currentState = toSet; this.arrayContentDidChange(0, oldLength, this.length); - //TODO Figure out to notify only on additions and maybe only if unloaded - this.relationship.notifyHasManyChanged(); - this.record.updateRecordArrays(); - }, - /** - `true` if the relationship is polymorphic, `false` otherwise. - @property {Boolean} isPolymorphic - @private - */ - isPolymorphic: false, - /** - The loading state of this array - @property {Boolean} isLoaded - */ - isLoaded: false, - - /** - The relationship which manages this array. - @property {ManyRelationship} relationship - @private - */ - relationship: null, - - /** - Metadata associated with the request for async hasMany relationships. - Example - Given that the server returns the following JSON payload when fetching a - hasMany relationship: - ```js - { - "comments": [{ - "id": 1, - "comment": "This is the first comment", - }, { - // ... - }], - "meta": { - "page": 1, - "total": 5 - } + if (isInitialized) { + //TODO Figure out to notify only on additions and maybe only if unloaded + this.relationship.notifyHasManyChanged(); } - ``` - You can then access the metadata via the `meta` property: - ```js - post.get('comments').then(function(comments) { - var meta = comments.get('meta'); - // meta.page => 1 - // meta.total => 5 - }); - ``` - @property {Object} meta - @public - */ - meta: null, + }, internalReplace: function (idx, amt, objects) { if (!objects) { objects = []; } this.arrayContentWillChange(idx, amt, objects.length); this.currentState.splice.apply(this.currentState, [idx, amt].concat(objects)); this.set('length', this.currentState.length); this.arrayContentDidChange(idx, amt, objects.length); - if (objects) { - //TODO(Igor) probably needed only for unloaded records - this.relationship.notifyHasManyChanged(); - } - this.record.updateRecordArrays(); }, //TODO(Igor) optimize internalRemoveRecords: function (records) { - var index; for (var i = 0; i < records.length; i++) { - index = this.currentState.indexOf(records[i]); + var index = this.currentState.indexOf(records[i]); this.internalReplace(index, 1); } }, //TODO(Igor) optimize @@ -1504,28 +1507,21 @@ } this.internalReplace(idx, 0, records); }, replace: function (idx, amt, objects) { - var records; + var records = undefined; if (amt > 0) { records = this.currentState.slice(idx, idx + amt); this.get('relationship').removeRecords(records); } if (objects) { this.get('relationship').addRecords(objects.map(function (obj) { return obj._internalModel; }), idx); } }, - /** - Used for async `hasMany` arrays - to keep track of when they will resolve. - @property {Ember.RSVP.Promise} promise - @private - */ - promise: null, /** @method loadingRecordsCount @param {Number} count @private @@ -1545,11 +1541,26 @@ this.trigger('didLoad'); } }, /** - @method reload + Reloads all of the records in the manyArray. If the manyArray + holds a relationship that was originally fetched using a links url + Ember Data will revisit the original links url to repopulate the + relationship. + If the manyArray holds the result of a `store.query()` reload will + re-run the original query. + Example + ```javascript + var user = store.peekRecord('user', 1) + user.login().then(function() { + user.get('permissions').then(function(permissions) { + return permissions.reload(); + }); + }); + ``` + @method reload @public */ reload: function () { return this.relationship.reload(); }, @@ -1570,14 +1581,14 @@ @method save @return {DS.PromiseArray} promise */ save: function () { var manyArray = this; - var promiseLabel = "DS: ManyArray#save " + get(this, 'type'); - var promise = _ember.default.RSVP.all(this.invoke("save"), promiseLabel).then(function (array) { + var promiseLabel = 'DS: ManyArray#save ' + get(this, 'type'); + var promise = _ember.default.RSVP.all(this.invoke("save"), promiseLabel).then(function () { return manyArray; - }, null, "DS: ManyArray#save return ManyArray"); + }, null, 'DS: ManyArray#save return ManyArray'); return _emberDataPrivateSystemPromiseProxies.PromiseArray.create({ promise: promise }); }, /** @@ -2194,18 +2205,40 @@ return !isEmpty(this.errorsFor(attribute)); } }); }); define("ember-data/-private/system/model/internal-model", ["exports", "ember", "ember-data/-private/debug", "ember-data/-private/system/model/states", "ember-data/-private/system/relationships/state/create", "ember-data/-private/system/snapshot", "ember-data/-private/system/empty-object", "ember-data/-private/features", "ember-data/-private/utils", "ember-data/-private/system/references"], function (exports, _ember, _emberDataPrivateDebug, _emberDataPrivateSystemModelStates, _emberDataPrivateSystemRelationshipsStateCreate, _emberDataPrivateSystemSnapshot, _emberDataPrivateSystemEmptyObject, _emberDataPrivateFeatures, _emberDataPrivateUtils, _emberDataPrivateSystemReferences) { - exports.default = InternalModel; + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - var Promise = _ember.default.RSVP.Promise; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + var get = _ember.default.get; var set = _ember.default.set; var copy = _ember.default.copy; + var EmberError = _ember.default.Error; + var inspect = _ember.default.inspect; + var isEmpty = _ember.default.isEmpty; + var isEqual = _ember.default.isEqual; + var emberRun = _ember.default.run; + var setOwner = _ember.default.setOwner; + var RSVP = _ember.default.RSVP; + var Promise = _ember.default.RSVP.Promise; + var assign = _ember.default.assign || _ember.default.merge; + /* + The TransitionChainMap caches the `state.enters`, `state.setups`, and final state reached + when transitioning from one state to another, so that future transitions can replay the + transition without needing to walk the state tree, collect these hook calls and determine + the state to transition into. + + A future optimization would be to build a single chained method out of the collected enters + and setups. It may also be faster to do a two level cache (from: { to }) instead of caching based + on a key that adds the two together. + */ + var TransitionChainMap = new _emberDataPrivateSystemEmptyObject.default(); + var _extractPivotNameCache = new _emberDataPrivateSystemEmptyObject.default(); var _splitOnDotCache = new _emberDataPrivateSystemEmptyObject.default(); function splitOnDot(name) { return _splitOnDotCache[name] || (_splitOnDotCache[name] = name.split('.')); @@ -2213,16 +2246,10 @@ function extractPivotName(name) { return _extractPivotNameCache[name] || (_extractPivotNameCache[name] = splitOnDot(name)[0]); } - function retrieveFromCurrentState(key) { - return function () { - return get(this.currentState, key); - }; - } - // this (and all heimdall instrumentation) will be stripped by a babel transform // https://github.com/heimdalljs/babel5-plugin-strip-heimdall /* `InternalModel` is the Model class that we use internally inside Ember Data to represent models. @@ -2239,793 +2266,1004 @@ @private @class InternalModel */ - function InternalModel(type, id, store, _, data) { - this.type = type; - this.id = id; - this.store = store; - this._data = data || new _emberDataPrivateSystemEmptyObject.default(); - this.modelName = type.modelName; - this.dataHasInitialized = false; - //Look into making this lazy - this._deferredTriggers = []; - this._attributes = new _emberDataPrivateSystemEmptyObject.default(); - this._inFlightAttributes = new _emberDataPrivateSystemEmptyObject.default(); - this._relationships = new _emberDataPrivateSystemRelationshipsStateCreate.default(this); - this._recordArrays = undefined; - this.currentState = _emberDataPrivateSystemModelStates.default.empty; - this.recordReference = new _emberDataPrivateSystemReferences.RecordReference(store, this); - this.references = {}; - this.isReloading = false; - this.isError = false; - this.error = null; - this.__ember_meta__ = null; - this[_ember.default.GUID_KEY] = _ember.default.guidFor(this); - /* - implicit relationships are relationship which have not been declared but the inverse side exists on - another record somewhere - For example if there was - ```app/models/comment.js - import DS from 'ember-data'; - export default DS.Model.extend({ - name: DS.attr() - }) - ``` - but there is also - ```app/models/post.js - import DS from 'ember-data'; - export default DS.Model.extend({ - name: DS.attr(), - comments: DS.hasMany('comment') - }) - ``` - would have a implicit post relationship in order to be do things like remove ourselves from the post - when we are deleted - */ - this._implicitRelationships = new _emberDataPrivateSystemEmptyObject.default(); - } + var InternalModel = (function () { + function InternalModel(modelClass, id, store, data) { + this.modelClass = modelClass; + this.id = id; + this.store = store; + this._data = data || new _emberDataPrivateSystemEmptyObject.default(); + this.modelName = modelClass.modelName; + this.dataHasInitialized = false; + this._loadingPromise = null; + this._recordArrays = undefined; + this._record = null; + this.currentState = _emberDataPrivateSystemModelStates.default.empty; + this.isReloading = false; + this._isDestroyed = false; + this.isError = false; + this.error = null; - InternalModel.prototype = { - isEmpty: retrieveFromCurrentState('isEmpty'), - isLoading: retrieveFromCurrentState('isLoading'), - isLoaded: retrieveFromCurrentState('isLoaded'), - hasDirtyAttributes: retrieveFromCurrentState('hasDirtyAttributes'), - isSaving: retrieveFromCurrentState('isSaving'), - isDeleted: retrieveFromCurrentState('isDeleted'), - isNew: retrieveFromCurrentState('isNew'), - isValid: retrieveFromCurrentState('isValid'), - dirtyType: retrieveFromCurrentState('dirtyType'), + // caches for lazy getters + this.__deferredTriggers = null; + this._references = null; + this._recordReference = null; + this.__inFlightAttributes = null; + this.__relationships = null; + this.__attributes = null; + this.__implicitRelationships = null; + } - constructor: InternalModel, - materializeRecord: function () { - - // lookupFactory should really return an object that creates - // instances with the injections applied - var createOptions = { - store: this.store, - _internalModel: this, - id: this.id, - currentState: get(this, 'currentState'), - isError: this.isError, - adapterError: this.error - }; - - if (_ember.default.setOwner) { - // ensure that `Ember.getOwner(this)` works inside a model instance - _ember.default.setOwner(createOptions, (0, _emberDataPrivateUtils.getOwner)(this.store)); - } else { - createOptions.container = this.store.container; + _createClass(InternalModel, [{ + key: "isEmpty", + value: function isEmpty() { + return this.currentState.isEmpty; } - - this.record = this.type._create(createOptions); - - this._triggerDeferredTriggers(); - }, - - recordObjectWillDestroy: function () { - this.record = null; - }, - - deleteRecord: function () { - this.send('deleteRecord'); - }, - - save: function (options) { - var promiseLabel = "DS: Model#save " + this; - var resolver = _ember.default.RSVP.defer(promiseLabel); - - this.store.scheduleSave(this, resolver, options); - return resolver.promise; - }, - - startedReloading: function () { - this.isReloading = true; - if (this.record) { - set(this.record, 'isReloading', true); + }, { + key: "isLoading", + value: function isLoading() { + return this.currentState.isLoading; } - }, - - finishedReloading: function () { - this.isReloading = false; - if (this.record) { - set(this.record, 'isReloading', false); + }, { + key: "isLoaded", + value: function isLoaded() { + return this.currentState.isLoaded; } - }, - - reload: function () { - this.startedReloading(); - var record = this; - var promiseLabel = "DS: Model#reload of " + this; - return new Promise(function (resolve) { - record.send('reloadRecord', resolve); - }, promiseLabel).then(function () { - record.didCleanError(); - return record; - }, function (error) { - record.didError(error); - throw error; - }, "DS: Model#reload complete, update flags").finally(function () { - record.finishedReloading(); - record.updateRecordArrays(); - }); - }, - - getRecord: function () { - if (!this.record) { - this.materializeRecord(); + }, { + key: "hasDirtyAttributes", + value: function hasDirtyAttributes() { + return this.currentState.hasDirtyAttributes; } - return this.record; - }, + }, { + key: "isSaving", + value: function isSaving() { + return this.currentState.isSaving; + } + }, { + key: "isDeleted", + value: function isDeleted() { + return this.currentState.isDeleted; + } + }, { + key: "isNew", + value: function isNew() { + return this.currentState.isNew; + } + }, { + key: "isValid", + value: function isValid() { + return this.currentState.isValid; + } + }, { + key: "dirtyType", + value: function dirtyType() { + return this.currentState.dirtyType; + } + }, { + key: "getRecord", + value: function getRecord() { + if (!this._record) { - unloadRecord: function () { - this.send('unloadRecord'); - }, + // lookupFactory should really return an object that creates + // instances with the injections applied + var createOptions = { + store: this.store, + _internalModel: this, + id: this.id, + currentState: this.currentState, + isError: this.isError, + adapterError: this.error + }; - eachRelationship: function (callback, binding) { - return this.type.eachRelationship(callback, binding); - }, + if (setOwner) { + // ensure that `getOwner(this)` works inside a model instance + setOwner(createOptions, (0, _emberDataPrivateUtils.getOwner)(this.store)); + } else { + createOptions.container = this.store.container; + } - eachAttribute: function (callback, binding) { - return this.type.eachAttribute(callback, binding); - }, + this._record = this.modelClass._create(createOptions); - inverseFor: function (key) { - return this.type.inverseFor(key); - }, + this._triggerDeferredTriggers(); + } - setupData: function (data) { - var changedKeys = this._changedKeys(data.attributes); - assign(this._data, data.attributes); - this.pushedData(); - if (this.record) { - this.record._notifyProperties(changedKeys); + return this._record; } - this.didInitializeData(); - }, - - becameReady: function () { - _ember.default.run.schedule('actions', this.store.recordArrayManager, this.store.recordArrayManager.recordWasLoaded, this); - }, - - didInitializeData: function () { - if (!this.dataHasInitialized) { - this.becameReady(); - this.dataHasInitialized = true; + }, { + key: "recordObjectWillDestroy", + value: function recordObjectWillDestroy() { + this._record = null; } - }, + }, { + key: "deleteRecord", + value: function deleteRecord() { + this.send('deleteRecord'); + } + }, { + key: "save", + value: function save(options) { + var promiseLabel = "DS: Model#save " + this; + var resolver = RSVP.defer(promiseLabel); - destroy: function () { - if (this.record) { - return this.record.destroy(); + this.store.scheduleSave(this, resolver, options); + return resolver.promise; } - }, + }, { + key: "startedReloading", + value: function startedReloading() { + this.isReloading = true; + if (this.hasRecord) { + set(this.record, 'isReloading', true); + } + } + }, { + key: "finishedReloading", + value: function finishedReloading() { + this.isReloading = false; + if (this.hasRecord) { + set(this.record, 'isReloading', false); + } + } + }, { + key: "reload", + value: function reload() { + this.startedReloading(); + var internalModel = this; + var promiseLabel = "DS: Model#reload of " + this; - /* - @method createSnapshot - @private - */ - createSnapshot: function (options) { - return new _emberDataPrivateSystemSnapshot.default(this, options); - }, - - /* - @method loadingData - @private - @param {Promise} promise - */ - loadingData: function (promise) { - this.send('loadingData', promise); - }, - - /* - @method loadedData - @private - */ - loadedData: function () { - this.send('loadedData'); - this.didInitializeData(); - }, - - /* - @method notFound - @private - */ - notFound: function () { - this.send('notFound'); - }, - - /* - @method pushedData - @private - */ - pushedData: function () { - this.send('pushedData'); - }, - - flushChangedAttributes: function () { - this._inFlightAttributes = this._attributes; - this._attributes = new _emberDataPrivateSystemEmptyObject.default(); - }, - - hasChangedAttributes: function () { - return Object.keys(this._attributes).length > 0; - }, - - /* - Checks if the attributes which are considered as changed are still - different to the state which is acknowledged by the server. - This method is needed when data for the internal model is pushed and the - pushed data might acknowledge dirty attributes as confirmed. - @method updateChangedAttributes - @private - */ - updateChangedAttributes: function () { - var changedAttributes = this.changedAttributes(); - var changedAttributeNames = Object.keys(changedAttributes); - - for (var i = 0, _length = changedAttributeNames.length; i < _length; i++) { - var attribute = changedAttributeNames[i]; - var data = changedAttributes[attribute]; - var oldData = data[0]; - var newData = data[1]; - - if (oldData === newData) { - delete this._attributes[attribute]; + return new Promise(function (resolve) { + internalModel.send('reloadRecord', resolve); + }, promiseLabel).then(function () { + internalModel.didCleanError(); + return internalModel; + }, function (error) { + internalModel.didError(error); + throw error; + }, "DS: Model#reload complete, update flags").finally(function () { + internalModel.finishedReloading(); + internalModel.updateRecordArrays(); + }); + } + }, { + key: "unloadRecord", + value: function unloadRecord() { + this.send('unloadRecord'); + } + }, { + key: "eachRelationship", + value: function eachRelationship(callback, binding) { + return this.modelClass.eachRelationship(callback, binding); + } + }, { + key: "eachAttribute", + value: function eachAttribute(callback, binding) { + return this.modelClass.eachAttribute(callback, binding); + } + }, { + key: "inverseFor", + value: function inverseFor(key) { + return this.modelClass.inverseFor(key); + } + }, { + key: "setupData", + value: function setupData(data) { + var changedKeys = this._changedKeys(data.attributes); + assign(this._data, data.attributes); + this.pushedData(); + if (this.hasRecord) { + this.record._notifyProperties(changedKeys); } + this.didInitializeData(); } - }, + }, { + key: "becameReady", + value: function becameReady() { + emberRun.schedule('actions', this.store.recordArrayManager, this.store.recordArrayManager.recordWasLoaded, this); + } + }, { + key: "didInitializeData", + value: function didInitializeData() { + if (!this.dataHasInitialized) { + this.becameReady(); + this.dataHasInitialized = true; + } + } + }, { + key: "destroy", + value: function destroy() { + this._isDestroyed = true; + if (this.hasRecord) { + return this.record.destroy(); + } + } - /* - Returns an object, whose keys are changed properties, and value is an - [oldProp, newProp] array. - @method changedAttributes - @private - */ - changedAttributes: function () { - var oldData = this._data; - var currentData = this._attributes; - var inFlightData = this._inFlightAttributes; - var newData = assign(copy(inFlightData), currentData); - var diffData = new _emberDataPrivateSystemEmptyObject.default(); + /* + @method createSnapshot + @private + */ + }, { + key: "createSnapshot", + value: function createSnapshot(options) { + return new _emberDataPrivateSystemSnapshot.default(this, options); + } - var newDataKeys = Object.keys(newData); + /* + @method loadingData + @private + @param {Promise} promise + */ + }, { + key: "loadingData", + value: function loadingData(promise) { + this.send('loadingData', promise); + } - for (var i = 0, _length2 = newDataKeys.length; i < _length2; i++) { - var key = newDataKeys[i]; - diffData[key] = [oldData[key], newData[key]]; + /* + @method loadedData + @private + */ + }, { + key: "loadedData", + value: function loadedData() { + this.send('loadedData'); + this.didInitializeData(); } - return diffData; - }, + /* + @method notFound + @private + */ + }, { + key: "notFound", + value: function notFound() { + this.send('notFound'); + } - /* - @method adapterWillCommit - @private - */ - adapterWillCommit: function () { - this.send('willCommit'); - }, + /* + @method pushedData + @private + */ + }, { + key: "pushedData", + value: function pushedData() { + this.send('pushedData'); + } + }, { + key: "flushChangedAttributes", + value: function flushChangedAttributes() { + this._inFlightAttributes = this._attributes; + this._attributes = new _emberDataPrivateSystemEmptyObject.default(); + } + }, { + key: "hasChangedAttributes", + value: function hasChangedAttributes() { + return Object.keys(this._attributes).length > 0; + } - /* - @method adapterDidDirty - @private - */ - adapterDidDirty: function () { - this.send('becomeDirty'); - this.updateRecordArraysLater(); - }, + /* + Checks if the attributes which are considered as changed are still + different to the state which is acknowledged by the server. + This method is needed when data for the internal model is pushed and the + pushed data might acknowledge dirty attributes as confirmed. + @method updateChangedAttributes + @private + */ + }, { + key: "updateChangedAttributes", + value: function updateChangedAttributes() { + var changedAttributes = this.changedAttributes(); + var changedAttributeNames = Object.keys(changedAttributes); + var attrs = this._attributes; - /* - @method send - @private - @param {String} name - @param {Object} context - */ - send: function (name, context) { - var currentState = get(this, 'currentState'); + for (var i = 0, _length = changedAttributeNames.length; i < _length; i++) { + var attribute = changedAttributeNames[i]; + var data = changedAttributes[attribute]; + var oldData = data[0]; + var newData = data[1]; - if (!currentState[name]) { - this._unhandledEvent(currentState, name, context); + if (oldData === newData) { + delete attrs[attribute]; + } + } } - return currentState[name](this, context); - }, + /* + Returns an object, whose keys are changed properties, and value is an + [oldProp, newProp] array. + @method changedAttributes + @private + */ + }, { + key: "changedAttributes", + value: function changedAttributes() { + var oldData = this._data; + var currentData = this._attributes; + var inFlightData = this._inFlightAttributes; + var newData = assign(copy(inFlightData), currentData); + var diffData = new _emberDataPrivateSystemEmptyObject.default(); + var newDataKeys = Object.keys(newData); - notifyHasManyAdded: function (key, record, idx) { - if (this.record) { - this.record.notifyHasManyAdded(key, record, idx); - } - }, + for (var i = 0, _length2 = newDataKeys.length; i < _length2; i++) { + var key = newDataKeys[i]; + diffData[key] = [oldData[key], newData[key]]; + } - notifyHasManyRemoved: function (key, record, idx) { - if (this.record) { - this.record.notifyHasManyRemoved(key, record, idx); + return diffData; } - }, - notifyBelongsToChanged: function (key, record) { - if (this.record) { - this.record.notifyBelongsToChanged(key, record); + /* + @method adapterWillCommit + @private + */ + }, { + key: "adapterWillCommit", + value: function adapterWillCommit() { + this.send('willCommit'); } - }, - notifyPropertyChange: function (key) { - if (this.record) { - this.record.notifyPropertyChange(key); + /* + @method adapterDidDirty + @private + */ + }, { + key: "adapterDidDirty", + value: function adapterDidDirty() { + this.send('becomeDirty'); + this.updateRecordArraysLater(); } - }, - rollbackAttributes: function () { - var dirtyKeys = Object.keys(this._attributes); + /* + @method send + @private + @param {String} name + @param {Object} context + */ + }, { + key: "send", + value: function send(name, context) { + var currentState = this.currentState; - this._attributes = new _emberDataPrivateSystemEmptyObject.default(); + if (!currentState[name]) { + this._unhandledEvent(currentState, name, context); + } - if (get(this, 'isError')) { - this._inFlightAttributes = new _emberDataPrivateSystemEmptyObject.default(); - this.didCleanError(); + return currentState[name](this, context); } - - //Eventually rollback will always work for relationships - //For now we support it only out of deleted state, because we - //have an explicit way of knowing when the server acked the relationship change - if (this.isDeleted()) { - //TODO: Should probably move this to the state machine somehow - this.becameReady(); + }, { + key: "notifyHasManyAdded", + value: function notifyHasManyAdded(key, record, idx) { + if (this.hasRecord) { + this.record.notifyHasManyAdded(key, record, idx); + } } - - if (this.isNew()) { - this.clearRelationships(); + }, { + key: "notifyHasManyRemoved", + value: function notifyHasManyRemoved(key, record, idx) { + if (this.hasRecord) { + this.record.notifyHasManyRemoved(key, record, idx); + } } - - if (this.isValid()) { - this._inFlightAttributes = new _emberDataPrivateSystemEmptyObject.default(); + }, { + key: "notifyBelongsToChanged", + value: function notifyBelongsToChanged(key, record) { + if (this.hasRecord) { + this.record.notifyBelongsToChanged(key, record); + } } + }, { + key: "notifyPropertyChange", + value: function notifyPropertyChange(key) { + if (this.hasRecord) { + this.record.notifyPropertyChange(key); + } + } + }, { + key: "rollbackAttributes", + value: function rollbackAttributes() { + var dirtyKeys = Object.keys(this._attributes); - this.send('rolledBack'); + this._attributes = new _emberDataPrivateSystemEmptyObject.default(); - this.record._notifyProperties(dirtyKeys); - }, + if (get(this, 'isError')) { + this._inFlightAttributes = new _emberDataPrivateSystemEmptyObject.default(); + this.didCleanError(); + } - /* - @method transitionTo - @private - @param {String} name - */ - transitionTo: function (name) { - // POSSIBLE TODO: Remove this code and replace with - // always having direct reference to state objects + //Eventually rollback will always work for relationships + //For now we support it only out of deleted state, because we + //have an explicit way of knowing when the server acked the relationship change + if (this.isDeleted()) { + //TODO: Should probably move this to the state machine somehow + this.becameReady(); + } - var pivotName = extractPivotName(name); - var currentState = get(this, 'currentState'); - var state = currentState; - - do { - if (state.exit) { - state.exit(this); + if (this.isNew()) { + this.clearRelationships(); } - state = state.parentState; - } while (!state.hasOwnProperty(pivotName)); - var path = splitOnDot(name); - var setups = []; - var enters = []; - var i, l; - - for (i = 0, l = path.length; i < l; i++) { - state = state[path[i]]; - - if (state.enter) { - enters.push(state); + if (this.isValid()) { + this._inFlightAttributes = new _emberDataPrivateSystemEmptyObject.default(); } - if (state.setup) { - setups.push(state); - } - } - for (i = 0, l = enters.length; i < l; i++) { - enters[i].enter(this); - } + this.send('rolledBack'); - set(this, 'currentState', state); - //TODO Consider whether this is the best approach for keeping these two in sync - if (this.record) { - set(this.record, 'currentState', state); + this.record._notifyProperties(dirtyKeys); } - for (i = 0, l = setups.length; i < l; i++) { - setups[i].setup(this); - } + /* + @method transitionTo + @private + @param {String} name + */ + }, { + key: "transitionTo", + value: function transitionTo(name) { + // POSSIBLE TODO: Remove this code and replace with + // always having direct reference to state objects - this.updateRecordArraysLater(); - }, + var pivotName = extractPivotName(name); + var state = this.currentState; + var transitionMapId = state.stateName + "->" + name; - _unhandledEvent: function (state, name, context) { - var errorMessage = "Attempted to handle event `" + name + "` "; - errorMessage += "on " + String(this) + " while in state "; - errorMessage += state.stateName + ". "; + do { + if (state.exit) { + state.exit(this); + } + state = state.parentState; + } while (!state[pivotName]); - if (context !== undefined) { - errorMessage += "Called with " + _ember.default.inspect(context) + "."; - } + var setups = undefined; + var enters = undefined; + var i = undefined; + var l = undefined; + var map = TransitionChainMap[transitionMapId]; - throw new _ember.default.Error(errorMessage); - }, + if (map) { + setups = map.setups; + enters = map.enters; + state = map.state; + } else { + setups = []; + enters = []; - triggerLater: function () { - var length = arguments.length; - var args = new Array(length); + var path = splitOnDot(name); - for (var i = 0; i < length; i++) { - args[i] = arguments[i]; - } + for (i = 0, l = path.length; i < l; i++) { + state = state[path[i]]; - if (this._deferredTriggers.push(args) !== 1) { - return; - } - _ember.default.run.scheduleOnce('actions', this, '_triggerDeferredTriggers'); - }, + if (state.enter) { + enters.push(state); + } + if (state.setup) { + setups.push(state); + } + } - _triggerDeferredTriggers: function () { - //TODO: Before 1.0 we want to remove all the events that happen on the pre materialized record, - //but for now, we queue up all the events triggered before the record was materialized, and flush - //them once we have the record - if (!this.record) { - return; - } - for (var i = 0, l = this._deferredTriggers.length; i < l; i++) { - this.record.trigger.apply(this.record, this._deferredTriggers[i]); - } + TransitionChainMap[transitionMapId] = { setups: setups, enters: enters, state: state }; + } - this._deferredTriggers.length = 0; - }, - /* - @method clearRelationships - @private - */ - clearRelationships: function () { - var _this = this; + for (i = 0, l = enters.length; i < l; i++) { + enters[i].enter(this); + } - this.eachRelationship(function (name, relationship) { - if (_this._relationships.has(name)) { - var rel = _this._relationships.get(name); - rel.clear(); - rel.destroy(); + this.currentState = state; + if (this.hasRecord) { + set(this.record, 'currentState', state); } - }); - Object.keys(this._implicitRelationships).forEach(function (key) { - _this._implicitRelationships[key].clear(); - _this._implicitRelationships[key].destroy(); - }); - }, - /* - When a find request is triggered on the store, the user can optionally pass in - attributes and relationships to be preloaded. These are meant to behave as if they - came back from the server, except the user obtained them out of band and is informing - the store of their existence. The most common use case is for supporting client side - nested URLs, such as `/posts/1/comments/2` so the user can do - `store.findRecord('comment', 2, { preload: { post: 1 } })` without having to fetch the post. - Preloaded data can be attributes and relationships passed in either as IDs or as actual - models. - @method _preloadData - @private - @param {Object} preload - */ - _preloadData: function (preload) { - var _this2 = this; - - //TODO(Igor) consider the polymorphic case - Object.keys(preload).forEach(function (key) { - var preloadValue = get(preload, key); - var relationshipMeta = _this2.type.metaForProperty(key); - if (relationshipMeta.isRelationship) { - _this2._preloadRelationship(key, preloadValue); - } else { - _this2._data[key] = preloadValue; + for (i = 0, l = setups.length; i < l; i++) { + setups[i].setup(this); } - }); - }, - _preloadRelationship: function (key, preloadValue) { - var relationshipMeta = this.type.metaForProperty(key); - var type = relationshipMeta.type; - if (relationshipMeta.kind === 'hasMany') { - this._preloadHasMany(key, preloadValue, type); - } else { - this._preloadBelongsTo(key, preloadValue, type); + this.updateRecordArraysLater(); } - }, + }, { + key: "_unhandledEvent", + value: function _unhandledEvent(state, name, context) { + var errorMessage = "Attempted to handle event `" + name + "` "; + errorMessage += "on " + String(this) + " while in state "; + errorMessage += state.stateName + ". "; - _preloadHasMany: function (key, preloadValue, type) { - var recordsToSet = new Array(preloadValue.length); + if (context !== undefined) { + errorMessage += "Called with " + inspect(context) + "."; + } - for (var i = 0; i < preloadValue.length; i++) { - var recordToPush = preloadValue[i]; - recordsToSet[i] = this._convertStringOrNumberIntoInternalModel(recordToPush, type); + throw new EmberError(errorMessage); } + }, { + key: "triggerLater", + value: function triggerLater() { + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } - //We use the pathway of setting the hasMany as if it came from the adapter - //because the user told us that they know this relationships exists already - this._relationships.get(key).updateRecordsFromAdapter(recordsToSet); - }, - - _preloadBelongsTo: function (key, preloadValue, type) { - var recordToSet = this._convertStringOrNumberIntoInternalModel(preloadValue, type); - - //We use the pathway of setting the hasMany as if it came from the adapter - //because the user told us that they know this relationships exists already - this._relationships.get(key).setRecord(recordToSet); - }, - - _convertStringOrNumberIntoInternalModel: function (value, type) { - if (typeof value === 'string' || typeof value === 'number') { - return this.store._internalModelForId(type, value); + if (this._deferredTriggers.push(args) !== 1) { + return; + } + emberRun.schedule('actions', this, this._triggerDeferredTriggers); } - if (value._internalModel) { - return value._internalModel; - } - return value; - }, + }, { + key: "_triggerDeferredTriggers", + value: function _triggerDeferredTriggers() { + //TODO: Before 1.0 we want to remove all the events that happen on the pre materialized record, + //but for now, we queue up all the events triggered before the record was materialized, and flush + //them once we have the record + if (!this.hasRecord) { + return; + } + for (var i = 0, l = this._deferredTriggers.length; i < l; i++) { + this.record.trigger.apply(this.record, this._deferredTriggers[i]); + } - /* - @method updateRecordArrays - @private - */ - updateRecordArrays: function () { - this._updatingRecordArraysLater = false; - this.store.dataWasUpdated(this.type, this); - }, - - setId: function (id) { - this.id = id; - if (this.record.get('id') !== id) { - this.record.set('id', id); + this._deferredTriggers.length = 0; } - }, - didError: function (error) { - this.error = error; - this.isError = true; + /* + @method clearRelationships + @private + */ + }, { + key: "clearRelationships", + value: function clearRelationships() { + var _this = this; - if (this.record) { - this.record.setProperties({ - isError: true, - adapterError: error + this.eachRelationship(function (name, relationship) { + if (_this._relationships.has(name)) { + var rel = _this._relationships.get(name); + rel.clear(); + rel.destroy(); + } }); + Object.keys(this._implicitRelationships).forEach(function (key) { + _this._implicitRelationships[key].clear(); + _this._implicitRelationships[key].destroy(); + }); } - }, - didCleanError: function () { - this.error = null; - this.isError = false; + /* + When a find request is triggered on the store, the user can optionally pass in + attributes and relationships to be preloaded. These are meant to behave as if they + came back from the server, except the user obtained them out of band and is informing + the store of their existence. The most common use case is for supporting client side + nested URLs, such as `/posts/1/comments/2` so the user can do + `store.findRecord('comment', 2, { preload: { post: 1 } })` without having to fetch the post. + Preloaded data can be attributes and relationships passed in either as IDs or as actual + models. + @method preloadData + @private + @param {Object} preload + */ + }, { + key: "preloadData", + value: function preloadData(preload) { + var _this2 = this; - if (this.record) { - this.record.setProperties({ - isError: false, - adapterError: null + //TODO(Igor) consider the polymorphic case + Object.keys(preload).forEach(function (key) { + var preloadValue = get(preload, key); + var relationshipMeta = _this2.modelClass.metaForProperty(key); + if (relationshipMeta.isRelationship) { + _this2._preloadRelationship(key, preloadValue); + } else { + _this2._data[key] = preloadValue; + } }); } - }, - /* - If the adapter did not return a hash in response to a commit, - merge the changed attributes and relationships into the existing - saved data. - @method adapterDidCommit - */ - adapterDidCommit: function (data) { - if (data) { - data = data.attributes; + }, { + key: "_preloadRelationship", + value: function _preloadRelationship(key, preloadValue) { + var relationshipMeta = this.modelClass.metaForProperty(key); + var modelClass = relationshipMeta.type; + if (relationshipMeta.kind === 'hasMany') { + this._preloadHasMany(key, preloadValue, modelClass); + } else { + this._preloadBelongsTo(key, preloadValue, modelClass); + } } + }, { + key: "_preloadHasMany", + value: function _preloadHasMany(key, preloadValue, modelClass) { + var recordsToSet = new Array(preloadValue.length); - this.didCleanError(); - var changedKeys = this._changedKeys(data); + for (var i = 0; i < preloadValue.length; i++) { + var recordToPush = preloadValue[i]; + recordsToSet[i] = this._convertStringOrNumberIntoInternalModel(recordToPush, modelClass); + } - assign(this._data, this._inFlightAttributes); - if (data) { - assign(this._data, data); + //We use the pathway of setting the hasMany as if it came from the adapter + //because the user told us that they know this relationships exists already + this._relationships.get(key).updateRecordsFromAdapter(recordsToSet); } + }, { + key: "_preloadBelongsTo", + value: function _preloadBelongsTo(key, preloadValue, modelClass) { + var recordToSet = this._convertStringOrNumberIntoInternalModel(preloadValue, modelClass); - this._inFlightAttributes = new _emberDataPrivateSystemEmptyObject.default(); + //We use the pathway of setting the hasMany as if it came from the adapter + //because the user told us that they know this relationships exists already + this._relationships.get(key).setRecord(recordToSet); + } + }, { + key: "_convertStringOrNumberIntoInternalModel", + value: function _convertStringOrNumberIntoInternalModel(value, modelClass) { + if (typeof value === 'string' || typeof value === 'number') { + return this.store._internalModelForId(modelClass, value); + } + if (value._internalModel) { + return value._internalModel; + } + return value; + } - this.send('didCommit'); - this.updateRecordArraysLater(); + /* + @method updateRecordArrays + @private + */ + }, { + key: "updateRecordArrays", + value: function updateRecordArrays() { + this._updatingRecordArraysLater = false; + this.store.dataWasUpdated(this.modelClass, this); + } + }, { + key: "setId", + value: function setId(id) { + this.id = id; + if (this.record.get('id') !== id) { + this.record.set('id', id); + } + } + }, { + key: "didError", + value: function didError(error) { + this.error = error; + this.isError = true; - if (!data) { - return; + if (this.hasRecord) { + this.record.setProperties({ + isError: true, + adapterError: error + }); + } } + }, { + key: "didCleanError", + value: function didCleanError() { + this.error = null; + this.isError = false; - this.record._notifyProperties(changedKeys); - }, - - /* - @method updateRecordArraysLater - @private - */ - updateRecordArraysLater: function () { - // quick hack (something like this could be pushed into run.once - if (this._updatingRecordArraysLater) { - return; + if (this.hasRecord) { + this.record.setProperties({ + isError: false, + adapterError: null + }); + } } - this._updatingRecordArraysLater = true; - _ember.default.run.schedule('actions', this, this.updateRecordArrays); - }, - addErrorMessageToAttribute: function (attribute, message) { - var record = this.getRecord(); - get(record, 'errors')._add(attribute, message); - }, + /* + If the adapter did not return a hash in response to a commit, + merge the changed attributes and relationships into the existing + saved data. + @method adapterDidCommit + */ + }, { + key: "adapterDidCommit", + value: function adapterDidCommit(data) { + if (data) { + data = data.attributes; + } - removeErrorMessageFromAttribute: function (attribute) { - var record = this.getRecord(); - get(record, 'errors')._remove(attribute); - }, + this.didCleanError(); + var changedKeys = this._changedKeys(data); - clearErrorMessages: function () { - var record = this.getRecord(); - get(record, 'errors')._clear(); - }, + assign(this._data, this._inFlightAttributes); + if (data) { + assign(this._data, data); + } - hasErrors: function () { - var record = this.getRecord(); - var errors = get(record, 'errors'); + this._inFlightAttributes = new _emberDataPrivateSystemEmptyObject.default(); - return !_ember.default.isEmpty(errors); - }, + this.send('didCommit'); + this.updateRecordArraysLater(); - // FOR USE DURING COMMIT PROCESS + if (!data) { + return; + } - /* - @method adapterDidInvalidate - @private - */ - adapterDidInvalidate: function (errors) { - var attribute; + this.record._notifyProperties(changedKeys); + } - for (attribute in errors) { - if (errors.hasOwnProperty(attribute)) { - this.addErrorMessageToAttribute(attribute, errors[attribute]); + /* + @method updateRecordArraysLater + @private + */ + }, { + key: "updateRecordArraysLater", + value: function updateRecordArraysLater() { + // quick hack (something like this could be pushed into run.once + if (this._updatingRecordArraysLater) { + return; } + this._updatingRecordArraysLater = true; + emberRun.schedule('actions', this, this.updateRecordArrays); } + }, { + key: "addErrorMessageToAttribute", + value: function addErrorMessageToAttribute(attribute, message) { + get(this.getRecord(), 'errors')._add(attribute, message); + } + }, { + key: "removeErrorMessageFromAttribute", + value: function removeErrorMessageFromAttribute(attribute) { + get(this.getRecord(), 'errors')._remove(attribute); + } + }, { + key: "clearErrorMessages", + value: function clearErrorMessages() { + get(this.getRecord(), 'errors')._clear(); + } + }, { + key: "hasErrors", + value: function hasErrors() { + var errors = get(this.getRecord(), 'errors'); - this.send('becameInvalid'); + return !isEmpty(errors); + } - this._saveWasRejected(); - }, + // FOR USE DURING COMMIT PROCESS - /* - @method adapterDidError - @private - */ - adapterDidError: function (error) { - this.send('becameError'); - this.didError(error); - this._saveWasRejected(); - }, + /* + @method adapterDidInvalidate + @private + */ + }, { + key: "adapterDidInvalidate", + value: function adapterDidInvalidate(errors) { + var attribute = undefined; - _saveWasRejected: function () { - var keys = Object.keys(this._inFlightAttributes); - for (var i = 0; i < keys.length; i++) { - if (this._attributes[keys[i]] === undefined) { - this._attributes[keys[i]] = this._inFlightAttributes[keys[i]]; + for (attribute in errors) { + if (errors.hasOwnProperty(attribute)) { + this.addErrorMessageToAttribute(attribute, errors[attribute]); + } } + + this.send('becameInvalid'); + + this._saveWasRejected(); } - this._inFlightAttributes = new _emberDataPrivateSystemEmptyObject.default(); - }, - /* - Ember Data has 3 buckets for storing the value of an attribute on an internalModel. - `_data` holds all of the attributes that have been acknowledged by - a backend via the adapter. When rollbackAttributes is called on a model all - attributes will revert to the record's state in `_data`. - `_attributes` holds any change the user has made to an attribute - that has not been acknowledged by the adapter. Any values in - `_attributes` are have priority over values in `_data`. - `_inFlightAttributes`. When a record is being synced with the - backend the values in `_attributes` are copied to - `_inFlightAttributes`. This way if the backend acknowledges the - save but does not return the new state Ember Data can copy the - values from `_inFlightAttributes` to `_data`. Without having to - worry about changes made to `_attributes` while the save was - happenign. - Changed keys builds a list of all of the values that may have been - changed by the backend after a successful save. - It does this by iterating over each key, value pair in the payload - returned from the server after a save. If the `key` is found in - `_attributes` then the user has a local changed to the attribute - that has not been synced with the server and the key is not - included in the list of changed keys. - - If the value, for a key differs from the value in what Ember Data - believes to be the truth about the backend state (A merger of the - `_data` and `_inFlightAttributes` objects where - `_inFlightAttributes` has priority) then that means the backend - has updated the value and the key is added to the list of changed - keys. - @method _changedKeys - @private - */ - _changedKeys: function (updates) { - var changedKeys = []; + /* + @method adapterDidError + @private + */ + }, { + key: "adapterDidError", + value: function adapterDidError(error) { + this.send('becameError'); + this.didError(error); + this._saveWasRejected(); + } + }, { + key: "_saveWasRejected", + value: function _saveWasRejected() { + var keys = Object.keys(this._inFlightAttributes); + var attrs = this._attributes; + for (var i = 0; i < keys.length; i++) { + if (attrs[keys[i]] === undefined) { + attrs[keys[i]] = this._inFlightAttributes[keys[i]]; + } + } + this._inFlightAttributes = new _emberDataPrivateSystemEmptyObject.default(); + } - if (updates) { - var original, i, value, key; - var keys = Object.keys(updates); - var length = keys.length; + /* + Ember Data has 3 buckets for storing the value of an attribute on an internalModel. + `_data` holds all of the attributes that have been acknowledged by + a backend via the adapter. When rollbackAttributes is called on a model all + attributes will revert to the record's state in `_data`. + `_attributes` holds any change the user has made to an attribute + that has not been acknowledged by the adapter. Any values in + `_attributes` are have priority over values in `_data`. + `_inFlightAttributes`. When a record is being synced with the + backend the values in `_attributes` are copied to + `_inFlightAttributes`. This way if the backend acknowledges the + save but does not return the new state Ember Data can copy the + values from `_inFlightAttributes` to `_data`. Without having to + worry about changes made to `_attributes` while the save was + happenign. + Changed keys builds a list of all of the values that may have been + changed by the backend after a successful save. + It does this by iterating over each key, value pair in the payload + returned from the server after a save. If the `key` is found in + `_attributes` then the user has a local changed to the attribute + that has not been synced with the server and the key is not + included in the list of changed keys. + + If the value, for a key differs from the value in what Ember Data + believes to be the truth about the backend state (A merger of the + `_data` and `_inFlightAttributes` objects where + `_inFlightAttributes` has priority) then that means the backend + has updated the value and the key is added to the list of changed + keys. + @method _changedKeys + @private + */ + }, { + key: "_changedKeys", + value: function _changedKeys(updates) { + var changedKeys = []; - original = assign(new _emberDataPrivateSystemEmptyObject.default(), this._data); - original = assign(original, this._inFlightAttributes); + if (updates) { + var original = undefined, + i = undefined, + value = undefined, + key = undefined; + var keys = Object.keys(updates); + var _length3 = keys.length; + var attrs = this._attributes; - for (i = 0; i < length; i++) { - key = keys[i]; - value = updates[key]; + original = assign(new _emberDataPrivateSystemEmptyObject.default(), this._data); + original = assign(original, this._inFlightAttributes); - // A value in _attributes means the user has a local change to - // this attributes. We never override this value when merging - // updates from the backend so we should not sent a change - // notification if the server value differs from the original. - if (this._attributes[key] !== undefined) { - continue; - } + for (i = 0; i < _length3; i++) { + key = keys[i]; + value = updates[key]; - if (!_ember.default.isEqual(original[key], value)) { - changedKeys.push(key); + // A value in _attributes means the user has a local change to + // this attributes. We never override this value when merging + // updates from the backend so we should not sent a change + // notification if the server value differs from the original. + if (attrs[key] !== undefined) { + continue; + } + + if (!isEqual(original[key], value)) { + changedKeys.push(key); + } } } - } - return changedKeys; - }, - - toString: function () { - if (this.record) { - return this.record.toString(); - } else { + return changedKeys; + } + }, { + key: "toString", + value: function toString() { return "<" + this.modelName + ":" + this.id + ">"; } - }, + }, { + key: "referenceFor", + value: function referenceFor(kind, name) { + var reference = this.references[name]; - referenceFor: function (type, name) { - var reference = this.references[name]; + if (!reference) { + var relationship = this._relationships.get(name); - if (!reference) { - var relationship = this._relationships.get(name); + if (kind === "belongsTo") { + reference = new _emberDataPrivateSystemReferences.BelongsToReference(this.store, this, relationship); + } else if (kind === "hasMany") { + reference = new _emberDataPrivateSystemReferences.HasManyReference(this.store, this, relationship); + } - if (type === "belongsTo") { - reference = new _emberDataPrivateSystemReferences.BelongsToReference(this.store, this, relationship); - } else if (type === "hasMany") { - reference = new _emberDataPrivateSystemReferences.HasManyReference(this.store, this, relationship); + this.references[name] = reference; } - this.references[name] = reference; + return reference; } + }, { + key: "type", + get: function () { + return this.modelClass; + } + }, { + key: "recordReference", + get: function () { + if (this._recordReference === null) { + this._recordReference = new _emberDataPrivateSystemReferences.RecordReference(this.store, this); + } + return this._recordReference; + } + }, { + key: "references", + get: function () { + if (this._references === null) { + this._references = new _emberDataPrivateSystemEmptyObject.default(); + } + return this._references; + } + }, { + key: "_deferredTriggers", + get: function () { + if (this.__deferredTriggers === null) { + this.__deferredTriggers = []; + } + return this.__deferredTriggers; + } + }, { + key: "_attributes", + get: function () { + if (this.__attributes === null) { + this.__attributes = new _emberDataPrivateSystemEmptyObject.default(); + } + return this.__attributes; + }, + set: function (v) { + this.__attributes = v; + } + }, { + key: "_relationships", + get: function () { + if (this.__relationships === null) { + this.__relationships = new _emberDataPrivateSystemRelationshipsStateCreate.default(this); + } - return reference; - } - }; + return this.__relationships; + } + }, { + key: "_inFlightAttributes", + get: function () { + if (this.__inFlightAttributes === null) { + this.__inFlightAttributes = new _emberDataPrivateSystemEmptyObject.default(); + } + return this.__inFlightAttributes; + }, + set: function (v) { + this.__inFlightAttributes = v; + } + /* + implicit relationships are relationship which have not been declared but the inverse side exists on + another record somewhere + For example if there was + ```app/models/comment.js + import DS from 'ember-data'; + export default DS.Model.extend({ + name: DS.attr() + }) + ``` + but there is also + ```app/models/post.js + import DS from 'ember-data'; + export default DS.Model.extend({ + name: DS.attr(), + comments: DS.hasMany('comment') + }) + ``` + would have a implicit post relationship in order to be do things like remove ourselves from the post + when we are deleted + */ + }, { + key: "_implicitRelationships", + get: function () { + if (this.__implicitRelationships === null) { + this.__implicitRelationships = new _emberDataPrivateSystemEmptyObject.default(); + } + return this.__implicitRelationships; + } + }, { + key: "record", + get: function () { + return this._record; + } + }, { + key: "isDestroyed", + get: function () { + return this._isDestroyed; + } + }, { + key: "hasRecord", + get: function () { + return !!this._record; + } + }]); + + return InternalModel; + })(); + + exports.default = InternalModel; + if (false) { /* Returns the latest truth for an attribute - the canonical value, or the in-flight value. @method lastAcknowledgedValue @@ -3038,18 +3276,18 @@ return this._data[key]; } }; } }); -define("ember-data/-private/system/model/model", ["exports", "ember", "ember-data/-private/debug", "ember-data/-private/system/promise-proxies", "ember-data/-private/system/model/errors", "ember-data/-private/system/debug/debug-info", "ember-data/-private/system/relationships/belongs-to", "ember-data/-private/system/relationships/has-many", "ember-data/-private/system/relationships/ext", "ember-data/-private/system/model/attr", "ember-data/-private/features"], function (exports, _ember, _emberDataPrivateDebug, _emberDataPrivateSystemPromiseProxies, _emberDataPrivateSystemModelErrors, _emberDataPrivateSystemDebugDebugInfo, _emberDataPrivateSystemRelationshipsBelongsTo, _emberDataPrivateSystemRelationshipsHasMany, _emberDataPrivateSystemRelationshipsExt, _emberDataPrivateSystemModelAttr, _emberDataPrivateFeatures) { +define("ember-data/-private/system/model/model", ["exports", "ember", "ember-data/-private/debug", "ember-data/-private/system/promise-proxies", "ember-data/-private/system/model/errors", "ember-data/-private/system/debug/debug-info", "ember-data/-private/system/relationships/belongs-to", "ember-data/-private/system/relationships/has-many", "ember-data/-private/system/relationships/ext", "ember-data/-private/system/model/attr", "ember-data/-private/features", "ember-data/-private/system/model/states"], function (exports, _ember, _emberDataPrivateDebug, _emberDataPrivateSystemPromiseProxies, _emberDataPrivateSystemModelErrors, _emberDataPrivateSystemDebugDebugInfo, _emberDataPrivateSystemRelationshipsBelongsTo, _emberDataPrivateSystemRelationshipsHasMany, _emberDataPrivateSystemRelationshipsExt, _emberDataPrivateSystemModelAttr, _emberDataPrivateFeatures, _emberDataPrivateSystemModelStates) { + var get = _ember.default.get; + var computed = _ember.default.computed; /** @module ember-data */ - var get = _ember.default.get; - function intersection(array1, array2) { var result = []; array1.forEach(function (element) { if (array2.indexOf(element) >= 0) { result.push(element); @@ -3059,11 +3297,11 @@ return result; } var RESERVED_MODEL_PROPS = ['currentState', 'data', 'store']; - var retrieveFromCurrentState = _ember.default.computed('currentState', function (key) { + var retrieveFromCurrentState = computed('currentState', function (key) { return get(this._internalModel.currentState, key); }).readOnly(); /** @@ -3141,11 +3379,11 @@ @since 1.13.0 @property hasDirtyAttributes @type {Boolean} @readOnly */ - hasDirtyAttributes: _ember.default.computed('currentState.isDirty', function () { + hasDirtyAttributes: computed('currentState.isDirty', function () { return this.get('currentState.isDirty'); }), /** If this property is `true` the record is in the `saving` state. A record enters the saving state when `save` is called, but the @@ -3296,10 +3534,11 @@ /** @property currentState @private @type {Object} */ + currentState: _emberDataPrivateSystemModelStates.default.empty, /** When the record is in the `invalid` state this object will contain any errors returned by the adapter. When present the errors hash contains keys corresponding to the invalid property names @@ -3340,11 +3579,11 @@ {{/each}} ``` @property errors @type {DS.Errors} */ - errors: _ember.default.computed(function () { + errors: computed(function () { var errors = _emberDataPrivateSystemModelErrors.default.create(); errors._registerHandlers(this._internalModel, function () { this.send('becameInvalid'); }, function () { @@ -3371,11 +3610,11 @@ @method serialize @param {Object} options @return {Object} an object whose values are primitive JSON values only */ serialize: function (options) { - return this.store.serialize(this, options); + return this._internalModel.createSnapshot().serialize(options); }, /** Use [DS.JSONSerializer](DS.JSONSerializer.html) to get the JSON representation of a record. @@ -3398,53 +3637,53 @@ /** Fired when the record is ready to be interacted with, that is either loaded from the server or created locally. @event ready */ - ready: _ember.default.K, + ready: function () {}, /** Fired when the record is loaded from the server. @event didLoad */ - didLoad: _ember.default.K, + didLoad: function () {}, /** Fired when the record is updated. @event didUpdate */ - didUpdate: _ember.default.K, + didUpdate: function () {}, /** Fired when a new record is commited to the server. @event didCreate */ - didCreate: _ember.default.K, + didCreate: function () {}, /** Fired when the record is deleted. @event didDelete */ - didDelete: _ember.default.K, + didDelete: function () {}, /** Fired when the record becomes invalid. @event becameInvalid */ - becameInvalid: _ember.default.K, + becameInvalid: function () {}, /** Fired when the record enters the error state. @event becameError */ - becameError: _ember.default.K, + becameError: function () {}, /** Fired when the record is rolled back. @event rolledBack */ - rolledBack: _ember.default.K, + rolledBack: function () {}, //TODO Do we want to deprecate these? /** @method send @private @@ -3762,11 +4001,13 @@ }); var blog = store.push({ type: 'blog', id: 1, relationships: { - user: { type: 'user', id: 1 } + user: { + data: { type: 'user', id: 1 } + } } }); var userRef = blog.belongsTo('user'); // check if the user relationship is loaded var isLoaded = userRef.value() !== null; @@ -3959,13 +4200,12 @@ Model.reopenClass(_emberDataPrivateSystemRelationshipsExt.RelationshipsClassMethodsMixin); Model.reopenClass(_emberDataPrivateSystemModelAttr.AttrClassMethodsMixin); exports.default = Model.extend(_emberDataPrivateSystemDebugDebugInfo.default, _emberDataPrivateSystemRelationshipsBelongsTo.BelongsToMixin, _emberDataPrivateSystemRelationshipsExt.DidDefinePropertyMixin, _emberDataPrivateSystemRelationshipsExt.RelationshipsInstanceMethodsMixin, _emberDataPrivateSystemRelationshipsHasMany.HasManyMixin, _emberDataPrivateSystemModelAttr.AttrInstanceMethodsMixin); }); -define('ember-data/-private/system/model/states', ['exports', 'ember', 'ember-data/-private/debug'], function (exports, _ember, _emberDataPrivateDebug) { +define('ember-data/-private/system/model/states', ['exports', 'ember-data/-private/debug'], function (exports, _emberDataPrivateDebug) { - var get = _ember.default.get; /* This file encapsulates the various states that a record can transition through during its lifecycle. */ /** @@ -4121,11 +4361,11 @@ * [isEmpty](DS.Model.html#property_isEmpty) * [isLoading](DS.Model.html#property_isLoading) * [isLoaded](DS.Model.html#property_isLoaded) - * [isDirty](DS.Model.html#property_isDirty) + * [hasDirtyAttributes](DS.Model.html#property_hasDirtyAttributes) * [isSaving](DS.Model.html#property_isSaving) * [isDeleted](DS.Model.html#property_isDeleted) * [isNew](DS.Model.html#property_isNew) * [isValid](DS.Model.html#property_isValid) @@ -4198,11 +4438,11 @@ // EVENTS didSetProperty: didSetProperty, //TODO(Igor) reloading now triggers a //loadingData event, though it seems fine? - loadingData: _ember.default.K, + loadingData: function () {}, propertyWasReset: function (internalModel, name) { if (!internalModel.hasChangedAttributes()) { internalModel.send('rolledBack'); } @@ -4214,11 +4454,11 @@ if (!internalModel.hasChangedAttributes()) { internalModel.transitionTo('loaded.saved'); } }, - becomeDirty: _ember.default.K, + becomeDirty: function () {}, willCommit: function (internalModel) { internalModel.transitionTo('inFlight'); }, @@ -4247,23 +4487,21 @@ // FLAGS isSaving: true, // EVENTS didSetProperty: didSetProperty, - becomeDirty: _ember.default.K, - pushedData: _ember.default.K, + becomeDirty: function () {}, + pushedData: function () {}, unloadRecord: assertAgainstUnloadRecord, // TODO: More robust semantics around save-while-in-flight - willCommit: _ember.default.K, + willCommit: function () {}, didCommit: function (internalModel) { - var dirtyType = get(this, 'dirtyType'); - internalModel.transitionTo('saved'); - internalModel.send('invokeLifecycleCallbacks', dirtyType); + internalModel.send('invokeLifecycleCallbacks', this.dirtyType); }, becameInvalid: function (internalModel) { internalModel.transitionTo('invalid'); internalModel.send('invokeLifecycleCallbacks'); @@ -4294,13 +4532,13 @@ if (!internalModel.hasErrors()) { this.becameValid(internalModel); } }, - becameInvalid: _ember.default.K, - becomeDirty: _ember.default.K, - pushedData: _ember.default.K, + becameInvalid: function () {}, + becomeDirty: function () {}, + pushedData: function () {}, willCommit: function (internalModel) { internalModel.clearErrorMessages(); internalModel.transitionTo('inFlight'); }, @@ -4325,11 +4563,11 @@ // chart so we can reopen their substates and add mixins as // necessary. function deepClone(object) { var clone = {}; - var value; + var value = undefined; for (var prop in object) { value = object[prop]; if (value && typeof value === 'object') { clone[prop] = deepClone(value); @@ -4361,10 +4599,11 @@ }); createdState.invalid.rolledBack = function (internalModel) { internalModel.transitionTo('deleted.saved'); }; + createdState.uncommitted.rolledBack = function (internalModel) { internalModel.transitionTo('deleted.saved'); }; var updatedState = dirtyState({ @@ -4388,11 +4627,11 @@ createdState.uncommitted.pushedData = function (internalModel) { internalModel.transitionTo('loaded.updated.uncommitted'); internalModel.triggerLater('didLoad'); }; - createdState.uncommitted.propertyWasReset = _ember.default.K; + createdState.uncommitted.propertyWasReset = function () {}; function assertAgainstUnloadRecord(internalModel) {} updatedState.inFlight.unloadRecord = assertAgainstUnloadRecord; @@ -4415,19 +4654,19 @@ // Trying to roll back if you're not in the dirty state // doesn't change your state. For example, if you're in the // in-flight state, rolling back the record doesn't move // you out of the in-flight state. - rolledBack: _ember.default.K, + rolledBack: function () {}, unloadRecord: function (internalModel) { // clear relationships before moving to deleted state // otherwise it fails internalModel.clearRelationships(); internalModel.transitionTo('deleted.saved'); }, - propertyWasReset: _ember.default.K, + propertyWasReset: function () {}, // SUBSTATES // A record begins its lifecycle in the `empty` state. // If its data will come from the adapter, it will @@ -4496,11 +4735,11 @@ // FLAGS isLoaded: true, //TODO(Igor) Reloading now triggers a loadingData event, //but it should be ok? - loadingData: _ember.default.K, + loadingData: function () {}, // SUBSTATES // If there are no local changes to a record, it remains // in the `saved` state. @@ -4512,11 +4751,11 @@ }, // EVENTS didSetProperty: didSetProperty, - pushedData: _ember.default.K, + pushedData: function () {}, becomeDirty: function (internalModel) { internalModel.transitionTo('updated.uncommitted'); }, @@ -4537,18 +4776,15 @@ // otherwise it fails internalModel.clearRelationships(); internalModel.transitionTo('deleted.saved'); }, - didCommit: function (internalModel) { - internalModel.send('invokeLifecycleCallbacks', get(internalModel, 'lastDirtyType')); - }, + didCommit: function () {}, // loaded.saved.notFound would be triggered by a failed // `reload()` on an unchanged record - notFound: _ember.default.K - + notFound: function () {} }, // A record is in this state after it has been locally // created but before the adapter has indicated that // it has been saved. @@ -4591,13 +4827,13 @@ rollback: function (internalModel) { internalModel.rollbackAttributes(); internalModel.triggerLater('ready'); }, - pushedData: _ember.default.K, - becomeDirty: _ember.default.K, - deleteRecord: _ember.default.K, + pushedData: function () {}, + becomeDirty: function () {}, + deleteRecord: function () {}, rolledBack: function (internalModel) { internalModel.transitionTo('loaded.saved'); internalModel.triggerLater('ready'); } @@ -4614,11 +4850,11 @@ // EVENTS unloadRecord: assertAgainstUnloadRecord, // TODO: More robust semantics around save-while-in-flight - willCommit: _ember.default.K, + willCommit: function () {}, didCommit: function (internalModel) { internalModel.transitionTo('saved'); internalModel.send('invokeLifecycleCallbacks'); }, @@ -4650,13 +4886,12 @@ invokeLifecycleCallbacks: function (internalModel) { internalModel.triggerLater('didDelete', internalModel); internalModel.triggerLater('didCommit', internalModel); }, - willCommit: _ember.default.K, - - didCommit: _ember.default.K + willCommit: function () {}, + didCommit: function () {} }, invalid: { isValid: false, @@ -4668,14 +4903,14 @@ if (!internalModel.hasErrors()) { this.becameValid(internalModel); } }, - becameInvalid: _ember.default.K, - becomeDirty: _ember.default.K, - deleteRecord: _ember.default.K, - willCommit: _ember.default.K, + becameInvalid: function () {}, + becomeDirty: function () {}, + deleteRecord: function () {}, + willCommit: function () {}, rolledBack: function (internalModel) { internalModel.clearErrorMessages(); internalModel.transitionTo('loaded.saved'); internalModel.triggerLater('ready'); @@ -4708,20 +4943,18 @@ for (var prop in object) { if (!object.hasOwnProperty(prop) || prop === 'parentState' || prop === 'stateName') { continue; } if (typeof object[prop] === 'object') { - object[prop] = wireState(object[prop], object, name + "." + prop); + object[prop] = wireState(object[prop], object, name + '.' + prop); } } return object; } - RootState = wireState(RootState, null, "root"); - - exports.default = RootState; + exports.default = wireState(RootState, null, 'root'); }); /** @module ember-data */ define('ember-data/-private/system/normalize-link', ['exports'], function (exports) { @@ -4810,13 +5043,16 @@ return this; }; }); define('ember-data/-private/system/promise-proxies', ['exports', 'ember', 'ember-data/-private/debug'], function (exports, _ember, _emberDataPrivateDebug) { - - var Promise = _ember.default.RSVP.Promise; + exports.promiseObject = promiseObject; + exports.promiseArray = promiseArray; + exports.proxyToContent = proxyToContent; + exports.promiseManyArray = promiseManyArray; var get = _ember.default.get; + var Promise = _ember.default.RSVP.Promise; /** A `PromiseArray` is an object that acts like both an `Ember.Array` and a promise. When the promise is resolved the resulting value will be set to the `PromiseArray`'s `content` property. This makes @@ -4845,10 +5081,11 @@ @extends Ember.ArrayProxy @uses Ember.PromiseProxyMixin */ var PromiseArray = _ember.default.ArrayProxy.extend(_ember.default.PromiseProxyMixin); + exports.PromiseArray = PromiseArray; /** A `PromiseObject` is an object that acts like both an `Ember.Object` and a promise. When the promise is resolved, then the resulting value will be set to the `PromiseObject`'s `content` property. This makes it easy to create data bindings with the `PromiseObject` that will @@ -4876,21 +5113,23 @@ @extends Ember.ObjectProxy @uses Ember.PromiseProxyMixin */ var PromiseObject = _ember.default.ObjectProxy.extend(_ember.default.PromiseProxyMixin); - var promiseObject = function (promise, label) { + exports.PromiseObject = PromiseObject; + + function promiseObject(promise, label) { return PromiseObject.create({ promise: Promise.resolve(promise, label) }); - }; + } - var promiseArray = function (promise, label) { + function promiseArray(promise, label) { return PromiseArray.create({ promise: Promise.resolve(promise, label) }); - }; + } /** A PromiseManyArray is a PromiseArray that also proxies certain method calls to the underlying manyArray. Right now we proxy: @@ -4908,12 +5147,13 @@ @extends Ember.ArrayProxy */ function proxyToContent(method) { return function () { - var content = get(this, 'content'); - return content[method].apply(content, arguments); + var _get; + + return (_get = get(this, 'content'))[method].apply(_get, arguments); }; } var PromiseManyArray = PromiseArray.extend({ reload: function () { @@ -4935,28 +5175,23 @@ off: proxyToContent('off'), has: proxyToContent('has') }); - var promiseManyArray = function (promise, label) { + exports.PromiseManyArray = PromiseManyArray; + + function promiseManyArray(promise, label) { return PromiseManyArray.create({ promise: Promise.resolve(promise, label) }); - }; - - exports.PromiseArray = PromiseArray; - exports.PromiseObject = PromiseObject; - exports.PromiseManyArray = PromiseManyArray; - exports.promiseArray = promiseArray; - exports.promiseObject = promiseObject; - exports.promiseManyArray = promiseManyArray; + } }); define("ember-data/-private/system/record-array-manager", ["exports", "ember", "ember-data/-private/system/record-arrays", "ember-data/-private/system/ordered-set"], function (exports, _ember, _emberDataPrivateSystemRecordArrays, _emberDataPrivateSystemOrderedSet) { + var get = _ember.default.get; var MapWithDefault = _ember.default.MapWithDefault; + var emberRun = _ember.default.run; - var get = _ember.default.get; - /** @class RecordArrayManager @namespace DS @private @extends Ember.Object @@ -4970,12 +5205,12 @@ return []; } }); this.liveRecordArrays = MapWithDefault.create({ - defaultValue: function (typeClass) { - return _this.createRecordArray(typeClass); + defaultValue: function (modelClass) { + return _this.createRecordArray(modelClass); } }); this.changedRecords = []; this._adapterPopulatedRecordArrays = []; @@ -4984,11 +5219,11 @@ recordDidChange: function (record) { if (this.changedRecords.push(record) !== 1) { return; } - _ember.default.run.schedule('actions', this, this.updateRecordArrays); + emberRun.schedule('actions', this, this.updateRecordArrays); }, recordArraysForRecord: function (record) { record._recordArrays = record._recordArrays || _emberDataPrivateSystemOrderedSet.default.create(); return record._recordArrays; @@ -5003,11 +5238,12 @@ */ updateRecordArrays: function () { var _this2 = this; this.changedRecords.forEach(function (internalModel) { - if (get(internalModel, 'record.isDestroyed') || get(internalModel, 'record.isDestroying') || get(internalModel, 'currentState.stateName') === 'root.deleted.saved') { + + if (internalModel.isDestroyed || internalModel.currentState.stateName === 'root.deleted.saved') { _this2._recordWasDeleted(internalModel); } else { _this2._recordWasChanged(internalModel); } }); @@ -5021,22 +5257,22 @@ if (!recordArrays) { return; } recordArrays.forEach(function (array) { - return array.removeInternalModel(record); + return array._removeInternalModels([record]); }); record._recordArrays = null; }, _recordWasChanged: function (record) { var _this3 = this; var typeClass = record.type; var recordArrays = this.filteredRecordArrays.get(typeClass); - var filter; + var filter = undefined; recordArrays.forEach(function (array) { filter = get(array, 'filterFunction'); _this3.updateFilterRecordArray(array, filter, typeClass, record); }); }, @@ -5045,59 +5281,78 @@ recordWasLoaded: function (record) { var _this4 = this; var typeClass = record.type; var recordArrays = this.filteredRecordArrays.get(typeClass); - var filter; + var filter = undefined; recordArrays.forEach(function (array) { filter = get(array, 'filterFunction'); _this4.updateFilterRecordArray(array, filter, typeClass, record); }); if (this.liveRecordArrays.has(typeClass)) { var liveRecordArray = this.liveRecordArrays.get(typeClass); - this._addRecordToRecordArray(liveRecordArray, record); + this._addInternalModelToRecordArray(liveRecordArray, record); } }, + /** Update an individual filter. @method updateFilterRecordArray @param {DS.FilteredRecordArray} array @param {Function} filter - @param {DS.Model} typeClass - @param {InternalModel} record + @param {DS.Model} modelClass + @param {InternalModel} internalModel */ - updateFilterRecordArray: function (array, filter, typeClass, record) { - var shouldBeInArray = filter(record.getRecord()); - var recordArrays = this.recordArraysForRecord(record); + updateFilterRecordArray: function (array, filter, modelClass, internalModel) { + var shouldBeInArray = filter(internalModel.getRecord()); + var recordArrays = this.recordArraysForRecord(internalModel); if (shouldBeInArray) { - this._addRecordToRecordArray(array, record); + this._addInternalModelToRecordArray(array, internalModel); } else { recordArrays.delete(array); - array.removeInternalModel(record); + array._removeInternalModels([internalModel]); } }, - _addRecordToRecordArray: function (array, record) { - var recordArrays = this.recordArraysForRecord(record); + _addInternalModelToRecordArray: function (array, internalModel) { + var recordArrays = this.recordArraysForRecord(internalModel); if (!recordArrays.has(array)) { - array.addInternalModel(record); + array._pushInternalModels([internalModel]); recordArrays.add(array); } }, - populateLiveRecordArray: function (array, modelName) { - var typeMap = this.store.typeMapFor(modelName); + syncLiveRecordArray: function (array, modelClass) { + var hasNoPotentialDeletions = this.changedRecords.length === 0; + var typeMap = this.store.typeMapFor(modelClass); + var hasNoInsertionsOrRemovals = typeMap.records.length === array.length; + + /* + Ideally the recordArrayManager has knowledge of the changes to be applied to + liveRecordArrays, and is capable of strategically flushing those changes and applying + small diffs if desired. However, until we've refactored recordArrayManager, this dirty + check prevents us from unnecessarily wiping out live record arrays returned by peekAll. + */ + if (hasNoPotentialDeletions && hasNoInsertionsOrRemovals) { + return; + } + + this.populateLiveRecordArray(array, modelClass); + }, + + populateLiveRecordArray: function (array, modelClass) { + var typeMap = this.store.typeMapFor(modelClass); var records = typeMap.records; - var record; + var record = undefined; for (var i = 0; i < records.length; i++) { record = records[i]; if (!record.isDeleted() && !record.isEmpty()) { - this._addRecordToRecordArray(array, record); + this._addInternalModelToRecordArray(array, record); } } }, /** @@ -5105,23 +5360,23 @@ changed on a `DS.FilteredRecordArray`. It essentially re-runs the filter from scratch. This same method is invoked when the filter is created in th first place. @method updateFilter @param {Array} array - @param {String} modelName + @param {Class} modelClass @param {Function} filter */ - updateFilter: function (array, modelName, filter) { - var typeMap = this.store.typeMapFor(modelName); + updateFilter: function (array, modelClass, filter) { + var typeMap = this.store.typeMapFor(modelClass); var records = typeMap.records; - var record; + var record = undefined; for (var i = 0; i < records.length; i++) { record = records[i]; if (!record.isDeleted() && !record.isEmpty()) { - this.updateFilterRecordArray(array, filter, modelName, record); + this.updateFilterRecordArray(array, filter, modelClass, record); } } }, /** @@ -5136,23 +5391,21 @@ }, /** Create a `DS.RecordArray` for a type. @method createRecordArray - @param {Class} typeClass + @param {Class} modelClass @return {DS.RecordArray} */ - createRecordArray: function (typeClass) { - var array = _emberDataPrivateSystemRecordArrays.RecordArray.create({ - type: typeClass, + createRecordArray: function (modelClass) { + return _emberDataPrivateSystemRecordArrays.RecordArray.create({ + type: modelClass, content: _ember.default.A(), store: this.store, isLoaded: true, manager: this }); - - return array; }, /** Create a `DS.FilteredRecordArray` for a type and register it for updates. @method createFilteredRecordArray @@ -5219,10 +5472,11 @@ So manager will not update this array. @method unregisterRecordArray @param {DS.RecordArray} array */ unregisterRecordArray: function (array) { + var typeClass = array.type; // unregister filtered record array var recordArrays = this.filteredRecordArrays.get(typeClass); var removedFromFiltered = remove(recordArrays, array); @@ -5257,11 +5511,11 @@ entry.destroy(); } function flatten(list) { var length = list.length; - var result = _ember.default.A(); + var result = []; for (var i = 0; i < length; i++) { result = result.concat(list[i]); } @@ -5288,11 +5542,11 @@ exports.AdapterPopulatedRecordArray = _emberDataPrivateSystemRecordArraysAdapterPopulatedRecordArray.default; }); /** @module ember-data */ -define("ember-data/-private/system/record-arrays/adapter-populated-record-array", ["exports", "ember", "ember-data/-private/system/record-arrays/record-array", "ember-data/-private/system/clone-null", "ember-data/-private/features"], function (exports, _ember, _emberDataPrivateSystemRecordArraysRecordArray, _emberDataPrivateSystemCloneNull, _emberDataPrivateFeatures) { +define("ember-data/-private/system/record-arrays/adapter-populated-record-array", ["exports", "ember", "ember-data/-private/system/record-arrays/record-array", "ember-data/-private/system/clone-null"], function (exports, _ember, _emberDataPrivateSystemRecordArraysRecordArray, _emberDataPrivateSystemCloneNull) { /** @module ember-data */ @@ -5334,12 +5588,19 @@ @class AdapterPopulatedRecordArray @namespace DS @extends DS.RecordArray */ exports.default = _emberDataPrivateSystemRecordArraysRecordArray.default.extend({ - query: null, + init: function () { + // yes we are touching `this` before super, but ArrayProxy has a bug that requires this. + this.set('content', this.get('content') || _ember.default.A()); + this._super.apply(this, arguments); + this.query = this.query || null; + this.links = null; + }, + replace: function () { var type = get(this, 'type').toString(); throw new Error("The result of a server query (on " + type + ") is immutable."); }, @@ -5350,33 +5611,32 @@ return store._query(modelName, query, this); }, /** - @method loadRecords - @param {Array} records + @method _setInternalModels + @param {Array} internalModels @param {Object} payload normalized payload @private */ - loadRecords: function (records, payload) { + _setInternalModels: function (internalModels, payload) { var _this = this; - //TODO Optimize - var internalModels = _ember.default.A(records).mapBy('_internalModel'); + // TODO: initial load should not cause change events at all, only + // subsequent. This requires changing the public api of adapter.query, but + // hopefully we can do that soon. + this.get('content').setObjects(internalModels); + this.setProperties({ - content: _ember.default.A(internalModels), isLoaded: true, isUpdating: false, - meta: (0, _emberDataPrivateSystemCloneNull.default)(payload.meta) + meta: (0, _emberDataPrivateSystemCloneNull.default)(payload.meta), + links: (0, _emberDataPrivateSystemCloneNull.default)(payload.links) }); - if (true) { - this.set('links', (0, _emberDataPrivateSystemCloneNull.default)(payload.links)); - } - internalModels.forEach(function (record) { - _this.manager.recordArraysForRecord(record).add(_this); + return _this.manager.recordArraysForRecord(record).add(_this); }); // TODO: should triggering didLoad event be the last action of the runLoop? _ember.default.run.once(this, 'trigger', 'didLoad'); } @@ -5399,10 +5659,16 @@ @class FilteredRecordArray @namespace DS @extends DS.RecordArray */ exports.default = _emberDataPrivateSystemRecordArraysRecordArray.default.extend({ + init: function () { + this._super.apply(this, arguments); + + this.set('filterFunction', this.get('filterFunction') || null); + this.isLoaded = true; + }, /** The filterFunction is a function used to test records from the store to determine if they should be part of the record array. Example ```javascript @@ -5420,36 +5686,36 @@ ``` @method filterFunction @param {DS.Model} record @return {Boolean} `true` if the record should be in the array */ - filterFunction: null, - isLoaded: true, replace: function () { var type = get(this, 'type').toString(); - throw new Error("The result of a client-side filter (on " + type + ") is immutable."); + throw new Error('The result of a client-side filter (on ' + type + ') is immutable.'); }, /** @method updateFilter @private */ _updateFilter: function () { - var manager = get(this, 'manager'); - manager.updateFilter(this, get(this, 'type'), get(this, 'filterFunction')); + if (get(this, 'isDestroying') || get(this, 'isDestroyed')) { + return; + } + get(this, 'manager').updateFilter(this, get(this, 'type'), get(this, 'filterFunction')); }, updateFilter: _ember.default.observer('filterFunction', function () { _ember.default.run.once(this, this._updateFilter); }) }); }); define("ember-data/-private/system/record-arrays/record-array", ["exports", "ember", "ember-data/-private/system/promise-proxies", "ember-data/-private/system/snapshot-record-array"], function (exports, _ember, _emberDataPrivateSystemPromiseProxies, _emberDataPrivateSystemSnapshotRecordArray) { - var get = _ember.default.get; var set = _ember.default.set; + var Promise = _ember.default.RSVP.Promise; /** A record array is an array that contains records of a certain type. The record array materializes records as needed when they are retrieved for the first time. You should not create record arrays yourself. Instead, an instance of @@ -5461,60 +5727,65 @@ @extends Ember.ArrayProxy @uses Ember.Evented */ exports.default = _ember.default.ArrayProxy.extend(_ember.default.Evented, { - /** - The model type contained by this record array. - @property type - @type DS.Model - */ - type: null, + init: function () { + this._super.apply(this, arguments); - /** - The array of client ids backing the record array. When a - record is requested from the record array, the record - for the client id at the same index is materialized, if - necessary, by the store. - @property content - @private - @type Ember.Array - */ - content: null, + /** + The model type contained by this record array. + @property type + @type DS.Model + */ + this.type = this.type || null; - /** + /** + The array of client ids backing the record array. When a + record is requested from the record array, the record + for the client id at the same index is materialized, if + necessary, by the store. + @property content + @private + @type Ember.Array + */ + this.set('content', this.content || null); + + /** The flag to signal a `RecordArray` is finished loading data. Example ```javascript var people = store.peekAll('person'); people.get('isLoaded'); // true ``` @property isLoaded @type Boolean - */ - isLoaded: false, - /** + */ + this.isLoaded = this.isLoaded || false; + /** The flag to signal a `RecordArray` is currently loading data. - Example - ```javascript + Example + ```javascript var people = store.peekAll('person'); people.get('isUpdating'); // false people.update(); people.get('isUpdating'); // true ``` - @property isUpdating + @property isUpdating @type Boolean - */ - isUpdating: false, + */ + this.isUpdating = false; - /** + /** The store that created this record array. - @property store + @property store @private @type DS.Store - */ - store: null, + */ + this.store = this.store || null; + this._updatingPromise = null; + }, replace: function () { var type = get(this, 'type').toString(); throw new Error("The result of a server query (for all " + type + " types) is immutable. To modify contents, use toArray()"); }, @@ -5525,12 +5796,11 @@ @private @param {Number} index @return {DS.Model} record */ objectAtContent: function (index) { - var content = get(this, 'content'); - var internalModel = content.objectAt(index); + var internalModel = get(this, 'content').objectAt(index); return internalModel && internalModel.getRecord(); }, /** Used to get the latest version of all of the records in this array @@ -5545,16 +5815,29 @@ people.get('isUpdating'); // true ``` @method update */ update: function () { + var _this = this; + if (get(this, 'isUpdating')) { - return; + return this._updatingPromise; } this.set('isUpdating', true); - return this._update(); + + var updatingPromise = this._update().finally(function () { + _this._updatingPromise = null; + if (_this.get('isDestroying') || _this.get('isDestroyed')) { + return; + } + _this.set('isUpdating', false); + }); + + this._updatingPromise = updatingPromise; + + return updatingPromise; }, /* Update this RecordArray and return a promise which resolves once the update is finished. @@ -5569,29 +5852,26 @@ /** Adds an internal model to the `RecordArray` without duplicates @method addInternalModel @private @param {InternalModel} internalModel - @param {number} an optional index to insert at */ - addInternalModel: function (internalModel, idx) { - var content = get(this, 'content'); - if (idx === undefined) { - content.addObject(internalModel); - } else if (!content.includes(internalModel)) { - content.insertAt(idx, internalModel); - } + _pushInternalModels: function (internalModels) { + // pushObjects because the internalModels._recordArrays set was already + // consulted for inclusion, so addObject and its on .contains call is not + // required. + get(this, 'content').pushObjects(internalModels); }, /** Removes an internalModel to the `RecordArray`. @method removeInternalModel @private @param {InternalModel} internalModel */ - removeInternalModel: function (internalModel) { - get(this, 'content').removeObject(internalModel); + _removeInternalModels: function (internalModels) { + get(this, 'content').removeObjects(internalModels); }, /** Saves all of the records in the `RecordArray`. Example @@ -5604,51 +5884,72 @@ ``` @method save @return {DS.PromiseArray} promise */ save: function () { - var recordArray = this; - var promiseLabel = "DS: RecordArray#save " + get(this, 'type'); - var promise = _ember.default.RSVP.all(this.invoke("save"), promiseLabel).then(function (array) { - return recordArray; - }, null, "DS: RecordArray#save return RecordArray"); + var _this2 = this; + var promiseLabel = 'DS: RecordArray#save ' + get(this, 'type'); + var promise = Promise.all(this.invoke('save'), promiseLabel).then(function () { + return _this2; + }, null, 'DS: RecordArray#save return RecordArray'); + return _emberDataPrivateSystemPromiseProxies.PromiseArray.create({ promise: promise }); }, _dissociateFromOwnRecords: function () { - var _this = this; + var _this3 = this; - this.get('content').forEach(function (record) { - var recordArrays = record._recordArrays; + this.get('content').forEach(function (internalModel) { + var recordArrays = internalModel._recordArrays; if (recordArrays) { - recordArrays.delete(_this); + recordArrays.delete(_this3); } }); }, /** @method _unregisterFromManager @private */ _unregisterFromManager: function () { - var manager = get(this, 'manager'); - manager.unregisterRecordArray(this); + get(this, 'manager').unregisterRecordArray(this); }, willDestroy: function () { this._unregisterFromManager(); this._dissociateFromOwnRecords(); - set(this, 'content', undefined); + // TODO: we should not do work during destroy: + // * when objects are destroyed, they should simply be left to do + // * if logic errors do to this, that logic needs to be more careful during + // teardown (ember provides isDestroying/isDestroyed) for this reason + // * the exception being: if an dominator has a reference to this object, + // and must be informed to release e.g. e.g. removing itself from th + // recordArrayMananger + set(this, 'content', null); set(this, 'length', 0); this._super.apply(this, arguments); }, - createSnapshot: function (options) { - var meta = this.get('meta'); - return new _emberDataPrivateSystemSnapshotRecordArray.default(this, meta, options); + /** + r @method _createSnapshot + @private + */ + _createSnapshot: function (options) { + // this is private for users, but public for ember-data internals + return new _emberDataPrivateSystemSnapshotRecordArray.default(this, this.get('meta'), options); + }, + + /** + r @method _takeSnapshot + @private + */ + _takeSnapshot: function () { + return get(this, 'content').map(function (internalModel) { + return internalModel.createSnapshot(); + }); } }); }); /** @module ember-data @@ -5658,10 +5959,19 @@ exports.BelongsToReference = _emberDataPrivateSystemReferencesBelongsTo.default; exports.HasManyReference = _emberDataPrivateSystemReferencesHasMany.default; }); define('ember-data/-private/system/references/belongs-to', ['exports', 'ember-data/model', 'ember', 'ember-data/-private/system/references/reference', 'ember-data/-private/features', 'ember-data/-private/debug'], function (exports, _emberDataModel, _ember, _emberDataPrivateSystemReferencesReference, _emberDataPrivateFeatures, _emberDataPrivateDebug) { + /** + A BelongsToReference is a low level API that allows users and + addon author to perform meta-operations on a belongs-to + relationship. + + @class BelongsToReference + @namespace DS + @extends DS.Reference + */ var BelongsToReference = function (store, parentInternalModel, belongsToRelationship) { this._super$constructor(store, parentInternalModel); this.belongsToRelationship = belongsToRelationship; this.type = belongsToRelationship.relationshipMeta.type; this.parent = parentInternalModel.recordReference; @@ -5671,31 +5981,221 @@ BelongsToReference.prototype = Object.create(_emberDataPrivateSystemReferencesReference.default.prototype); BelongsToReference.prototype.constructor = BelongsToReference; BelongsToReference.prototype._super$constructor = _emberDataPrivateSystemReferencesReference.default; + /** + This returns a string that represents how the reference will be + looked up when it is loaded. If the relationship has a link it will + use the "link" otherwise it defaults to "id". + + Example + + ```javascript + // models/blog.js + export default DS.Model.extend({ + user: DS.belongsTo({ async: true }) + }); + + var blog = store.push({ + type: 'blog', + id: 1, + relationships: { + user: { + data: { type: 'user', id: 1 } + } + } + }); + var userRef = blog.belongsTo('user'); + + // get the identifier of the reference + if (userRef.remoteType() === "id") { + var id = userRef.id(); + } else if (userRef.remoteType() === "link") { + var link = userRef.link(); + } + ``` + + @method remoteType + @return {String} The name of the remote type. This should either be "link" or "id" + */ BelongsToReference.prototype.remoteType = function () { if (this.belongsToRelationship.link) { return "link"; } return "id"; }; + /** + The `id` of the record that this reference refers to. Together, the + `type()` and `id()` methods form a composite key for the identity + map. This can be used to access the id of an async relationship + without triggering a fetch that would normally happen if you + attempted to use `record.get('relationship.id')`. + + Example + + ```javascript + // models/blog.js + export default DS.Model.extend({ + user: DS.belongsTo({ async: true }) + }); + + var blog = store.push({ + data: { + type: 'blog', + id: 1, + relationships: { + user: { + data: { type: 'user', id: 1 } + } + } + } + }); + var userRef = blog.belongsTo('user'); + + // get the identifier of the reference + if (userRef.remoteType() === "id") { + var id = userRef.id(); + } + ``` + + @method id + @return {String} The id of the record in this belongsTo relationship. + */ BelongsToReference.prototype.id = function () { var inverseRecord = this.belongsToRelationship.inverseRecord; return inverseRecord && inverseRecord.id; }; + /** + The link Ember Data will use to fetch or reload this belongs-to + relationship. + + Example + + ```javascript + // models/blog.js + export default DS.Model.extend({ + user: DS.belongsTo({ async: true }) + }); + + var blog = store.push({ + data: { + type: 'blog', + id: 1, + relationships: { + user: { + links: { + related: '/articles/1/author' + } + } + } + } + }); + var userRef = blog.belongsTo('user'); + + // get the identifier of the reference + if (userRef.remoteType() === "link") { + var link = userRef.link(); + } + ``` + + @method link + @return {String} The link Ember Data will use to fetch or reload this belongs-to relationship. + */ BelongsToReference.prototype.link = function () { return this.belongsToRelationship.link; }; + /** + The meta data for the belongs-to relationship. + + Example + + ```javascript + // models/blog.js + export default DS.Model.extend({ + user: DS.belongsTo({ async: true }) + }); + + var blog = store.push({ + data: { + type: 'blog', + id: 1, + relationships: { + user: { + links: { + related: { + href: '/articles/1/author', + meta: { + lastUpdated: 1458014400000 + } + } + } + } + } + } + }); + + var userRef = blog.belongsTo('user'); + + userRef.meta() // { lastUpdated: 1458014400000 } + ``` + + @method meta + @return {Object} The meta information for the belongs-oo relationship. + */ BelongsToReference.prototype.meta = function () { return this.belongsToRelationship.meta; }; + /** + `push` can be used to update the data in the relationship and Ember + Data will treat the new data as the conanical value of this + relationship on the backend. + + Example + + ```javascript + // models/blog.js + export default DS.Model.extend({ + user: DS.belongsTo({ async: true }) + }); + + var blog = store.push({ + data: { + type: 'blog', + id: 1, + relationships: { + user: { + data: { type: 'user', id: 1 } + } + } + } + }); + var userRef = blog.belongsTo('user'); + + // provide data for reference + userRef.push({ + data: { + type: 'user', + id: 1, + attributes: { + username: "@user" + } + } + }).then(function(user) { + userRef.value() === user; + }); + ``` + + @method push + @param {Object|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship. + @return {Promise<record>} A promise that resolves with the new value in this belongs-to relationship. + */ BelongsToReference.prototype.push = function (objectOrPromise) { var _this = this; return _ember.default.RSVP.resolve(objectOrPromise).then(function (data) { var record; @@ -5711,20 +6211,103 @@ return record; }); }; + /** + `value()` synchronously returns the current value of the belongs-to + relationship. Unlike `record.get('relationshipName')`, calling + `value()` on a reference does not trigger a fetch if the async + relationship is not yet loaded. If the relationship is not loaded + it will always return `null`. + + Example + + ```javascript + // models/blog.js + export default DS.Model.extend({ + user: DS.belongsTo({ async: true }) + }); + + var blog = store.push({ + data: { + type: 'blog', + id: 1, + relationships: { + user: { + data: { type: 'user', id: 1 } + } + } + } + }); + var userRef = blog.belongsTo('user'); + + userRef.value(); // null + + // provide data for reference + userRef.push({ + data: { + type: 'user', + id: 1, + attributes: { + username: "@user" + } + } + }).then(function(user) { + userRef.value(); // user + }); + ``` + + @method value + @param {Object|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship. + @return {DS.Model} the record in this relationship + */ BelongsToReference.prototype.value = function () { var inverseRecord = this.belongsToRelationship.inverseRecord; - if (inverseRecord && inverseRecord.record) { - return inverseRecord.record; + if (inverseRecord && inverseRecord.isLoaded()) { + return inverseRecord.getRecord(); } return null; }; + /** + Loads a record in a belongs to relationship if it is not already + loaded. If the relationship is already loaded this method does not + trigger a new load. + + Example + + ```javascript + // models/blog.js + export default DS.Model.extend({ + user: DS.belongsTo({ async: true }) + }); + + var blog = store.push({ + data: { + type: 'blog', + id: 1, + relationships: { + user: { + data: { type: 'user', id: 1 } + } + } + } + }); + var userRef = blog.belongsTo('user'); + + userRef.value(); // null + + userRef.load().then(function(user) { + userRef.value() === user + }); + + @method load + @return {Promise} a promise that resolves with the record in this belongs-to relationship. + */ BelongsToReference.prototype.load = function () { var _this2 = this; if (this.remoteType() === "id") { return this.belongsToRelationship.getRecord(); @@ -5735,10 +6318,44 @@ return _this2.value(); }); } }; + /** + Triggers a reload of the value in this relationship. If the + remoteType is `"link"` Ember Data will use the relationship link to + reload the relationship. Otherwise it will reload the record by its + id. + + Example + + ```javascript + // models/blog.js + export default DS.Model.extend({ + user: DS.belongsTo({ async: true }) + }); + + var blog = store.push({ + data: { + type: 'blog', + id: 1, + relationships: { + user: { + data: { type: 'user', id: 1 } + } + } + } + }); + var userRef = blog.belongsTo('user'); + + userRef.reload().then(function(user) { + userRef.value() === user + }); + + @method reload + @return {Promise} a promise that resolves with the record in this belongs-to relationship after the reload has completed. + */ BelongsToReference.prototype.reload = function () { var _this3 = this; return this.belongsToRelationship.reload().then(function (internalModel) { return _this3.value(); @@ -5746,11 +6363,11 @@ }; exports.default = BelongsToReference; }); define('ember-data/-private/system/references/has-many', ['exports', 'ember', 'ember-data/-private/system/references/reference', 'ember-data/-private/debug', 'ember-data/-private/features'], function (exports, _ember, _emberDataPrivateSystemReferencesReference, _emberDataPrivateDebug, _emberDataPrivateFeatures) { - + var resolve = _ember.default.RSVP.resolve; var get = _ember.default.get; var HasManyReference = function (store, parentInternalModel, hasManyRelationship) { this._super$constructor(store, parentInternalModel); this.hasManyRelationship = hasManyRelationship; @@ -5775,26 +6392,25 @@ HasManyReference.prototype.link = function () { return this.hasManyRelationship.link; }; HasManyReference.prototype.ids = function () { - var members = this.hasManyRelationship.members; - var ids = members.toArray().map(function (internalModel) { + var members = this.hasManyRelationship.members.toArray(); + + return members.map(function (internalModel) { return internalModel.id; }); - - return ids; }; HasManyReference.prototype.meta = function () { - return this.hasManyRelationship.manyArray.meta; + return this.hasManyRelationship.meta; }; HasManyReference.prototype.push = function (objectOrPromise) { var _this = this; - return _ember.default.RSVP.resolve(objectOrPromise).then(function (payload) { + return resolve(objectOrPromise).then(function (payload) { var array = payload; if (false) {} var useLegacyArrayPush = true; @@ -5821,88 +6437,203 @@ internalModels = _ember.default.A(records).mapBy('_internalModel'); } _this.hasManyRelationship.computeChanges(internalModels); - return _this.hasManyRelationship.manyArray; + return _this.hasManyRelationship.getManyArray(); }); }; HasManyReference.prototype._isLoaded = function () { var hasData = get(this.hasManyRelationship, 'hasData'); if (!hasData) { return false; } var members = this.hasManyRelationship.members.toArray(); - var isEveryLoaded = members.every(function (internalModel) { + + return members.every(function (internalModel) { return internalModel.isLoaded() === true; }); - - return isEveryLoaded; }; HasManyReference.prototype.value = function () { if (this._isLoaded()) { - return this.hasManyRelationship.manyArray; + return this.hasManyRelationship.getManyArray(); } return null; }; HasManyReference.prototype.load = function () { if (!this._isLoaded()) { return this.hasManyRelationship.getRecords(); } - var manyArray = this.hasManyRelationship.manyArray; - return _ember.default.RSVP.resolve(manyArray); + return resolve(this.hasManyRelationship.getManyArray()); }; HasManyReference.prototype.reload = function () { return this.hasManyRelationship.reload(); }; exports.default = HasManyReference; }); define('ember-data/-private/system/references/record', ['exports', 'ember', 'ember-data/-private/system/references/reference'], function (exports, _ember, _emberDataPrivateSystemReferencesReference) { + /** + An RecordReference is a low level API that allows users and + addon author to perform meta-operations on a record. + + @class RecordReference + @namespace DS + */ var RecordReference = function (store, internalModel) { this._super$constructor(store, internalModel); this.type = internalModel.modelName; this._id = internalModel.id; }; RecordReference.prototype = Object.create(_emberDataPrivateSystemReferencesReference.default.prototype); RecordReference.prototype.constructor = RecordReference; RecordReference.prototype._super$constructor = _emberDataPrivateSystemReferencesReference.default; + /** + The `id` of the record that this reference refers to. + + Together, the `type` and `id` properties form a composite key for + the identity map. + + Example + + ```javascript + var userRef = store.getReference('user', 1); + + userRef.id(); // '1' + ``` + + @method id + @return {String} The id of the record. + */ RecordReference.prototype.id = function () { return this._id; }; + /** + How the reference will be looked up when it is loaded: Currently + this always return `identity` to signifying that a record will be + loaded by the `type` and `id`. + + Example + + ```javascript + var userRef = store.getReference('user', 1); + + userRef.remoteType(); // 'identity' + ``` + + @method remoteType + @return {String} 'identity' + */ RecordReference.prototype.remoteType = function () { return 'identity'; }; + /** + This API allows you to provide a reference with new data. The + simplest usage of this API is similar to `store.push`: you provide a + normalized hash of data and the object represented by the reference + will update. + + If you pass a promise to `push`, Ember Data will not ask the adapter + for the data if another attempt to fetch it is made in the + interim. When the promise resolves, the underlying object is updated + with the new data, and the promise returned by *this function* is resolved + with that object. + + For example, `recordReference.push(promise)` will be resolved with a + record. + + Example + + ```javascript + var userRef = store.getReference('user', 1); + + // provide data for reference + userRef.push({ data: { id: 1, username: "@user" }}).then(function(user) { + userRef.value() === user; + }); + ``` + + @method + @param {Promise|Object} + @returns Promise<record> a promise for the value (record or relationship) + */ RecordReference.prototype.push = function (objectOrPromise) { var _this = this; return _ember.default.RSVP.resolve(objectOrPromise).then(function (data) { - var record = _this.store.push(data); - return record; + return _this.store.push(data); }); }; + /** + If the entity referred to by the reference is already loaded, it is + present as `reference.value`. Otherwise the value returned by this function + is `null`. + + Example + + ```javascript + var userRef = store.getReference('user', 1); + + userRef.value(); // user + ``` + + @method value + @return {DS.Model} the record for this RecordReference + */ RecordReference.prototype.value = function () { return this.internalModel.record; }; + /** + Triggers a fetch for the backing entity based on its `remoteType` + (see `remoteType` definitions per reference type). + + Example + + ```javascript + var userRef = store.getReference('user', 1); + + // load user (via store.find) + userRef.load().then(...) + ``` + + @method load + @return {Promise<record>} the record for this RecordReference + */ RecordReference.prototype.load = function () { return this.store.findRecord(this.type, this._id); }; + /** + Reloads the record if it is already loaded. If the record is not + loaded it will load the record via `store.findRecord` + + Example + + ```javascript + var userRef = store.getReference('user', 1); + + // or trigger a reload + userRef.reload().then(...) + ``` + + @method reload + @return {Promise<record>} the record for this RecordReference + */ RecordReference.prototype.reload = function () { var record = this.value(); if (record) { return record.reload(); } @@ -5942,10 +6673,11 @@ return { key: meta.key, kind: meta.kind, type: typeForRelationshipMeta(meta), options: meta.options, + name: meta.name, parentType: meta.parentType, isRelationship: true }; } }); @@ -6044,10 +6776,11 @@ var meta = { type: userEnteredModelName, isRelationship: true, options: opts, kind: 'belongsTo', + name: 'Belongs To', key: null }; return _ember.default.computed({ get: function (key) { @@ -6241,11 +6974,11 @@ import DS from 'ember-data'; export default DS.Model.extend({ comments: DS.hasMany('comment') }); ``` - Calling `App.Post.typeForRelationship('comments')` will return `App.Comment`. + Calling `store.modelFor('post').typeForRelationship('comments', store)` will return `Comment`. @method typeForRelationship @static @param {String} name the name of the relationship @param {store} store an instance of DS.Store @return {DS.Model} the type of the relationship, or undefined @@ -6272,15 +7005,16 @@ import DS from 'ember-data'; export default DS.Model.extend({ owner: DS.belongsTo('post') }); ``` - App.Post.inverseFor('comments') -> { type: App.Message, name: 'owner', kind: 'belongsTo' } - App.Message.inverseFor('owner') -> { type: App.Post, name: 'comments', kind: 'hasMany' } + store.modelFor('post').inverseFor('comments', store) -> { type: App.Message, name: 'owner', kind: 'belongsTo' } + store.modelFor('message').inverseFor('owner', store) -> { type: App.Post, name: 'comments', kind: 'hasMany' } @method inverseFor @static @param {String} name the name of the relationship + @param {DS.Store} store @return {Object} the inverse relationship, or null */ inverseFor: function (name, store) { var inverseMap = get(this, 'inverseMap'); if (inverseMap[name]) { @@ -6394,15 +7128,17 @@ This computed property would return a map describing these relationships, like this: ```javascript import Ember from 'ember'; import Blog from 'app/models/blog'; + import User from 'app/models/user'; + import Post from 'app/models/post'; var relationships = Ember.get(Blog, 'relationships'); - relationships.get(App.User); + relationships.get(User); //=> [ { name: 'users', kind: 'hasMany' }, // { name: 'owner', kind: 'belongsTo' } ] - relationships.get(App.Post); + relationships.get(Post); //=> [ { name: 'posts', kind: 'hasMany' } ] ``` @property relationships @static @type Ember.Map @@ -6469,11 +7205,11 @@ This property would contain the following: ```javascript import Ember from 'ember'; import Blog from 'app/models/blog'; var relatedTypes = Ember.get(Blog, 'relatedTypes'); - //=> [ App.User, App.Post ] + //=> [ User, Post ] ``` @property relatedTypes @static @type Ember.Array @readOnly @@ -6798,10 +7534,11 @@ var meta = { type: type, isRelationship: true, options: options, kind: 'hasMany', + name: 'Has Many', key: null }; return _ember.default.computed({ get: function (key) { @@ -6863,12 +7600,10 @@ this.addCanonicalRecord(newRecord); } else if (this.canonicalState) { this.removeCanonicalRecord(this.canonicalState); } this.flushCanonicalLater(); - this.setHasData(true); - this.setHasLoaded(true); }; BelongsToRelationship.prototype._super$addCanonicalRecord = _emberDataPrivateSystemRelationshipsStateRelationship.default.prototype.addCanonicalRecord; BelongsToRelationship.prototype.addCanonicalRecord = function (newRecord) { if (this.canonicalMembers.has(newRecord)) { @@ -6888,12 +7623,14 @@ //temporary fix to not remove newly created records if server returned null. //TODO remove once we have proper diffing if (this.inverseRecord && this.inverseRecord.isNew() && !this.canonicalState) { return; } - this.inverseRecord = this.canonicalState; - this.record.notifyBelongsToChanged(this.key); + if (this.inverseRecord !== this.canonicalState) { + this.inverseRecord = this.canonicalState; + this.record.notifyBelongsToChanged(this.key); + } this._super$flushCanonical(); }; BelongsToRelationship.prototype._super$addRecord = _emberDataPrivateSystemRelationshipsStateRelationship.default.prototype.addRecord; BelongsToRelationship.prototype.addRecord = function (newRecord) { @@ -6992,16 +7729,21 @@ if (this.link) { return this.fetchLink(); } // reload record, if it is already loaded - if (this.inverseRecord && this.inverseRecord.record) { + if (this.inverseRecord && this.inverseRecord.hasRecord) { return this.inverseRecord.record.reload(); } return this.findRecord(); }; + + BelongsToRelationship.prototype.updateData = function (data) { + var internalModel = this.store._pushResourceIdentifier(this, data); + this.setCanonicalRecord(internalModel); + }; }); define("ember-data/-private/system/relationships/state/create", ["exports", "ember", "ember-data/-private/system/relationships/state/has-many", "ember-data/-private/system/relationships/state/belongs-to", "ember-data/-private/system/empty-object"], function (exports, _ember, _emberDataPrivateSystemRelationshipsStateHasMany, _emberDataPrivateSystemRelationshipsStateBelongsTo, _emberDataPrivateSystemEmptyObject) { exports.default = Relationships; var get = _ember.default.get; @@ -7052,33 +7794,44 @@ function ManyRelationship(store, record, inverseKey, relationshipMeta) { this._super$constructor(store, record, inverseKey, relationshipMeta); this.belongsToType = relationshipMeta.type; this.canonicalState = []; - this.manyArray = _emberDataPrivateSystemManyArray.default.create({ - canonicalState: this.canonicalState, - store: this.store, - relationship: this, - type: this.store.modelFor(this.belongsToType), - record: record - }); this.isPolymorphic = relationshipMeta.options.polymorphic; - this.manyArray.isPolymorphic = this.isPolymorphic; } ManyRelationship.prototype = Object.create(_emberDataPrivateSystemRelationshipsStateRelationship.default.prototype); + ManyRelationship.prototype.getManyArray = function () { + if (!this._manyArray) { + this._manyArray = _emberDataPrivateSystemManyArray.default.create({ + canonicalState: this.canonicalState, + store: this.store, + relationship: this, + type: this.store.modelFor(this.belongsToType), + record: this.record, + meta: this.meta, + isPolymorphic: this.isPolymorphic + }); + } + return this._manyArray; + }; + ManyRelationship.prototype.constructor = ManyRelationship; ManyRelationship.prototype._super$constructor = _emberDataPrivateSystemRelationshipsStateRelationship.default; ManyRelationship.prototype.destroy = function () { - this.manyArray.destroy(); + if (this._manyArray) { + this._manyArray.destroy(); + } }; ManyRelationship.prototype._super$updateMeta = _emberDataPrivateSystemRelationshipsStateRelationship.default.prototype.updateMeta; ManyRelationship.prototype.updateMeta = function (meta) { this._super$updateMeta(meta); - this.manyArray.set('meta', meta); + if (this._manyArray) { + this._manyArray.set('meta', meta); + } }; ManyRelationship.prototype._super$addCanonicalRecord = _emberDataPrivateSystemRelationshipsStateRelationship.default.prototype.addCanonicalRecord; ManyRelationship.prototype.addCanonicalRecord = function (record, idx) { if (this.canonicalMembers.has(record)) { @@ -7096,11 +7849,12 @@ ManyRelationship.prototype.addRecord = function (record, idx) { if (this.members.has(record)) { return; } this._super$addRecord(record, idx); - this.manyArray.internalAddRecords([record], idx); + // make lazy later + this.getManyArray().internalAddRecords([record], idx); }; ManyRelationship.prototype._super$removeCanonicalRecordFromOwn = _emberDataPrivateSystemRelationshipsStateRelationship.default.prototype.removeCanonicalRecordFromOwn; ManyRelationship.prototype.removeCanonicalRecordFromOwn = function (record, idx) { var i = idx; @@ -7116,53 +7870,55 @@ this._super$removeCanonicalRecordFromOwn(record, idx); }; ManyRelationship.prototype._super$flushCanonical = _emberDataPrivateSystemRelationshipsStateRelationship.default.prototype.flushCanonical; ManyRelationship.prototype.flushCanonical = function () { - this.manyArray.flushCanonical(); + if (this._manyArray) { + this._manyArray.flushCanonical(); + } this._super$flushCanonical(); }; ManyRelationship.prototype._super$removeRecordFromOwn = _emberDataPrivateSystemRelationshipsStateRelationship.default.prototype.removeRecordFromOwn; ManyRelationship.prototype.removeRecordFromOwn = function (record, idx) { if (!this.members.has(record)) { return; } this._super$removeRecordFromOwn(record, idx); + var manyArray = this.getManyArray(); if (idx !== undefined) { //TODO(Igor) not used currently, fix - this.manyArray.currentState.removeAt(idx); + manyArray.currentState.removeAt(idx); } else { - this.manyArray.internalRemoveRecords([record]); + manyArray.internalRemoveRecords([record]); } }; ManyRelationship.prototype.notifyRecordRelationshipAdded = function (record, idx) { this.record.notifyHasManyAdded(this.key, record, idx); }; ManyRelationship.prototype.reload = function () { - var _this = this; + var manyArray = this.getManyArray(); + var manyArrayLoadedState = manyArray.get('isLoaded'); - var manyArrayLoadedState = this.manyArray.get('isLoaded'); - if (this._loadingPromise) { if (this._loadingPromise.get('isPending')) { return this._loadingPromise; } if (this._loadingPromise.get('isRejected')) { - this.manyArray.set('isLoaded', manyArrayLoadedState); + manyArray.set('isLoaded', manyArrayLoadedState); } } if (this.link) { this._loadingPromise = (0, _emberDataPrivateSystemPromiseProxies.promiseManyArray)(this.fetchLink(), 'Reload with link'); return this._loadingPromise; } else { - this._loadingPromise = (0, _emberDataPrivateSystemPromiseProxies.promiseManyArray)(this.store.scheduleFetchMany(this.manyArray.toArray()).then(function () { - return _this.manyArray; + this._loadingPromise = (0, _emberDataPrivateSystemPromiseProxies.promiseManyArray)(this.store._scheduleFetchMany(manyArray.currentState).then(function () { + return manyArray; }), 'Reload with ids'); return this._loadingPromise; } }; @@ -7197,79 +7953,84 @@ this.addCanonicalRecord(record, i); } }; ManyRelationship.prototype.fetchLink = function () { - var _this2 = this; + var _this = this; return this.store.findHasMany(this.record, this.link, this.relationshipMeta).then(function (records) { if (records.hasOwnProperty('meta')) { - _this2.updateMeta(records.meta); + _this.updateMeta(records.meta); } - _this2.store._backburner.join(function () { - _this2.updateRecordsFromAdapter(records); - _this2.manyArray.set('isLoaded', true); + _this.store._backburner.join(function () { + _this.updateRecordsFromAdapter(records); + _this.getManyArray().set('isLoaded', true); }); - return _this2.manyArray; + return _this.getManyArray(); }); }; ManyRelationship.prototype.findRecords = function () { - var _this3 = this; + var manyArray = this.getManyArray(); + var array = manyArray.toArray(); + var internalModels = new Array(array.length); - var manyArray = this.manyArray.toArray(); - var internalModels = new Array(manyArray.length); - - for (var i = 0; i < manyArray.length; i++) { - internalModels[i] = manyArray[i]._internalModel; + for (var i = 0; i < array.length; i++) { + internalModels[i] = array[i]._internalModel; } //TODO CLEANUP return this.store.findMany(internalModels).then(function () { - if (!_this3.manyArray.get('isDestroyed')) { + if (!manyArray.get('isDestroyed')) { //Goes away after the manyArray refactor - _this3.manyArray.set('isLoaded', true); + manyArray.set('isLoaded', true); } - return _this3.manyArray; + return manyArray; }); }; ManyRelationship.prototype.notifyHasManyChanged = function () { this.record.notifyHasManyAdded(this.key); }; ManyRelationship.prototype.getRecords = function () { - var _this4 = this; + var _this2 = this; //TODO(Igor) sync server here, once our syncing is not stupid + var manyArray = this.getManyArray(); if (this.isAsync) { var promise; if (this.link) { if (this.hasLoaded) { promise = this.findRecords(); } else { promise = this.findLink().then(function () { - return _this4.findRecords(); + return _this2.findRecords(); }); } } else { promise = this.findRecords(); } this._loadingPromise = _emberDataPrivateSystemPromiseProxies.PromiseManyArray.create({ - content: this.manyArray, + content: manyArray, promise: promise }); return this._loadingPromise; } else { //TODO(Igor) WTF DO I DO HERE? - if (!this.manyArray.get('isDestroyed')) { - this.manyArray.set('isLoaded', true); + if (!manyArray.get('isDestroyed')) { + manyArray.set('isLoaded', true); } - return this.manyArray; + return manyArray; } }; + ManyRelationship.prototype.updateData = function (data) { + var internalModels = this.store._pushResourceIdentifiers(this, data); + this.updateRecordsFromAdapter(internalModels); + }; + function setForArray(array) { var set = new _emberDataPrivateSystemOrderedSet.default(); if (array) { for (var i = 0, l = array.length; i < l; i++) { @@ -7278,11 +8039,11 @@ } return set; } }); -define("ember-data/-private/system/relationships/state/relationship", ["exports", "ember", "ember-data/-private/debug", "ember-data/-private/system/ordered-set"], function (exports, _ember, _emberDataPrivateDebug, _emberDataPrivateSystemOrderedSet) { +define("ember-data/-private/system/relationships/state/relationship", ["exports", "ember-data/-private/debug", "ember-data/-private/system/ordered-set", "ember-data/-private/system/normalize-link"], function (exports, _emberDataPrivateDebug, _emberDataPrivateSystemOrderedSet, _emberDataPrivateSystemNormalizeLink) { exports.default = Relationship; function Relationship(store, record, inverseKey, relationshipMeta) { var async = relationshipMeta.options.async; this.members = new _emberDataPrivateSystemOrderedSet.default(); @@ -7303,11 +8064,11 @@ } Relationship.prototype = { constructor: Relationship, - destroy: _ember.default.K, + destroy: function () {}, updateMeta: function (meta) { this.meta = meta; }, @@ -7475,16 +8236,14 @@ return _this3.store._backburner.schedule('syncRelationships', _this3, _this3.flushCanonical); }); }, updateLink: function (link) { - if (link !== this.link) { - this.link = link; - this.linkPromise = null; - this.setHasLoaded(false); - this.record.notifyPropertyChange(this.key); - } + + this.link = link; + this.linkPromise = null; + this.record.notifyPropertyChange(this.key); }, findLink: function () { if (this.linkPromise) { return this.linkPromise; @@ -7499,16 +8258,14 @@ updateRecordsFromAdapter: function (records) { //TODO(Igor) move this to a proper place //TODO Once we have adapter support, we need to handle updated and canonical changes this.computeChanges(records); - this.setHasData(true); - this.setHasLoaded(true); }, - notifyRecordRelationshipAdded: _ember.default.K, - notifyRecordRelationshipRemoved: _ember.default.K, + notifyRecordRelationshipAdded: function () {}, + notifyRecordRelationshipRemoved: function () {}, /* `hasData` for a relationship is a flag to indicate if we consider the content of this relationship "known". Snapshots uses this to tell the difference between unknown (`undefined`) or empty (`null`). The reason for @@ -7529,11 +8286,60 @@ (`hasData === true`). Updating the link will automatically set `hasLoaded` to `false`. */ setHasLoaded: function (value) { this.hasLoaded = value; - } + }, + + /* + `push` for a relationship allows the store to push a JSON API Relationship + Object onto the relationship. The relationship will then extract and set the + meta, data and links of that relationship. + `push` use `updateMeta`, `updateData` and `updateLink` to update the state + of the relationship. + */ + push: function (payload) { + + var hasData = false; + var hasLink = false; + + if (payload.meta) { + this.updateMeta(payload.meta); + } + + if (payload.data !== undefined) { + hasData = true; + this.updateData(payload.data); + } + + if (payload.links && payload.links.related) { + var relatedLink = (0, _emberDataPrivateSystemNormalizeLink.default)(payload.links.related); + if (relatedLink && relatedLink.href && relatedLink.href !== this.link) { + hasLink = true; + this.updateLink(relatedLink.href); + } + } + + /* + Data being pushed into the relationship might contain only data or links, + or a combination of both. + If we got data we want to set both hasData and hasLoaded to true since + this would indicate that we should prefer the local state instead of + trying to fetch the link or call findRecord(). + If we have no data but a link is present we want to set hasLoaded to false + without modifying the hasData flag. This will ensure we fetch the updated + link next time the relationship is accessed. + */ + if (hasData) { + this.setHasData(true); + this.setHasLoaded(true); + } else if (hasLink) { + this.setHasLoaded(false); + } + }, + + updateData: function () {} }; }); /* global heimdall */ define('ember-data/-private/system/snapshot-record-array', ['exports'], function (exports) { exports.default = SnapshotRecordArray; @@ -7599,16 +8405,16 @@ Get snapshots of the underlying record array @method snapshots @return {Array} Array of snapshots */ SnapshotRecordArray.prototype.snapshots = function () { - if (this._snapshots) { + if (this._snapshots !== null) { return this._snapshots; } - var recordArray = this._recordArray; - this._snapshots = recordArray.invoke('createSnapshot'); + this._snapshots = this._recordArray._takeSnapshot(); + return this._snapshots; }; }); define("ember-data/-private/system/snapshot", ["exports", "ember", "ember-data/-private/system/empty-object"], function (exports, _ember, _emberDataPrivateSystemEmptyObject) { exports.default = Snapshot; @@ -7649,11 +8455,10 @@ A hash of adapter options @property adapterOptions @type {Object} */ this.adapterOptions = options.adapterOptions; - this.include = options.include; this._changedAttributes = record.changedAttributes(); } @@ -7934,39 +8739,46 @@ }; }); /** @module ember-data */ -define('ember-data/-private/system/store', ['exports', 'ember', 'ember-data/model', 'ember-data/-private/debug', 'ember-data/-private/system/normalize-link', 'ember-data/-private/system/normalize-model-name', 'ember-data/adapters/errors', 'ember-data/-private/system/promise-proxies', 'ember-data/-private/system/store/common', 'ember-data/-private/system/store/serializer-response', 'ember-data/-private/system/store/serializers', 'ember-data/-private/system/store/finders', 'ember-data/-private/utils', 'ember-data/-private/system/coerce-id', 'ember-data/-private/system/record-array-manager', 'ember-data/-private/system/store/container-instance-cache', 'ember-data/-private/system/model/internal-model', 'ember-data/-private/system/empty-object', 'ember-data/-private/features'], function (exports, _ember, _emberDataModel, _emberDataPrivateDebug, _emberDataPrivateSystemNormalizeLink, _emberDataPrivateSystemNormalizeModelName, _emberDataAdaptersErrors, _emberDataPrivateSystemPromiseProxies, _emberDataPrivateSystemStoreCommon, _emberDataPrivateSystemStoreSerializerResponse, _emberDataPrivateSystemStoreSerializers, _emberDataPrivateSystemStoreFinders, _emberDataPrivateUtils, _emberDataPrivateSystemCoerceId, _emberDataPrivateSystemRecordArrayManager, _emberDataPrivateSystemStoreContainerInstanceCache, _emberDataPrivateSystemModelInternalModel, _emberDataPrivateSystemEmptyObject, _emberDataPrivateFeatures) { +define('ember-data/-private/system/store', ['exports', 'ember', 'ember-data/model', 'ember-data/-private/debug', 'ember-data/-private/system/normalize-model-name', 'ember-data/adapters/errors', 'ember-data/-private/system/promise-proxies', 'ember-data/-private/system/store/common', 'ember-data/-private/system/store/serializer-response', 'ember-data/-private/system/store/serializers', 'ember-data/-private/system/store/finders', 'ember-data/-private/utils', 'ember-data/-private/system/coerce-id', 'ember-data/-private/system/record-array-manager', 'ember-data/-private/system/store/container-instance-cache', 'ember-data/-private/system/model/internal-model', 'ember-data/-private/system/empty-object', 'ember-data/-private/features'], function (exports, _ember, _emberDataModel, _emberDataPrivateDebug, _emberDataPrivateSystemNormalizeModelName, _emberDataAdaptersErrors, _emberDataPrivateSystemPromiseProxies, _emberDataPrivateSystemStoreCommon, _emberDataPrivateSystemStoreSerializerResponse, _emberDataPrivateSystemStoreSerializers, _emberDataPrivateSystemStoreFinders, _emberDataPrivateUtils, _emberDataPrivateSystemCoerceId, _emberDataPrivateSystemRecordArrayManager, _emberDataPrivateSystemStoreContainerInstanceCache, _emberDataPrivateSystemModelInternalModel, _emberDataPrivateSystemEmptyObject, _emberDataPrivateFeatures) { var badIdFormatAssertion = '`id` passed to `findRecord()` has to be non-empty string or number'; exports.badIdFormatAssertion = badIdFormatAssertion; + var A = _ember.default.A; var Backburner = _ember.default._Backburner; - var Map = _ember.default.Map; + var computed = _ember.default.computed; + var copy = _ember.default.copy; + var ENV = _ember.default.ENV; + var EmberError = _ember.default.Error; + var get = _ember.default.get; + var guidFor = _ember.default.guidFor; + var inspect = _ember.default.inspect; + var isNone = _ember.default.isNone; + var isPresent = _ember.default.isPresent; + var MapWithDefault = _ember.default.MapWithDefault; + var emberRun = _ember.default.run; + var set = _ember.default.set; + var RSVP = _ember.default.RSVP; + var Service = _ember.default.Service; + var typeOf = _ember.default.typeOf; + var Promise = RSVP.Promise; //Get the materialized model from the internalModel/promise that returns //an internal model and return it in a promiseObject. Useful for returning //from find methods - function promiseRecord(internalModel, label) { - var toReturn = internalModel.then(function (model) { - return model.getRecord(); + function promiseRecord(internalModelPromise, label) { + var toReturn = internalModelPromise.then(function (internalModel) { + return internalModel.getRecord(); }); + return (0, _emberDataPrivateSystemPromiseProxies.promiseObject)(toReturn, label); } - var once = _ember.default.run.once; - var Promise = _ember.default.RSVP.Promise; - var Store; + var Store = undefined; - var copy = _ember.default.copy; - var get = _ember.default.get; - var GUID_KEY = _ember.default.GUID_KEY; - var isNone = _ember.default.isNone; - var isPresent = _ember.default.isPresent; - var set = _ember.default.set; - var Service = _ember.default.Service; - // Implementors Note: // // The variables in this file are consistently named according to the following // scheme: // @@ -8066,25 +8878,32 @@ this.typeMaps = {}; this.recordArrayManager = _emberDataPrivateSystemRecordArrayManager.default.create({ store: this }); this._pendingSave = []; - this._instanceCache = new _emberDataPrivateSystemStoreContainerInstanceCache.default((0, _emberDataPrivateUtils.getOwner)(this)); + this._instanceCache = new _emberDataPrivateSystemStoreContainerInstanceCache.default((0, _emberDataPrivateUtils.getOwner)(this), this); + //Used to keep track of all the find requests that need to be coalesced - this._pendingFetch = Map.create(); + this._pendingFetch = MapWithDefault.create({ defaultValue: function () { + return []; + } }); }, /** - The adapter to use to communicate to a backend server or other persistence layer. - This can be specified as an instance, class, or string. + The default adapter to use to communicate to a backend server or + other persistence layer. This will be overridden by an application + adapter if present. If you want to specify `app/adapters/custom.js` as a string, do: ```js - adapter: 'custom' + import DS from 'ember-data'; + export default DS.Store.extend({ + adapter: 'custom', + }); ``` @property adapter - @default DS.JSONAPIAdapter - @type {(DS.Adapter|String)} + @default '-json-api' + @type {String} */ adapter: '-json-api', /** Returns a JSON representation of the record using a custom @@ -8092,14 +8911,16 @@ The available options are: * `includeId`: `true` if the record's ID should be included in the JSON representation @method serialize @private + @deprecated @param {DS.Model} record the record to serialize @param {Object} options an options hash */ serialize: function (record, options) { + if (true) {} var snapshot = record._internalModel.createSnapshot(); return snapshot.serialize(options); }, /** @@ -8112,16 +8933,14 @@ adapter class should be used for the lifetime of the store. @property defaultAdapter @private @return DS.Adapter */ - defaultAdapter: _ember.default.computed('adapter', function () { + defaultAdapter: computed('adapter', function () { var adapter = get(this, 'adapter'); - adapter = this.retrieveManagedInstance('adapter', adapter); - - return adapter; + return this.adapterFor(adapter); }), // ..................... // . CREATE NEW RECORD . // ..................... @@ -8135,11 +8954,11 @@ title: "Rails is omakase" }); ``` To create a new instance of a `Post` that has a relationship with a `User` record: ```js - var user = this.store.peekRecord('user', 1); + let user = this.store.peekRecord('user', 1); store.createRecord('post', { title: "Rails is omakase", user: user }); ``` @@ -8148,11 +8967,11 @@ @param {Object} inputProperties a hash of properties to set on the newly created record. @return {DS.Model} record */ createRecord: function (modelName, inputProperties) { - var typeClass = this.modelFor(modelName); + var modelClass = this.modelFor(modelName); var properties = copy(inputProperties) || new _emberDataPrivateSystemEmptyObject.default(); // If the passed properties do not include a primary key, // give the adapter an opportunity to generate one. Typically, // client-side ID generators will use something like uuid.js @@ -8163,20 +8982,23 @@ } // Coerce ID to a string properties.id = (0, _emberDataPrivateSystemCoerceId.default)(properties.id); - var internalModel = this.buildInternalModel(typeClass, properties.id); + var internalModel = this.buildInternalModel(modelClass, properties.id); var record = internalModel.getRecord(); // Move the record out of its initial `empty` state into // the `loaded` state. + // TODO @runspired this seems really bad, store should not be changing the state internalModel.loadedData(); // Set the properties specified on the record. + // TODO @runspired this is probably why we do the bad thing above record.setProperties(properties); + // TODO @runspired this should also be coalesced into some form of internalModel.setState() internalModel.eachRelationship(function (key, descriptor) { internalModel._relationships.get(key).setHasData(true); }); return record; @@ -8207,11 +9029,11 @@ /** For symmetry, a record can be deleted via the store. Example ```javascript - var post = store.createRecord('post', { + let post = store.createRecord('post', { title: "Rails is omakase" }); store.deleteRecord(post); ``` @method deleteRecord @@ -8254,11 +9076,11 @@ // that's why we have to keep this method around even though `findRecord` is // the public way to get a record by modelName and id. if (arguments.length === 1) {} - if (_ember.default.typeOf(id) === 'object') {} + if (typeOf(id) === 'object') {} if (options) {} return this.findRecord(modelName, id); }, @@ -8331,11 +9153,11 @@ id: 1, type: 'post', revision: 1 } }); - var blogPost = store.findRecord('post', 1).then(function(post) { + let blogPost = store.findRecord('post', 1).then(function(post) { post.get('revision'); // 1 }); // later, once adapter#findRecord resolved with // [ // { @@ -8379,10 +9201,43 @@ // ... } }); ``` See [peekRecord](#method_peekRecord) to get the cached version of a record. + ### Retrieving Related Model Records + If you use an adapter such as Ember's default + [`JSONAPIAdapter`](http://emberjs.com/api/data/classes/DS.JSONAPIAdapter.html) + that supports the [JSON API specification](http://jsonapi.org/) and if your server + endpoint supports the use of an + ['include' query parameter](http://jsonapi.org/format/#fetching-includes), + you can use `findRecord()` to automatically retrieve additional records related to + the one you request by supplying an `include` parameter in the `options` object. + For example, given a `post` model that has a `hasMany` relationship with a `comment` + model, when we retrieve a specific post we can have the server also return that post's + comments in the same request: + ```app/routes/post.js + import Ember from 'ember'; + export default Ember.Route.extend({ + model: function(params) { + return this.store.findRecord('post', params.post_id, {include: 'comments'}); + } + }); + ``` + In this case, the post's comments would then be available in your template as + `model.comments`. + Multiple relationships can be requested using an `include` parameter consisting of a + comma-separated list (without white-space) while nested relationships can be specified + using a dot-separated sequence of relationship names. So to request both the post's + comments and the authors of those comments the request would look like this: + ```app/routes/post.js + import Ember from 'ember'; + export default Ember.Route.extend({ + model: function(params) { + return this.store.findRecord('post', params.post_id, {include: 'comments,comments.author'}); + } + }); + ``` @since 1.13.0 @method findRecord @param {String} modelName @param {(String|Integer)} id @param {Object} options @@ -8403,50 +9258,50 @@ }, _findRecord: function (internalModel, options) { // Refetch if the reload option is passed if (options.reload) { - return this.scheduleFetch(internalModel, options); + return this._scheduleFetch(internalModel, options); } var snapshot = internalModel.createSnapshot(options); - var typeClass = internalModel.type; - var adapter = this.adapterFor(typeClass.modelName); + var modelClass = internalModel.type; + var adapter = this.adapterFor(modelClass.modelName); // Refetch the record if the adapter thinks the record is stale if (adapter.shouldReloadRecord(this, snapshot)) { - return this.scheduleFetch(internalModel, options); + return this._scheduleFetch(internalModel, options); } if (options.backgroundReload === false) { return Promise.resolve(internalModel); } // Trigger the background refetch if backgroundReload option is passed if (options.backgroundReload || adapter.shouldBackgroundReloadRecord(this, snapshot)) { - this.scheduleFetch(internalModel, options); + this._scheduleFetch(internalModel, options); } // Return the cached record return Promise.resolve(internalModel); }, _findByInternalModel: function (internalModel, options) { options = options || {}; if (options.preload) { - internalModel._preloadData(options.preload); + internalModel.preloadData(options.preload); } var fetchedInternalModel = this._findEmptyInternalModel(internalModel, options); return promiseRecord(fetchedInternalModel, "DS: Store#findRecord " + internalModel.typeKey + " with id: " + get(internalModel, 'id')); }, _findEmptyInternalModel: function (internalModel, options) { if (internalModel.isEmpty()) { - return this.scheduleFetch(internalModel, options); + return this._scheduleFetch(internalModel, options); } //TODO double check about reloading if (internalModel.isLoading()) { return internalModel._loadingPromise; @@ -8469,175 +9324,197 @@ for (var i = 0; i < ids.length; i++) { promises[i] = this.findRecord(modelName, ids[i]); } - return (0, _emberDataPrivateSystemPromiseProxies.promiseArray)(_ember.default.RSVP.all(promises).then(_ember.default.A, null, "DS: Store#findByIds of " + modelName + " complete")); + return (0, _emberDataPrivateSystemPromiseProxies.promiseArray)(RSVP.all(promises).then(A, null, "DS: Store#findByIds of " + modelName + " complete")); }, /** This method is called by `findRecord` if it discovers that a particular type/id pair hasn't been loaded yet to kick off a request to the adapter. - @method fetchRecord + @method _fetchRecord @private @param {InternalModel} internalModel model @return {Promise} promise */ - // TODO rename this to have an underscore - fetchRecord: function (internalModel, options) { - var typeClass = internalModel.type; + _fetchRecord: function (internalModel, options) { + var modelClass = internalModel.type; var id = internalModel.id; - var adapter = this.adapterFor(typeClass.modelName); + var adapter = this.adapterFor(modelClass.modelName); - var promise = (0, _emberDataPrivateSystemStoreFinders._find)(adapter, this, typeClass, id, internalModel, options); - return promise; + return (0, _emberDataPrivateSystemStoreFinders._find)(adapter, this, modelClass, id, internalModel, options); }, - scheduleFetchMany: function (records) { - var internalModels = new Array(records.length); - var fetches = new Array(records.length); - for (var i = 0; i < records.length; i++) { - internalModels[i] = records[i]._internalModel; - } + _scheduleFetchMany: function (internalModels) { + var fetches = new Array(internalModels.length); for (var i = 0; i < internalModels.length; i++) { - fetches[i] = this.scheduleFetch(internalModels[i]); + fetches[i] = this._scheduleFetch(internalModels[i]); } - return _ember.default.RSVP.Promise.all(fetches); + return Promise.all(fetches); }, - scheduleFetch: function (internalModel, options) { - var typeClass = internalModel.type; - + _scheduleFetch: function (internalModel, options) { if (internalModel._loadingPromise) { return internalModel._loadingPromise; } - var resolver = _ember.default.RSVP.defer('Fetching ' + typeClass + 'with id: ' + internalModel.id); + var modelClass = internalModel.type; + var resolver = RSVP.defer('Fetching ' + modelClass.modelName + ' with id: ' + internalModel.id); var pendingFetchItem = { - record: internalModel, + internalModel: internalModel, resolver: resolver, options: options }; var promise = resolver.promise; internalModel.loadingData(promise); + this._pendingFetch.get(modelClass).push(pendingFetchItem); - if (!this._pendingFetch.get(typeClass)) { - this._pendingFetch.set(typeClass, [pendingFetchItem]); - } else { - this._pendingFetch.get(typeClass).push(pendingFetchItem); - } - _ember.default.run.scheduleOnce('afterRender', this, this.flushAllPendingFetches); + emberRun.scheduleOnce('afterRender', this, this.flushAllPendingFetches); return promise; }, flushAllPendingFetches: function () { if (this.isDestroyed || this.isDestroying) { return; } this._pendingFetch.forEach(this._flushPendingFetchForType, this); - this._pendingFetch = Map.create(); + this._pendingFetch.clear(); }, - _flushPendingFetchForType: function (pendingFetchItems, typeClass) { + _flushPendingFetchForType: function (pendingFetchItems, modelClass) { var store = this; - var adapter = store.adapterFor(typeClass.modelName); + var adapter = store.adapterFor(modelClass.modelName); var shouldCoalesce = !!adapter.findMany && adapter.coalesceFindRequests; - var records = _ember.default.A(pendingFetchItems).mapBy('record'); + var totalItems = pendingFetchItems.length; + var internalModels = new Array(totalItems); + var seeking = new _emberDataPrivateSystemEmptyObject.default(); + for (var i = 0; i < totalItems; i++) { + var pendingItem = pendingFetchItems[i]; + var internalModel = pendingItem.internalModel; + internalModels[i] = internalModel; + seeking[internalModel.id] = pendingItem; + } + function _fetchRecord(recordResolverPair) { - recordResolverPair.resolver.resolve(store.fetchRecord(recordResolverPair.record, recordResolverPair.options)); // TODO adapter options + var recordFetch = store._fetchRecord(recordResolverPair.internalModel, recordResolverPair.options); // TODO adapter options + + recordResolverPair.resolver.resolve(recordFetch); } - function resolveFoundRecords(records) { - records.forEach(function (record) { - var pair = _ember.default.A(pendingFetchItems).findBy('record', record); + function handleFoundRecords(foundInternalModels, expectedInternalModels) { + // resolve found records + var found = new _emberDataPrivateSystemEmptyObject.default(); + for (var i = 0, l = foundInternalModels.length; i < l; i++) { + var internalModel = foundInternalModels[i]; + var pair = seeking[internalModel.id]; + found[internalModel.id] = internalModel; + if (pair) { var resolver = pair.resolver; - resolver.resolve(record); + resolver.resolve(internalModel); } - }); - return records; - } + } - function makeMissingRecordsRejector(requestedRecords) { - return function rejectMissingRecords(resolvedRecords) { - resolvedRecords = _ember.default.A(resolvedRecords); - var missingRecords = requestedRecords.reject(function (record) { - return resolvedRecords.includes(record); - }); - if (missingRecords.length) {} - rejectRecords(missingRecords); - }; - } + // reject missing records + var missingInternalModels = []; - function makeRecordsRejector(records) { - return function (error) { - rejectRecords(records, error); - }; + for (var i = 0, l = expectedInternalModels.length; i < l; i++) { + var internalModel = expectedInternalModels[i]; + + if (!found[internalModel.id]) { + missingInternalModels.push(internalModel); + } + } + + if (missingInternalModels.length) { + rejectInternalModels(missingInternalModels); + } } - function rejectRecords(records, error) { - records.forEach(function (record) { - var pair = _ember.default.A(pendingFetchItems).findBy('record', record); + function rejectInternalModels(internalModels, error) { + for (var i = 0, l = internalModels.length; i < l; i++) { + var pair = seeking[internalModels[i].id]; + if (pair) { - var resolver = pair.resolver; - resolver.reject(error); + pair.resolver.reject(error); } - }); + } } - if (pendingFetchItems.length === 1) { - _fetchRecord(pendingFetchItems[0]); - } else if (shouldCoalesce) { - + if (shouldCoalesce) { // TODO: Improve records => snapshots => records => snapshots // // We want to provide records to all store methods and snapshots to all // adapter methods. To make sure we're doing that we're providing an array // of snapshots to adapter.groupRecordsForFindMany(), which in turn will // return grouped snapshots instead of grouped records. // // But since the _findMany() finder is a store method we need to get the // records from the grouped snapshots even though the _findMany() finder // will once again convert the records to snapshots for adapter.findMany() + var snapshots = new Array(totalItems); + for (var i = 0; i < totalItems; i++) { + snapshots[i] = internalModels[i].createSnapshot(); + } - var snapshots = _ember.default.A(records).invoke('createSnapshot'); var groups = adapter.groupRecordsForFindMany(this, snapshots); - groups.forEach(function (groupOfSnapshots) { - var groupOfRecords = _ember.default.A(groupOfSnapshots).mapBy('_internalModel'); - var requestedRecords = _ember.default.A(groupOfRecords); - var ids = requestedRecords.mapBy('id'); - if (ids.length > 1) { - (0, _emberDataPrivateSystemStoreFinders._findMany)(adapter, store, typeClass, ids, requestedRecords).then(resolveFoundRecords).then(makeMissingRecordsRejector(requestedRecords)).then(null, makeRecordsRejector(requestedRecords)); + + var _loop = function (i, l) { + var group = groups[i]; + var totalInGroup = groups[i].length; + var ids = new Array(totalInGroup); + var groupedInternalModels = new Array(totalInGroup); + + for (var j = 0; j < totalInGroup; j++) { + var internalModel = group[j]._internalModel; + + groupedInternalModels[j] = internalModel; + ids[j] = internalModel.id; + } + + if (totalInGroup > 1) { + (0, _emberDataPrivateSystemStoreFinders._findMany)(adapter, store, modelClass, ids, groupedInternalModels).then(function (foundInternalModels) { + handleFoundRecords(foundInternalModels, groupedInternalModels); + }).catch(function (error) { + rejectInternalModels(groupedInternalModels, error); + }); } else if (ids.length === 1) { - var pair = _ember.default.A(pendingFetchItems).findBy('record', groupOfRecords[0]); + var pair = seeking[groupedInternalModels[0].id]; _fetchRecord(pair); } else {} - }); + }; + + for (var i = 0, l = groups.length; i < l; i++) { + _loop(i, l); + } } else { - pendingFetchItems.forEach(_fetchRecord); + for (var i = 0; i < totalItems; i++) { + _fetchRecord(pendingFetchItems[i]); + } } }, /** Get the reference for the specified record. Example ```javascript - var userRef = store.getReference('user', 1); + let userRef = store.getReference('user', 1); // check if the user is loaded - var isLoaded = userRef.value() !== null; + let isLoaded = userRef.value() !== null; // get the record of the reference (null if not yet available) - var user = userRef.value(); + let user = userRef.value(); // get the identifier of the reference if (userRef.remoteType() === "id") { - var id = userRef.id(); + let id = userRef.id(); } // load user (via store.find) userRef.load().then(...) // or trigger a reload userRef.reload().then(...) @@ -8661,11 +9538,11 @@ This method will synchronously return the record if it is available in the store, otherwise it will return `null`. A record is available if it has been fetched earlier, or pushed manually into the store. _Note: This is an synchronous method and does not return a promise._ ```js - var post = store.peekRecord('post', 1); + let post = store.peekRecord('post', 1); post.get('id'); // 1 ``` @since 1.13.0 @method peekRecord @param {String} modelName @@ -8688,29 +9565,41 @@ @method reloadRecord @private @param {DS.Model} internalModel @return {Promise} promise */ + // TODO @runspired this should be underscored reloadRecord: function (internalModel) { var modelName = internalModel.type.modelName; var adapter = this.adapterFor(modelName); var id = internalModel.id; - return this.scheduleFetch(internalModel); + return this._scheduleFetch(internalModel); }, /** - Returns true if a record for a given type and ID is already loaded. + This method returns true if a record for a given modelName and id is already + loaded in the store. Use this function to know beforehand if a findRecord() + will result in a request or that it will be a cache hit. + Example + ```javascript + store.hasRecordForId('post', 1); // false + store.findRecord('post', 1).then(function() { + store.hasRecordForId('post', 1); // true + }); + ``` @method hasRecordForId - @param {(String|DS.Model)} modelName - @param {(String|Integer)} inputId + @param {String} modelName + @param {(String|Integer)} id @return {Boolean} */ - hasRecordForId: function (modelName, inputId) { - var typeClass = this.modelFor(modelName); - var id = (0, _emberDataPrivateSystemCoerceId.default)(inputId); - var internalModel = this.typeMapFor(typeClass).idToRecord[id]; + hasRecordForId: function (modelName, id) { + + var trueId = (0, _emberDataPrivateSystemCoerceId.default)(id); + var modelClass = this.modelFor(modelName); + var internalModel = this.typeMapFor(modelClass).idToRecord[trueId]; + return !!internalModel && internalModel.isLoaded(); }, /** Returns id record for a given type and ID. If one isn't already loaded, @@ -8723,21 +9612,21 @@ */ recordForId: function (modelName, id) { return this._internalModelForId(modelName, id).getRecord(); }, - _internalModelForId: function (typeName, inputId) { - var typeClass = this.modelFor(typeName); + _internalModelForId: function (modelName, inputId) { + var modelClass = this.modelFor(modelName); var id = (0, _emberDataPrivateSystemCoerceId.default)(inputId); - var idToRecord = this.typeMapFor(typeClass).idToRecord; - var record = idToRecord[id]; + var idToRecord = this.typeMapFor(modelClass).idToRecord; + var internalModel = idToRecord[id]; - if (!record || !idToRecord[id]) { - record = this.buildInternalModel(typeClass, id); + if (!internalModel || !idToRecord[id]) { + internalModel = this.buildInternalModel(modelClass, id); } - return record; + return internalModel; }, /** @method findMany @private @@ -8829,23 +9718,25 @@ query: function (modelName, query) { return this._query(modelName, query); }, _query: function (modelName, query, array) { - var typeClass = this.modelFor(modelName); - array = array || this.recordArrayManager.createAdapterPopulatedRecordArray(typeClass, query); + var modelClass = this.modelFor(modelName); + array = array || this.recordArrayManager.createAdapterPopulatedRecordArray(modelClass, query); + var adapter = this.adapterFor(modelName); - var pA = (0, _emberDataPrivateSystemPromiseProxies.promiseArray)((0, _emberDataPrivateSystemStoreFinders._query)(adapter, this, typeClass, query, array)); + var pA = (0, _emberDataPrivateSystemPromiseProxies.promiseArray)((0, _emberDataPrivateSystemStoreFinders._query)(adapter, this, modelClass, query, array)); return pA; }, /** This method makes a request for one record, where the `id` is not known - beforehand (if the `id` is known, use `findRecord` instead). + beforehand (if the `id` is known, use [`findRecord`](#method_findRecord) + instead). This method can be used when it is certain that the server will return a single object for the primary data. Let's assume our API provides an endpoint for the currently logged in user via: ``` @@ -8864,12 +9755,11 @@ let username = user.get('username'); console.log(`Currently logged in as ${username}`); }); ``` The request is made through the adapters' `queryRecord`: - ```javascript - // app/adapters/user.js + ```app/adapters/user.js import DS from "ember-data"; export default DS.Adapter.extend({ queryRecord(modelName, query) { return Ember.$.getJSON("/api/current_user"); } @@ -8918,14 +9808,22 @@ @param {any} query an opaque query to be used by the adapter @return {Promise} promise which resolves with the found record or `null` */ queryRecord: function (modelName, query) { - var typeClass = this.modelFor(modelName); + var modelClass = this.modelFor(modelName); var adapter = this.adapterFor(modelName); - return (0, _emberDataPrivateSystemPromiseProxies.promiseObject)((0, _emberDataPrivateSystemStoreFinders._queryRecord)(adapter, this, typeClass, query)); + return (0, _emberDataPrivateSystemPromiseProxies.promiseObject)((0, _emberDataPrivateSystemStoreFinders._queryRecord)(adapter, this, modelClass, query).then(function (internalModel) { + // the promise returned by store.queryRecord is expected to resolve with + // an instance of DS.Model + if (internalModel) { + return internalModel.getRecord(); + } + + return null; + })); }, /** `findAll` asks the adapter's `findAll` method to find the records for the given type, and returns a promise which will resolve with all records of @@ -8986,11 +9884,11 @@ data: { id: 'first', type: 'author' } }); - var allAuthors; + let allAuthors; store.findAll('author').then(function(authors) { authors.getEach('id'); // ['first'] allAuthors = authors; }); // later, once adapter#findAll resolved with @@ -9036,69 +9934,101 @@ } }); ``` See [peekAll](#method_peekAll) to get an array of current records in the store, without waiting until a reload is finished. + ### Retrieving Related Model Records + If you use an adapter such as Ember's default + [`JSONAPIAdapter`](http://emberjs.com/api/data/classes/DS.JSONAPIAdapter.html) + that supports the [JSON API specification](http://jsonapi.org/) and if your server + endpoint supports the use of an + ['include' query parameter](http://jsonapi.org/format/#fetching-includes), + you can use `findAll()` to automatically retrieve additional records related to + those requested by supplying an `include` parameter in the `options` object. + For example, given a `post` model that has a `hasMany` relationship with a `comment` + model, when we retrieve all of the post records we can have the server also return + all of the posts' comments in the same request: + ```app/routes/posts.js + import Ember from 'ember'; + export default Ember.Route.extend({ + model: function() { + return this.store.findAll('post', {include: 'comments'}); + } + }); + ``` + Multiple relationships can be requested using an `include` parameter consisting of a + comma-separated list (without white-space) while nested relationships can be specified + using a dot-separated sequence of relationship names. So to request both the posts' + comments and the authors of those comments the request would look like this: + ```app/routes/posts.js + import Ember from 'ember'; + export default Ember.Route.extend({ + model: function() { + return this.store.findAll('post', {include: 'comments,comments.author'}); + } + }); + ``` See [query](#method_query) to only get a subset of records from the server. @since 1.13.0 @method findAll @param {String} modelName @param {Object} options @return {Promise} promise */ findAll: function (modelName, options) { + var modelClass = this.modelFor(modelName); - var typeClass = this.modelFor(modelName); + var fetch = this._fetchAll(modelClass, this.peekAll(modelName), options); - var fetch = this._fetchAll(typeClass, this.peekAll(modelName), options); - return fetch; }, /** @method _fetchAll @private - @param {DS.Model} typeClass + @param {DS.Model} modelClass @param {DS.RecordArray} array @return {Promise} promise */ - _fetchAll: function (typeClass, array, options) { + _fetchAll: function (modelClass, array, options) { options = options || {}; - var adapter = this.adapterFor(typeClass.modelName); - var sinceToken = this.typeMapFor(typeClass).metadata.since; + var adapter = this.adapterFor(modelClass.modelName); + var sinceToken = this.typeMapFor(modelClass).metadata.since; + if (options.reload) { set(array, 'isUpdating', true); - return (0, _emberDataPrivateSystemPromiseProxies.promiseArray)((0, _emberDataPrivateSystemStoreFinders._findAll)(adapter, this, typeClass, sinceToken, options)); + return (0, _emberDataPrivateSystemPromiseProxies.promiseArray)((0, _emberDataPrivateSystemStoreFinders._findAll)(adapter, this, modelClass, sinceToken, options)); } - var snapshotArray = array.createSnapshot(options); + var snapshotArray = array._createSnapshot(options); if (adapter.shouldReloadAll(this, snapshotArray)) { set(array, 'isUpdating', true); - return (0, _emberDataPrivateSystemPromiseProxies.promiseArray)((0, _emberDataPrivateSystemStoreFinders._findAll)(adapter, this, typeClass, sinceToken, options)); + return (0, _emberDataPrivateSystemPromiseProxies.promiseArray)((0, _emberDataPrivateSystemStoreFinders._findAll)(adapter, this, modelClass, sinceToken, options)); } if (options.backgroundReload === false) { return (0, _emberDataPrivateSystemPromiseProxies.promiseArray)(Promise.resolve(array)); } if (options.backgroundReload || adapter.shouldBackgroundReloadAll(this, snapshotArray)) { set(array, 'isUpdating', true); - (0, _emberDataPrivateSystemStoreFinders._findAll)(adapter, this, typeClass, sinceToken, options); + (0, _emberDataPrivateSystemStoreFinders._findAll)(adapter, this, modelClass, sinceToken, options); } return (0, _emberDataPrivateSystemPromiseProxies.promiseArray)(Promise.resolve(array)); }, /** @method didUpdateAll - @param {DS.Model} typeClass + @param {DS.Model} modelClass @private */ - didUpdateAll: function (typeClass) { - var liveRecordArray = this.recordArrayManager.liveRecordArrayFor(typeClass); + didUpdateAll: function (modelClass) { + var liveRecordArray = this.recordArrayManager.liveRecordArrayFor(modelClass); + set(liveRecordArray, 'isUpdating', false); }, /** This method returns a filtered array that contains all of the @@ -9110,37 +10040,39 @@ [store.findAll](#method_findAll). Also note that multiple calls to `peekAll` for a given type will always return the same `RecordArray`. Example ```javascript - var localPosts = store.peekAll('post'); + let localPosts = store.peekAll('post'); ``` @since 1.13.0 @method peekAll @param {String} modelName @return {DS.RecordArray} */ peekAll: function (modelName) { - var typeClass = this.modelFor(modelName); + var modelClass = this.modelFor(modelName); + var liveRecordArray = this.recordArrayManager.liveRecordArrayFor(modelClass); - var liveRecordArray = this.recordArrayManager.liveRecordArrayFor(typeClass); - this.recordArrayManager.populateLiveRecordArray(liveRecordArray, typeClass); + this.recordArrayManager.syncLiveRecordArray(liveRecordArray, modelClass); return liveRecordArray; }, /** This method unloads all records in the store. + It schedules unloading to happen during the next run loop. Optionally you can pass a type which unload all records for a given type. ```javascript store.unloadAll(); store.unloadAll('post'); ``` @method unloadAll @param {String} modelName */ unloadAll: function (modelName) { + if (arguments.length === 0) { var typeMaps = this.typeMaps; var keys = Object.keys(typeMaps); var types = new Array(keys.length); @@ -9148,12 +10080,12 @@ types[i] = typeMaps[keys[i]]['type'].modelName; } types.forEach(this.unloadAll, this); } else { - var typeClass = this.modelFor(modelName); - var typeMap = this.typeMapFor(typeClass); + var modelClass = this.modelFor(modelName); + var typeMap = this.typeMapFor(modelClass); var records = typeMap.records.slice(); var record = undefined; for (var i = 0; i < records.length; i++) { record = records[i]; @@ -9194,11 +10126,11 @@ ```javascript store.filter('post', { unread: true }, function(post) { return post.get('unread'); }).then(function(unreadPosts) { unreadPosts.get('length'); // 5 - var unreadPost = unreadPosts.objectAt(0); + let unreadPost = unreadPosts.objectAt(0); unreadPost.set('unread', false); unreadPosts.get('length'); // 4 }); ``` @method filter @@ -9209,15 +10141,15 @@ @return {DS.PromiseArray} @deprecated */ filter: function (modelName, query, filter) { - if (!_ember.default.ENV.ENABLE_DS_FILTER) {} + if (!ENV.ENABLE_DS_FILTER) {} - var promise; + var promise = undefined; var length = arguments.length; - var array; + var array = undefined; var hasQuery = length === 3; // allow an optional server query if (hasQuery) { promise = this.query(modelName, query); @@ -9239,21 +10171,14 @@ return array; }, null, 'DS: Store#filter of ' + modelName)); }, /** - This method returns if a certain record is already loaded - in the store. Use this function to know beforehand if a findRecord() - will result in a request or that it will be a cache hit. - Example - ```javascript - store.recordIsLoaded('post', 1); // false - store.findRecord('post', 1).then(function() { - store.recordIsLoaded('post', 1); // true - }); - ``` - @method recordIsLoaded + This method has been deprecated and is an alias for store.hasRecordForId, which should + be used instead. + @deprecated + @method recordIsLoaded @param {String} modelName @param {string} id @return {boolean} */ recordIsLoaded: function (modelName, id) { @@ -9298,11 +10223,11 @@ internalModel.adapterWillCommit(); this._pendingSave.push({ snapshot: snapshot, resolver: resolver }); - once(this, 'flushPendingSave'); + emberRun.once(this, this.flushPendingSave); }, /** This method is called at the end of the run loop, and flushes any records passed into `scheduleSave` @@ -9317,12 +10242,12 @@ pending.forEach(function (pendingItem) { var snapshot = pendingItem.snapshot; var resolver = pendingItem.resolver; var record = snapshot._internalModel; - var adapter = _this.adapterFor(record.type.modelName); - var operation; + var adapter = _this.adapterFor(record.modelClass.modelName); + var operation = undefined; if (get(record, 'currentState.stateName') === 'root.deleted.saved') { return resolver.resolve(); } else if (record.isNew()) { operation = 'createRecord'; @@ -9346,17 +10271,17 @@ @private @param {InternalModel} internalModel the in-flight internal model @param {Object} data optional data (see above) */ didSaveRecord: function (internalModel, dataArg) { - var data; + var data = undefined; if (dataArg) { data = dataArg.data; } if (data) { // normalize relationship IDs into records - this._backburner.schedule('normalizeRelationships', this, '_setupRelationships', internalModel, data); + this._backburner.schedule('normalizeRelationships', this, this._setupRelationships, internalModel, data); this.updateId(internalModel, data); } else {} //We first make sure the primary data has been updated //TODO try to move notification to the user to the end of the runloop @@ -9419,27 +10344,27 @@ /** Returns a map of IDs to client IDs for a given type. @method typeMapFor @private - @param {DS.Model} typeClass + @param {DS.Model} modelClass @return {Object} typeMap */ - typeMapFor: function (typeClass) { + typeMapFor: function (modelClass) { var typeMaps = get(this, 'typeMaps'); - var guid = _ember.default.guidFor(typeClass); + var guid = guidFor(modelClass); var typeMap = typeMaps[guid]; if (typeMap) { return typeMap; } typeMap = { idToRecord: new _emberDataPrivateSystemEmptyObject.default(), records: [], metadata: new _emberDataPrivateSystemEmptyObject.default(), - type: typeClass + type: modelClass }; typeMaps[guid] = typeMap; return typeMap; @@ -9467,14 +10392,14 @@ }, /* In case someone defined a relationship to a mixin, for example: ``` - var Comment = DS.Model.extend({ + let Comment = DS.Model.extend({ owner: belongsTo('commentable'. { polymorphic: true}) }); - var Commentable = Ember.Mixin.create({ + let Commentable = Ember.Mixin.create({ comments: hasMany('comment') }); ``` we want to look up a Commentable class which has all the necessary relationship metadata. Thus, we look up the mixin and create a mock @@ -9519,11 +10444,11 @@ if (!factory) { //Support looking up mixins as base types for polymorphic relationships factory = this._modelForMixin(modelName); } if (!factory) { - throw new _ember.default.Error("No model was found for '" + modelName + "'"); + throw new EmberError("No model was found for '" + modelName + "'"); } factory.modelName = factory.modelName || (0, _emberDataPrivateSystemNormalizeModelName.default)(modelName); return factory; }, @@ -9664,23 +10589,53 @@ @param {Object} data @return {DS.Model|Array} the record(s) that was created or updated. */ push: function (data) { + var pushed = this._push(data); + + if (Array.isArray(pushed)) { + var records = pushed.map(function (internalModel) { + return internalModel.getRecord(); + }); + + return records; + } + + if (pushed === null) { + return null; + } + + var record = pushed.getRecord(); + + return record; + }, + + /* + Push some data into the store, without creating materialized records. + @method _push + @private + @param {Object} data + @return {DS.InternalModel|Array<DS.InternalModel>} pushed InternalModel(s) + */ + _push: function (data) { var included = data.included; - var i, length; + var i = undefined, + length = undefined; + if (included) { for (i = 0, length = included.length; i < length; i++) { this._pushInternalModel(included[i]); } } if (Array.isArray(data.data)) { length = data.data.length; var internalModels = new Array(length); + for (i = 0; i < length; i++) { - internalModels[i] = this._pushInternalModel(data.data[i]).getRecord(); + internalModels[i] = this._pushInternalModel(data.data[i]); } return internalModels; } @@ -9688,17 +10643,15 @@ return null; } var internalModel = this._pushInternalModel(data.data); - var record = internalModel.getRecord(); - - return record; + return internalModel; }, - _hasModelFor: function (type) { - return !!(0, _emberDataPrivateUtils.getOwner)(this)._lookupFactory('model:' + type); + _hasModelFor: function (modelName) { + return !!(0, _emberDataPrivateUtils.getOwner)(this)._lookupFactory('model:' + modelName); }, _pushInternalModel: function (data) { var _this2 = this; @@ -9706,11 +10659,11 @@ // Actually load the record into the store. var internalModel = this._load(data); this._backburner.join(function () { - _this2._backburner.schedule('normalizeRelationships', _this2, '_setupRelationships', internalModel, data); + _this2._backburner.schedule('normalizeRelationships', _this2, _this2._setupRelationships, internalModel, data); }); return internalModel; }, @@ -9731,11 +10684,11 @@ ```app/serializers/application.js import DS from 'ember-data'; export default DS.ActiveModelSerializer; ``` ```js - var pushData = { + let pushData = { posts: [ { id: 1, post_title: "Great post", comment_ids: [2] } ], comments: [ { id: 2, comment_body: "Insightful comment" } @@ -9764,12 +10717,12 @@ @param {Object} inputPayload */ pushPayload: function (modelName, inputPayload) { var _this3 = this; - var serializer; - var payload; + var serializer = undefined; + var payload = undefined; if (!inputPayload) { payload = modelName; serializer = defaultSerializer(this); } else { payload = inputPayload; @@ -9791,12 +10744,12 @@ `normalize` converts a json payload into the normalized form that [push](#method_push) expects. Example ```js socket.on('message', function(message) { - var modelName = message.model; - var data = message.data; + let modelName = message.model; + let data = message.data; store.push(store.normalize(modelName, data)); }); ``` @method normalize @param {String} modelName The name of the model type for this payload @@ -9812,22 +10765,22 @@ /** Build a brand new record for a given type, ID, and initial data. @method buildRecord @private - @param {DS.Model} type + @param {DS.Model} modelClass @param {String} id @param {Object} data @return {InternalModel} internal model */ - buildInternalModel: function (type, id, data) { - var typeMap = this.typeMapFor(type); + buildInternalModel: function (modelClass, id, data) { + var typeMap = this.typeMapFor(modelClass); var idToRecord = typeMap.idToRecord; // lookupFactory should really return an object that creates // instances with the injections applied - var internalModel = new _emberDataPrivateSystemModelInternalModel.default(type, id, this, null, data); + var internalModel = new _emberDataPrivateSystemModelInternalModel.default(modelClass, id, this, data); // if we're creating an item, this process will be done // later, once the object has been persisted. if (id) { idToRecord[id] = internalModel; @@ -9853,12 +10806,12 @@ @method _dematerializeRecord @private @param {InternalModel} internalModel */ _dematerializeRecord: function (internalModel) { - var type = internalModel.type; - var typeMap = this.typeMapFor(type); + var modelClass = internalModel.type; + var typeMap = this.typeMapFor(modelClass); var id = internalModel.id; internalModel.updateRecordArrays(); if (id) { @@ -9887,11 +10840,13 @@ @param {String} modelName @return DS.Adapter */ adapterFor: function (modelName) { - return this.lookupAdapter(modelName); + var normalizedModelName = (0, _emberDataPrivateSystemNormalizeModelName.default)(modelName); + + return this._instanceCache.get('adapter', normalizedModelName); }, _adapterRun: function (fn) { return this._backburner.run(fn); }, @@ -9917,105 +10872,78 @@ @param {String} modelName the record to serialize @return {DS.Serializer} */ serializerFor: function (modelName) { - var fallbacks = ['application', this.adapterFor(modelName).get('defaultSerializer'), '-default']; - - var serializer = this.lookupSerializer(modelName, fallbacks); - return serializer; - }, - - /** - Retrieve a particular instance from the - container cache. If not found, creates it and - placing it in the cache. - Enabled a store to manage local instances of - adapters and serializers. - @method retrieveManagedInstance - @private - @param {String} modelName the object modelName - @param {String} name the object name - @param {Array} fallbacks the fallback objects to lookup if the lookup for modelName or 'application' fails - @return {Ember.Object} - */ - retrieveManagedInstance: function (type, modelName, fallbacks) { var normalizedModelName = (0, _emberDataPrivateSystemNormalizeModelName.default)(modelName); - var instance = this._instanceCache.get(type, normalizedModelName, fallbacks); - set(instance, 'store', this); - return instance; + return this._instanceCache.get('serializer', normalizedModelName); }, lookupAdapter: function (name) { - return this.retrieveManagedInstance('adapter', name, this.get('_adapterFallbacks')); + return this.adapterFor(name); }, - _adapterFallbacks: _ember.default.computed('adapter', function () { - var adapter = this.get('adapter'); - return ['application', adapter, '-json-api']; - }), - - lookupSerializer: function (name, fallbacks) { - return this.retrieveManagedInstance('serializer', name, fallbacks); + lookupSerializer: function (name) { + return this.serializerFor(name); }, willDestroy: function () { this._super.apply(this, arguments); this.recordArrayManager.destroy(); + this._instanceCache.destroy(); this.unloadAll(); - } + }, - }); + _pushResourceIdentifier: function (relationship, resourceIdentifier) { + if (isNone(resourceIdentifier)) { + return; + } - function deserializeRecordId(store, key, relationship, id) { - if (isNone(id)) { - return; - } + //TODO:Better asserts + return this._internalModelForId(resourceIdentifier.type, resourceIdentifier.id); + }, - //TODO:Better asserts - return store._internalModelForId(id.type, id.id); - } + _pushResourceIdentifiers: function (relationship, resourceIdentifiers) { + if (isNone(resourceIdentifiers)) { + return; + } - function deserializeRecordIds(store, key, relationship, ids) { - if (isNone(ids)) { - return; + var _internalModels = new Array(resourceIdentifiers.length); + for (var i = 0; i < resourceIdentifiers.length; i++) { + _internalModels[i] = this._pushResourceIdentifier(relationship, resourceIdentifiers[i]); + } + return _internalModels; } + }); - var _ids = new Array(ids.length); - - for (var i = 0; i < ids.length; i++) { - _ids[i] = deserializeRecordId(store, key, relationship, ids[i]); - } - - return _ids; - } - // Delegation to the adapter and promise management function defaultSerializer(store) { return store.serializerFor('application'); } function _commit(adapter, store, operation, snapshot) { var internalModel = snapshot._internalModel; var modelName = snapshot.modelName; - var typeClass = store.modelFor(modelName); - var promise = adapter[operation](store, typeClass, snapshot); + var modelClass = store.modelFor(modelName); + + var promise = adapter[operation](store, modelClass, snapshot); var serializer = (0, _emberDataPrivateSystemStoreSerializers.serializerForAdapter)(store, adapter, modelName); var label = 'DS: Extract and notify about ' + operation + ' completion of ' + internalModel; promise = Promise.resolve(promise, label); promise = (0, _emberDataPrivateSystemStoreCommon._guard)(promise, (0, _emberDataPrivateSystemStoreCommon._bind)(_emberDataPrivateSystemStoreCommon._objectIsAlive, store)); promise = (0, _emberDataPrivateSystemStoreCommon._guard)(promise, (0, _emberDataPrivateSystemStoreCommon._bind)(_emberDataPrivateSystemStoreCommon._objectIsAlive, internalModel)); return promise.then(function (adapterPayload) { store._adapterRun(function () { - var payload, data; + var payload = undefined, + data = undefined; if (adapterPayload) { - payload = (0, _emberDataPrivateSystemStoreSerializerResponse.normalizeResponseHelper)(serializer, store, typeClass, adapterPayload, snapshot.id, operation); + payload = (0, _emberDataPrivateSystemStoreSerializerResponse.normalizeResponseHelper)(serializer, store, modelClass, adapterPayload, snapshot.id, operation); if (payload.included) { store.push({ data: payload.included }); } data = payload.data; } @@ -10023,11 +10951,12 @@ }); return internalModel; }, function (error) { if (error instanceof _emberDataAdaptersErrors.InvalidError) { - var errors = serializer.extractErrors(store, typeClass, error, snapshot.id); + var errors = serializer.extractErrors(store, modelClass, error, snapshot.id); + store.recordWasInvalid(internalModel, errors); } else { store.recordWasError(internalModel, error); } @@ -10039,70 +10968,27 @@ if (!data.relationships) { return; } record.type.eachRelationship(function (key, descriptor) { - var kind = descriptor.kind; - if (!data.relationships[key]) { return; } - var relationship; - - if (data.relationships[key].links && data.relationships[key].links.related) { - var relatedLink = (0, _emberDataPrivateSystemNormalizeLink.default)(data.relationships[key].links.related); - if (relatedLink && relatedLink.href) { - relationship = record._relationships.get(key); - relationship.updateLink(relatedLink.href); - } - } - - if (data.relationships[key].meta) { - relationship = record._relationships.get(key); - relationship.updateMeta(data.relationships[key].meta); - } - - // If the data contains a relationship that is specified as an ID (or IDs), - // normalizeRelationship will convert them into DS.Model instances - // (possibly unloaded) before we push the payload into the store. - normalizeRelationship(store, key, descriptor, data.relationships[key]); - - var value = data.relationships[key].data; - - if (value !== undefined) { - if (kind === 'belongsTo') { - relationship = record._relationships.get(key); - relationship.setCanonicalRecord(value); - } else if (kind === 'hasMany') { - relationship = record._relationships.get(key); - relationship.updateRecordsFromAdapter(value); - } - } + var relationship = record._relationships.get(key); + relationship.push(data.relationships[key]); }); } - function normalizeRelationship(store, key, relationship, jsonPayload) { - var data = jsonPayload.data; - if (data) { - var kind = relationship.kind; - if (kind === 'belongsTo') { - jsonPayload.data = deserializeRecordId(store, key, relationship, data); - } else if (kind === 'hasMany') { - jsonPayload.data = deserializeRecordIds(store, key, relationship, data); - } - } - } - exports.Store = Store; exports.default = Store; }); /** @module ember-data */ -// If Ember.ENV.DS_WARN_ON_UNKNOWN_KEYS is set to true and the payload +// If ENV.DS_WARN_ON_UNKNOWN_KEYS is set to true and the payload // contains unknown attributes or relationships, log a warning. // Check unknown attributes // Check unknown relationships @@ -10134,97 +11020,136 @@ function _objectIsAlive(object) { return !(get(object, "isDestroyed") || get(object, "isDestroying")); } }); define('ember-data/-private/system/store/container-instance-cache', ['exports', 'ember', 'ember-data/-private/system/empty-object'], function (exports, _ember, _emberDataPrivateSystemEmptyObject) { - exports.default = ContainerInstanceCache; + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - var assign = _ember.default.assign || _ember.default.merge; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + var set = _ember.default.set; + /* * The `ContainerInstanceCache` serves as a lazy cache for looking up * instances of serializers and adapters. It has some additional logic for * finding the 'fallback' adapter or serializer. * * The 'fallback' adapter or serializer is an adapter or serializer that is looked up * when the preferred lookup fails. For example, say you try to look up `adapter:post`, * but there is no entry (app/adapters/post.js in EmberCLI) for `adapter:post` in the registry. * - * The `fallbacks` array passed will then be used; the first entry in the fallbacks array - * that exists in the container will then be cached for `adapter:post`. So, the next time you - * look up `adapter:post`, you'll get the `adapter:application` instance (or whatever the fallback - * was if `adapter:application` doesn't exist). + * When an adapter or serializer is unfound, getFallbacks will be invoked with the current namespace + * ('adapter' or 'serializer') and the 'preferredKey' (usually a modelName). The method should return + * an array of keys to check against. * + * The first entry in the fallbacks array that exists in the container will then be cached for + * `adapter:post`. So, the next time you look up `adapter:post`, you'll get the `adapter:application` + * instance (or whatever the fallback was if `adapter:application` doesn't exist). + * * @private * @class ContainerInstanceCache * */ - function ContainerInstanceCache(owner) { - this._owner = owner; - this._cache = new _emberDataPrivateSystemEmptyObject.default(); - } + var ContainerInstanceCache = (function () { + function ContainerInstanceCache(owner, store) { + this._owner = owner; + this._store = store; + this._namespaces = { + adapter: new _emberDataPrivateSystemEmptyObject.default(), + serializer: new _emberDataPrivateSystemEmptyObject.default() + }; + } - ContainerInstanceCache.prototype = new _emberDataPrivateSystemEmptyObject.default(); + _createClass(ContainerInstanceCache, [{ + key: 'get', + value: function get(namespace, preferredKey) { + var cache = this._namespaces[namespace]; - assign(ContainerInstanceCache.prototype, { - get: function (type, preferredKey, fallbacks) { - var cache = this._cache; - var preferredLookupKey = type + ':' + preferredKey; - - if (!(preferredLookupKey in cache)) { - var instance = this.instanceFor(preferredLookupKey) || this._findInstance(type, fallbacks); - if (instance) { - cache[preferredLookupKey] = instance; + if (cache[preferredKey]) { + return cache[preferredKey]; } - } - return cache[preferredLookupKey]; - }, - _findInstance: function (type, fallbacks) { - for (var i = 0, _length = fallbacks.length; i < _length; i++) { - var fallback = fallbacks[i]; - var lookupKey = type + ':' + fallback; - var instance = this.instanceFor(lookupKey); + var preferredLookupKey = namespace + ':' + preferredKey; + var instance = this._instanceFor(preferredLookupKey) || this._findInstance(namespace, this._fallbacksFor(namespace, preferredKey)); if (instance) { - return instance; + cache[preferredKey] = instance; + set(instance, 'store', this._store); } - } - }, - instanceFor: function (key) { - var cache = this._cache; - if (!cache[key]) { - var instance = this._owner.lookup(key); - if (instance) { - cache[key] = instance; + return cache[preferredKey]; + } + }, { + key: '_fallbacksFor', + value: function _fallbacksFor(namespace, preferredKey) { + if (namespace === 'adapter') { + return ['application', this._store.get('adapter'), '-json-api']; } + + // serializer + return ['application', this.get('adapter', preferredKey).get('defaultSerializer'), '-default']; } - return cache[key]; - }, + }, { + key: '_findInstance', + value: function _findInstance(namespace, fallbacks) { + var cache = this._namespaces[namespace]; - destroy: function () { - var cache = this._cache; - var cacheEntries = Object.keys(cache); + for (var i = 0, _length = fallbacks.length; i < _length; i++) { + var fallback = fallbacks[i]; - for (var i = 0, _length2 = cacheEntries.length; i < _length2; i++) { - var cacheKey = cacheEntries[i]; - var cacheEntry = cache[cacheKey]; - if (cacheEntry) { - cacheEntry.destroy(); + if (cache[fallback]) { + return cache[fallback]; + } + + var lookupKey = namespace + ':' + fallback; + var instance = this._instanceFor(lookupKey); + + if (instance) { + cache[fallback] = instance; + return instance; + } } } - this._owner = null; - }, + }, { + key: '_instanceFor', + value: function _instanceFor(key) { + return this._owner.lookup(key); + } + }, { + key: 'destroyCache', + value: function destroyCache(cache) { + var cacheEntries = Object.keys(cache); - constructor: ContainerInstanceCache, + for (var i = 0, _length2 = cacheEntries.length; i < _length2; i++) { + var cacheKey = cacheEntries[i]; + var cacheEntry = cache[cacheKey]; + if (cacheEntry) { + cacheEntry.destroy(); + } + } + } + }, { + key: 'destroy', + value: function destroy() { + this.destroyCache(this._namespaces.adapter); + this.destroyCache(this._namespaces.serializer); + this._namespaces = null; + this._store = null; + this._owner = null; + } + }, { + key: 'toString', + value: function toString() { + return 'ContainerInstanceCache'; + } + }]); - toString: function () { - return 'ContainerInstanceCache'; - } - }); + return ContainerInstanceCache; + })(); + + exports.default = ContainerInstanceCache; }); /* global heimdall */ define("ember-data/-private/system/store/finders", ["exports", "ember", "ember-data/-private/debug", "ember-data/-private/system/store/common", "ember-data/-private/system/store/serializer-response", "ember-data/-private/system/store/serializers"], function (exports, _ember, _emberDataPrivateDebug, _emberDataPrivateSystemStoreCommon, _emberDataPrivateSystemStoreSerializerResponse, _emberDataPrivateSystemStoreSerializers) { exports._find = _find; exports._findMany = _findMany; @@ -10255,13 +11180,12 @@ return promise.then(function (adapterPayload) { return store._adapterRun(function () { var payload = (0, _emberDataPrivateSystemStoreSerializerResponse.normalizeResponseHelper)(serializer, store, typeClass, adapterPayload, id, 'findRecord'); - //TODO Optimize - var record = store.push(payload); - return record._internalModel; + var internalModel = store._push(payload); + return internalModel; }); }, function (error) { internalModel.notFound(); if (internalModel.isEmpty()) { internalModel.unloadRecord(); @@ -10285,18 +11209,11 @@ promise = (0, _emberDataPrivateSystemStoreCommon._guard)(promise, (0, _emberDataPrivateSystemStoreCommon._bind)(_emberDataPrivateSystemStoreCommon._objectIsAlive, store)); return promise.then(function (adapterPayload) { return store._adapterRun(function () { var payload = (0, _emberDataPrivateSystemStoreSerializerResponse.normalizeResponseHelper)(serializer, store, typeClass, adapterPayload, null, 'findMany'); - //TODO Optimize, no need to materialize here - var records = store.push(payload); - var internalModels = new Array(records.length); - - for (var i = 0; i < records.length; i++) { - internalModels[i] = records[i]._internalModel; - } - + var internalModels = store._push(payload); return internalModels; }); }, null, "DS: Extract payload of " + typeClass); } @@ -10342,33 +11259,31 @@ if (!payload.data) { return null; } - //TODO Optimize - var record = store.push(payload); - return record._internalModel; + var internalModel = store._push(payload); + return internalModel; }); }, null, "DS: Extract payload of " + internalModel + " : " + relationship.type); } function _findAll(adapter, store, typeClass, sinceToken, options) { var modelName = typeClass.modelName; var recordArray = store.peekAll(modelName); - var snapshotArray = recordArray.createSnapshot(options); + var snapshotArray = recordArray._createSnapshot(options); var promise = adapter.findAll(store, typeClass, sinceToken, snapshotArray); var serializer = (0, _emberDataPrivateSystemStoreSerializers.serializerForAdapter)(store, adapter, modelName); var label = "DS: Handle Adapter#findAll of " + typeClass; promise = Promise.resolve(promise, label); promise = (0, _emberDataPrivateSystemStoreCommon._guard)(promise, (0, _emberDataPrivateSystemStoreCommon._bind)(_emberDataPrivateSystemStoreCommon._objectIsAlive, store)); return promise.then(function (adapterPayload) { store._adapterRun(function () { var payload = (0, _emberDataPrivateSystemStoreSerializerResponse.normalizeResponseHelper)(serializer, store, typeClass, adapterPayload, null, 'findAll'); - //TODO Optimize - store.push(payload); + store._push(payload); }); store.didUpdateAll(typeClass); return store.peekAll(modelName); }, null, "DS: Extract payload of findAll " + typeClass); @@ -10377,27 +11292,27 @@ function _query(adapter, store, typeClass, query, recordArray) { var modelName = typeClass.modelName; var promise = adapter.query(store, typeClass, query, recordArray); var serializer = (0, _emberDataPrivateSystemStoreSerializers.serializerForAdapter)(store, adapter, modelName); - var label = "DS: Handle Adapter#query of " + typeClass; + var label = 'DS: Handle Adapter#query of ' + typeClass; promise = Promise.resolve(promise, label); promise = (0, _emberDataPrivateSystemStoreCommon._guard)(promise, (0, _emberDataPrivateSystemStoreCommon._bind)(_emberDataPrivateSystemStoreCommon._objectIsAlive, store)); return promise.then(function (adapterPayload) { - var records, payload; + var internalModels = undefined, + payload = undefined; store._adapterRun(function () { payload = (0, _emberDataPrivateSystemStoreSerializerResponse.normalizeResponseHelper)(serializer, store, typeClass, adapterPayload, null, 'query'); - //TODO Optimize - records = store.push(payload); + internalModels = store._push(payload); }); - recordArray.loadRecords(records, payload); + recordArray._setInternalModels(internalModels, payload); return recordArray; - }, null, "DS: Extract payload of query " + typeClass); + }, null, 'DS: Extract payload of query ' + typeClass); } function _queryRecord(adapter, store, typeClass, query) { var modelName = typeClass.modelName; var promise = adapter.queryRecord(store, typeClass, query); @@ -10406,19 +11321,18 @@ promise = Promise.resolve(promise, label); promise = (0, _emberDataPrivateSystemStoreCommon._guard)(promise, (0, _emberDataPrivateSystemStoreCommon._bind)(_emberDataPrivateSystemStoreCommon._objectIsAlive, store)); return promise.then(function (adapterPayload) { - var record; + var internalModel; store._adapterRun(function () { var payload = (0, _emberDataPrivateSystemStoreSerializerResponse.normalizeResponseHelper)(serializer, store, typeClass, adapterPayload, null, 'queryRecord'); - //TODO Optimize - record = store.push(payload); + internalModel = store._push(payload); }); - return record; + return internalModel; }, null, "DS: Extract payload of queryRecord " + typeClass); } }); define('ember-data/-private/system/store/serializer-response', ['exports', 'ember', 'ember-data/-private/debug'], function (exports, _ember, _emberDataPrivateDebug) { exports.validateDocumentStructure = validateDocumentStructure; @@ -10528,11 +11442,11 @@ exports.NumberTransform = _emberDataPrivateTransformsNumber.default; exports.DateTransform = _emberDataPrivateTransformsDate.default; exports.StringTransform = _emberDataPrivateTransformsString.default; exports.BooleanTransform = _emberDataPrivateTransformsBoolean.default; }); -define('ember-data/-private/transforms/boolean', ['exports', 'ember', 'ember-data/transform', 'ember-data/-private/features'], function (exports, _ember, _emberDataTransform, _emberDataPrivateFeatures) { +define("ember-data/-private/transforms/boolean", ["exports", "ember", "ember-data/transform"], function (exports, _ember, _emberDataTransform) { var isNone = _ember.default.isNone; /** The `DS.BooleanTransform` class is used to serialize and deserialize boolean attributes on Ember Data record objects. This transform is @@ -10571,14 +11485,12 @@ */ exports.default = _emberDataTransform.default.extend({ deserialize: function (serialized, options) { var type = typeof serialized; - if (true) { - if (isNone(serialized) && options.allowNull === true) { - return null; - } + if (isNone(serialized) && options.allowNull === true) { + return null; } if (type === "boolean") { return serialized; } else if (type === "string") { @@ -10589,14 +11501,12 @@ return false; } }, serialize: function (deserialized, options) { - if (true) { - if (isNone(deserialized) && options.allowNull === true) { - return null; - } + if (isNone(deserialized) && options.allowNull === true) { + return null; } return Boolean(deserialized); } }); @@ -10822,11 +11732,10 @@ return headers; } }); define('ember-data/adapter', ['exports', 'ember'], function (exports, _ember) { - var get = _ember.default.get; /** An adapter is an object that receives requests from a store and translates them into the appropriate action to take against your persistence layer. The persistence layer is usually an HTTP API, but @@ -11062,11 +11971,11 @@ @param {DS.Snapshot} snapshot @param {Object} options @return {Object} serialized snapshot */ serialize: function (snapshot, options) { - return get(snapshot.record, 'store').serializerFor(snapshot.modelName).serialize(snapshot, options); + return snapshot.serialize(options); }, /** Implement this method in a subclass to handle the creation of new records. @@ -11403,10 +12312,73 @@ var SOURCE_POINTER_REGEXP = /^\/?data\/(attributes|relationships)\/(.*)/; var SOURCE_POINTER_PRIMARY_REGEXP = /^\/?data/; var PRIMARY_ATTRIBUTE_KEY = 'base'; /** + A `DS.AdapterError` is used by an adapter to signal that an error occurred + during a request to an external API. It indicates a generic error, and + subclasses are used to indicate specific error states. The following + subclasses are provided: + + - `DS.InvalidError` + - `DS.TimeoutError` + - `DS.AbortError` + - `DS.UnauthorizedError` + - `DS.ForbiddenError` + - `DS.NotFoundError` + - `DS.ConflictError` + - `DS.ServerError` + + To create a custom error to signal a specific error state in communicating + with an external API, extend the `DS.AdapterError`. For example if the + external API exclusively used HTTP `503 Service Unavailable` to indicate + it was closed for maintenance: + + ```app/adapters/maintenance-error.js + import DS from 'ember-data'; + + export default DS.AdapterError.extend({ message: "Down for maintenance." }); + ``` + + This error would then be returned by an adapter's `handleResponse` method: + + ```app/adapters/application.js + import DS from 'ember-data'; + import MaintenanceError from './maintenance-error'; + + export default DS.JSONAPIAdapter.extend({ + handleResponse(status) { + if (503 === status) { + return new MaintenanceError(); + } + + return this._super(...arguments); + } + }); + ``` + + And can then be detected in an application and used to send the user to an + `under-maintenance` route: + + ```app/routes/application.js + import Ember from 'ember'; + import MaintenanceError from '../adapters/maintenance-error'; + + export default Ember.Route.extend({ + actions: { + error(error, transition) { + if (error instanceof MaintenanceError) { + this.transitionTo('under-maintenance'); + return; + } + + // ...other error handling logic + } + } + }); + ``` + @class AdapterError @namespace DS */ function AdapterError(errors) { @@ -11515,61 +12487,213 @@ */ var InvalidError = extend(AdapterError, 'The adapter rejected the commit because it was invalid'); exports.InvalidError = InvalidError; /** + A `DS.TimeoutError` is used by an adapter to signal that a request + to the external API has timed out. I.e. no response was received from + the external API within an allowed time period. + + An example use case would be to warn the user to check their internet + connection if an adapter operation has timed out: + + ```app/routes/application.js + import Ember from 'ember'; + import DS from 'ember-data'; + + const { TimeoutError } = DS; + + export default Ember.Route.extend({ + actions: { + error(error, transition) { + if (error instanceof TimeoutError) { + // alert the user + alert('Are you still connected to the internet?'); + return; + } + + // ...other error handling logic + } + } + }); + ``` + @class TimeoutError @namespace DS */ var TimeoutError = extend(AdapterError, 'The adapter operation timed out'); exports.TimeoutError = TimeoutError; /** + A `DS.AbortError` is used by an adapter to signal that a request to + the external API was aborted. For example, this can occur if the user + navigates away from the current page after a request to the external API + has been initiated but before a response has been received. + @class AbortError @namespace DS */ var AbortError = extend(AdapterError, 'The adapter operation was aborted'); exports.AbortError = AbortError; /** + A `DS.UnauthorizedError` equates to a HTTP `401 Unauthorized` response + status. It is used by an adapter to signal that a request to the external + API was rejected because authorization is required and has failed or has not + yet been provided. + + An example use case would be to redirect the user to a log in route if a + request is unauthorized: + + ```app/routes/application.js + import Ember from 'ember'; + import DS from 'ember-data'; + + const { UnauthorizedError } = DS; + + export default Ember.Route.extend({ + actions: { + error(error, transition) { + if (error instanceof UnauthorizedError) { + // go to the sign in route + this.transitionTo('login'); + return; + } + + // ...other error handling logic + } + } + }); + ``` + @class UnauthorizedError @namespace DS */ var UnauthorizedError = extendedErrorsEnabled ? extend(AdapterError, 'The adapter operation is unauthorized') : null; exports.UnauthorizedError = UnauthorizedError; /** + A `DS.ForbiddenError` equates to a HTTP `403 Forbidden` response status. + It is used by an adapter to signal that a request to the external API was + valid but the server is refusing to respond to it. If authorization was + provided and is valid, then the authenticated user does not have the + necessary permissions for the request. + @class ForbiddenError @namespace DS */ var ForbiddenError = extendedErrorsEnabled ? extend(AdapterError, 'The adapter operation is forbidden') : null; exports.ForbiddenError = ForbiddenError; /** + A `DS.NotFoundError` equates to a HTTP `404 Not Found` response status. + It is used by an adapter to signal that a request to the external API + was rejected because the resource could not be found on the API. + + An example use case would be to detect if the user has entered a route + for a specific model that does not exist. For example: + + ```app/routes/post.js + import Ember from 'ember'; + import DS from 'ember-data'; + + const { NotFoundError } = DS; + + export default Ember.Route.extend({ + model(params) { + return this.get('store').findRecord('post', params.post_id); + }, + + actions: { + error(error, transition) { + if (error instanceof NotFoundError) { + // redirect to a list of all posts instead + this.transitionTo('posts'); + } else { + // otherwise let the error bubble + return true; + } + } + } + }); + ``` + @class NotFoundError @namespace DS */ var NotFoundError = extendedErrorsEnabled ? extend(AdapterError, 'The adapter could not find the resource') : null; exports.NotFoundError = NotFoundError; /** + A `DS.ConflictError` equates to a HTTP `409 Conflict` response status. + It is used by an adapter to indicate that the request could not be processed + because of a conflict in the request. An example scenario would be when + creating a record with a client generated id but that id is already known + to the external API. + @class ConflictError @namespace DS */ var ConflictError = extendedErrorsEnabled ? extend(AdapterError, 'The adapter operation failed due to a conflict') : null; exports.ConflictError = ConflictError; /** + A `DS.ServerError` equates to a HTTP `500 Internal Server Error` response + status. It is used by the adapter to indicate that a request has failed + because of an error in the external API. + @class ServerError @namespace DS */ var ServerError = extendedErrorsEnabled ? extend(AdapterError, 'The adapter operation failed due to a server error') : null; exports.ServerError = ServerError; /** + Convert an hash of errors into an array with errors in JSON-API format. + + ```javascript + import DS from 'ember-data'; + + const { errorsHashToArray } = DS; + + let errors = { + base: "Invalid attributes on saving this record", + name: "Must be present", + age: ["Must be present", "Must be a number"] + }; + + let errorsArray = errorsHashToArray(errors); + // [ + // { + // title: "Invalid Document", + // detail: "Invalid attributes on saving this record", + // source: { pointer: "/data" } + // }, + // { + // title: "Invalid Attribute", + // detail: "Must be present", + // source: { pointer: "/data/attributes/name" } + // }, + // { + // title: "Invalid Attribute", + // detail: "Must be present", + // source: { pointer: "/data/attributes/age" } + // }, + // { + // title: "Invalid Attribute", + // detail: "Must be a number", + // source: { pointer: "/data/attributes/age" } + // } + // ] + ``` + @method errorsHashToArray - @private + @public + @namespace + @for DS + @param {Object} errors hash with errors as properties + @return {Array} array of errors in JSON-API format */ function errorsHashToArray(errors) { var out = []; @@ -11596,12 +12720,48 @@ return out; } /** + Convert an array of errors in JSON-API format into an object. + + ```javascript + import DS from 'ember-data'; + + const { errorsArrayToHash } = DS; + + let errorsArray = [ + { + title: "Invalid Attribute", + detail: "Must be present", + source: { pointer: "/data/attributes/name" } + }, + { + title: "Invalid Attribute", + detail: "Must be present", + source: { pointer: "/data/attributes/age" } + }, + { + title: "Invalid Attribute", + detail: "Must be a number", + source: { pointer: "/data/attributes/age" } + } + ]; + + let errors = errorsArrayToHash(errorsArray); + // { + // "name": ["Must be present"], + // "age": ["Must be present", "must be a number"] + // } + ``` + @method errorsArrayToHash - @private + @public + @namespace + @for DS + @param {Array} errors array of errors in JSON-API format + @return {Object} */ function errorsArrayToHash(errors) { var out = {}; @@ -11628,10 +12788,139 @@ } }); define('ember-data/adapters/json-api', ['exports', 'ember', 'ember-data/adapters/rest', 'ember-data/-private/features', 'ember-data/-private/debug'], function (exports, _ember, _emberDataAdaptersRest, _emberDataPrivateFeatures, _emberDataPrivateDebug) { /** + The `JSONAPIAdapter` is the default adapter used by Ember Data. It + is responsible for transforming the store's requests into HTTP + requests that follow the [JSON API](http://jsonapi.org/format/) + format. + + ## JSON API Conventions + + The JSONAPIAdapter uses JSON API conventions for building the url + for a record and selecting the HTTP verb to use with a request. The + actions you can take on a record map onto the following URLs in the + JSON API adapter: + + <table> + <tr> + <th> + Action + </th> + <th> + HTTP Verb + </th> + <th> + URL + </th> + </tr> + <tr> + <th> + `store.findRecord('post', 123)` + </th> + <td> + GET + </td> + <td> + /posts/123 + </td> + </tr> + <tr> + <th> + `store.findAll('post')` + </th> + <td> + GET + </td> + <td> + /posts + </td> + </tr> + <tr> + <th> + Update `postRecord.save()` + </th> + <td> + PATCH + </td> + <td> + /posts/123 + </td> + </tr> + <tr> + <th> + Create `store.createRecord('post').save()` + </th> + <td> + POST + </td> + <td> + /posts + </td> + </tr> + <tr> + <th> + Delete `postRecord.destroyRecord()` + </th> + <td> + DELETE + </td> + <td> + /posts/123 + </td> + </tr> + </table> + + ## Success and failure + + The JSONAPIAdapter will consider a success any response with a + status code of the 2xx family ("Success"), as well as 304 ("Not + Modified"). Any other status code will be considered a failure. + + On success, the request promise will be resolved with the full + response payload. + + Failed responses with status code 422 ("Unprocessable Entity") will + be considered "invalid". The response will be discarded, except for + the `errors` key. The request promise will be rejected with a + `DS.InvalidError`. This error object will encapsulate the saved + `errors` value. + + Any other status codes will be treated as an adapter error. The + request promise will be rejected, similarly to the invalid case, + but with an instance of `DS.AdapterError` instead. + + ### Endpoint path customization + + Endpoint paths can be prefixed with a `namespace` by setting the + namespace property on the adapter: + + ```app/adapters/application.js + import DS from 'ember-data'; + + export default DS.JSONAPIAdapter.extend({ + namespace: 'api/1' + }); + ``` + Requests for the `person` model would now target `/api/1/people/1`. + + ### Host customization + + An adapter can target other hosts by setting the `host` property. + + ```app/adapters/application.js + import DS from 'ember-data'; + + export default DS.JSONAPIAdapter.extend({ + host: 'https://api.example.com' + }); + ``` + + Requests for the `person` model would now target + `https://api.example.com/people/1`. + @since 1.13.0 @class JSONAPIAdapter @constructor @namespace DS @extends DS.RESTAdapter @@ -11671,13 +12960,21 @@ ids as a query string, you can set coalesceFindRequests to true to coalesce all find requests within a single runloop. For example, if you have an initial payload of: ```javascript { - post: { + data: { id: 1, - comments: [1, 2] + type: 'post', + relationship: { + comments: { + data: [ + { id: 1, type: 'comment' }, + { id: 2, type: 'comment' } + ] + } + } } } ``` By default calling `post.get('comments')` will trigger the following requests(assuming the comments haven't been loaded before): @@ -11701,45 +12998,25 @@ @property coalesceFindRequests @type {boolean} */ coalesceFindRequests: false, - /** - @method findMany - @param {DS.Store} store - @param {DS.Model} type - @param {Array} ids - @param {Array} snapshots - @return {Promise} promise - */ findMany: function (store, type, ids, snapshots) { if (false && !this._hasCustomizedAjax()) { return this._super.apply(this, arguments); } else { var url = this.buildURL(type.modelName, ids, snapshots, 'findMany'); return this.ajax(url, 'GET', { data: { filter: { id: ids.join(',') } } }); } }, - /** - @method pathForType - @param {String} modelName - @return {String} path - **/ pathForType: function (modelName) { var dasherized = _ember.default.String.dasherize(modelName); return _ember.default.String.pluralize(dasherized); }, // TODO: Remove this once we have a better way to override HTTP verbs. - /** - @method updateRecord - @param {DS.Store} store - @param {DS.Model} type - @param {DS.Snapshot} snapshot - @return {Promise} promise - */ updateRecord: function (store, type, snapshot) { if (false && !this._hasCustomizedAjax()) { return this._super.apply(this, arguments); } else { var data = {}; @@ -11875,11 +13152,11 @@ root property. For example, in response to a `GET` request for `/posts/1`, the JSON should look like this: ```js { - "post": { + "posts": { "id": 1, "title": "I'm Running to Reform the W3C's Tag", "author": "Yehuda Katz" } } @@ -11930,19 +13207,64 @@ The JSON returned should look like this: ```js { - "person": { + "people": { "id": 5, "firstName": "Barack", "lastName": "Obama", "occupation": "President" } } ``` + #### Relationships + + Relationships are usually represented by ids to the record in the + relationship. The related records can then be sideloaded in the + response under a key for the type. + + ```js + { + "posts": { + "id": 5, + "title": "I'm Running to Reform the W3C's Tag", + "author": "Yehuda Katz", + "comments": [1, 2] + }, + "comments": [{ + "id": 1, + "author": "User 1", + "message": "First!", + }, { + "id": 2, + "author": "User 2", + "message": "Good Luck!", + }] + } + ``` + + If the records in the relationship are not known when the response + is serialized its also possible to represent the relationship as a + url using the `links` key in the response. Ember Data will fetch + this url to resolve the relationship when it is accessed for the + first time. + + ```js + { + "posts": { + "id": 5, + "title": "I'm Running to Reform the W3C's Tag", + "author": "Yehuda Katz", + "links": { + "comments": "/posts/5/comments" + } + } + } + ``` + ### Errors If a response is considered a failure, the JSON payload is expected to include a top-level key `errors`, detailing any specific issues. For example: @@ -12373,10 +13695,11 @@ * Links beginning with a single `/` will have the current adapter's `host` value prepended to it. * Links with no beginning `/` will have a parentURL prepended to it, via the current adapter's `buildURL`. @method findHasMany @param {DS.Store} store @param {DS.Snapshot} snapshot + @param {Object} relationship meta object describing the relationship @param {String} url @return {Promise} promise */ findHasMany: function (store, snapshot, url, relationship) { if (false && !this._hasCustomizedAjax()) { @@ -13444,11 +14767,11 @@ */ exports.default = { name: 'data-adapter', before: 'store', - initialize: _ember.default.K + initialize: function () {} }; }); define('ember-data/initializers/ember-data', ['exports', 'ember-data/setup-container', 'ember-data/-private/core'], function (exports, _emberDataSetupContainer, _emberDataPrivateCore) { /* @@ -13498,11 +14821,11 @@ */ exports.default = { name: 'injectStore', before: 'store', - initialize: _ember.default.K + initialize: function () {} }; }); define('ember-data/initializers/store', ['exports', 'ember'], function (exports, _ember) { /* @@ -13513,11 +14836,11 @@ */ exports.default = { name: 'store', after: 'ember-data', - initialize: _ember.default.K + initialize: function () {} }; }); define('ember-data/initializers/transforms', ['exports', 'ember'], function (exports, _ember) { /* @@ -13528,11 +14851,11 @@ */ exports.default = { name: 'transforms', before: 'store', - initialize: _ember.default.K + initialize: function () {} }; }); define("ember-data/instance-initializers/ember-data", ["exports", "ember-data/-private/instance-initializers/initialize-store-service"], function (exports, _emberDataPrivateInstanceInitializersInitializeStoreService) { exports.default = { name: "ember-data", @@ -13572,23 +14895,49 @@ */ exports.default = _ember.default.Object.extend({ /** - The `store` property is the application's `store` that contains all records. - It's injected as a service. - It can be used to push records from a non flat data structure server - response. + The `store` property is the application's `store` that contains + all records. It can be used to look up serializers for other model + types that may be nested inside the payload response. + Example: + ```js + Serializer.extend({ + extractRelationship: function(relationshipModelName, relationshipHash) { + var modelClass = this.store.modelFor(relationshipModelName); + var relationshipSerializer = this.store.serializerFor(relationshipModelName); + return relationshipSerializer.normalize(modelClass, relationshipHash); + } + }); + ``` @property store @type {DS.Store} @public */ /** The `normalizeResponse` method is used to normalize a payload from the server to a JSON-API Document. http://jsonapi.org/format/#document-structure + Example: + ```js + Serializer.extend({ + normalizeResponse(store, primaryModelClass, payload, id, requestType) { + if (requestType === 'findRecord') { + return this.normalize(primaryModelClass, payload); + } else { + return payload.reduce(function(documentHash, item) { + let { data, included } = this.normalize(primaryModelClass, item); + documentHash.included.push(...included); + documentHash.data.push(data); + return documentHash; + }, { data: [], included: [] }) + } + } + }); + ``` @since 1.13.0 @method normalizeResponse @param {DS.Store} store @param {DS.Model} primaryModelClass @param {Object} payload @@ -13602,22 +14951,56 @@ The `serialize` method is used when a record is saved in order to convert the record into the form that your external data source expects. `serialize` takes an optional `options` hash with a single option: - `includeId`: If this is `true`, `serialize` should include the ID in the serialized object it builds. + Example: + ```js + Serializer.extend({ + serialize(snapshot, options) { + var json = { + id: snapshot.id + }; + snapshot.eachAttribute((key, attribute) => { + json[key] = snapshot.attr(key); + }); + snapshot.eachRelationship((key, relationship) => { + if (relationship.kind === 'belongsTo') { + json[key] = snapshot.belongsTo(key, { id: true }); + } else if (relationship.kind === 'hasMany') { + json[key] = snapshot.hasMany(key, { ids: true }); + } + }); + return json; + }, + }); + ``` @method serialize - @param {DS.Model} record + @param {DS.Snapshot} snapshot @param {Object} [options] @return {Object} */ serialize: null, /** The `normalize` method is used to convert a payload received from your external data source into the normalized form `store.push()` expects. You should override this method, munge the hash and return the normalized payload. + Example: + ```js + Serializer.extend({ + normalize(modelClass, resourceHash) { + var data = { + id: resourceHash.id, + type: modelClass.modelName, + attributes: resourceHash + }; + return { data: data }; + } + }) + ``` @method normalize @param {DS.Model} typeClass @param {Object} hash @return {Object} */ @@ -13988,11 +15371,11 @@ this._serializeHasManyAsIdsAndTypes(snapshot, json, relationship); } } }, - /** + /* Serializes a hasMany relationship as an array of objects containing only `id` and `type` keys. This has its use case on polymorphic hasMany relationships where the server is not storing all records in the same table using STI, and therefore the `id` is not enough information TODO: Make the default in Ember-data 3.0?? @@ -14297,10 +15680,43 @@ } ``` to the format that the Ember Data store expects. + ### Customizing meta + + Since a JSON API Document can have meta defined in multiple locations you can + use the specific serializer hooks if you need to customize the meta. + + One scenario would be to camelCase the meta keys of your payload. The example + below shows how this could be done using `normalizeArrayResponse` and + `extractRelationship`. + + ```app/serializers/application.js + export default JSONAPISerializer.extend({ + + normalizeArrayResponse(store, primaryModelClass, payload, id, requestType) { + let normalizedDocument = this._super(...arguments); + + // Customize document meta + normalizedDocument.meta = camelCaseKeys(normalizedDocument.meta); + + return normalizedDocument; + }, + + extractRelationship(relationshipHash) { + let normalizedRelationship = this._super(...arguments); + + // Customize relationship meta + normalizedRelationship.meta = camelCaseKeys(normalizedRelationship.meta); + + return normalizedRelationship; + } + + }); + ``` + @since 1.13.0 @class JSONAPISerializer @namespace DS @extends DS.JSONSerializer */ @@ -14441,16 +15857,10 @@ var normalized = this._super.apply(this, arguments); return normalized; }, - /** - @method extractAttributes - @param {DS.Model} modelClass - @param {Object} resourceHash - @return {Object} - */ extractAttributes: function (modelClass, resourceHash) { var _this = this; var attributes = {}; @@ -14464,15 +15874,10 @@ } return attributes; }, - /** - @method extractRelationship - @param {Object} relationshipHash - @return {Object} - */ extractRelationship: function (relationshipHash) { if (_ember.default.typeOf(relationshipHash.data) === 'object') { relationshipHash.data = this._normalizeRelationshipDataHelper(relationshipHash.data); } @@ -14489,16 +15894,10 @@ } return relationshipHash; }, - /** - @method extractRelationships - @param {Object} modelClass - @param {Object} resourceHash - @return {Object} - */ extractRelationships: function (modelClass, resourceHash) { var _this2 = this; var relationships = {}; @@ -14538,35 +15937,36 @@ return this.modelNameFromPayloadKey(resourceHash.type); } }, /** - @method modelNameFromPayloadKey + Dasherizes and singularizes the model name in the payload to match + the format Ember Data uses internally for the model name. + For example the key `posts` would be converted to `post` and the + key `studentAssesments` would be converted to `student-assesment`. + @method modelNameFromPayloadKey @param {String} key @return {String} the model's modelName */ // TODO @deprecated Use modelNameFromPayloadType instead modelNameFromPayloadKey: function (key) { return (0, _emberInflector.singularize)((0, _emberDataPrivateSystemNormalizeModelName.default)(key)); }, /** - @method payloadKeyFromModelName + Converts the model name to a pluralized version of the model name. + For example `post` would be converted to `posts` and + `student-assesment` would be converted to `student-assesments`. + @method payloadKeyFromModelName @param {String} modelName @return {String} */ // TODO @deprecated Use payloadTypeFromModelName instead payloadKeyFromModelName: function (modelName) { return (0, _emberInflector.pluralize)(modelName); }, - /** - @method normalize - @param {DS.Model} modelClass - @param {Object} resourceHash the resource hash from the adapter - @return {Object} the normalized resource hash - */ normalize: function (modelClass, resourceHash) { if (resourceHash.attributes) { this.normalizeUsingDeclaredMapping(modelClass, resourceHash.attributes); } @@ -14635,16 +16035,10 @@ */ keyForRelationship: function (key, typeClass, method) { return dasherize(key); }, - /** - @method serialize - @param {DS.Snapshot} snapshot - @param {Object} options - @return {Object} json - */ serialize: function (snapshot, options) { var data = this._super.apply(this, arguments); var payloadType = undefined; if (false) { @@ -14661,17 +16055,10 @@ data.type = payloadType; return { data: data }; }, - /** - @method serializeAttribute - @param {DS.Snapshot} snapshot - @param {Object} json - @param {String} key - @param {Object} attribute - */ serializeAttribute: function (snapshot, json, key, attribute) { var type = attribute.type; if (this._canSerialize(key)) { json.attributes = json.attributes || {}; @@ -14690,16 +16077,10 @@ json.attributes[payloadKey] = value; } }, - /** - @method serializeBelongsTo - @param {DS.Snapshot} snapshot - @param {Object} json - @param {Object} relationship - */ serializeBelongsTo: function (snapshot, json, relationship) { var key = relationship.key; if (this._canSerialize(key)) { var belongsTo = snapshot.belongsTo(key); @@ -14737,16 +16118,10 @@ json.relationships[payloadKey] = { data: data }; } } }, - /** - @method serializeHasMany - @param {DS.Snapshot} snapshot - @param {Object} json - @param {Object} relationship - */ serializeHasMany: function (snapshot, json, relationship) { var key = relationship.key; var shouldSerializeHasMany = '_shouldSerializeHasMany'; if (false) { shouldSerializeHasMany = 'shouldSerializeHasMany'; @@ -15578,46 +16953,21 @@ modelNameFromPayloadKey: function (key) { return (0, _emberDataPrivateSystemNormalizeModelName.default)(key); }, /** - @method normalizeAttributes - @private - */ - normalizeAttributes: function (typeClass, hash) { - var _this4 = this; - - var payloadKey; - - if (this.keyForAttribute) { - typeClass.eachAttribute(function (key) { - payloadKey = _this4.keyForAttribute(key, 'deserialize'); - if (key === payloadKey) { - return; - } - if (hash[payloadKey] === undefined) { - return; - } - - hash[key] = hash[payloadKey]; - delete hash[payloadKey]; - }); - } - }, - - /** @method normalizeRelationships @private */ normalizeRelationships: function (typeClass, hash) { - var _this5 = this; + var _this4 = this; var payloadKey; if (this.keyForRelationship) { typeClass.eachRelationship(function (key, relationship) { - payloadKey = _this5.keyForRelationship(key, relationship.kind, 'deserialize'); + payloadKey = _this4.keyForRelationship(key, relationship.kind, 'deserialize'); if (key === payloadKey) { return; } if (hash[payloadKey] === undefined) { return; @@ -15862,31 +17212,34 @@ @param {DS.Snapshot} snapshot @param {Object} options @return {Object} json */ serialize: function (snapshot, options) { - var _this6 = this; + var _this5 = this; var json = {}; if (options && options.includeId) { - var id = snapshot.id; - - if (id) { - json[get(this, 'primaryKey')] = id; + if (false) { + this.serializeId(snapshot, json, get(this, 'primaryKey')); + } else { + var id = snapshot.id; + if (id) { + json[get(this, 'primaryKey')] = id; + } } } snapshot.eachAttribute(function (key, attribute) { - _this6.serializeAttribute(snapshot, json, key, attribute); + _this5.serializeAttribute(snapshot, json, key, attribute); }); snapshot.eachRelationship(function (key, relationship) { if (relationship.kind === 'belongsTo') { - _this6.serializeBelongsTo(snapshot, json, relationship); + _this5.serializeBelongsTo(snapshot, json, relationship); } else if (relationship.kind === 'hasMany') { - _this6.serializeHasMany(snapshot, json, relationship); + _this5.serializeHasMany(snapshot, json, relationship); } }); return json; }, @@ -16076,11 +17429,11 @@ @method serializePolymorphicType @param {DS.Snapshot} snapshot @param {Object} json @param {Object} relationship */ - serializePolymorphicType: _ember.default.K, + serializePolymorphicType: function () {}, /** `extractMeta` is used to deserialize any meta information in the adapter payload. By default Ember Data expects meta information to be located on the `meta` property of the payload object. @@ -16182,27 +17535,27 @@ @param {Object} payload @param {(String|Number)} id @return {Object} json The deserialized errors */ extractErrors: function (store, typeClass, payload, id) { - var _this7 = this; + var _this6 = this; if (payload && typeof payload === 'object' && payload.errors) { payload = (0, _emberDataAdaptersErrors.errorsArrayToHash)(payload.errors); this.normalizeUsingDeclaredMapping(typeClass, payload); typeClass.eachAttribute(function (name) { - var key = _this7.keyForAttribute(name, 'deserialize'); + var key = _this6.keyForAttribute(name, 'deserialize'); if (key !== name && payload[key] !== undefined) { payload[name] = payload[key]; delete payload[key]; } }); typeClass.eachRelationship(function (name) { - var key = _this7.keyForRelationship(name, 'deserialize'); + var key = _this6.keyForRelationship(name, 'deserialize'); if (key !== name && payload[key] !== undefined) { payload[name] = payload[key]; delete payload[key]; } }); @@ -16302,10 +17655,43 @@ } }); } + if (false) { + + JSONSerializer.reopen({ + + /** + serializeId can be used to customize how id is serialized + For example, your server may expect integer datatype of id + By default the snapshot's id (String) is set on the json hash via json[primaryKey] = snapshot.id. + ```app/serializers/application.js + import DS from 'ember-data'; + export default DS.JSONSerializer.extend({ + serializeId(snapshot, json, primaryKey) { + var id = snapshot.id; + json[primaryKey] = parseInt(id, 10); + } + }); + ``` + @method serializeId + @public + @param {DS.Snapshot} snapshot + @param {Object} json + @param {String} primaryKey + */ + serializeId: function (snapshot, json, primaryKey) { + var id = snapshot.id; + + if (id) { + json[primaryKey] = id; + } + } + }); + } + exports.default = JSONSerializer; }); define("ember-data/serializers/rest", ["exports", "ember", "ember-data/-private/debug", "ember-data/serializers/json", "ember-data/-private/system/normalize-model-name", "ember-inflector", "ember-data/-private/system/coerce-id", "ember-data/-private/utils", "ember-data/-private/features"], function (exports, _ember, _emberDataPrivateDebug, _emberDataSerializersJson, _emberDataPrivateSystemNormalizeModelName, _emberInflector, _emberDataPrivateSystemCoerceId, _emberDataPrivateUtils, _emberDataPrivateFeatures) { function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } } @@ -17278,10 +18664,10 @@ */ deserialize: null }); }); define("ember-data/version", ["exports"], function (exports) { - exports.default = "2.10.0"; + exports.default = "2.11.0-beta.1"; }); define("ember-inflector", ["exports", "ember", "ember-inflector/lib/system", "ember-inflector/lib/ext/string"], function (exports, _ember, _emberInflectorLibSystem, _emberInflectorLibExtString) { _emberInflectorLibSystem.Inflector.defaultRules = _emberInflectorLibSystem.defaultRules; _ember.default.Inflector = _emberInflectorLibSystem.Inflector;