summaryrefslogtreecommitdiff
path: root/public/js/libs/ember-data.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/js/libs/ember-data.js')
-rw-r--r--public/js/libs/ember-data.js883
1 files changed, 767 insertions, 116 deletions
diff --git a/public/js/libs/ember-data.js b/public/js/libs/ember-data.js
index 6db1c0f..00a6e25 100644
--- a/public/js/libs/ember-data.js
+++ b/public/js/libs/ember-data.js
@@ -1,7 +1,10 @@
+// Last commit: 5fd6d65 (2013-03-28 01:13:50 +0100)
+
+
(function() {
window.DS = Ember.Namespace.create({
- // this one goes to 11
- CURRENT_API_REVISION: 11
+ // this one goes past 11
+ CURRENT_API_REVISION: 12
});
})();
@@ -746,7 +749,7 @@ var classify = Ember.String.classify, get = Ember.get;
For example, the DS.Adapter class can behave like a map, with
more semantic API, via the `map` API:
- DS.Adapter.map('App.Person', { firstName: { keyName: 'FIRST' } });
+ DS.Adapter.map('App.Person', { firstName: { key: 'FIRST' } });
Class configuration via a map-like API has a few common requirements
that differentiate it from the standard Ember.Map implementation.
@@ -846,7 +849,7 @@ DS._Mappable = Ember.Mixin.create({
instanceMap.set(transformedKey, newValue);
}
- },
+ }
});
@@ -970,6 +973,8 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
// `isLoaded` and fires a `didLoad` event.
this.loadingRecordArrays = {};
+ this._recordsToSave = Ember.OrderedSet.create();
+
set(this, 'defaultTransaction', this.transaction());
},
@@ -1542,7 +1547,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
if (!Ember.isArray(ids)) {
var adapter = this.adapterForType(type);
if (adapter && adapter.findHasMany) { adapter.findHasMany(this, record, relationship, ids); }
- else { throw fmt("Adapter is either null or does not implement `findHasMany` method", this); }
+ else if (ids !== undefined) { throw fmt("Adapter is either null or does not implement `findHasMany` method", this); }
return this.createManyArray(type, Ember.A());
}
@@ -1780,6 +1785,39 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
}
},
+ // .................
+ // . BASIC ADAPTER .
+ // .................
+
+ scheduleSave: function(record) {
+ this._recordsToSave.add(record);
+ Ember.run.once(this, 'flushSavedRecords');
+ },
+
+ flushSavedRecords: function() {
+ var created = Ember.OrderedSet.create();
+ var updated = Ember.OrderedSet.create();
+ var deleted = Ember.OrderedSet.create();
+
+ this._recordsToSave.forEach(function(record) {
+ if (get(record, 'isNew')) {
+ created.add(record);
+ } else if (get(record, 'isDeleted')) {
+ deleted.add(record);
+ } else {
+ updated.add(record);
+ }
+ });
+
+ this._recordsToSave.clear();
+
+ get(this, '_adapter').commit(this, {
+ created: created,
+ updated: updated,
+ deleted: deleted
+ });
+ },
+
// ..............
// . PERSISTING .
// ..............
@@ -2268,7 +2306,6 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
}
var content = get(array, 'content');
- var alreadyInArray = content.indexOf(clientId) !== -1;
var recordArrays = this.recordArraysForClientId(clientId);
var reference = this.referenceForClientId(clientId);
@@ -2413,7 +2450,6 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
if (prematerialized && prematerialized.id) {
id = prematerialized.id;
} else if (id === undefined) {
- var adapter = this.adapterForType(type);
id = this.preprocessData(type, data);
}
@@ -2470,7 +2506,10 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
// TODO (tomdale) this assumes that loadHasMany *always* means
// that the records for the provided IDs are loaded.
- if (relationship) { set(relationship, 'isLoaded', true); }
+ if (relationship) {
+ set(relationship, 'isLoaded', true);
+ relationship.trigger('didLoad');
+ }
},
/** @private
@@ -2492,6 +2531,8 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
clientIds = typeMap.clientIds,
cidToData = this.clientIdToData;
+ Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToClientIdMap[id]);
+
var clientId = ++this.clientIdCounter;
cidToData[clientId] = data;
@@ -2518,7 +2559,7 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
this.recordCache[clientId] = record = type._create({
store: this,
- clientId: clientId,
+ clientId: clientId
});
set(record, 'id', id);
@@ -2546,12 +2587,10 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
if (id) { delete typeMap.idToCid[id]; }
},
- destroy: function() {
+ willDestroy: function() {
if (get(DS, 'defaultStore') === this) {
set(DS, 'defaultStore', null);
}
-
- return this._super();
},
// ........................
@@ -2722,7 +2761,7 @@ DS.Store.reopenClass({
(function() {
-var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor,
+var get = Ember.get, set = Ember.set,
once = Ember.run.once, arrayMap = Ember.ArrayPolyfills.map;
/**
@@ -2883,14 +2922,6 @@ var stateProperty = Ember.computed(function(key) {
}
}).property();
-var isEmptyObject = function(object) {
- for (var name in object) {
- if (object.hasOwnProperty(name)) { return false; }
- }
-
- return true;
-};
-
var hasDefinedProperties = function(object) {
for (var name in object) {
if (object.hasOwnProperty(name) && object[name]) { return true; }
@@ -2917,12 +2948,6 @@ var didSetProperty = function(manager, context) {
change.sync();
};
-// Whenever a property is set, recompute all dependent filters
-var updateRecordArrays = function(manager) {
- var record = manager.get('record');
- record.updateRecordArraysLater();
-};
-
DS.State = Ember.State.extend({
isLoaded: stateProperty,
isReloading: stateProperty,
@@ -3267,7 +3292,7 @@ var states = {
exit: function(manager) {
var record = get(manager, 'record');
- Ember.run.once(function() {
+ once(function() {
record.trigger('didLoad');
});
}
@@ -3503,11 +3528,11 @@ DS.StateManager = Ember.StateManager.extend({
(function() {
var LoadPromise = DS.LoadPromise; // system/mixins/load_promise
-var get = Ember.get, set = Ember.set, none = Ember.isNone, map = Ember.EnumerableUtils.map;
+var get = Ember.get, set = Ember.set, map = Ember.EnumerableUtils.map;
-var retrieveFromCurrentState = Ember.computed(function(key) {
+var retrieveFromCurrentState = Ember.computed(function(key, value) {
return get(get(this, 'stateManager.currentState'), key);
-}).property('stateManager.currentState');
+}).property('stateManager.currentState').readOnly();
DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
isLoaded: retrieveFromCurrentState,
@@ -3542,6 +3567,11 @@ DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
return store.serialize(this, options);
},
+ toJSON: function(options) {
+ var serializer = DS.JSONSerializer.create();
+ return serializer.serialize(this, options);
+ },
+
didLoad: Ember.K,
didReload: Ember.K,
didUpdate: Ember.K,
@@ -3634,6 +3664,9 @@ DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
clearRelationships: function() {
this.eachRelationship(function(name, relationship) {
+ // if the relationship is unmaterialized, move on
+ if (this.cacheFor(name) === undefined) { return; }
+
if (relationship.kind === 'belongsTo') {
set(this, name, null);
} else if (relationship.kind === 'hasMany') {
@@ -3782,6 +3815,12 @@ DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
becameInFlight: function() {
},
+ // FOR USE BY THE BASIC ADAPTER
+
+ save: function() {
+ this.get('store').scheduleSave(this);
+ },
+
// FOR USE DURING COMMIT PROCESS
adapterDidUpdateAttribute: function(attributeName, value) {
@@ -3835,6 +3874,7 @@ var storeAlias = function(methodName) {
args = [].slice.call(arguments);
args.unshift(this);
+ Ember.assert("Your application does not have a 'Store' property defined. Attempts to call '" + methodName + "' on model classes will fail. Please provide one as with 'YourAppName.Store = DS.Store.extend()'", !!store);
return store[methodName].apply(store, args);
};
};
@@ -3843,6 +3883,7 @@ DS.Model.reopenClass({
isLoaded: storeAlias('recordIsLoaded'),
find: storeAlias('find'),
all: storeAlias('all'),
+ query: storeAlias('findQuery'),
filter: storeAlias('filter'),
_create: DS.Model.create,
@@ -3941,8 +3982,6 @@ DS.attr = function(type, options) {
};
return Ember.computed(function(key, value, oldValue) {
- var data;
-
if (arguments.length > 1) {
Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('<type>')` from " + this.constructor.toString(), key !== 'id');
} else {
@@ -4063,7 +4102,7 @@ var hasRelationship = function(type, options) {
}
ids = data[key];
- relationship = store.findMany(type, ids || [], this, meta);
+ relationship = store.findMany(type, ids, this, meta);
set(relationship, 'owner', this);
set(relationship, 'name', key);
@@ -4180,7 +4219,6 @@ DS.Model.reopenClass({
App.Blog = DS.Model.extend({
users: DS.hasMany(App.User),
owner: DS.belongsTo(App.User),
-
posts: DS.hasMany(App.Post)
});
@@ -4257,6 +4295,52 @@ DS.Model.reopenClass({
}),
/**
+ An array of types directly related to a model. Each type will be
+ included once, regardless of the number of relationships it has with
+ the model.
+
+ For example, given a model with this definition:
+
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany(App.User),
+ owner: DS.belongsTo(App.User),
+ posts: DS.hasMany(App.Post)
+ });
+
+ This property would contain the following:
+
+ var relatedTypes = Ember.get(App.Blog, 'relatedTypes');
+ //=> [ App.User, App.Post ]
+
+ @type Ember.Array
+ @readOnly
+ */
+ relatedTypes: Ember.computed(function() {
+ var type,
+ types = Ember.A([]);
+
+ // Loop through each computed property on the class,
+ // and create an array of the unique types involved
+ // in relationships
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ type = meta.type;
+
+ if (typeof type === 'string') {
+ type = get(this, type, false) || get(Ember.lookup, type);
+ }
+
+ if (!types.contains(type)) {
+ Ember.assert("Trying to sideload " + name + " on " + this.toString() + " but the type doesn't exist.", !!type);
+ types.push(type);
+ }
+ }
+ });
+
+ return types;
+ }),
+
+ /**
A map whose keys are the relationships of a model and whose values are
relationship descriptors.
@@ -4357,6 +4441,21 @@ DS.Model.reopenClass({
get(this, 'relationshipsByName').forEach(function(name, relationship) {
callback.call(binding, name, relationship);
});
+ },
+
+ /**
+ Given a callback, iterates over each of the types related to a model,
+ invoking the callback with the related type's class. Each type will be
+ returned just once, regardless of how many different relationships it has
+ with a model.
+
+ @param {Function} callback the callback to invoke
+ @param {any} binding the value to which the callback's `this` should be bound
+ */
+ eachRelatedType: function(callback, binding) {
+ get(this, 'relatedTypes').forEach(function(type) {
+ callback.call(binding, type);
+ });
}
});
@@ -4447,6 +4546,8 @@ var get = Ember.get, set = Ember.set;
var forEach = Ember.EnumerableUtils.forEach;
DS.RelationshipChange = function(options) {
+ this.parentReference = options.parentReference;
+ this.childReference = options.childReference;
this.firstRecordReference = options.firstRecordReference;
this.firstRecordKind = options.firstRecordKind;
this.firstRecordName = options.firstRecordName;
@@ -4502,7 +4603,7 @@ DS.RelationshipChange.determineRelationshipType = function(recordType, knownSide
var knownContainerType = knownSide.kind;
var options = recordType.metaForProperty(knownKey).options;
var otherType = DS._inverseTypeFor(recordType, knownKey);
-
+
if(options.inverse){
key = options.inverse;
otherContainerType = get(otherType, 'relationshipsByName').get(key).kind;
@@ -4536,10 +4637,10 @@ DS.RelationshipChange.createChange = function(firstRecordReference, secondRecord
return DS.OneToManyChange.createChange(secondRecordReference, firstRecordReference, store, options);
}
else if (changeType === "oneToNone"){
- return DS.OneToNoneChange.createChange(firstRecordReference, {}, store, options);
+ return DS.OneToNoneChange.createChange(firstRecordReference, secondRecordReference, store, options);
}
else if (changeType === "manyToNone"){
- return DS.ManyToNoneChange.createChange(firstRecordReference, {}, store, options);
+ return DS.ManyToNoneChange.createChange(firstRecordReference, secondRecordReference, store, options);
}
else if (changeType === "oneToOne"){
return DS.OneToOneChange.createChange(firstRecordReference, secondRecordReference, store, options);
@@ -4553,6 +4654,8 @@ DS.RelationshipChange.createChange = function(firstRecordReference, secondRecord
DS.OneToNoneChange.createChange = function(childReference, parentReference, store, options) {
var key = options.key;
var change = DS.RelationshipChange._createChange({
+ parentReference: parentReference,
+ childReference: childReference,
firstRecordReference: childReference,
store: store,
changeType: options.changeType,
@@ -4563,12 +4666,14 @@ DS.OneToNoneChange.createChange = function(childReference, parentReference, stor
store.addRelationshipChangeFor(childReference, key, parentReference, null, change);
return change;
-};
+};
/** @private */
DS.ManyToNoneChange.createChange = function(childReference, parentReference, store, options) {
var key = options.key;
var change = DS.RelationshipChange._createChange({
+ parentReference: childReference,
+ childReference: parentReference,
secondRecordReference: childReference,
store: store,
changeType: options.changeType,
@@ -4578,14 +4683,14 @@ DS.ManyToNoneChange.createChange = function(childReference, parentReference, sto
store.addRelationshipChangeFor(childReference, key, parentReference, null, change);
return change;
-};
+};
/** @private */
DS.ManyToManyChange.createChange = function(childReference, parentReference, store, options) {
// Get the type of the child based on the child's client ID
var childType = childReference.type, key;
-
+
// If the name of the belongsTo side of the relationship is specified,
// use that
// If the type of the parent is specified, look it up on the child's type
@@ -4593,6 +4698,8 @@ DS.ManyToManyChange.createChange = function(childReference, parentReference, sto
key = options.key;
var change = DS.RelationshipChange._createChange({
+ parentReference: parentReference,
+ childReference: childReference,
firstRecordReference: childReference,
secondRecordReference: parentReference,
firstRecordKind: "hasMany",
@@ -4612,7 +4719,7 @@ DS.ManyToManyChange.createChange = function(childReference, parentReference, sto
DS.OneToOneChange.createChange = function(childReference, parentReference, store, options) {
// Get the type of the child based on the child's client ID
var childType = childReference.type, key;
-
+
// If the name of the belongsTo side of the relationship is specified,
// use that
// If the type of the parent is specified, look it up on the child's type
@@ -4627,6 +4734,8 @@ DS.OneToOneChange.createChange = function(childReference, parentReference, store
}
var change = DS.RelationshipChange._createChange({
+ parentReference: parentReference,
+ childReference: childReference,
firstRecordReference: childReference,
secondRecordReference: parentReference,
firstRecordKind: "belongsTo",
@@ -4663,7 +4772,7 @@ DS.OneToOneChange.maintainInvariant = function(options, store, childReference, k
DS.OneToManyChange.createChange = function(childReference, parentReference, store, options) {
// Get the type of the child based on the child's client ID
var childType = childReference.type, key;
-
+
// If the name of the belongsTo side of the relationship is specified,
// use that
// If the type of the parent is specified, look it up on the child's type
@@ -4678,6 +4787,8 @@ DS.OneToManyChange.createChange = function(childReference, parentReference, stor
}
var change = DS.RelationshipChange._createChange({
+ parentReference: parentReference,
+ childReference: childReference,
firstRecordReference: childReference,
secondRecordReference: parentReference,
firstRecordKind: "belongsTo",
@@ -4764,13 +4875,13 @@ DS.RelationshipChange.prototype = {
/** @private */
destroy: function() {
- var childReference = this.firstRecordReference,
+ var childReference = this.childReference,
belongsToName = this.getFirstRecordName(),
hasManyName = this.getSecondRecordName(),
store = this.store,
child, oldParent, newParent, lastParent, transaction;
- store.removeRelationshipChangeFor(childReference, belongsToName, this.secondRecordReference, hasManyName, this.changeType);
+ store.removeRelationshipChangeFor(childReference, belongsToName, this.parentReference, hasManyName, this.changeType);
if (transaction = this.transaction) {
transaction.relationshipBecameClean(this);
@@ -4920,7 +5031,9 @@ DS.RelationshipChangeRemove.prototype.sync = function() {
if (secondRecord && firstRecord) {
if(this.secondRecordKind === "belongsTo"){
+ secondRecord.suspendRelationshipObservers(function(){
set(secondRecord, secondRecordName, null);
+ });
}
else if(this.secondRecordKind === "hasMany"){
secondRecord.suspendRelationshipObservers(function(){
@@ -5050,7 +5163,7 @@ Ember.onLoad('Ember.Application', function(Application) {
name: "store",
initialize: function(container, application) {
- container.register('store', 'main', application.Store);
+ application.register('store:main', application.Store);
// Eagerly generate the store so defaultStore is populated.
// TODO: Do this in a finisher hook
@@ -5061,9 +5174,9 @@ Ember.onLoad('Ember.Application', function(Application) {
Application.initializer({
name: "injectStore",
- initialize: function(container) {
- container.typeInjection('controller', 'store', 'store:main');
- container.typeInjection('route', 'store', 'store:main');
+ initialize: function(container, application) {
+ application.inject('controller', 'store', 'store:main');
+ application.inject('route', 'store', 'store:main');
}
});
}
@@ -5138,20 +5251,29 @@ function mustImplement(name) {
by implementing `keyForAttributeName`:
```javascript
- keyForAttributeName: function(type, name) {
- return name.underscore.toUpperCase();
- }
+ keyForAttributeName: function(type, name) {
+ return name.underscore.toUpperCase();
+ }
```
If your attribute names are not predictable, you can re-map them
- one-by-one using the `map` API:
+ one-by-one using the adapter's `map` API:
```javascript
- App.Person.map('App.Person', {
+ App.Adapter.map('App.Person', {
firstName: { key: '*API_USER_FIRST_NAME*' }
});
```
+ This API will also work for relationships and primary keys. For
+ example:
+
+ ```javascript
+ App.Adapter.map('App.Person', {
+ primaryKey: '_id'
+ });
+ ```
+
## Serialization
During the serialization process, a record or records are converted
@@ -5608,7 +5730,7 @@ DS.Serializer = Ember.Object.extend({
primaryKey: function(type) {
// If the type is `BlogPost`, this will return
// `blog_post_id`.
- var typeString = type.toString.split(".")[1].underscore();
+ var typeString = type.toString().split(".")[1].underscore();
return typeString + "_id";
}
});
@@ -5986,10 +6108,10 @@ DS.Serializer = Ember.Object.extend({
registerEnumTransform: function(type, objects) {
var transform = {
deserialize: function(deserialized) {
- return objects.objectAt(deserialized);
+ return Ember.A(objects).objectAt(deserialized);
},
serialize: function(serialized) {
- return objects.indexOf(serialized);
+ return Ember.EnumerableUtils.indexOf(objects, serialized);
},
values: objects
};
@@ -6240,8 +6362,6 @@ DS.JSONTransforms = {
return dayOfWeek + ", " + dayOfMonth + " " + month + " " + utcYear + " " +
pad(utcHours) + ":" + pad(utcMinutes) + ":" + pad(utcSeconds) + " GMT";
- } else if (date === undefined) {
- return undefined;
} else {
return null;
}
@@ -6283,6 +6403,13 @@ DS.JSONSerializer = DS.Serializer.extend({
if (sideloadAs) {
this.sideloadMapping.set(sideloadAs, type);
+
+ // Set a flag indicating that mappings may need to be normalized
+ // (i.e. converted from strings -> types) before sideloading.
+ // We can't do this conversion immediately here, because `configure`
+ // may be called before certain types have been defined.
+ this.sideloadMapping.normalized = false;
+
delete configuration.sideloadAs;
}
@@ -6352,7 +6479,7 @@ DS.JSONSerializer = DS.Serializer.extend({
if (this.embeddedType(type, name)) {
if (embeddedChild = get(record, name)) {
- value = this.serialize(embeddedChild, { include: true });
+ value = this.serialize(embeddedChild, { includeId: true });
}
hash[key] = value;
@@ -6444,46 +6571,88 @@ DS.JSONSerializer = DS.Serializer.extend({
}
},
+ /**
+ @private
+
+ Iterates over the `json` payload and attempts to load any data
+ included alongside `root`.
+
+ The keys expected for sideloaded data are based upon the types related
+ to the root model. Recursion is used to ensure that types related to
+ related types can be loaded as well. Any custom keys specified by
+ `sideloadAs` mappings will also be respected.
+
+ @param {DS.Store subclass} loader
+ @param {DS.Model subclass} type
+ @param {Object} json
+ @param {String} root
+ */
sideload: function(loader, type, json, root) {
- var sideloadedType, mappings, loaded = {};
+ var sideloadedType;
- loaded[root] = true;
+ this.normalizeSideloadMappings();
+ this.configureSideloadMappingForType(type);
for (var prop in json) {
- if (!json.hasOwnProperty(prop)) { continue; }
- if (prop === root) { continue; }
- if (prop === this.configOption(type, 'meta')) { continue; }
+ if (!json.hasOwnProperty(prop) ||
+ prop === root ||
+ prop === this.configOption(type, 'meta')) {
+ continue;
+ }
- sideloadedType = type.typeForRelationship(prop);
+ sideloadedType = this.sideloadMapping.get(prop);
+ Ember.assert("Your server returned a hash with the key " + prop +
+ " but you have no mapping for it",
+ !!sideloadedType);
- if (!sideloadedType) {
- sideloadedType = this.sideloadMapping.get(prop);
+ this.loadValue(loader, sideloadedType, json[prop]);
+ }
+ },
- if (typeof sideloadedType === 'string') {
- sideloadedType = get(Ember.lookup, sideloadedType);
- }
+ /**
+ @private
- Ember.assert("Your server returned a hash with the key " + prop + " but you have no mapping for it", !!sideloadedType);
- }
+ Iterates over all the `sideloadAs` mappings and converts any that are
+ strings to their equivalent types.
- this.sideloadRelationships(loader, sideloadedType, json, prop, loaded);
+ This is an optimization used to avoid performing lookups for every
+ call to `sideload`.
+ */
+ normalizeSideloadMappings: function() {
+ if (! this.sideloadMapping.normalized) {
+ this.sideloadMapping.forEach(function(key, value) {
+ if (typeof value === 'string') {
+ this.sideloadMapping.set(key, get(Ember.lookup, value));
+ }
+ }, this);
+ this.sideloadMapping.normalized = true;
}
},
- sideloadRelationships: function(loader, type, json, prop, loaded) {
- loaded[prop] = true;
+ /**
+ @private
- get(type, 'relationshipsByName').forEach(function(key, meta) {
- key = meta.key || key;
- if (meta.kind === 'belongsTo') {
- key = this.pluralize(key);
- }
- if (json[key] && !loaded[key]) {
- this.sideloadRelationships(loader, meta.type, json, key, loaded);
+ Configures possible sideload mappings for the types related to a
+ particular model. This recursive method ensures that sideloading
+ works for related models as well.
+
+ @param {DS.Model subclass} type
+ @param {Ember.A} configured an array of types that have already been configured
+ */
+ configureSideloadMappingForType: function(type, configured) {
+ if (!configured) {configured = Ember.A([]);}
+ configured.pushObject(type);
+
+ type.eachRelatedType(function(relatedType) {
+ if (!configured.contains(relatedType)) {
+ var root = this.sideloadMappingForType(relatedType);
+ if (!root) {
+ root = this.defaultSideloadRootForType(relatedType);
+ this.sideloadMapping.set(root, relatedType);
+ }
+ this.configureSideloadMappingForType(relatedType, configured);
}
}, this);
-
- this.loadValue(loader, type, json[prop]);
},
loadValue: function(loader, type, value) {
@@ -6505,6 +6674,36 @@ DS.JSONSerializer = DS.Serializer.extend({
return (plurals && plurals[name]) || name + "s";
},
+ // use the same plurals hash to determine
+ // special-case singularization
+ singularize: function(name) {
+ var plurals = this.configurations.get('plurals');
+ if (plurals) {
+ for (var i in plurals) {
+ if (plurals[i] === name) {
+ return i;
+ }
+ }
+ }
+ if (name.lastIndexOf('s') === name.length - 1) {
+ return name.substring(0, name.length - 1);
+ } else {
+ return name;
+ }
+ },
+
+ /**
+ @private
+
+ Determines the singular root name for a particular type.
+
+ This is an underscored, lowercase version of the model name.
+ For example, the type `App.UserGroup` will have the root
+ `user_group`.
+
+ @param {DS.Model subclass} type
+ @returns {String} name of the root element
+ */
rootForType: function(type) {
var typeString = type.toString();
@@ -6514,6 +6713,34 @@ DS.JSONSerializer = DS.Serializer.extend({
var parts = typeString.split(".");
var name = parts[parts.length - 1];
return name.replace(/([A-Z])/g, '_$1').toLowerCase().slice(1);
+ },
+
+ /**
+ @private
+
+ Determines the root name mapped to a particular sideloaded type.
+
+ @param {DS.Model subclass} type
+ @returns {String} name of the root element, if any is registered
+ */
+ sideloadMappingForType: function(type) {
+ this.sideloadMapping.forEach(function(key, value) {
+ if (type === value) {
+ return key;
+ }
+ });
+ },
+
+ /**
+ @private
+
+ The default root name for a particular sideloaded type.
+
+ @param {DS.Model subclass} type
+ @returns {String} name of the root element
+ */
+ defaultSideloadRootForType: function(type) {
+ return this.pluralize(this.rootForType(type));
}
});
@@ -6685,7 +6912,6 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
if (payload) {
var loader = DS.loaderFor(store);
- var serializer = get(this, 'serializer');
loader.load = function(type, data, prematerialized) {
store.updateId(record, data);
@@ -7236,14 +7462,122 @@ DS.Adapter.reopenClass({
(function() {
-var get = Ember.get;
+var get = Ember.get, set = Ember.set;
+
+DS.FixtureSerializer = DS.Serializer.extend({
+ deserializeValue: function(value, attributeType) {
+ return value;
+ },
+
+ serializeValue: function(value, attributeType) {
+ return value;
+ },
+
+ addId: function(data, key, id) {
+ data[key] = id;
+ },
+
+ addAttribute: function(hash, key, value) {
+ hash[key] = value;
+ },
+
+ addBelongsTo: function(hash, record, key, relationship) {
+ var id = get(record, relationship.key+'.id');
+ if (!Ember.isNone(id)) { hash[key] = id; }
+ },
+
+ addHasMany: function(hash, record, key, relationship) {
+ var ids = get(record, relationship.key).map(function(item) {
+ return item.get('id');
+ });
+
+ hash[relationship.key] = ids;
+ },
+
+ /**
+ @private
+
+ Creates an empty hash that will be filled in by the hooks called from the
+ `serialize()` method.
+
+ @return {Object}
+ */
+ createSerializedForm: function() {
+ return {};
+ },
+
+ extract: function(loader, fixture, type, record) {
+ if (record) { loader.updateId(record, fixture); }
+ this.extractRecordRepresentation(loader, type, fixture);
+ },
+
+ extractMany: function(loader, fixtures, type, records) {
+ var objects = fixtures, references = [];
+ if (records) { records = records.toArray(); }
+
+ for (var i = 0; i < objects.length; i++) {
+ if (records) { loader.updateId(records[i], objects[i]); }
+ var reference = this.extractRecordRepresentation(loader, type, objects[i]);
+ references.push(reference);
+ }
+
+ loader.populateArray(references);
+ },
+
+ extractId: function(type, hash) {
+ var primaryKey = this._primaryKey(type);
+
+ if (hash.hasOwnProperty(primaryKey)) {
+ // Ensure that we coerce IDs to strings so that record
+ // IDs remain consistent between application runs; especially
+ // if the ID is serialized and later deserialized from the URL,
+ // when type information will have been lost.
+ return hash[primaryKey]+'';
+ } else {
+ return null;
+ }
+ },
+
+ extractAttribute: function(type, hash, attributeName) {
+ var key = this._keyForAttributeName(type, attributeName);
+ return hash[key];
+ },
+
+ extractHasMany: function(type, hash, key) {
+ return hash[key];
+ },
+
+ extractBelongsTo: function(type, hash, key) {
+ return hash[key];
+ }
+});
+})();
+
+
+
+(function() {
+var get = Ember.get, fmt = Ember.String.fmt,
+ dump = Ember.get(window, 'JSON.stringify') || function(object) { return object.toString(); };
+
+/**
+ `DS.FixtureAdapter` is an adapter that loads records from memory.
+ Its primarily used for development and testing. You can also use
+ `DS.FixtureAdapter` while working on the API but are not ready to
+ integrate yet. It is a fully functioning adapter. All CRUD methods
+ are implemented. You can also implement query logic that a remote
+ system would do. Its possible to do develop your entire application
+ with `DS.FixtureAdapter`.
+
+*/
DS.FixtureAdapter = DS.Adapter.extend({
simulateRemoteResponse: true,
latency: 50,
+ serializer: DS.FixtureSerializer,
+
/*
Implement this method in order to provide data associated with a type
*/
@@ -7252,7 +7586,7 @@ DS.FixtureAdapter = DS.Adapter.extend({
var fixtures = Ember.A(type.FIXTURES);
return fixtures.map(function(fixture){
if(!fixture.id){
- throw new Error('the id property must be defined for fixture %@'.fmt(fixture));
+ throw new Error(fmt('the id property must be defined for fixture %@', [dump(fixture)]));
}
fixture.id = fixture.id + '';
return fixture;
@@ -7265,7 +7599,19 @@ DS.FixtureAdapter = DS.Adapter.extend({
Implement this method in order to query fixtures data
*/
queryFixtures: function(fixtures, query, type) {
- return fixtures;
+ Ember.assert('Not implemented: You must override the DS.FixtureAdapter::queryFixtures method to support querying the fixture store.');
+ },
+
+ updateFixtures: function(type, fixture) {
+ if(!type.FIXTURES) {
+ type.FIXTURES = [];
+ }
+
+ var fixtures = type.FIXTURES;
+
+ this.deleteLoadedFixture(type, fixture);
+
+ fixtures.push(fixture);
},
/*
@@ -7283,18 +7629,19 @@ DS.FixtureAdapter = DS.Adapter.extend({
},
find: function(store, type, id) {
- var fixtures = this.fixturesForType(type);
+ var fixtures = this.fixturesForType(type),
+ fixture;
- Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
+ Ember.warn("Unable to find fixtures for model type " + type.toString(), fixtures);
if (fixtures) {
- fixtures = fixtures.findProperty('id', id);
+ fixture = Ember.A(fixtures).findProperty('id', id);
}
- if (fixtures) {
+ if (fixture) {
this.simulateRemoteCall(function() {
- store.load(type, fixtures);
- }, store, type);
+ this.didFindRecord(store, type, fixture, id);
+ }, this);
}
},
@@ -7311,8 +7658,8 @@ DS.FixtureAdapter = DS.Adapter.extend({
if (fixtures) {
this.simulateRemoteCall(function() {
- store.loadMany(type, fixtures);
- }, store, type);
+ this.didFindMany(store, type, fixtures);
+ }, this);
}
},
@@ -7322,9 +7669,8 @@ DS.FixtureAdapter = DS.Adapter.extend({
Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
this.simulateRemoteCall(function() {
- store.loadMany(type, fixtures);
- store.didUpdateAll(type);
- }, store, type);
+ this.didFindAll(store, type, fixtures);
+ }, this);
},
findQuery: function(store, type, query, array) {
@@ -7336,43 +7682,82 @@ DS.FixtureAdapter = DS.Adapter.extend({
if (fixtures) {
this.simulateRemoteCall(function() {
- array.load(fixtures);
- }, store, type);
+ this.didFindQuery(store, type, fixtures, array);
+ }, this);
}
},
createRecord: function(store, type, record) {
var fixture = this.mockJSON(type, record);
- fixture.id = this.generateIdForRecord(store, record);
+ this.updateFixtures(type, fixture);
this.simulateRemoteCall(function() {
- store.didSaveRecord(record, fixture);
- }, store, type, record);
+ this.didCreateRecord(store, type, record, fixture);
+ }, this);
},
updateRecord: function(store, type, record) {
var fixture = this.mockJSON(type, record);
+ this.updateFixtures(type, fixture);
+
this.simulateRemoteCall(function() {
- store.didSaveRecord(record, fixture);
- }, store, type, record);
+ this.didUpdateRecord(store, type, record, fixture);
+ }, this);
},
deleteRecord: function(store, type, record) {
+ var fixture = this.mockJSON(type, record);
+
+ this.deleteLoadedFixture(type, fixture);
+
this.simulateRemoteCall(function() {
- store.didSaveRecord(record);
- }, store, type, record);
+ this.didDeleteRecord(store, type, record);
+ }, this);
},
/*
@private
*/
- simulateRemoteCall: function(callback, store, type, record) {
+ deleteLoadedFixture: function(type, record) {
+ var id = this.extractId(type, record);
+
+ var existingFixture = this.findExistingFixture(type, record);
+
+ if(existingFixture) {
+ var index = type.FIXTURES.indexOf(existingFixture);
+ type.FIXTURES.splice(index, 1);
+ return true;
+ }
+ },
+
+ findExistingFixture: function(type, record) {
+ var fixtures = this.fixturesForType(type);
+ var id = this.extractId(type, record);
+
+ return this.findFixtureById(fixtures, id);
+ },
+
+ findFixtureById: function(fixtures, id) {
+ var adapter = this;
+
+ return Ember.A(fixtures).find(function(r) {
+ if(''+get(r, 'id') === ''+id) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+ },
+
+ simulateRemoteCall: function(callback, context) {
if (get(this, 'simulateRemoteResponse')) {
- setTimeout(callback, get(this, 'latency'));
+ // Schedule with setTimeout
+ Ember.run.later(context, callback, get(this, 'latency'));
} else {
- callback();
+ // Asynchronous, but at the of the runloop with zero latency
+ Ember.run.once(context, callback);
}
}
});
@@ -7395,6 +7780,16 @@ DS.RESTSerializer = DS.JSONSerializer.extend({
}
return key + "_id";
+ },
+
+ keyForHasMany: function(type, name) {
+ var key = this.keyForAttributeName(type, name);
+
+ if (this.embeddedType(type, name)) {
+ return key;
+ }
+
+ return this.singularize(key) + "_ids";
}
});
@@ -7499,12 +7894,25 @@ DS.RESTAdapter = DS.Adapter.extend({
},
dirtyRecordsForRecordChange: function(dirtySet, record) {
+ this._dirtyTree(dirtySet, record);
+ },
+
+ dirtyRecordsForHasManyChange: function(dirtySet, record, relationship) {
+ var embeddedType = get(this, 'serializer').embeddedType(record.constructor, relationship.secondRecordName);
+
+ if (embeddedType === 'always') {
+ relationship.childReference.parent = relationship.parentReference;
+ this._dirtyTree(dirtySet, record);
+ }
+ },
+
+ _dirtyTree: function(dirtySet, record) {
dirtySet.add(record);
get(this, 'serializer').eachEmbeddedRecord(record, function(embeddedRecord, embeddedType) {
if (embeddedType !== 'always') { return; }
if (dirtySet.has(embeddedRecord)) { return; }
- this.dirtyRecordsForRecordChange(dirtySet, embeddedRecord);
+ this._dirtyTree(dirtySet, embeddedRecord);
}, this);
var reference = record.get('_reference');
@@ -7512,12 +7920,10 @@ DS.RESTAdapter = DS.Adapter.extend({
if (reference.parent) {
var store = get(record, 'store');
var parent = store.recordForReference(reference.parent);
- this.dirtyRecordsForRecordChange(dirtySet, parent);
+ this._dirtyTree(dirtySet, parent);
}
},
- dirtyRecordsForHasManyChange: Ember.K,
-
createRecords: function(store, type, records) {
if (get(this, 'bulkCommit') === false) {
return this._super(store, type, records);
@@ -7763,6 +8169,251 @@ DS.RESTAdapter = DS.Adapter.extend({
(function() {
+var camelize = Ember.String.camelize,
+ capitalize = Ember.String.capitalize,
+ get = Ember.get,
+ map = Ember.ArrayPolyfills.map,
+ registeredTransforms;
+
+var passthruTransform = {
+ serialize: function(value) { return value; },
+ deserialize: function(value) { return value; }
+};
+
+var defaultTransforms = {
+ string: passthruTransform,
+ boolean: passthruTransform,
+ number: passthruTransform
+};
+
+function camelizeKeys(json) {
+ var value;
+
+ for (var prop in json) {
+ value = json[prop];
+ delete json[prop];
+ json[camelize(prop)] = value;
+ }
+}
+
+function munge(json, callback) {
+ callback(json);
+}
+
+function applyTransforms(json, type, transformType) {
+ var transforms = registeredTransforms[transformType];
+
+ Ember.assert("You are trying to apply the '" + transformType + "' transforms, but you didn't register any transforms with that name", transforms);
+
+ get(type, 'attributes').forEach(function(name, attribute) {
+ var attributeType = attribute.type,
+ value = json[name];
+
+ var transform = transforms[attributeType] || defaultTransforms[attributeType];
+
+ Ember.assert("Your model specified the '" + attributeType + "' type for the '" + name + "' attribute, but no transform for that type was registered", transform);
+
+ json[name] = transform.deserialize(value);
+ });
+}
+
+function ObjectProcessor(json, type, store) {
+ this.json = json;
+ this.type = type;
+ this.store = store;
+}
+
+ObjectProcessor.prototype = {
+ camelizeKeys: function() {
+ camelizeKeys(this.json);
+ return this;
+ },
+
+ munge: function(callback) {
+ munge(this.json, callback);
+ return this;
+ },
+
+ applyTransforms: function(transformType) {
+ applyTransforms(this.json, this.type, transformType);
+ return this;
+ }
+};
+
+function LoadObjectProcessor() {
+ ObjectProcessor.apply(this, arguments);
+}
+
+LoadObjectProcessor.prototype = Ember.create(ObjectProcessor.prototype);
+
+LoadObjectProcessor.prototype.load = function() {
+ this.store.load(this.type, {}, this.json);
+};
+
+function loadObjectProcessorFactory(store, type) {
+ return function(json) {
+ return new LoadObjectProcessor(json, type, store);
+ };
+}
+
+function ArrayProcessor(json, type, array, store) {
+ this.json = json;
+ this.type = type;
+ this.array = array;
+ this.store = store;
+}
+
+ArrayProcessor.prototype = {
+ load: function() {
+ var store = this.store,
+ type = this.type;
+
+ var references = this.json.map(function(object) {
+ return store.load(type, {}, object);
+ });
+
+ this.array.load(references);
+ },
+
+ camelizeKeys: function() {
+ this.json.forEach(camelizeKeys);
+ return this;
+ },
+
+ munge: function(callback) {
+ this.json.forEach(function(object) {
+ munge(object, callback);
+ });
+ return this;
+ },
+
+ applyTransforms: function(transformType) {
+ var type = this.type;
+
+ this.json.forEach(function(object) {
+ applyTransforms(object, type, transformType);
+ });
+
+ return this;
+ }
+};
+
+function arrayProcessorFactory(store, type, array) {
+ return function(json) {
+ return new ArrayProcessor(json, type, array, store);
+ };
+}
+
+var HasManyProcessor = function(json, store, record, relationship) {
+ this.json = json;
+ this.store = store;
+ this.record = record;
+ this.type = record.constructor;
+ this.relationship = relationship;
+};
+
+HasManyProcessor.prototype = Ember.create(ArrayProcessor.prototype);
+
+HasManyProcessor.prototype.load = function() {
+ var store = this.store;
+ var ids = map.call(this.json, function(obj) { return obj.id; });
+
+ store.loadMany(this.relationship.type, this.json);
+ store.loadHasMany(this.record, this.relationship.key, ids);
+};
+
+function hasManyProcessorFactory(store, record, relationship) {
+ return function(json) {
+ return new HasManyProcessor(json, store, record, relationship);
+ };
+}
+
+function SaveProcessor(record, store, type, includeId) {
+ this.record = record;
+ ObjectProcessor.call(this, record.toJSON({ includeId: includeId }), type, store);
+}
+
+SaveProcessor.prototype = Ember.create(ObjectProcessor.prototype);
+
+SaveProcessor.prototype.save = function(callback) {
+ callback(this.json);
+};
+
+function saveProcessorFactory(store, type, includeId) {
+ return function(record) {
+ return new SaveProcessor(record, store, type, includeId);
+ };
+}
+
+DS.BasicAdapter = DS.Adapter.extend({
+ find: function(store, type, id) {
+ var sync = type.sync;
+
+ Ember.assert("You are trying to use the BasicAdapter to find id '" + id + "' of " + type + " but " + type + ".sync was not found", sync);
+ Ember.assert("The sync code on " + type + " does not implement find(), but you are trying to find id '" + id + "'.", sync.find);
+
+ sync.find(id, loadObjectProcessorFactory(store, type));
+ },
+
+ findQuery: function(store, type, query, recordArray) {
+ var sync = type.sync;
+
+ Ember.assert("You are trying to use the BasicAdapter to query " + type + " but " + type + ".sync was not found", sync);
+ Ember.assert("The sync code on " + type + " does not implement query(), but you are trying to query " + type + ".", sync.query);
+
+ sync.query(query, arrayProcessorFactory(store, type, recordArray));
+ },
+
+ findHasMany: function(store, record, relationship, data) {
+ var name = capitalize(relationship.key),
+ sync = record.constructor.sync,
+ processor = hasManyProcessorFactory(store, record, relationship);
+
+ var options = {
+ relationship: relationship.key,
+ data: data
+ };
+
+ if (sync['find'+name]) {
+ sync['find' + name](record, options, processor);
+ } else if (sync.findHasMany) {
+ sync.findHasMany(record, options, processor);
+ } else {
+ Ember.assert("You are trying to use the BasicAdapter to find the " + relationship.key + " has-many relationship, but " + record.constructor + ".sync did not implement findHasMany or find" + name + ".", false);
+ }
+ },
+
+ createRecord: function(store, type, record) {
+ var sync = type.sync;
+ sync.createRecord(record, saveProcessorFactory(store, type));
+ },
+
+ updateRecord: function(store, type, record) {
+ var sync = type.sync;
+ sync.updateRecord(record, saveProcessorFactory(store, type, true));
+ },
+
+ deleteRecord: function(store, type, record) {
+ var sync = type.sync;
+ sync.deleteRecord(record, saveProcessorFactory(store, type, true));
+ }
+});
+
+DS.registerTransforms = function(kind, object) {
+ registeredTransforms[kind] = object;
+};
+
+DS.clearTransforms = function() {
+ registeredTransforms = {};
+};
+
+DS.clearTransforms();
+
+})();
+
+
+
+(function() {
})();