Update ember-model and ember.js

This commit is contained in:
Piotr Sarnacki 2013-07-30 18:15:07 +02:00
parent 359c97cad0
commit 7fb090fe99
2 changed files with 996 additions and 659 deletions

View File

@ -1,36 +1,24 @@
(function() { (function() {
function mustImplement(message) {
var fn = function() {
throw new Error(message);
};
fn.isUnimplemented = true;
return fn;
}
Ember.Adapter = Ember.Object.extend({ Ember.Adapter = Ember.Object.extend({
find: function(record, id) { find: mustImplement('Ember.Adapter subclasses must implement find'),
throw new Error('Ember.Adapter subclasses must implement find'); findQuery: mustImplement('Ember.Adapter subclasses must implement findQuery'),
}, findMany: mustImplement('Ember.Adapter subclasses must implement findMany'),
findAll: mustImplement('Ember.Adapter subclasses must implement findAll'),
findQuery: function(klass, records, params) { createRecord: mustImplement('Ember.Adapter subclasses must implement createRecord'),
throw new Error('Ember.Adapter subclasses must implement findQuery'); saveRecord: mustImplement('Ember.Adapter subclasses must implement saveRecord'),
}, deleteRecord: mustImplement('Ember.Adapter subclasses must implement deleteRecord'),
findMany: function(klass, records, ids) {
throw new Error('Ember.Adapter subclasses must implement findMany');
},
findAll: function(klass, records) {
throw new Error('Ember.Adapter subclasses must implement findAll');
},
load: function(record, id, data) { load: function(record, id, data) {
record.load(id, data); record.load(id, data);
},
createRecord: function(record) {
throw new Error('Ember.Adapter subclasses must implement createRecord');
},
saveRecord: function(record) {
throw new Error('Ember.Adapter subclasses must implement saveRecord');
},
deleteRecord: function(record) {
throw new Error('Ember.Adapter subclasses must implement deleteRecord');
} }
}); });
@ -43,8 +31,9 @@ var get = Ember.get;
Ember.FixtureAdapter = Ember.Adapter.extend({ Ember.FixtureAdapter = Ember.Adapter.extend({
_findData: function(klass, id) { _findData: function(klass, id) {
var fixtures = klass.FIXTURES, var fixtures = klass.FIXTURES,
idAsString = id.toString(),
primaryKey = get(klass, 'primaryKey'), primaryKey = get(klass, 'primaryKey'),
data = Ember.A(fixtures).find(function(el) { return el[primaryKey] === id; }); data = Ember.A(fixtures).find(function(el) { return (el[primaryKey]).toString() === idAsString; });
return data; return data;
}, },
@ -53,10 +42,10 @@ Ember.FixtureAdapter = Ember.Adapter.extend({
var data = this._findData(record.constructor, id); var data = this._findData(record.constructor, id);
return new Ember.RSVP.Promise(function(resolve, reject) { return new Ember.RSVP.Promise(function(resolve, reject) {
setTimeout(function() { Ember.run.later(this, function() {
Ember.run(record, record.load, id, data); Ember.run(record, record.load, id, data);
resolve(record); resolve(record);
}); }, 0);
}); });
}, },
@ -69,10 +58,10 @@ Ember.FixtureAdapter = Ember.Adapter.extend({
} }
return new Ember.RSVP.Promise(function(resolve, reject) { return new Ember.RSVP.Promise(function(resolve, reject) {
setTimeout(function() { Ember.run.later(this, function() {
Ember.run(records, records.load, klass, requestedData); Ember.run(records, records.load, klass, requestedData);
resolve(records); resolve(records);
}); }, 0);
}); });
}, },
@ -80,10 +69,10 @@ Ember.FixtureAdapter = Ember.Adapter.extend({
var fixtures = klass.FIXTURES; var fixtures = klass.FIXTURES;
return new Ember.RSVP.Promise(function(resolve, reject) { return new Ember.RSVP.Promise(function(resolve, reject) {
setTimeout(function() { Ember.run.later(this, function() {
Ember.run(records, records.load, klass, fixtures); Ember.run(records, records.load, klass, fixtures);
resolve(records); resolve(records);
}); }, 0);
}); });
}, },
@ -92,29 +81,29 @@ Ember.FixtureAdapter = Ember.Adapter.extend({
fixtures = klass.FIXTURES; fixtures = klass.FIXTURES;
return new Ember.RSVP.Promise(function(resolve, reject) { return new Ember.RSVP.Promise(function(resolve, reject) {
setTimeout(function() { Ember.run.later(this, function() {
fixtures.push(klass.findFromCacheOrLoad(record.toJSON())); fixtures.push(klass.findFromCacheOrLoad(record.toJSON()));
record.didCreateRecord(); record.didCreateRecord();
resolve(record); resolve(record);
}); }, 0);
}); });
}, },
saveRecord: function(record) { saveRecord: function(record) {
return new Ember.RSVP.Promise(function(resolve, reject) { return new Ember.RSVP.Promise(function(resolve, reject) {
setTimeout(function() { Ember.run.later(this, function() {
record.didSaveRecord(); record.didSaveRecord();
resolve(record); resolve(record);
}); }, 0);
}); });
}, },
deleteRecord: function(record) { deleteRecord: function(record) {
return new Ember.RSVP.Promise(function(resolve, reject) { return new Ember.RSVP.Promise(function(resolve, reject) {
setTimeout(function() { Ember.run.later(this, function() {
record.didDeleteRecord(); record.didDeleteRecord();
resolve(record); resolve(record);
}); }, 0);
}); });
} }
}); });
@ -151,6 +140,15 @@ Ember.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, {
return Ember.A(data.map(function(el) { return Ember.A(data.map(function(el) {
return klass.findFromCacheOrLoad(el); // FIXME return klass.findFromCacheOrLoad(el); // FIXME
})); }));
},
reload: function() {
var modelClass = this.get('modelClass');
Ember.assert("Reload can only be called on findAll RecordArrays",
modelClass && modelClass._findAllRecordArray === this);
set(this, 'isLoaded', false);
modelClass.adapter.findAll(modelClass, this);
} }
}); });
@ -229,15 +227,7 @@ Ember.ManyArray = Ember.RecordArray.extend({
if (!content.length) { return; } if (!content.length) { return; }
// TODO: Create a LazilyMaterializedRecordArray class and test it return this.materializeRecord(idx);
if (this._records && this._records[idx]) { return this._records[idx]; }
var record = this.materializeRecord(idx);
if (!this._records) { this._records = {}; }
this._records[idx] = record;
return record;
}, },
save: function() { save: function() {
@ -306,7 +296,8 @@ Ember.EmbeddedHasManyArray = Ember.ManyArray.extend({
return reference.record; return reference.record;
} else { } else {
var record = klass.create({ _reference: reference }); var record = klass.create({ _reference: reference });
if(attrs) { reference.record = record;
if (attrs) {
record.load(attrs[primaryKey], attrs); record.load(attrs[primaryKey], attrs);
} }
return record; return record;
@ -354,6 +345,36 @@ function hasCachedValue(object, key) {
} }
} }
function extractDirty(object, attrsOrRelations, dirtyAttributes) {
var key, desc, descMeta, type, dataValue, cachedValue, isDirty, dataType;
for (var i = 0, l = attrsOrRelations.length; i < l; i++) {
key = attrsOrRelations[i];
if (!hasCachedValue(object, key)) { continue; }
cachedValue = object.cacheFor(key);
dataValue = get(object, '_data.' + object.dataKey(key));
desc = meta(object).descs[key];
descMeta = desc && desc.meta();
type = descMeta.type;
dataType = Ember.Model.dataTypes[type];
if (type && type.isEqual) {
isDirty = !type.isEqual(dataValue, cachedValue);
} else if (dataType && dataType.isEqual) {
isDirty = !dataType.isEqual(dataValue, cachedValue);
} else if (dataValue && cachedValue instanceof Ember.Model) { // belongsTo case
isDirty = get(cachedValue, 'isDirty');
} else if (dataValue !== cachedValue) {
isDirty = true;
} else {
isDirty = false;
}
if (isDirty) {
dirtyAttributes.push(key);
}
}
}
Ember.run.queues.push('data'); Ember.run.queues.push('data');
Ember.Model = Ember.Object.extend(Ember.Evented, { Ember.Model = Ember.Object.extend(Ember.Evented, {
@ -376,29 +397,12 @@ Ember.Model = Ember.Object.extend(Ember.Evented, {
isDirty: Ember.computed(function() { isDirty: Ember.computed(function() {
var attributes = this.attributes, var attributes = this.attributes,
dirtyAttributes = Ember.A(), // just for removeObject relationships = this.relationships,
key, cachedValue, dataValue, desc, descMeta, type, isDirty; dirtyAttributes = Ember.A(); // just for removeObject
for (var i = 0, l = attributes.length; i < l; i++) { extractDirty(this, attributes, dirtyAttributes);
key = attributes[i]; if (relationships) {
if (!hasCachedValue(this, key)) { continue; } extractDirty(this, relationships, dirtyAttributes);
cachedValue = this.cacheFor(key);
dataValue = get(this, 'data.'+this.dataKey(key));
desc = meta(this).descs[key];
descMeta = desc && desc.meta();
type = descMeta.type;
if (type && type.isEqual) {
isDirty = !type.isEqual(dataValue, cachedValue);
} else if (dataValue !== cachedValue) {
isDirty = true;
} else {
isDirty = false;
}
if (isDirty) {
dirtyAttributes.push(key);
}
} }
if (dirtyAttributes.length) { if (dirtyAttributes.length) {
@ -412,6 +416,10 @@ Ember.Model = Ember.Object.extend(Ember.Evented, {
dataKey: function(key) { dataKey: function(key) {
var camelizeKeys = get(this.constructor, 'camelizeKeys'); 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; return camelizeKeys ? underscore(key) : key;
}, },
@ -444,7 +452,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, {
load: function(id, hash) { load: function(id, hash) {
var data = {}; var data = {};
data[get(this.constructor, 'primaryKey')] = id; data[get(this.constructor, 'primaryKey')] = id;
set(this, 'data', Ember.merge(data, hash)); set(this, '_data', Ember.merge(data, hash));
set(this, 'isLoaded', true); set(this, 'isLoaded', true);
set(this, 'isNew', false); set(this, 'isNew', false);
this._createReference(); this._createReference();
@ -456,10 +464,10 @@ Ember.Model = Ember.Object.extend(Ember.Evented, {
var meta = value.meta(); var meta = value.meta();
if (meta.isAttribute) { if (meta.isAttribute) {
if (!proto.attributes) { proto.attributes = []; } proto.attributes = proto.attributes ? proto.attributes.slice() : [];
proto.attributes.push(key); proto.attributes.push(key);
} else if (meta.isRelationship) { } else if (meta.isRelationship) {
if (!proto.relationships) { proto.relationships = []; } proto.relationships = proto.relationships ? proto.relationships.slice() : [];
proto.relationships.push(key); proto.relationships.push(key);
} }
} }
@ -472,33 +480,37 @@ Ember.Model = Ember.Object.extend(Ember.Evented, {
serializeBelongsTo: function(key, meta) { serializeBelongsTo: function(key, meta) {
if (meta.options.embedded) { if (meta.options.embedded) {
var record = this.get(key); var record = this.get(key);
if (record) { return record ? record.toJSON() : null;
return record.toJSON();
}
} else { } else {
var primaryKey = get(meta.type, 'primaryKey'); var primaryKey = get(meta.getType(), 'primaryKey');
return this.get(key + '.' + primaryKey); return this.get(key + '.' + primaryKey);
} }
}, },
toJSON: function() { toJSON: function() {
var key, meta, var key, meta,
properties = this.getProperties(this.attributes); json = {},
properties = this.attributes ? this.getProperties(this.attributes) : {},
rootKey = get(this.constructor, 'rootKey');
for (key in properties) { for (key in properties) {
meta = this.constructor.metaForProperty(key); meta = this.constructor.metaForProperty(key);
if (meta.type && meta.type.serialize) { if (meta.type && meta.type.serialize) {
properties[key] = meta.type.serialize(properties[key]); json[this.dataKey(key)] = meta.type.serialize(properties[key]);
} else if (meta.type && Ember.Model.dataTypes[meta.type]) { } else if (meta.type && Ember.Model.dataTypes[meta.type]) {
properties[key] = Ember.Model.dataTypes[meta.type].serialize(properties[key]); json[this.dataKey(key)] = Ember.Model.dataTypes[meta.type].serialize(properties[key]);
} else {
json[this.dataKey(key)] = properties[key];
} }
} }
if (this.relationships) { if (this.relationships) {
var data; var data, relationshipKey;
for(var i = 0; i < this.relationships.length; i++) { for(var i = 0; i < this.relationships.length; i++) {
key = this.relationships[i]; key = this.relationships[i];
meta = this.constructor.metaForProperty(key); meta = this.constructor.metaForProperty(key);
relationshipKey = meta.options.key || key;
if (meta.kind === 'belongsTo') { if (meta.kind === 'belongsTo') {
data = this.serializeBelongsTo(key, meta); data = this.serializeBelongsTo(key, meta);
@ -506,19 +518,17 @@ Ember.Model = Ember.Object.extend(Ember.Evented, {
data = this.serializeHasMany(key, meta); data = this.serializeHasMany(key, meta);
} }
if (data) { json[relationshipKey] = data;
properties[key] = data;
}
} }
} }
if (this.constructor.rootKey) { if (rootKey) {
var json = {}; var jsonRoot = {};
json[this.constructor.rootKey] = properties; jsonRoot[rootKey] = json;
return jsonRoot;
return json;
} else { } else {
return properties; return json;
} }
}, },
@ -545,7 +555,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, {
revert: function() { revert: function() {
if (this.get('isDirty')) { if (this.get('isDirty')) {
var data = get(this, 'data'), var data = get(this, '_data') || {},
reverts = {}; reverts = {};
for (var i = 0; i < this._dirtyAttributes.length; i++) { for (var i = 0; i < this._dirtyAttributes.length; i++) {
var attr = this._dirtyAttributes[i]; var attr = this._dirtyAttributes[i];
@ -564,7 +574,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, {
if (!this.constructor.recordCache) this.constructor.recordCache = {}; if (!this.constructor.recordCache) this.constructor.recordCache = {};
this.constructor.recordCache[id] = this; this.constructor.recordCache[id] = this;
this.load(id, this.getProperties(this.attributes)); this._copyDirtyAttributesToData();
this.constructor.addToRecordArrays(this); this.constructor.addToRecordArrays(this);
this.trigger('didCreateRecord'); this.trigger('didCreateRecord');
this.didSaveRecord(); this.didSaveRecord();
@ -589,12 +599,12 @@ Ember.Model = Ember.Object.extend(Ember.Evented, {
_copyDirtyAttributesToData: function() { _copyDirtyAttributesToData: function() {
if (!this._dirtyAttributes) { return; } if (!this._dirtyAttributes) { return; }
var dirtyAttributes = this._dirtyAttributes, var dirtyAttributes = this._dirtyAttributes,
data = get(this, 'data'), data = get(this, '_data'),
key; key;
if (!data) { if (!data) {
data = {}; data = {};
set(this, 'data', data); set(this, '_data', data);
} }
for (var i = 0, l = dirtyAttributes.length; i < l; i++) { for (var i = 0, l = dirtyAttributes.length; i < l; i++) {
// TODO: merge Object.create'd object into prototype // TODO: merge Object.create'd object into prototype
@ -606,7 +616,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, {
dataDidChange: Ember.observer(function() { dataDidChange: Ember.observer(function() {
this._reloadHasManys(); this._reloadHasManys();
}, 'data'), }, '_data'),
_registerHasManyArray: function(array) { _registerHasManyArray: function(array) {
if (!this._hasManyArrays) { this._hasManyArrays = Ember.A([]); } if (!this._hasManyArrays) { this._hasManyArrays = Ember.A([]); }
@ -625,7 +635,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, {
}, },
_getHasManyContent: function(key, type, embedded) { _getHasManyContent: function(key, type, embedded) {
var content = get(this, 'data.' + key); var content = get(this, '_data.' + key);
if (content) { if (content) {
var mapFunction, primaryKey, reference; var mapFunction, primaryKey, reference;
@ -693,7 +703,7 @@ Ember.Model.reopenClass({
findAll: function() { findAll: function() {
if (this._findAllRecordArray) { return this._findAllRecordArray; } if (this._findAllRecordArray) { return this._findAllRecordArray; }
var records = this._findAllRecordArray = Ember.RecordArray.create(); var records = this._findAllRecordArray = Ember.RecordArray.create({modelClass: this});
this.adapter.findAll(this, records); this.adapter.findAll(this, records);
@ -723,7 +733,7 @@ Ember.Model.reopenClass({
_fetchById: function(record, id) { _fetchById: function(record, id) {
var adapter = get(this, 'adapter'); var adapter = get(this, 'adapter');
if (adapter.findMany) { if (adapter.findMany && !adapter.findMany.isUnimplemented) {
if (this._currentBatchIds) { if (this._currentBatchIds) {
if (!contains(this._currentBatchIds, id)) { this._currentBatchIds.push(id); } if (!contains(this._currentBatchIds, id)) { this._currentBatchIds.push(id); }
} else { } else {
@ -793,11 +803,11 @@ Ember.Model.reopenClass({
attrs = {isLoaded: false}; attrs = {isLoaded: false};
attrs[primaryKey] = id; attrs[primaryKey] = id;
record = this.create(attrs); record = this.create(attrs);
this.recordCache[id] = record;
var sideloadedData = this.sideloadedData && this.sideloadedData[id]; var sideloadedData = this.sideloadedData && this.sideloadedData[id];
if (sideloadedData) { if (sideloadedData) {
record.load(id, sideloadedData); record.load(id, sideloadedData);
} }
this.recordCache[id] = record;
} }
return record; return record;
@ -897,6 +907,16 @@ Ember.Model.reopenClass({
} }
return reference; return reference;
},
resetData: function() {
this._idToReference = null;
this.sideloadedData = null;
this.recordCache = null;
this.recordArrays = null;
this._currentBatchIds = null;
this._hasManyArrays = null;
this._findAllRecordArray = null;
} }
}); });
@ -948,31 +968,45 @@ Ember.Model.reopen({
var get = Ember.get; var get = Ember.get;
function getType() {
if (typeof this.type === "string") {
this.type = Ember.get(Ember.lookup, this.type);
}
return this.type;
}
Ember.belongsTo = function(type, options) { Ember.belongsTo = function(type, options) {
options = options || {}; options = options || {};
var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo' }, var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo', getType: getType },
key = options.key; relationshipKey = options.key;
return Ember.computed(function() { return Ember.computed(function(key, value) {
if (typeof type === "string") { type = meta.getType();
type = Ember.get(Ember.lookup, type);
if (arguments.length === 2) {
if (value) {
Ember.assert(Ember.String.fmt('Attempted to set property of type: %@ with a value of type: %@',
[value.constructor, type]),
value instanceof type);
}
return value === undefined ? null : value;
} else {
return this.getBelongsTo(relationshipKey, type, meta);
} }
}).property('_data').meta(meta);
return this.getBelongsTo(key, type, meta);
}).property('data').meta(meta);
}; };
Ember.Model.reopen({ Ember.Model.reopen({
getBelongsTo: function(key, type, meta) { getBelongsTo: function(key, type, meta) {
var idOrAttrs = get(this, 'data.' + key), var idOrAttrs = get(this, '_data.' + key),
record; record;
if(Ember.isNone(idOrAttrs)) { if (Ember.isNone(idOrAttrs)) {
return null; return null;
} }
if(meta.options.embedded) { if (meta.options.embedded) {
var primaryKey = get(type, 'primaryKey'); var primaryKey = get(type, 'primaryKey');
record = type.create({ isLoaded: false }); record = type.create({ isLoaded: false });
record.load(idOrAttrs[primaryKey], idOrAttrs); record.load(idOrAttrs[primaryKey], idOrAttrs);
@ -1029,6 +1063,11 @@ Ember.Model.dataTypes[Date] = {
serialize: function (date) { serialize: function (date) {
if(!date) { return null; } if(!date) { return null; }
return date.toISOString(); 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;
} }
}; };
@ -1054,9 +1093,9 @@ function deserialize(value, type) {
} }
Ember.attr = function(type) { Ember.attr = function(type, options) {
return Ember.computed(function(key, value) { return Ember.computed(function(key, value) {
var data = get(this, 'data'), var data = get(this, '_data'),
dataKey = this.dataKey(key), dataKey = this.dataKey(key),
dataValue = data && get(data, dataKey), dataValue = data && get(data, dataKey),
beingCreated = meta(this).proto === this; beingCreated = meta(this).proto === this;
@ -1064,14 +1103,14 @@ Ember.attr = function(type) {
if (arguments.length === 2) { if (arguments.length === 2) {
if (beingCreated && !data) { if (beingCreated && !data) {
data = {}; data = {};
set(this, 'data', data); set(this, '_data', data);
data[dataKey] = value; data[dataKey] = value;
} }
return wrapObject(value); return wrapObject(value);
} }
return this.getAttr(key, deserialize(dataValue, type)); return this.getAttr(key, deserialize(dataValue, type));
}).property('data').meta({isAttribute: true, type: type}); }).property('_data').meta({isAttribute: true, type: type, options: options});
}; };
@ -1191,12 +1230,16 @@ Ember.RESTAdapter = Ember.Adapter.extend({
} }
}, },
_ajax: function(url, params, method) { ajaxSettings: function(url, method) {
var settings = { return {
url: url, url: url,
type: method, type: method,
dataType: "json" dataType: "json"
}; };
},
_ajax: function(url, params, method) {
var settings = this.ajaxSettings(url, method);
return new Ember.RSVP.Promise(function(resolve, reject) { return new Ember.RSVP.Promise(function(resolve, reject) {
if (params) { if (params) {

File diff suppressed because it is too large Load Diff