(function() { var VERSION = '3.0.0'; if (Ember.libraries) { Ember.libraries.register('Ember Model', VERSION); } })(); (function() { function mustImplement(message) { var fn = function() { var className = this.constructor.toString(); throw new Error(message.replace('{{className}}', className)); }; fn.isUnimplemented = true; return fn; } Ember.Adapter = Ember.Object.extend({ find: mustImplement('{{className}} must implement find'), findQuery: mustImplement('{{className}} must implement findQuery'), findMany: mustImplement('{{className}} must implement findMany'), findAll: mustImplement('{{className}} must implement findAll'), createRecord: mustImplement('{{className}} must implement createRecord'), saveRecord: mustImplement('{{className}} must implement saveRecord'), deleteRecord: mustImplement('{{className}} must implement deleteRecord'), load: function(record, id, data) { record.load(id, data); } }); })(); (function() { var get = Ember.get, set = Ember.set; Ember.FixtureAdapter = Ember.Adapter.extend({ _counter: 0, _findData: function(klass, id) { var fixtures = klass.FIXTURES, idAsString = id.toString(), primaryKey = get(klass, 'primaryKey'), data = Ember.A(fixtures).find(function(el) { return (el[primaryKey]).toString() === idAsString; }); return data; }, _setPrimaryKey: function(record) { var klass = record.constructor, fixtures = klass.FIXTURES, primaryKey = get(klass, 'primaryKey'); if(record.get(primaryKey)) { return; } set(record, primaryKey, this._generatePrimaryKey()); }, _generatePrimaryKey: function() { var counter = this.get("_counter"); this.set("_counter", counter + 1); return "fixture-" + counter; }, find: function(record, id) { var data = this._findData(record.constructor, id); return new Ember.RSVP.Promise(function(resolve, reject) { Ember.run.later(this, function() { Ember.run(record, record.load, id, data); resolve(record); }, 0); }); }, findMany: function(klass, records, ids) { var fixtures = klass.FIXTURES, requestedData = []; for (var i = 0, l = ids.length; i < l; i++) { requestedData.push(this._findData(klass, ids[i])); } return new Ember.RSVP.Promise(function(resolve, reject) { Ember.run.later(this, function() { Ember.run(records, records.load, klass, requestedData); resolve(records); }, 0); }); }, findAll: function(klass, records) { var fixtures = klass.FIXTURES; return new Ember.RSVP.Promise(function(resolve, reject) { Ember.run.later(this, function() { Ember.run(records, records.load, klass, fixtures); resolve(records); }, 0); }); }, createRecord: function(record) { var klass = record.constructor, fixtures = klass.FIXTURES, self = this; return new Ember.RSVP.Promise(function(resolve, reject) { Ember.run.later(this, function() { var rootKey = record.constructor.rootKey, json; self._setPrimaryKey(record); json = rootKey ? record.toJSON()[rootKey] : record.toJSON(); fixtures.push(klass.findFromCacheOrLoad(json)); record.didCreateRecord(); resolve(record); }, 0); }); }, saveRecord: function(record) { return new Ember.RSVP.Promise(function(resolve, reject) { Ember.run.later(this, function() { record.didSaveRecord(); resolve(record); }, 0); }); }, deleteRecord: function(record) { return new Ember.RSVP.Promise(function(resolve, reject) { Ember.run.later(this, function() { record.didDeleteRecord(); resolve(record); }, 0); }); } }); })(); (function() { var get = Ember.get, set = Ember.set; Ember.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, { isLoaded: false, isLoading: Ember.computed.not('isLoaded'), load: function(klass, data) { set(this, 'content', this.materializeData(klass, data)); this.notifyLoaded(); }, loadForFindMany: function(klass) { var self = this; var content = get(this, '_ids').map(function(id) { return klass.cachedRecordForId(id, Ember.getOwner(self)); }); set(this, 'content', Ember.A(content)); this.notifyLoaded(); }, notifyLoaded: function() { set(this, 'isLoaded', true); this.trigger('didLoad'); }, materializeData: function(klass, data) { var self = this; var owner = Ember.getOwner(this); return Ember.A(data.map(function(el) { return klass.findFromCacheOrLoad(el, owner); })); }, reload: function() { var modelClass = this.get('modelClass'), self = this, promises; set(this, 'isLoaded', false); if (modelClass._findAllRecordArray === this) { return modelClass.adapter.findAll(modelClass, this); } else if (this._query) { return modelClass.adapter.findQuery(modelClass, this, this._query); } else { promises = this.map(function(record) { return record.reload(); }); return Ember.RSVP.all(promises).then(function(data) { self.notifyLoaded(); }); } } }); })(); (function() { var get = Ember.get; Ember.FilteredRecordArray = Ember.RecordArray.extend({ init: function() { if (!get(this, 'modelClass')) { throw new Error('FilteredRecordArrays must be created with a modelClass'); } if (!get(this, 'filterFunction')) { throw new Error('FilteredRecordArrays must be created with a filterFunction'); } if (!get(this, 'filterProperties')) { throw new Error('FilteredRecordArrays must be created with filterProperties'); } var modelClass = get(this, 'modelClass'); modelClass.registerRecordArray(this); this.registerObservers(); this.updateFilter(); this._super(); }, updateFilter: function() { var self = this, results = []; get(this, 'modelClass').forEachCachedRecord(function(record) { if (self.filterFunction(record)) { results.push(record); } }); this.set('content', Ember.A(results)); }, updateFilterForRecord: function(record) { var results = get(this, 'content'); if (this.filterFunction(record) && !results.includes(record)) { results.pushObject(record); } }, registerObservers: function() { var self = this; get(this, 'modelClass').forEachCachedRecord(function(record) { self.registerObserversOnRecord(record); }); }, registerObserversOnRecord: function(record) { var self = this, filterProperties = get(this, 'filterProperties'); for (var i = 0, l = get(filterProperties, 'length'); i < l; i++) { record.addObserver(filterProperties[i], self, 'updateFilterForRecord'); } } }); })(); (function() { var get = Ember.get, set = Ember.set; Ember.ManyArray = Ember.RecordArray.extend({ _records: null, originalContent: null, _modifiedRecords: null, unloadObject: function(record) { var obj = get(this, 'content').findBy('clientId', record._reference.clientId); get(this, 'content').removeObject(obj); var originalObj = get(this, 'originalContent').findBy('clientId', record._reference.clientId); get(this, 'originalContent').removeObject(originalObj); }, isDirty: Ember.computed('content.[]', 'originalContent.[]', '_modifiedRecords.[]', function() { var originalContent = get(this, 'originalContent'), originalContentLength = get(originalContent, 'length'), content = get(this, 'content'), contentLength = get(content, 'length'); if (originalContentLength !== contentLength) { return true; } if (this._modifiedRecords && this._modifiedRecords.length) { return true; } var isDirty = false; for (var i = 0, l = contentLength; i < l; i++) { if (!originalContent.includes(content[i])) { isDirty = true; break; } } return isDirty; }), objectAtContent: function(idx) { var content = get(this, 'content'); if (!content.length || idx >= content.length) { return; } // need to add observer if it wasn't materialized before var observerNeeded = (content[idx].record) ? false : true; var owner = Ember.getOwner(this); var record = this.materializeRecord(idx, owner); if (observerNeeded) { var isDirtyRecord = record.get('isDirty'), isNewRecord = record.get('isNew'); if (isDirtyRecord || isNewRecord) { this._modifiedRecords.pushObject(content[idx]); } Ember.addObserver(content[idx], 'record.isDirty', this, 'recordStateChanged'); record.registerParentHasManyArray(this); } return record; }, save: function() { // TODO: loop over dirty records only return Ember.RSVP.all(this.map(function(record) { return record.save(); })).then(Ember.A); }, replaceContent: function(index, removed, added) { added = added.map(function(record) { return record._reference; }); this._super(index, removed, added); }, _contentDidChange: Ember.observer('content', function() { var content = get(this, 'content'); var contentPrev = this._content; if (contentPrev && contentPrev !== content) { this.arrayWillChange(contentPrev, 0, get(contentPrev, 'length'), 0); contentPrev.removeArrayObserver(this); this._setupOriginalContent(content); } if (content) { content.addArrayObserver(this); this.arrayDidChange(content, 0, 0, get(content, 'length')); } this._content = content; }), arrayWillChange: function(item, idx, removedCnt, addedCnt) { var content = item; for (var i = idx; i < idx+removedCnt; i++) { var currentItem = content[i]; if (currentItem && currentItem.record) { this._modifiedRecords.removeObject(currentItem); currentItem.record.unregisterParentHasManyArray(this); Ember.removeObserver(currentItem, 'record.isDirty', this, 'recordStateChanged'); } } }, arrayDidChange: function(item, idx, removedCnt, addedCnt) { var parent = get(this, 'parent'), relationshipKey = get(this, 'relationshipKey'), isDirty = get(this, 'isDirty'); var content = item; for (var i = idx; i < idx+addedCnt; i++) { var currentItem = content[i]; if (currentItem && currentItem.record) { var isDirtyRecord = currentItem.record.get('isDirty'), isNewRecord = currentItem.record.get('isNew'); // why newly created object is not dirty? if (isDirtyRecord || isNewRecord) { this._modifiedRecords.pushObject(currentItem); } Ember.addObserver(currentItem, 'record.isDirty', this, 'recordStateChanged'); currentItem.record.registerParentHasManyArray(this); } } if (isDirty) { parent._relationshipBecameDirty(relationshipKey); } else { parent._relationshipBecameClean(relationshipKey); } }, load: function(content) { Ember.setProperties(this, { content: content, originalContent: Ember.A(content.slice()) }); set(this, '_modifiedRecords', Ember.A([])); }, revert: function() { this._setupOriginalContent(); }, _setupOriginalContent: function(content) { content = content || get(this, 'content'); if (content) { set(this, 'originalContent', Ember.A(content.slice())); } set(this, '_modifiedRecords', Ember.A([])); }, init: function() { this._super(); this._setupOriginalContent(); this._contentDidChange(); }, recordStateChanged: function(obj, keyName) { var parent = get(this, 'parent'), relationshipKey = get(this, 'relationshipKey'); if (obj.record.get('isDirty')) { if (this._modifiedRecords.indexOf(obj) === -1) { this._modifiedRecords.pushObject(obj); } parent._relationshipBecameDirty(relationshipKey); } else { if (this._modifiedRecords.indexOf(obj) > -1) { this._modifiedRecords.removeObject(obj); } if (!this.get('isDirty')) { parent._relationshipBecameClean(relationshipKey); } } } }); Ember.HasManyArray = Ember.ManyArray.extend({ materializeRecord: function(idx, owner) { var klass = get(this, 'modelClass'), content = get(this, 'content'), reference = content.objectAt(idx), record = reference.record; if (record) { Ember.setOwner(record, owner); return record; } return klass._findFetchById(reference.id, false, owner); }, toJSON: function() { var ids = [], content = this.get('content'); content.forEach(function(reference) { if (reference.id) { ids.push(reference.id); } }); return ids; } }); Ember.EmbeddedHasManyArray = Ember.ManyArray.extend({ create: function(attrs) { var klass = get(this, 'modelClass'), record = klass.create(attrs); this.pushObject(record); return record; // FIXME: inject parent's id }, materializeRecord: function(idx, owner) { var klass = get(this, 'modelClass'), primaryKey = get(klass, 'primaryKey'), content = get(this, 'content'), reference = content.objectAt(idx), attrs = reference.data; var record; if (reference.record) { record = reference.record; } else { record = klass.create({ _reference: reference }); reference.record = record; Ember.setOwner(record, owner); if (attrs) { record.load(attrs[primaryKey], attrs); } } return record; }, toJSON: function() { return this.map(function(record) { return record.toJSON(); }); } }); })(); (function() { var get = Ember.get, set = Ember.set, setProperties = Ember.setProperties, meta = Ember.meta, underscore = Ember.String.underscore; function contains(array, element) { for (var i = 0, l = array.length; i < l; i++) { if (array[i] === element) { return true; } } return false; } function concatUnique(toArray, fromArray) { var e; for (var i = 0, l = fromArray.length; i < l; i++) { e = fromArray[i]; if (!contains(toArray, e)) { toArray.push(e); } } return toArray; } function hasCachedValue(object, key) { var objectMeta = meta(object, false); if (objectMeta) { return key in objectMeta.cache; } } function isDescriptor(value) { // Ember < 1.11 if (Ember.Descriptor !== undefined) { return value instanceof Ember.Descriptor; } // Ember >= 1.11 return value && typeof value === 'object' && value.isDescriptor; } Ember.run.queues.push('data'); Ember.Model = Ember.Object.extend(Ember.Evented, { isLoaded: true, isLoading: Ember.computed.not('isLoaded'), isNew: true, isDeleted: false, _dirtyAttributes: null, /** Called when attribute is accessed. @method getAttr @param key {String} key which is being accessed @param value {Object} value, which will be returned from getter by default */ getAttr: function(key, value) { return value; }, isDirty: Ember.computed('_dirtyAttributes.length', function() { var dirtyAttributes = get(this, '_dirtyAttributes'); return dirtyAttributes && dirtyAttributes.length !== 0 || false; }), _relationshipBecameDirty: function(name) { var dirtyAttributes = get(this, '_dirtyAttributes'); if (dirtyAttributes) { dirtyAttributes.addObject(name); } else { set(this, '_dirtyAttributes', Ember.A([name])); } }, _relationshipBecameClean: function(name) { var dirtyAttributes = get(this, '_dirtyAttributes'); if (dirtyAttributes) { dirtyAttributes.removeObject(name); } }, dataKey: function(key) { var camelizeKeys = get(this.constructor, 'camelizeKeys'); var meta = this.constructor.metaForProperty(key); if (meta.options && meta.options.key) { return camelizeKeys ? underscore(meta.options.key) : meta.options.key; } return camelizeKeys ? underscore(key) : key; }, init: function() { this._createReference(); if (!this._dirtyAttributes) { set(this, '_dirtyAttributes', Ember.A([])); } this._super(); }, _createReference: function() { var reference = this._reference, id = this.getPrimaryKey(); if (!reference) { reference = this.constructor._getOrCreateReferenceForId(id); set(reference, 'record', this); this._reference = reference; } else if (reference.id !== id) { reference.id = id; this.constructor._cacheReference(reference); } if (!reference.id) { reference.id = id; } return reference; }, getPrimaryKey: function() { return get(this, get(this.constructor, 'primaryKey')); }, load: function(id, hash) { var data = {}; data[get(this.constructor, 'primaryKey')] = id; set(this, '_data', Ember.merge(data, hash)); this.getWithDefault('_dirtyAttributes', Ember.A([])).clear(); this._reloadHasManys(); // eagerly load embedded data var relationships = this.constructor._relationships || [], meta = Ember.meta(this), relationshipKey, relationship, relationshipMeta, relationshipData, relationshipType; for (var i = 0, l = relationships.length; i < l; i++) { relationshipKey = relationships[i]; relationshipMeta = this.constructor.metaForProperty(relationshipKey); if (relationshipMeta.options.embedded) { relationshipType = relationshipMeta.type; if (typeof relationshipType === "string") { relationshipType = Ember.get(Ember.lookup, relationshipType) || Ember.getOwner(this).resolveRegistration('model:'+ relationshipType); } relationshipData = data[relationshipKey]; if (relationshipData) { relationshipType.load(relationshipData); } } } set(this, 'isNew', false); set(this, 'isLoaded', true); this._createReference(); this.trigger('didLoad'); }, didDefineProperty: function(proto, key, value) { if (isDescriptor(value)) { var meta = value.meta(); var klass = proto.constructor; if (meta.isAttribute) { if (!klass._attributes) { klass._attributes = []; } klass._attributes.push(key); } else if (meta.isRelationship) { if (!klass._relationships) { klass._relationships = []; } klass._relationships.push(key); meta.relationshipKey = key; } } }, serializeHasMany: function(key, meta) { return this.get(key).toJSON(); }, serializeBelongsTo: function(key, meta) { if (meta.options.embedded) { var record = this.get(key); return record ? record.toJSON() : null; } else { var primaryKey = get(meta.getType(this), 'primaryKey'); return this.get(key + '.' + primaryKey); } }, toJSON: function() { var key, meta, json = {}, attributes = this.constructor.getAttributes(), relationships = this.constructor.getRelationships(), properties = attributes ? this.getProperties(attributes) : {}, rootKey = get(this.constructor, 'rootKey'); for (key in properties) { meta = this.constructor.metaForProperty(key); if (meta.type && meta.type.serialize) { json[this.dataKey(key)] = meta.type.serialize(properties[key]); } else if (meta.type && Ember.Model.dataTypes[meta.type]) { json[this.dataKey(key)] = Ember.Model.dataTypes[meta.type].serialize(properties[key]); } else { json[this.dataKey(key)] = properties[key]; } } if (relationships) { var data, relationshipKey; for(var i = 0; i < relationships.length; i++) { key = relationships[i]; meta = this.constructor.metaForProperty(key); relationshipKey = meta.options.key || key; if (meta.kind === 'belongsTo') { data = this.serializeBelongsTo(key, meta); } else { data = this.serializeHasMany(key, meta); } json[relationshipKey] = data; } } if (rootKey) { var jsonRoot = {}; jsonRoot[rootKey] = json; return jsonRoot; } else { return json; } }, save: function() { var adapter = this.constructor.adapter; set(this, 'isSaving', true); if (get(this, 'isNew')) { return adapter.createRecord(this); } else if (get(this, 'isDirty')) { return adapter.saveRecord(this); } else { // noop, return a resolved promise var self = this, promise = new Ember.RSVP.Promise(function(resolve, reject) { resolve(self); }); set(this, 'isSaving', false); return promise; } }, reload: function() { this.getWithDefault('_dirtyAttributes', Ember.A([])).clear(); return this.constructor.reload(this.get(get(this.constructor, 'primaryKey')), Ember.getOwner(this)); }, revert: function() { this.getWithDefault('_dirtyAttributes', Ember.A([])).clear(); this.notifyPropertyChange('_data'); this._reloadHasManys(true); }, didCreateRecord: function() { var primaryKey = get(this.constructor, 'primaryKey'), id = get(this, primaryKey); set(this, 'isNew', false); set(this, '_dirtyAttributes', Ember.A([])); this.constructor.addToRecordArrays(this); this.trigger('didCreateRecord'); this.didSaveRecord(); }, didSaveRecord: function() { set(this, 'isSaving', false); this.trigger('didSaveRecord'); if (this.get('isDirty')) { this._copyDirtyAttributesToData(); } }, deleteRecord: function() { return this.constructor.adapter.deleteRecord(this); }, didDeleteRecord: function() { this.constructor.removeFromRecordArrays(this); set(this, 'isDeleted', true); this.trigger('didDeleteRecord'); }, _copyDirtyAttributesToData: function() { if (!this._dirtyAttributes) { return; } var dirtyAttributes = this._dirtyAttributes, data = get(this, '_data'), key; if (!data) { data = {}; set(this, '_data', data); } for (var i = 0, l = dirtyAttributes.length; i < l; i++) { // TODO: merge Object.create'd object into prototype key = dirtyAttributes[i]; data[this.dataKey(key)] = this.cacheFor(key); } set(this, '_dirtyAttributes', Ember.A([])); this._resetDirtyStateInNestedObjects(this); // we need to reset isDirty state to all child objects in embedded relationships }, _resetDirtyStateInNestedObjects: function(object) { var i, obj; if (object._hasManyArrays) { for (i = 0; i < object._hasManyArrays.length; i++) { var array = object._hasManyArrays[i]; array.revert(); if (array.embedded) { for (var j = 0; j < array.get('length'); j++) { obj = array.objectAt(j); obj._copyDirtyAttributesToData(); } } } } if (object._belongsTo) { for (i = 0; i < object._belongsTo.length; i++) { var belongsTo = object._belongsTo[i]; if (belongsTo.options.embedded) { obj = this.get(belongsTo.relationshipKey); if (obj) { obj._copyDirtyAttributesToData(); } } } } }, _registerHasManyArray: function(array) { if (!this._hasManyArrays) { this._hasManyArrays = Ember.A([]); } this._hasManyArrays.pushObject(array); }, registerParentHasManyArray: function(array) { if (!this._parentHasManyArrays) { this._parentHasManyArrays = Ember.A([]); } this._parentHasManyArrays.pushObject(array); }, unregisterParentHasManyArray: function(array) { if (!this._parentHasManyArrays) { return; } this._parentHasManyArrays.removeObject(array); }, _reloadHasManys: function(reverting) { if (!this._hasManyArrays) { return; } var i, j; for (i = 0; i < this._hasManyArrays.length; i++) { var array = this._hasManyArrays[i], hasManyContent = this._getHasManyContent(get(array, 'key'), get(array, 'modelClass'), get(array, 'embedded')); if (!reverting) { for (j = 0; j < array.get('length'); j++) { if (array.objectAt(j).get('isNew') && !array.objectAt(j).get('isDeleted')) { hasManyContent.addObject(array.objectAt(j)._reference); } } } array.load(hasManyContent); } }, _getHasManyContent: function(key, type, embedded) { var content = get(this, '_data.' + key); if (content) { var mapFunction, primaryKey, reference; if (embedded) { primaryKey = get(type, 'primaryKey'); mapFunction = function(attrs) { reference = type._getOrCreateReferenceForId(attrs[primaryKey]); reference.data = attrs; return reference; }; } else { mapFunction = function(id) { return type._getOrCreateReferenceForId(id); }; } content = content.map(mapFunction); } return Ember.A(content || []); }, _registerBelongsTo: function(key) { if (!this._belongsTo) { this._belongsTo = Ember.A([]); } this._belongsTo.pushObject(key); } }); Ember.Model.reopenClass({ primaryKey: 'id', adapter: Ember.Adapter.create(), _clientIdCounter: 1, getAttributes: function() { this.proto(); // force class "compilation" if it hasn't been done. var attributes = this._attributes || []; if (typeof this.superclass.getAttributes === 'function') { attributes = this.superclass.getAttributes().concat(attributes); } return attributes; }, getRelationships: function() { this.proto(); // force class "compilation" if it hasn't been done. var relationships = this._relationships || []; if (typeof this.superclass.getRelationships === 'function') { relationships = this.superclass.getRelationships().concat(relationships); } return relationships; }, fetch: function(id) { if (!arguments.length) { return this._findFetchAll(true); } else if (Ember.isArray(id)) { return this._findFetchMany(id, true); } else if (typeof id === 'object') { return this._findFetchQuery(id, true); } else { return this._findFetchById(id, true); } }, find: function(id) { if (!arguments.length) { return this._findFetchAll(false); } else if (Ember.isArray(id)) { return this._findFetchMany(id, false); } else if (typeof id === 'object') { return this._findFetchQuery(id, false); } else { return this._findFetchById(id, false); } }, findQuery: function(params) { return this._findFetchQuery(params, false); }, fetchQuery: function(params) { return this._findFetchQuery(params, true); }, _findFetchQuery: function(params, isFetch, owner) { var records = Ember.RecordArray.create({modelClass: this, _query: params}); Ember.setOwner(records, owner); var promise = this.adapter.findQuery(this, records, params); return isFetch ? promise : records; }, findMany: function(ids) { return this._findFetchMany(ids, false); }, fetchMany: function(ids) { return this._findFetchMany(ids, true); }, _findFetchMany: function(ids, isFetch, owner) { Ember.assert("findFetchMany requires an array", Ember.isArray(ids)); var records = Ember.RecordArray.create({_ids: ids, modelClass: this}), deferred; Ember.setOwner(records, owner); if (!this.recordArrays) { this.recordArrays = []; } this.recordArrays.push(records); if (this._currentBatchIds) { concatUnique(this._currentBatchIds, ids); this._currentBatchRecordArrays.push(records); } else { this._currentBatchIds = concatUnique([], ids); this._currentBatchRecordArrays = [records]; } if (isFetch) { deferred = Ember.RSVP.defer(); Ember.set(deferred, 'resolveWith', records); if (!this._currentBatchDeferreds) { this._currentBatchDeferreds = []; } this._currentBatchDeferreds.push(deferred); } Ember.run.scheduleOnce('data', this, this._executeBatch, owner); return isFetch ? deferred.promise : records; }, findAll: function() { return this._findFetchAll(false); }, fetchAll: function() { return this._findFetchAll(true); }, _findFetchAll: function(isFetch, owner) { var self = this; var currentFetchPromise = this._currentFindFetchAllPromise; if (isFetch && currentFetchPromise) { return currentFetchPromise; } else if (this._findAllRecordArray) { if (isFetch) { return new Ember.RSVP.Promise(function(resolve) { resolve(self._findAllRecordArray); }); } else { return this._findAllRecordArray; } } var records = this._findAllRecordArray = Ember.RecordArray.create({modelClass: this}); Ember.setOwner(records, owner); var promise = this._currentFindFetchAllPromise = this.adapter.findAll(this, records); promise['finally'](function() { self._currentFindFetchAllPromise = null; }); // Remove the cached record array if the promise is rejected if (promise.then) { promise.then(null, function() { self._findAllRecordArray = null; return Ember.RSVP.reject.apply(null, arguments); }); } return isFetch ? promise : records; }, findById: function(id) { return this._findFetchById(id, false); }, fetchById: function(id) { return this._findFetchById(id, true); }, _findFetchById: function(id, isFetch, owner) { var record = this.cachedRecordForId(id, owner), isLoaded = get(record, 'isLoaded'), adapter = get(this, 'adapter'), deferredOrPromise; if (isLoaded) { if (isFetch) { return new Ember.RSVP.Promise(function(resolve, reject) { resolve(record); }); } else { return record; } } deferredOrPromise = this._fetchById(record, id); return isFetch ? deferredOrPromise : record; }, _currentBatchIds: null, _currentBatchRecordArrays: null, _currentBatchDeferreds: null, reload: function(id, owner) { var record = this.cachedRecordForId(id, owner); record.set('isLoaded', false); return this._fetchById(record, id); }, _fetchById: function(record, id) { var adapter = get(this, 'adapter'), deferred; if (adapter.findMany && !adapter.findMany.isUnimplemented) { if (this._currentBatchIds) { if (!contains(this._currentBatchIds, id)) { this._currentBatchIds.push(id); } } else { this._currentBatchIds = [id]; this._currentBatchRecordArrays = []; } deferred = Ember.RSVP.defer(); //Attached the record to the deferred so we can resolve it later. Ember.set(deferred, 'resolveWith', record); if (!this._currentBatchDeferreds) { this._currentBatchDeferreds = []; } this._currentBatchDeferreds.push(deferred); var owner = Ember.getOwner(record); Ember.run.scheduleOnce('data', this, this._executeBatch, owner); return deferred.promise; } else { return adapter.find(record, id); } }, _executeBatch: function(owner) { var batchIds = this._currentBatchIds, batchRecordArrays = this._currentBatchRecordArrays, batchDeferreds = this._currentBatchDeferreds, self = this, requestIds = [], promise, i; this._currentBatchIds = null; this._currentBatchRecordArrays = null; this._currentBatchDeferreds = null; for (i = 0; i < batchIds.length; i++) { if (!this.cachedRecordForId(batchIds[i], owner).get('isLoaded')) { requestIds.push(batchIds[i]); } } if (requestIds.length === 1) { promise = get(this, 'adapter').find(this.cachedRecordForId(requestIds[0], owner), requestIds[0]); } else { var recordArray = Ember.RecordArray.create({_ids: batchIds }); Ember.setOwner(recordArray, owner); if (requestIds.length === 0) { promise = new Ember.RSVP.Promise(function(resolve, reject) { resolve(recordArray); }); recordArray.notifyLoaded(); } else { promise = get(this, 'adapter').findMany(this, recordArray, requestIds); } } promise.then(function() { for (var i = 0, l = batchRecordArrays.length; i < l; i++) { batchRecordArrays[i].loadForFindMany(self); } if (batchDeferreds) { for (i = 0, l = batchDeferreds.length; i < l; i++) { var resolveWith = Ember.get(batchDeferreds[i], 'resolveWith'); batchDeferreds[i].resolve(resolveWith); } } }).then(null, function(errorXHR) { if (batchDeferreds) { for (var i = 0, l = batchDeferreds.length; i < l; i++) { batchDeferreds[i].reject(errorXHR); } } }); }, getCachedReferenceRecord: function(id){ var ref = this._getReferenceById(id); if(ref && ref.record) { return ref.record; } return undefined; }, cachedRecordForId: function(id, owner) { var record; if (!this.transient) { record = this.getCachedReferenceRecord(id); } if (!record) { var primaryKey = get(this, 'primaryKey'), attrs = {isLoaded: false}; attrs[primaryKey] = id; record = this.create(attrs); Ember.setOwner(record, owner); if (!this.transient) { var sideloadedData = this.sideloadedData && this.sideloadedData[id]; if (sideloadedData) { record.load(id, sideloadedData); } } } return record; }, addToRecordArrays: function(record) { if (this._findAllRecordArray) { this._findAllRecordArray.addObject(record); } if (this.recordArrays) { this.recordArrays.forEach(function(recordArray) { if (recordArray instanceof Ember.FilteredRecordArray) { recordArray.registerObserversOnRecord(record); recordArray.updateFilter(); } else { recordArray.addObject(record); } }); } }, unload: function (record) { this.removeFromHasManyArrays(record); this.removeFromRecordArrays(record); var primaryKey = record.get(get(this, 'primaryKey')); this.removeFromCache(primaryKey); }, clearCache: function () { this.sideloadedData = undefined; this._referenceCache = undefined; this._findAllRecordArray = undefined; }, removeFromCache: function (key) { if (this.sideloadedData && this.sideloadedData[key]) { delete this.sideloadedData[key]; } if(this._referenceCache && this._referenceCache[key]) { delete this._referenceCache[key]; } }, removeFromHasManyArrays: function(record) { if (record._parentHasManyArrays) { record._parentHasManyArrays.forEach(function(hasManyArray) { hasManyArray.unloadObject(record); }); record._parentHasManyArrays = null; } }, removeFromRecordArrays: function(record) { if (this._findAllRecordArray) { this._findAllRecordArray.removeObject(record); } if (this.recordArrays) { this.recordArrays.forEach(function(recordArray) { recordArray.removeObject(record); }); } }, // FIXME findFromCacheOrLoad: function(data, owner) { var record; if (!data[get(this, 'primaryKey')]) { record = this.create({isLoaded: false}); } else { record = this.cachedRecordForId(data[get(this, 'primaryKey')], owner); } record.load(data[get(this, 'primaryKey')], data); Ember.setOwner(record, owner); return record; }, registerRecordArray: function(recordArray) { if (!this.recordArrays) { this.recordArrays = []; } this.recordArrays.push(recordArray); }, unregisterRecordArray: function(recordArray) { if (!this.recordArrays) { return; } Ember.A(this.recordArrays).removeObject(recordArray); }, forEachCachedRecord: function(callback) { if (!this._referenceCache) { return; } var ids = Object.keys(this._referenceCache); ids.map(function(id) { return this._getReferenceById(id).record; }, this).forEach(callback); }, load: function(hashes) { if (Ember.typeOf(hashes) !== 'array') { hashes = [hashes]; } if (!this.sideloadedData) { this.sideloadedData = {}; } for (var i = 0, l = hashes.length; i < l; i++) { var hash = hashes[i], primaryKey = hash[get(this, 'primaryKey')], record = this.getCachedReferenceRecord(primaryKey); if (record) { record.load(primaryKey, hash); } else { this.sideloadedData[primaryKey] = hash; } } }, _getReferenceById: function(id) { if (!this._referenceCache) { this._referenceCache = {}; } return this._referenceCache[id]; }, _getOrCreateReferenceForId: function(id) { var reference = this._getReferenceById(id); if (!reference) { reference = this._createReference(id); } return reference; }, _createReference: function(id) { if (!this._referenceCache) { this._referenceCache = {}; } Ember.assert('The id ' + id + ' has already been used with another record of type ' + this.toString() + '.', !id || !this._referenceCache[id]); var reference = { id: id, clientId: this._clientIdCounter++ }; this._cacheReference(reference); return reference; }, _cacheReference: function(reference) { if (!this._referenceCache) { this._referenceCache = {}; } // if we're creating an item, this process will be done // later, once the object has been persisted. if (!Ember.isEmpty(reference.id)) { this._referenceCache[reference.id] = reference; } } }); })(); (function() { var supportsComputedGetterSetter; try { Ember.computed({ get: function() { }, set: function() { } }); supportsComputedGetterSetter = true; } catch(e) { supportsComputedGetterSetter = false; } Ember.Model.computed = function() { var polyfillArguments = []; var config = arguments[arguments.length - 1]; if (typeof config === 'function' || supportsComputedGetterSetter) { return Ember.computed.apply(null, arguments); } for (var i = 0, l = arguments.length - 1; i < l; i++) { polyfillArguments.push(arguments[i]); } var func; if (config.set) { func = function(key, value, oldValue) { if (arguments.length > 1) { return config.set.call(this, key, value, oldValue); } else { return config.get.call(this, key); } }; } else { func = function(key) { return config.get.call(this, key); }; } polyfillArguments.push(func); return Ember.computed.apply(null, polyfillArguments); }; })(); (function() { var get = Ember.get; function getType(record) { var type = this.type; if (typeof this.type === "string" && this.type) { this.type = get(Ember.lookup, this.type); if (!this.type) { var emstore = Ember.getOwner(record).lookup('emstore:main'); this.type = emstore.modelFor(type); this.type.reopenClass({ adapter: emstore.adapterFor(type) }); } } return this.type; } Ember.hasMany = function(type, options) { options = options || {}; var meta = { type: type, isRelationship: true, options: options, kind: 'hasMany', getType: getType}; return Ember.Model.computed({ get: function(propertyKey) { type = meta.getType(this); Ember.assert("Type cannot be empty", !Ember.isEmpty(type)); var key = options.key || propertyKey; var owner = Ember.getOwner(this); return this.getHasMany(key, type, meta, owner); }, set: function(propertyKey, newContentArray, existingArray) { if (!existingArray) { existingArray = this.getHasMany(options.key || propertyKey, type, meta, Ember.getOwner(this)); } return existingArray.setObjects(newContentArray); } }).meta(meta); }; Ember.Model.reopen({ getHasMany: function(key, type, meta, owner) { var embedded = meta.options.embedded, collectionClass = embedded ? Ember.EmbeddedHasManyArray : Ember.HasManyArray; var collection = collectionClass.create({ parent: this, modelClass: type, content: this._getHasManyContent(key, type, embedded), embedded: embedded, key: key, relationshipKey: meta.relationshipKey }); Ember.setOwner(collection, owner); this._registerHasManyArray(collection); return collection; } }); })(); (function() { var get = Ember.get, set = Ember.set; function storeFor(record) { var owner = Ember.getOwner(record); if (owner) { return owner.lookup('emstore:main'); } return null; } function getType(record) { var type = this.type; if (typeof this.type === "string" && this.type) { type = Ember.get(Ember.lookup, this.type); if (!type) { var emstore = storeFor(record); type = emstore.modelFor(this.type); type.reopenClass({ adapter: emstore.adapterFor(this.type) }); } } return type; } Ember.belongsTo = function(type, options) { options = options || {}; var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo', getType: getType}; return Ember.Model.computed("_data", { get: function(propertyKey){ type = meta.getType(this); Ember.assert("Type cannot be empty.", !Ember.isEmpty(type)); var key = options.key || propertyKey, self = this; var dirtyChanged = function(sender) { if (sender.get('isDirty')) { self._relationshipBecameDirty(propertyKey); } else { self._relationshipBecameClean(propertyKey); } }; var emstore = storeFor(this), value = this.getBelongsTo(key, type, meta, emstore); this._registerBelongsTo(meta); if (value !== null && meta.options.embedded) { value.get('isDirty'); // getter must be called before adding observer value.addObserver('isDirty', dirtyChanged); } return value; }, set: function(propertyKey, value, oldValue){ type = meta.getType(this); Ember.assert("Type cannot be empty.", !Ember.isEmpty(type)); var dirtyAttributes = get(this, '_dirtyAttributes'), createdDirtyAttributes = false, self = this; var dirtyChanged = function(sender) { if (sender.get('isDirty')) { self._relationshipBecameDirty(propertyKey); } else { self._relationshipBecameClean(propertyKey); } }; if (!dirtyAttributes) { dirtyAttributes = []; createdDirtyAttributes = true; } if (value) { Ember.assert('Attempted to set property of type: ' + value.constructor + ' with a value of type: ' + type, value instanceof type); } if (oldValue !== value) { dirtyAttributes.pushObject(propertyKey); } else { dirtyAttributes.removeObject(propertyKey); } if (createdDirtyAttributes) { set(this, '_dirtyAttributes', dirtyAttributes); } if (meta.options.embedded) { if (oldValue) { oldValue.removeObserver('isDirty', dirtyChanged); } if (value) { value.addObserver('isDirty', dirtyChanged); } } return value === undefined ? null : value; } }).meta(meta); }; Ember.Model.reopen({ getBelongsTo: function(key, type, meta, emstore) { var idOrAttrs = get(this, '_data.' + key), record; if (Ember.isNone(idOrAttrs)) { return null; } if (meta.options.embedded) { var primaryKey = get(type, 'primaryKey'), id = idOrAttrs[primaryKey]; record = type.create({ isLoaded: false, id: id }); var owner = Ember.getOwner(this); Ember.setOwner(record, owner); record.load(id, idOrAttrs); } else { if (emstore) { record = emstore._findSync(meta.type, idOrAttrs); } else { record = type.find(idOrAttrs); } } return record; } }); })(); (function() { var get = Ember.get, set = Ember.set, meta = Ember.meta; Ember.Model.dataTypes = {}; Ember.Model.dataTypes[Date] = { deserialize: function(string) { if (!string) { return null; } return new Date(string); }, serialize: function (date) { if (!date) { return null; } return date.toISOString(); }, isEqual: function(obj1, obj2) { if (obj1 instanceof Date) { obj1 = this.serialize(obj1); } if (obj2 instanceof Date) { obj2 = this.serialize(obj2); } return obj1 === obj2; } }; Ember.Model.dataTypes[Number] = { deserialize: function(string) { if (!string && string !== 0) { return null; } return Number(string); }, serialize: function (number) { if (!number && number !== 0) { return null; } return Number(number); } }; function deserialize(value, type) { if (type && type.deserialize) { return type.deserialize(value); } else if (type && Ember.Model.dataTypes[type]) { return Ember.Model.dataTypes[type].deserialize(value); } else { return value; } } function serialize(value, type) { if (type && type.serialize) { return type.serialize(value); } else if (type && Ember.Model.dataTypes[type]) { return Ember.Model.dataTypes[type].serialize(value); } else { return value; } } Ember.attr = function(type, options) { return Ember.Model.computed("_data", { get: function(key){ var data = get(this, '_data'), dataKey = this.dataKey(key), dataValue = data && get(data, dataKey); if (dataValue==null && options && options.defaultValue!=null) { return Ember.copy(options.defaultValue); } return this.getAttr(key, deserialize(dataValue, type)); }, set: function(key, value){ var data = get(this, '_data'), dataKey = this.dataKey(key), dataValue = data && get(data, dataKey), beingCreated = meta(this).proto === this, dirtyAttributes = get(this, '_dirtyAttributes'), createdDirtyAttributes = false; if (!dirtyAttributes) { dirtyAttributes = Ember.A([]); createdDirtyAttributes = true; } if (beingCreated) { if (!data) { data = {}; set(this, '_data', data); } dataValue = data[dataKey] = value; } if (dataValue !== serialize(value, type)) { dirtyAttributes.pushObject(key); } else { dirtyAttributes.removeObject(key); } if (createdDirtyAttributes) { set(this, '_dirtyAttributes', dirtyAttributes); } return value; } }).meta({isAttribute: true, type: type, options: options}); }; })(); (function() { var get = Ember.get; Ember.RESTAdapter = Ember.Adapter.extend({ find: function(record, id) { var url = this.buildURL(record.constructor, id), self = this; return this.ajax(url).then(function(data) { self.didFind(record, id, data); return record; }); }, didFind: function(record, id, data) { var rootKey = get(record.constructor, 'rootKey'), dataToLoad = rootKey ? get(data, rootKey) : data; record.load(id, dataToLoad); }, findAll: function(klass, records) { var url = this.buildURL(klass), self = this; return this.ajax(url).then(function(data) { self.didFindAll(klass, records, data); return records; }); }, didFindAll: function(klass, records, data) { var collectionKey = get(klass, 'collectionKey'), dataToLoad = collectionKey ? get(data, collectionKey) : data; records.load(klass, dataToLoad); }, findQuery: function(klass, records, params) { var url = this.buildURL(klass), self = this; return this.ajax(url, params).then(function(data) { self.didFindQuery(klass, records, params, data); return records; }); }, didFindQuery: function(klass, records, params, data) { var collectionKey = get(klass, 'collectionKey'), dataToLoad = collectionKey ? get(data, collectionKey) : data; records.load(klass, dataToLoad); }, createRecord: function(record) { var url = this.buildURL(record.constructor), self = this; return this.ajax(url, record.toJSON(), "POST").then(function(data) { self.didCreateRecord(record, data); return record; }); }, didCreateRecord: function(record, data) { this._loadRecordFromData(record, data); record.didCreateRecord(); }, saveRecord: function(record) { var primaryKey = get(record.constructor, 'primaryKey'), url = this.buildURL(record.constructor, get(record, primaryKey)), self = this; return this.ajax(url, record.toJSON(), "PUT").then(function(data) { // TODO: Some APIs may or may not return data self.didSaveRecord(record, data); return record; }); }, didSaveRecord: function(record, data) { this._loadRecordFromData(record, data); record.didSaveRecord(); }, deleteRecord: function(record) { var primaryKey = get(record.constructor, 'primaryKey'), url = this.buildURL(record.constructor, get(record, primaryKey)), self = this; return this.ajax(url, record.toJSON(), "DELETE").then(function(data) { self.didDeleteRecord(record, data); }); }, didDeleteRecord: function(record, data) { record.didDeleteRecord(); }, ajax: function(url, params, method, settings) { return this._ajax(url, params, (method || "GET"), settings); }, buildURL: function(klass, id) { var urlRoot = get(klass, 'url'); var urlSuffix = get(klass, 'urlSuffix') || ''; if (!urlRoot) { throw new Error('Ember.RESTAdapter requires a `url` property to be specified'); } if (!Ember.isEmpty(id)) { return urlRoot + "/" + id + urlSuffix; } else { return urlRoot + urlSuffix; } }, ajaxSettings: function(url, method) { return { url: url, type: method, dataType: "json" }; }, _ajax: function(url, params, method, settings) { var self = this; if (!settings) { settings = this.ajaxSettings(url, method); } return new Ember.RSVP.Promise(function(resolve, reject) { if (params) { if (method === "GET") { settings.data = params; } else { settings.contentType = "application/json; charset=utf-8"; settings.data = JSON.stringify(params); } } settings.success = function(json) { Ember.run(null, resolve, json); }; settings.error = function(jqXHR, textStatus, errorThrown) { // https://github.com/ebryn/ember-model/issues/202 if (jqXHR && typeof jqXHR === 'object') { jqXHR.then = null; } self._handleRejections(method, jqXHR, resolve, reject); }; Ember.$.ajax(settings); }, "ember-model: RESTAdapter#ajax ${method} to " + url); }, _handleRejections: function(method, jqXHR, resolve, reject) { if (method === "DELETE" && jqXHR.status >= 200 && jqXHR.status < 300) { Ember.run(null, resolve, null); } else { Ember.run(null, reject, jqXHR); } }, _loadRecordFromData: function(record, data) { var rootKey = get(record.constructor, 'rootKey'), primaryKey = get(record.constructor, 'primaryKey'); // handle HEAD response where no data is provided by server if (data) { data = rootKey ? get(data, rootKey) : data; if (!Ember.isEmpty(data)) { record.load(data[primaryKey], data); } } } }); })(); (function() { var get = Ember.get; Ember.loadPromise = function(target) { if (Ember.isNone(target)) { return null; } else if (target.then) { return target; } else { var deferred = Ember.RSVP.defer(); if (get(target, 'isLoaded') && !get(target, 'isNew')) { deferred.resolve(target); } else { target.one('didLoad', this, function() { deferred.resolve(target); }); } return deferred.promise; } }; })(); (function() { // This is a debug adapter for the Ember Extension, don't let the fact this is called an "adapter" confuse you. // Most copied from: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/debug/debug_adapter.js if (!Ember.DataAdapter) { return; } var get = Ember.get, capitalize = Ember.String.capitalize, underscore = Ember.String.underscore; var DebugAdapter = Ember.DataAdapter.extend({ getFilters: function() { return [ { name: 'isNew', desc: 'New' }, { name: 'isModified', desc: 'Modified' }, { name: 'isClean', desc: 'Clean' } ]; }, detect: function(klass) { return klass !== Ember.Model && Ember.Model.detect(klass); }, columnsForType: function(type) { var columns = [], count = 0, self = this; type.getAttributes().forEach(function(name, meta) { if (count++ > self.attributeLimit) { return false; } var desc = capitalize(underscore(name).replace('_', ' ')); columns.push({ name: name, desc: desc }); }); return columns; }, getRecords: function(type) { var records = []; type.forEachCachedRecord(function(record) { records.push(record); }); return records; }, getRecordColumnValues: function(record) { var self = this, count = 0, columnValues = { id: get(record, 'id') }; record.constructor.getAttributes().forEach(function(key) { if (count++ > self.attributeLimit) { return false; } var value = get(record, key); columnValues[key] = value; }); return columnValues; }, getRecordKeywords: function(record) { var keywords = [], keys = Ember.A(['id']); record.constructor.getAttributes().forEach(function(key) { keys.push(key); }); keys.forEach(function(key) { keywords.push(get(record, key)); }); return keywords; }, getRecordFilterValues: function(record) { return { isNew: record.get('isNew'), isModified: record.get('isDirty') && !record.get('isNew'), isClean: !record.get('isDirty') }; }, getRecordColor: function(record) { var color = 'black'; if (record.get('isNew')) { color = 'green'; } else if (record.get('isDirty')) { color = 'blue'; } return color; }, observeRecord: function(record, recordUpdated) { var releaseMethods = Ember.A(), self = this, keysToObserve = Ember.A(['id', 'isNew', 'isDirty']); record.constructor.getAttributes().forEach(function(key) { keysToObserve.push(key); }); keysToObserve.forEach(function(key) { var handler = function() { recordUpdated(self.wrapRecord(record)); }; Ember.addObserver(record, key, handler); releaseMethods.push(function() { Ember.removeObserver(record, key, handler); }); }); var release = function() { releaseMethods.forEach(function(fn) { fn(); } ); }; return release; } }); Ember.onLoad('Ember.Application', function(Application) { Application.initializer({ name: "em-data-adapter", initialize: function() { var application = arguments[1] || arguments[0]; application.register('em-data-adapter:main', DebugAdapter); } }); }); })(); (function() { function NIL() {} Ember.Model.Store = Ember.Service.extend({ modelFor: function(type) { return Ember.getOwner(this).factoryFor('model:' + type).class; }, adapterFor: function(type) { var adapter = this.modelFor(type).adapter; var owner = Ember.getOwner(this); if (adapter && adapter !== Ember.Model.adapter) { return adapter; } else { adapter = owner.factoryFor('adapter:' + type) || owner.factoryFor('adapter:application') || owner.factoryFor('adapter:REST'); return adapter ? adapter.create() : adapter; } }, createRecord: function(type, props) { var klass = this.modelFor(type); klass.reopenClass({adapter: this.adapterFor(type)}); var record = klass.create(props); var owner = Ember.getOwner(this); Ember.setOwner(record, owner); return record; }, find: function(type, id) { if (arguments.length === 1) { id = NIL; } return this._find(type, id, true); }, _find: function(type, id, isAsync) { var klass = this.modelFor(type); // if (!klass.adapter) { klass.reopenClass({adapter: this.adapterFor(type)}); // } var owner = Ember.getOwner(this); if (id === NIL) { return klass._findFetchAll(isAsync, owner); } else if (Ember.isArray(id)) { return klass._findFetchMany(id, isAsync, owner); } else if (typeof id === 'object') { return klass._findFetchQuery(id, isAsync, owner); } else { return klass._findFetchById(id, isAsync, owner); } }, _findSync: function(type, id) { return this._find(type, id, false); } }); Ember.onLoad('Ember.Application', function(Application) { Application.initializer({ name: "emstore", initialize: function() { var application = arguments[1] || arguments[0]; var store = application.Store || Ember.Model.Store; application.register('emstore:application', store); application.register('emstore:main', store); application.inject('route', 'emstore', 'emstore:main'); application.inject('controller', 'emstore', 'emstore:main'); } }); }); })();