From 248982cd957188d961b8410c78bee614916e2a1e Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Sat, 25 Jan 2014 21:06:01 -0500 Subject: [PATCH] Add Ember Data 1.0.0-beta.6. --- .../ember-data.js/1.0.0-beta.6/ember-data.js | 10620 +++++++++++++++ .../1.0.0-beta.6/ember-data.min.js | 10 + .../1.0.0-beta.6/ember-data.prod.js | 10626 ++++++++++++++++ ajax/libs/ember-data.js/package.json | 2 +- 4 files changed, 21257 insertions(+), 1 deletion(-) create mode 100644 ajax/libs/ember-data.js/1.0.0-beta.6/ember-data.js create mode 100644 ajax/libs/ember-data.js/1.0.0-beta.6/ember-data.min.js create mode 100644 ajax/libs/ember-data.js/1.0.0-beta.6/ember-data.prod.js diff --git a/ajax/libs/ember-data.js/1.0.0-beta.6/ember-data.js b/ajax/libs/ember-data.js/1.0.0-beta.6/ember-data.js new file mode 100644 index 000000000..762a9d779 --- /dev/null +++ b/ajax/libs/ember-data.js/1.0.0-beta.6/ember-data.js @@ -0,0 +1,10620 @@ +/*! + * @overview Ember Data + * @copyright Copyright 2011-2014 Tilde Inc. and contributors. + * Portions Copyright 2011 LivingSocial Inc. + * @license Licensed under MIT license (see license.js) + * @version 1.0.0-beta.6 + */ + + +(function() { +var define, requireModule; + +(function() { + var registry = {}, seen = {}; + + define = function(name, deps, callback) { + registry[name] = { deps: deps, callback: callback }; + }; + + requireModule = function(name) { + if (seen[name]) { return seen[name]; } + seen[name] = {}; + + var mod, deps, callback, reified , exports; + + mod = registry[name]; + + if (!mod) { + throw new Error("Module '" + name + "' not found."); + } + + deps = mod.deps; + callback = mod.callback; + reified = []; + exports; + + for (var i=0, l=deps.length; i 0; i--) { + var proxyPair = proxyPairs[i - 1], + deprecated = proxyPair['deprecated'], + valid = proxyPair['valid']; + + this.registerDeprecation(deprecated, valid); + } +}; + +DS.ContainerProxy = ContainerProxy; + +})(); + + + +(function() { +var get = Ember.get, set = Ember.set, isNone = Ember.isNone; + +// Simple dispatcher to support overriding the aliased +// method in subclasses. +function aliasMethod(methodName) { + return function() { + return this[methodName].apply(this, arguments); + }; +} + +/** + In Ember Data a Serializer is used to serialize and deserialize + records when they are transferred in and out of an external source. + This process involves normalizing property names, transforming + attribute values and serializing relationships. + + For maximum performance Ember Data recommends you use the + [RESTSerializer](DS.RESTSerializer.html) or one of its subclasses. + + `JSONSerializer` is useful for simpler or legacy backends that may + not support the http://jsonapi.org/ spec. + + @class JSONSerializer + @namespace DS +*/ +DS.JSONSerializer = Ember.Object.extend({ + /** + The primaryKey is used when serializing and deserializing + data. Ember Data always uses the `id` property to store the id of + the record. The external source may not always follow this + convention. In these cases it is useful to override the + primaryKey property to match the primaryKey of your external + store. + + Example + + ```javascript + App.ApplicationSerializer = DS.JSONSerializer.extend({ + primaryKey: '_id' + }); + ``` + + @property primaryKey + @type {String} + @default 'id' + */ + primaryKey: 'id', + + /** + Given a subclass of `DS.Model` and a JSON object this method will + iterate through each attribute of the `DS.Model` and invoke the + `DS.Transform#deserialize` method on the matching property of the + JSON object. This method is typically called after the + serializer's `normalize` method. + + @method applyTransforms + @private + @param {subclass of DS.Model} type + @param {Object} data The data to transform + @return {Object} data The transformed data object + */ + applyTransforms: function(type, data) { + type.eachTransformedAttribute(function(key, type) { + var transform = this.transformFor(type); + data[key] = transform.deserialize(data[key]); + }, this); + + return data; + }, + + /** + Normalizes a part of the JSON payload returned by + the server. You should override this method, munge the hash + and call super if you have generic normalization to do. + + It takes the type of the record that is being normalized + (as a DS.Model class), the property where the hash was + originally found, and the hash to normalize. + + You can use this method, for example, to normalize underscored keys to camelized + or other general-purpose normalizations. + + Example + + ```javascript + App.ApplicationSerializer = DS.JSONSerializer.extend({ + normalize: function(type, hash) { + var fields = Ember.get(type, 'fields'); + fields.forEach(function(field) { + var payloadField = Ember.String.underscore(field); + if (field === payloadField) { return; } + + hash[field] = hash[payloadField]; + delete hash[payloadField]; + }); + return this._super.apply(this, arguments); + } + }); + ``` + + @method normalize + @param {subclass of DS.Model} type + @param {Object} hash + @return {Object} + */ + normalize: function(type, hash) { + if (!hash) { return hash; } + + this.applyTransforms(type, hash); + return hash; + }, + + // SERIALIZE + /** + Called when a record is saved in order to convert the + record into JSON. + + By default, it creates a JSON object with a key for + each attribute and belongsTo relationship. + + For example, consider this model: + + ```javascript + App.Comment = DS.Model.extend({ + title: DS.attr(), + body: DS.attr(), + + author: DS.belongsTo('user') + }); + ``` + + The default serialization would create a JSON object like: + + ```javascript + { + "title": "Rails is unagi", + "body": "Rails? Omakase? O_O", + "author": 12 + } + ``` + + By default, attributes are passed through as-is, unless + you specified an attribute type (`DS.attr('date')`). If + you specify a transform, the JavaScript value will be + serialized when inserted into the JSON hash. + + By default, belongs-to relationships are converted into + IDs when inserted into the JSON hash. + + ## IDs + + `serialize` takes an options hash with a single option: + `includeId`. If this option is `true`, `serialize` will, + by default include the ID in the JSON object it builds. + + The adapter passes in `includeId: true` when serializing + a record for `createRecord`, but not for `updateRecord`. + + ## Customization + + Your server may expect a different JSON format than the + built-in serialization format. + + In that case, you can implement `serialize` yourself and + return a JSON hash of your choosing. + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + serialize: function(post, options) { + var json = { + POST_TTL: post.get('title'), + POST_BDY: post.get('body'), + POST_CMS: post.get('comments').mapProperty('id') + } + + if (options.includeId) { + json.POST_ID_ = post.get('id'); + } + + return json; + } + }); + ``` + + ## Customizing an App-Wide Serializer + + If you want to define a serializer for your entire + application, you'll probably want to use `eachAttribute` + and `eachRelationship` on the record. + + ```javascript + App.ApplicationSerializer = DS.JSONSerializer.extend({ + serialize: function(record, options) { + var json = {}; + + record.eachAttribute(function(name) { + json[serverAttributeName(name)] = record.get(name); + }) + + record.eachRelationship(function(name, relationship) { + if (relationship.kind === 'hasMany') { + json[serverHasManyName(name)] = record.get(name).mapBy('id'); + } + }); + + if (options.includeId) { + json.ID_ = record.get('id'); + } + + return json; + } + }); + + function serverAttributeName(attribute) { + return attribute.underscore().toUpperCase(); + } + + function serverHasManyName(name) { + return serverAttributeName(name.singularize()) + "_IDS"; + } + ``` + + This serializer will generate JSON that looks like this: + + ```javascript + { + "TITLE": "Rails is omakase", + "BODY": "Yep. Omakase.", + "COMMENT_IDS": [ 1, 2, 3 ] + } + ``` + + ## Tweaking the Default JSON + + If you just want to do some small tweaks on the default JSON, + you can call super first and make the tweaks on the returned + JSON. + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + serialize: function(record, options) { + var json = this._super.apply(this, arguments); + + json.subject = json.title; + delete json.title; + + return json; + } + }); + ``` + + @method serialize + @param {subclass of DS.Model} record + @param {Object} options + @return {Object} json + */ + serialize: function(record, options) { + var json = {}; + + if (options && options.includeId) { + var id = get(record, 'id'); + + if (id) { + json[get(this, 'primaryKey')] = id; + } + } + + record.eachAttribute(function(key, attribute) { + this.serializeAttribute(record, json, key, attribute); + }, this); + + record.eachRelationship(function(key, relationship) { + if (relationship.kind === 'belongsTo') { + this.serializeBelongsTo(record, json, relationship); + } else if (relationship.kind === 'hasMany') { + this.serializeHasMany(record, json, relationship); + } + }, this); + + return json; + }, + + /** + `serializeAttribute` can be used to customize how `DS.attr` + properties are serialized + + For example if you wanted to ensure all you attributes were always + serialized as properties on an `attributes` object you could + write: + + ```javascript + App.ApplicationSerializer = DS.JSONSerializer.extend({ + serializeAttribute: function(record, json, key, attributes) { + json.attributes = json.attributes || {}; + this._super(record, json.attributes, key, attributes); + } + }); + ``` + + @method serializeAttribute + @param {DS.Model} record + @param {Object} json + @param {String} key + @param {Object} attribute + */ + serializeAttribute: function(record, json, key, attribute) { + var attrs = get(this, 'attrs'); + var value = get(record, key), type = attribute.type; + + if (type) { + var transform = this.transformFor(type); + value = transform.serialize(value); + } + + // if provided, use the mapping provided by `attrs` in + // the serializer + key = attrs && attrs[key] || (this.keyForAttribute ? this.keyForAttribute(key) : key); + + json[key] = value; + }, + + /** + `serializeBelongsTo` can be used to customize how `DS.belongsTo` + properties are serialized. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + serializeBelongsTo: function(record, json, relationship) { + var key = relationship.key; + + var belongsTo = get(record, key); + + key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key; + + json[key] = Ember.isNone(belongsTo) ? belongsTo : belongsTo.toJSON(); + } + }); + ``` + + @method serializeBelongsTo + @param {DS.Model} record + @param {Object} json + @param {Object} relationship + */ + serializeBelongsTo: function(record, json, relationship) { + var key = relationship.key; + + var belongsTo = get(record, key); + + key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key; + + if (isNone(belongsTo)) { + json[key] = belongsTo; + } else { + json[key] = get(belongsTo, 'id'); + } + + if (relationship.options.polymorphic) { + this.serializePolymorphicType(record, json, relationship); + } + }, + + /** + `serializeHasMany` can be used to customize how `DS.hasMany` + properties are serialized. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + serializeHasMany: function(record, json, relationship) { + var key = relationship.key; + if (key === 'comments') { + return; + } else { + this._super.apply(this, arguments); + } + } + }); + ``` + + @method serializeHasMany + @param {DS.Model} record + @param {Object} json + @param {Object} relationship + */ + serializeHasMany: function(record, json, relationship) { + var key = relationship.key; + + var relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship); + + if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') { + json[key] = get(record, key).mapBy('id'); + // TODO support for polymorphic manyToNone and manyToMany relationships + } + }, + + /** + You can use this method to customize how polymorphic objects are + serialized. Objects are considered to be polymorphic if + `{polymorphic: true}` is pass as the second argument to the + `DS.belongsTo` function. + + Example + + ```javascript + App.CommentSerializer = DS.JSONSerializer.extend({ + serializePolymorphicType: function(record, json, relationship) { + var key = relationship.key, + belongsTo = get(record, key); + key = this.keyForAttribute ? this.keyForAttribute(key) : key; + json[key + "_type"] = belongsTo.constructor.typeKey; + } + }); + ``` + + @method serializePolymorphicType + @param {DS.Model} record + @param {Object} json + @param {Object} relationship + */ + serializePolymorphicType: Ember.K, + + // EXTRACT + + /** + The `extract` method is used to deserialize payload data from the + server. By default the `JSONSerializer` does not push the records + into the store. However records that subclass `JSONSerializer` + such as the `RESTSerializer` may push records into the store as + part of the extract call. + + This method delegates to a more specific extract method based on + the `requestType`. + + Example + + ```javascript + var get = Ember.get; + socket.on('message', function(message) { + var modelName = message.model; + var data = message.data; + var type = store.modelFor(modelName); + var serializer = store.serializerFor(type.typeKey); + var record = serializer.extract(store, type, data, get(data, 'id'), 'single'); + store.push(modelName, record); + }); + ``` + + @method extract + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @param {String or Number} id + @param {String} requestType + @return {Object} json The deserialized payload + */ + extract: function(store, type, payload, id, requestType) { + this.extractMeta(store, type, payload); + + var specificExtract = "extract" + requestType.charAt(0).toUpperCase() + requestType.substr(1); + return this[specificExtract](store, type, payload, id, requestType); + }, + + /** + `extractFindAll` is a hook into the extract method used when a + call is made to `DS.Store#findAll`. By default this method is an + alias for [extractArray](#method_extractArray). + + @method extractFindAll + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractFindAll: aliasMethod('extractArray'), + /** + `extractFindQuery` is a hook into the extract method used when a + call is made to `DS.Store#findQuery`. By default this method is an + alias for [extractArray](#method_extractArray). + + @method extractFindQuery + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractFindQuery: aliasMethod('extractArray'), + /** + `extractFindMany` is a hook into the extract method used when a + call is made to `DS.Store#findMany`. By default this method is + alias for [extractArray](#method_extractArray). + + @method extractFindMany + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractFindMany: aliasMethod('extractArray'), + /** + `extractFindHasMany` is a hook into the extract method used when a + call is made to `DS.Store#findHasMany`. By default this method is + alias for [extractArray](#method_extractArray). + + @method extractFindHasMany + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractFindHasMany: aliasMethod('extractArray'), + + /** + `extractCreateRecord` is a hook into the extract method used when a + call is made to `DS.Store#createRecord`. By default this method is + alias for [extractSave](#method_extractSave). + + @method extractCreateRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractCreateRecord: aliasMethod('extractSave'), + /** + `extractUpdateRecord` is a hook into the extract method used when + a call is made to `DS.Store#update`. By default this method is alias + for [extractSave](#method_extractSave). + + @method extractUpdateRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractUpdateRecord: aliasMethod('extractSave'), + /** + `extractDeleteRecord` is a hook into the extract method used when + a call is made to `DS.Store#deleteRecord`. By default this method is + alias for [extractSave](#method_extractSave). + + @method extractDeleteRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractDeleteRecord: aliasMethod('extractSave'), + + /** + `extractFind` is a hook into the extract method used when + a call is made to `DS.Store#find`. By default this method is + alias for [extractSingle](#method_extractSingle). + + @method extractFind + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractFind: aliasMethod('extractSingle'), + /** + `extractFindBelongsTo` is a hook into the extract method used when + a call is made to `DS.Store#findBelongsTo`. By default this method is + alias for [extractSingle](#method_extractSingle). + + @method extractFindBelongsTo + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractFindBelongsTo: aliasMethod('extractSingle'), + /** + `extractSave` is a hook into the extract method used when a call + is made to `DS.Model#save`. By default this method is alias + for [extractSingle](#method_extractSingle). + + @method extractSave + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractSave: aliasMethod('extractSingle'), + + /** + `extractSingle` is used to deserialize a single record returned + from the adapter. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + extractSingle: function(store, type, payload) { + payload.comments = payload._embedded.comment; + delete payload._embedded; + + return this._super(store, type, payload); + }, + }); + ``` + + @method extractSingle + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractSingle: function(store, type, payload) { + return this.normalize(type, payload); + }, + + /** + `extractArray` is used to deserialize an array of records + returned from the adapter. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + extractArray: function(store, type, payload) { + return payload.map(function(json) { + return this.extractSingle(json); + }, this); + } + }); + ``` + + @method extractArray + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractArray: function(store, type, payload) { + return this.normalize(type, payload); + }, + + /** + `extractMeta` is used to deserialize any meta information in the + adapter payload. By default Ember Data expects meta information to + be located on the `meta` property of the payload object. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + extractMeta: function(store, type, payload) { + if (payload && payload._pagination) { + store.metaForType(type, payload._pagination); + delete payload._pagination; + } + } + }); + ``` + + @method extractMeta + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + */ + extractMeta: function(store, type, payload) { + if (payload && payload.meta) { + store.metaForType(type, payload.meta); + delete payload.meta; + } + }, + + /** + `keyForAttribute` can be used to define rules for how to convert an + attribute name in your model to a key in your JSON. + + Example + + ```javascript + App.ApplicationSerializer = DS.RESTSerializer.extend({ + keyForAttribute: function(attr) { + return Ember.String.underscore(attr).toUpperCase(); + } + }); + ``` + + @method keyForAttribute + @param {String} key + @return {String} normalized key + */ + + + /** + `keyForRelationship` can be used to define a custom key when + serializing relationship properties. By default `JSONSerializer` + does not provide an implementation of this method. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + keyForRelationship: function(key, relationship) { + return 'rel_' + Ember.String.underscore(key); + } + }); + ``` + + @method keyForRelationship + @param {String} key + @param {String} relationship type + @return {String} normalized key + */ + + // HELPERS + + /** + @method transformFor + @private + @param {String} attributeType + @param {Boolean} skipAssertion + @return {DS.Transform} transform + */ + transformFor: function(attributeType, skipAssertion) { + var transform = this.container.lookup('transform:' + attributeType); + Ember.assert("Unable to find transform for '" + attributeType + "'", skipAssertion || !!transform); + return transform; + } +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ +var get = Ember.get, capitalize = Ember.String.capitalize, underscore = Ember.String.underscore, DS = window.DS ; + +/** + Extend `Ember.DataAdapter` with ED specific code. + + @class DebugAdapter + @namespace DS + @extends Ember.DataAdapter + @private +*/ +DS.DebugAdapter = Ember.DataAdapter.extend({ + getFilters: function() { + return [ + { name: 'isNew', desc: 'New' }, + { name: 'isModified', desc: 'Modified' }, + { name: 'isClean', desc: 'Clean' } + ]; + }, + + detect: function(klass) { + return klass !== DS.Model && DS.Model.detect(klass); + }, + + columnsForType: function(type) { + var columns = [{ name: 'id', desc: 'Id' }], count = 0, self = this; + get(type, 'attributes').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) { + return this.get('store').all(type); + }, + + getRecordColumnValues: function(record) { + var self = this, count = 0, + columnValues = { id: get(record, 'id') }; + + record.eachAttribute(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.eachAttribute(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.eachAttribute(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; + } + +}); + +})(); + + + +(function() { +/** + The `DS.Transform` class is used to serialize and deserialize model + attributes when they are saved or loaded from an + adapter. Subclassing `DS.Transform` is useful for creating custom + attributes. All subclasses of `DS.Transform` must implement a + `serialize` and a `deserialize` method. + + Example + + ```javascript + App.RawTransform = DS.Transform.extend({ + deserialize: function(serialized) { + return serialized; + }, + serialize: function(deserialized) { + return deserialized; + } + }); + ``` + + Usage + + ```javascript + var attr = DS.attr; + App.Requirement = DS.Model.extend({ + name: attr('string'), + optionsArray: attr('raw') + }); + ``` + + @class Transform + @namespace DS + */ +DS.Transform = Ember.Object.extend({ + /** + When given a deserialized value from a record attribute this + method must return the serialized value. + + Example + + ```javascript + serialize: function(deserialized) { + return Ember.isEmpty(deserialized) ? null : Number(deserialized); + } + ``` + + @method serialize + @param deserialized The deserialized value + @return The serialized value + */ + serialize: Ember.required(), + + /** + When given a serialize value from a JSON object this method must + return the deserialized value for the record attribute. + + Example + + ```javascript + deserialize: function(serialized) { + return empty(serialized) ? null : Number(serialized); + } + ``` + + @method deserialize + @param serialized The serialized value + @return The deserialized value + */ + deserialize: Ember.required() + +}); + +})(); + + + +(function() { + +/** + The `DS.BooleanTransform` class is used to serialize and deserialize + boolean attributes on Ember Data record objects. This transform is + used when `boolean` is passed as the type parameter to the + [DS.attr](../../data#method_attr) function. + + Usage + + ```javascript + var attr = DS.attr; + App.User = DS.Model.extend({ + isAdmin: attr('boolean'), + name: attr('string'), + email: attr('string') + }); + ``` + + @class BooleanTransform + @extends DS.Transform + @namespace DS + */ +DS.BooleanTransform = DS.Transform.extend({ + deserialize: function(serialized) { + var type = typeof serialized; + + if (type === "boolean") { + return serialized; + } else if (type === "string") { + return serialized.match(/^true$|^t$|^1$/i) !== null; + } else if (type === "number") { + return serialized === 1; + } else { + return false; + } + }, + + serialize: function(deserialized) { + return Boolean(deserialized); + } +}); + +})(); + + + +(function() { +/** + The `DS.DateTransform` class is used to serialize and deserialize + date attributes on Ember Data record objects. This transform is used + when `date` is passed as the type parameter to the + [DS.attr](../../data#method_attr) function. + + ```javascript + var attr = DS.attr; + App.Score = DS.Model.extend({ + value: attr('number'), + player: DS.belongsTo('player'), + date: attr('date') + }); + ``` + + @class DateTransform + @extends DS.Transform + @namespace DS + */ +DS.DateTransform = DS.Transform.extend({ + + deserialize: function(serialized) { + var type = typeof serialized; + + if (type === "string") { + return new Date(Ember.Date.parse(serialized)); + } else if (type === "number") { + return new Date(serialized); + } else if (serialized === null || serialized === undefined) { + // if the value is not present in the data, + // return undefined, not null. + return serialized; + } else { + return null; + } + }, + + serialize: function(date) { + if (date instanceof Date) { + // Serialize it as a number to maintain millisecond precision + return Number(date); + } else { + return null; + } + } + +}); + +})(); + + + +(function() { +var empty = Ember.isEmpty; +/** + The `DS.NumberTransform` class is used to serialize and deserialize + numeric attributes on Ember Data record objects. This transform is + used when `number` is passed as the type parameter to the + [DS.attr](../../data#method_attr) function. + + Usage + + ```javascript + var attr = DS.attr; + App.Score = DS.Model.extend({ + value: attr('number'), + player: DS.belongsTo('player'), + date: attr('date') + }); + ``` + + @class NumberTransform + @extends DS.Transform + @namespace DS + */ +DS.NumberTransform = DS.Transform.extend({ + + deserialize: function(serialized) { + return empty(serialized) ? null : Number(serialized); + }, + + serialize: function(deserialized) { + return empty(deserialized) ? null : Number(deserialized); + } +}); + +})(); + + + +(function() { +var none = Ember.isNone; + +/** + The `DS.StringTransform` class is used to serialize and deserialize + string attributes on Ember Data record objects. This transform is + used when `string` is passed as the type parameter to the + [DS.attr](../../data#method_attr) function. + + Usage + + ```javascript + var attr = DS.attr; + App.User = DS.Model.extend({ + isAdmin: attr('boolean'), + name: attr('string'), + email: attr('string') + }); + ``` + + @class StringTransform + @extends DS.Transform + @namespace DS + */ +DS.StringTransform = DS.Transform.extend({ + + deserialize: function(serialized) { + return none(serialized) ? null : String(serialized); + }, + + serialize: function(deserialized) { + return none(deserialized) ? null : String(deserialized); + } + +}); + +})(); + + + +(function() { + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var set = Ember.set; + +/* + This code registers an injection for Ember.Application. + + If an Ember.js developer defines a subclass of DS.Store on their application, + this code will automatically instantiate it and make it available on the + router. + + Additionally, after an application's controllers have been injected, they will + each have the store made available to them. + + For example, imagine an Ember.js application with the following classes: + + App.Store = DS.Store.extend({ + adapter: 'custom' + }); + + App.PostsController = Ember.ArrayController.extend({ + // ... + }); + + When the application is initialized, `App.Store` will automatically be + instantiated, and the instance of `App.PostsController` will have its `store` + property set to that instance. + + Note that this code will only be run if the `ember-application` package is + loaded. If Ember Data is being used in an environment other than a + typical application (e.g., node.js where only `ember-runtime` is available), + this code will be ignored. +*/ + +Ember.onLoad('Ember.Application', function(Application) { + Application.initializer({ + name: "store", + + initialize: function(container, application) { + application.register('store:main', application.Store || DS.Store); + + // allow older names to be looked up + + var proxy = new DS.ContainerProxy(container); + proxy.registerDeprecations([ + {deprecated: 'serializer:_default', valid: 'serializer:-default'}, + {deprecated: 'serializer:_rest', valid: 'serializer:-rest'}, + {deprecated: 'adapter:_rest', valid: 'adapter:-rest'} + ]); + + // new go forward paths + application.register('serializer:-default', DS.JSONSerializer); + application.register('serializer:-rest', DS.RESTSerializer); + application.register('adapter:-rest', DS.RESTAdapter); + + // Eagerly generate the store so defaultStore is populated. + // TODO: Do this in a finisher hook + container.lookup('store:main'); + } + }); + + Application.initializer({ + name: "transforms", + before: "store", + + initialize: function(container, application) { + application.register('transform:boolean', DS.BooleanTransform); + application.register('transform:date', DS.DateTransform); + application.register('transform:number', DS.NumberTransform); + application.register('transform:string', DS.StringTransform); + } + }); + + Application.initializer({ + name: "data-adapter", + before: "store", + + initialize: function(container, application) { + application.register('data-adapter:main', DS.DebugAdapter); + } + }); + + Application.initializer({ + name: "injectStore", + before: "store", + + initialize: function(container, application) { + application.inject('controller', 'store', 'store:main'); + application.inject('route', 'store', 'store:main'); + application.inject('serializer', 'store', 'store:main'); + application.inject('data-adapter', 'store', 'store:main'); + } + }); + +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +/** + Date.parse with progressive enhancement for ISO 8601 + + © 2011 Colin Snover + + Released under MIT license. + + @class Date + @namespace Ember + @static +*/ +Ember.Date = Ember.Date || {}; + +var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; + +/** + @method parse + @param date +*/ +Ember.Date.parse = function (date) { + var timestamp, struct, minutesOffset = 0; + + // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string + // before falling back to any implementation-specific date parsing, so that’s what we do, even if native + // implementations could be faster + // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm + if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) { + // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC + for (var i = 0, k; (k = numericKeys[i]); ++i) { + struct[k] = +struct[k] || 0; + } + + // allow undefined days and months + struct[2] = (+struct[2] || 1) - 1; + struct[3] = +struct[3] || 1; + + if (struct[8] !== 'Z' && struct[9] !== undefined) { + minutesOffset = struct[10] * 60 + struct[11]; + + if (struct[9] === '+') { + minutesOffset = 0 - minutesOffset; + } + } + + timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); + } + else { + timestamp = origParse ? origParse(date) : NaN; + } + + return timestamp; +}; + +if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Date) { + Date.parse = Ember.Date.parse; +} + +})(); + + + +(function() { + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set; + +/** + A record array is an array that contains records of a certain type. The record + array materializes records as needed when they are retrieved for the first + time. You should not create record arrays yourself. Instead, an instance of + `DS.RecordArray` or its subclasses will be returned by your application's store + in response to queries. + + @class RecordArray + @namespace DS + @extends Ember.ArrayProxy + @uses Ember.Evented +*/ + +DS.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, { + /** + The model type contained by this record array. + + @property type + @type DS.Model + */ + type: null, + + /** + The array of client ids backing the record array. When a + record is requested from the record array, the record + for the client id at the same index is materialized, if + necessary, by the store. + + @property content + @private + @type Ember.Array + */ + content: null, + + /** + The flag to signal a `RecordArray` is currently loading data. + + Example + + ```javascript + var people = store.all(App.Person); + people.get('isLoaded'); // true + ``` + + @property isLoaded + @type Boolean + */ + isLoaded: false, + /** + The flag to signal a `RecordArray` is currently loading data. + + Example + + ```javascript + var people = store.all(App.Person); + people.get('isUpdating'); // false + people.update(); + people.get('isUpdating'); // true + ``` + + @property isUpdating + @type Boolean + */ + isUpdating: false, + + /** + The store that created this record array. + + @property store + @private + @type DS.Store + */ + store: null, + + /** + Retrieves an object from the content by index. + + @method objectAtContent + @private + @param {Number} index + @return {DS.Model} record + */ + objectAtContent: function(index) { + var content = get(this, 'content'); + + return content.objectAt(index); + }, + + /** + Used to get the latest version of all of the records in this array + from the adapter. + + Example + + ```javascript + var people = store.all(App.Person); + people.get('isUpdating'); // false + people.update(); + people.get('isUpdating'); // true + ``` + + @method update + */ + update: function() { + if (get(this, 'isUpdating')) { return; } + + var store = get(this, 'store'), + type = get(this, 'type'); + + return store.fetchAll(type, this); + }, + + /** + Adds a record to the `RecordArray`. + + @method addRecord + @private + @param {DS.Model} record + */ + addRecord: function(record) { + get(this, 'content').addObject(record); + }, + + /** + Removes a record to the `RecordArray`. + + @method removeRecord + @private + @param {DS.Model} record + */ + removeRecord: function(record) { + get(this, 'content').removeObject(record); + }, + + /** + Saves all of the records in the `RecordArray`. + + Example + + ```javascript + var messages = store.all(App.Message); + messages.forEach(function(message) { + message.set('hasBeenSeen', true); + }); + messages.save(); + ``` + + @method save + @return {DS.PromiseArray} promise + */ + save: function() { + var promiseLabel = "DS: RecordArray#save " + get(this, 'type'); + var promise = Ember.RSVP.all(this.invoke("save"), promiseLabel).then(function(array) { + return Ember.A(array); + }, null, "DS: RecordArray#save apply Ember.NativeArray"); + + return DS.PromiseArray.create({ promise: promise }); + } +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get; + +/** + Represents a list of records whose membership is determined by the + store. As records are created, loaded, or modified, the store + evaluates them to determine if they should be part of the record + array. + + @class FilteredRecordArray + @namespace DS + @extends DS.RecordArray +*/ +DS.FilteredRecordArray = DS.RecordArray.extend({ + /** + The filterFunction is a function used to test records from the store to + determine if they should be part of the record array. + + Example + + ```javascript + var allPeople = store.all('person'); + allPeople.mapBy('name'); // ["Tom Dale", "Yehuda Katz", "Trek Glowacki"] + + var people = store.filter('person', function(person) { + if (person.get('name').match(/Katz$/)) { return true; } + }); + people.mapBy('name'); // ["Yehuda Katz"] + + var notKatzFilter = function(person) { + return !person.get('name').match(/Katz$/); + }; + people.set('filterFunction', notKatzFilter); + people.mapBy('name'); // ["Tom Dale", "Trek Glowacki"] + ``` + + @method filterFunction + @param {DS.Model} record + @return {Boolean} `true` if the record should be in the array + */ + filterFunction: null, + isLoaded: true, + + replace: function() { + var type = get(this, 'type').toString(); + throw new Error("The result of a client-side filter (on " + type + ") is immutable."); + }, + + /** + @method updateFilter + @private + */ + updateFilter: Ember.observer(function() { + var manager = get(this, 'manager'); + manager.updateFilter(this, get(this, 'type'), get(this, 'filterFunction')); + }, 'filterFunction') +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set; + +/** + Represents an ordered list of records whose order and membership is + determined by the adapter. For example, a query sent to the adapter + may trigger a search on the server, whose results would be loaded + into an instance of the `AdapterPopulatedRecordArray`. + + @class AdapterPopulatedRecordArray + @namespace DS + @extends DS.RecordArray +*/ +DS.AdapterPopulatedRecordArray = DS.RecordArray.extend({ + query: null, + + replace: function() { + var type = get(this, 'type').toString(); + throw new Error("The result of a server query (on " + type + ") is immutable."); + }, + + /** + @method load + @private + @param {Array} data + */ + load: function(data) { + var store = get(this, 'store'), + type = get(this, 'type'), + records = store.pushMany(type, data), + meta = store.metadataFor(type); + + this.setProperties({ + content: Ember.A(records), + isLoaded: true, + meta: meta + }); + + // TODO: does triggering didLoad event should be the last action of the runLoop? + Ember.run.once(this, 'trigger', 'didLoad'); + } +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set; +var map = Ember.EnumerableUtils.map; + +/** + A `ManyArray` is a `RecordArray` that represents the contents of a has-many + relationship. + + The `ManyArray` is instantiated lazily the first time the relationship is + requested. + + ### Inverses + + Often, the relationships in Ember Data applications will have + an inverse. For example, imagine the following models are + defined: + + ```javascript + App.Post = DS.Model.extend({ + comments: DS.hasMany('comment') + }); + + App.Comment = DS.Model.extend({ + post: DS.belongsTo('post') + }); + ``` + + If you created a new instance of `App.Post` and added + a `App.Comment` record to its `comments` has-many + relationship, you would expect the comment's `post` + property to be set to the post that contained + the has-many. + + We call the record to which a relationship belongs the + relationship's _owner_. + + @class ManyArray + @namespace DS + @extends DS.RecordArray +*/ +DS.ManyArray = DS.RecordArray.extend({ + init: function() { + this._super.apply(this, arguments); + this._changesToSync = Ember.OrderedSet.create(); + }, + + /** + The property name of the relationship + + @property {String} name + @private + */ + name: null, + + /** + The record to which this relationship belongs. + + @property {DS.Model} owner + @private + */ + owner: null, + + /** + `true` if the relationship is polymorphic, `false` otherwise. + + @property {Boolean} isPolymorphic + @private + */ + isPolymorphic: false, + + // LOADING STATE + + isLoaded: false, + + /** + Used for async `hasMany` arrays + to keep track of when they will resolve. + + @property {Ember.RSVP.Promise} promise + @private + */ + promise: null, + + /** + @method loadingRecordsCount + @param {Number} count + @private + */ + loadingRecordsCount: function(count) { + this.loadingRecordsCount = count; + }, + + /** + @method loadedRecord + @private + */ + loadedRecord: function() { + this.loadingRecordsCount--; + if (this.loadingRecordsCount === 0) { + set(this, 'isLoaded', true); + this.trigger('didLoad'); + } + }, + + /** + @method fetch + @private + */ + fetch: function() { + var records = get(this, 'content'), + store = get(this, 'store'), + owner = get(this, 'owner'), + resolver = Ember.RSVP.defer("DS: ManyArray#fetch " + get(this, 'type')); + + var unloadedRecords = records.filterProperty('isEmpty', true); + store.fetchMany(unloadedRecords, owner, resolver); + }, + + // Overrides Ember.Array's replace method to implement + replaceContent: function(index, removed, added) { + // Map the array of record objects into an array of client ids. + added = map(added, function(record) { + Ember.assert("You cannot add '" + record.constructor.typeKey + "' records to this relationship (only '" + this.type.typeKey + "' allowed)", !this.type || record instanceof this.type); + return record; + }, this); + + this._super(index, removed, added); + }, + + arrangedContentDidChange: function() { + Ember.run.once(this, 'fetch'); + }, + + arrayContentWillChange: function(index, removed, added) { + var owner = get(this, 'owner'), + name = get(this, 'name'); + + if (!owner._suspendedRelationships) { + // This code is the first half of code that continues inside + // of arrayContentDidChange. It gets or creates a change from + // the child object, adds the current owner as the old + // parent if this is the first time the object was removed + // from a ManyArray, and sets `newParent` to null. + // + // Later, if the object is added to another ManyArray, + // the `arrayContentDidChange` will set `newParent` on + // the change. + for (var i=index; i "root.created.uncommitted" + ``` + + The hierarchy of valid states that ship with ember data looks like + this: + + ```text + * root + * deleted + * saved + * uncommitted + * inFlight + * empty + * loaded + * created + * uncommitted + * inFlight + * saved + * updated + * uncommitted + * inFlight + * loading + ``` + + The `DS.Model` states are themselves stateless. What we mean is + that, the hierarchical states that each of *those* points to is a + shared data structure. For performance reasons, instead of each + record getting its own copy of the hierarchy of states, each record + points to this global, immutable shared instance. How does a state + know which record it should be acting on? We pass the record + instance into the state's event handlers as the first argument. + + The record passed as the first parameter is where you should stash + state about the record if needed; you should never store data on the state + object itself. + + ### Events and Flags + + A state may implement zero or more events and flags. + + #### Events + + Events are named functions that are invoked when sent to a record. The + record will first look for a method with the given name on the + current state. If no method is found, it will search the current + state's parent, and then its grandparent, and so on until reaching + the top of the hierarchy. If the root is reached without an event + handler being found, an exception will be raised. This can be very + helpful when debugging new features. + + Here's an example implementation of a state with a `myEvent` event handler: + + ```javascript + aState: DS.State.create({ + myEvent: function(manager, param) { + console.log("Received myEvent with", param); + } + }) + ``` + + To trigger this event: + + ```javascript + record.send('myEvent', 'foo'); + //=> "Received myEvent with foo" + ``` + + Note that an optional parameter can be sent to a record's `send()` method, + which will be passed as the second parameter to the event handler. + + Events should transition to a different state if appropriate. This can be + done by calling the record's `transitionTo()` method with a path to the + desired state. The state manager will attempt to resolve the state path + relative to the current state. If no state is found at that path, it will + attempt to resolve it relative to the current state's parent, and then its + parent, and so on until the root is reached. For example, imagine a hierarchy + like this: + + * created + * uncommitted <-- currentState + * inFlight + * updated + * inFlight + + If we are currently in the `uncommitted` state, calling + `transitionTo('inFlight')` would transition to the `created.inFlight` state, + while calling `transitionTo('updated.inFlight')` would transition to + the `updated.inFlight` state. + + Remember that *only events* should ever cause a state transition. You should + never call `transitionTo()` from outside a state's event handler. If you are + tempted to do so, create a new event and send that to the state manager. + + #### Flags + + Flags are Boolean values that can be used to introspect a record's current + state in a more user-friendly way than examining its state path. For example, + instead of doing this: + + ```javascript + var statePath = record.get('stateManager.currentPath'); + if (statePath === 'created.inFlight') { + doSomething(); + } + ``` + + You can say: + + ```javascript + if (record.get('isNew') && record.get('isSaving')) { + doSomething(); + } + ``` + + If your state does not set a value for a given flag, the value will + be inherited from its parent (or the first place in the state hierarchy + where it is defined). + + The current set of flags are defined below. If you want to add a new flag, + in addition to the area below, you will also need to declare it in the + `DS.Model` class. + + + * [isEmpty](DS.Model.html#property_isEmpty) + * [isLoading](DS.Model.html#property_isLoading) + * [isLoaded](DS.Model.html#property_isLoaded) + * [isDirty](DS.Model.html#property_isDirty) + * [isSaving](DS.Model.html#property_isSaving) + * [isDeleted](DS.Model.html#property_isDeleted) + * [isNew](DS.Model.html#property_isNew) + * [isValid](DS.Model.html#property_isValid) + + @namespace DS + @class RootState +*/ + +var hasDefinedProperties = function(object) { + // Ignore internal property defined by simulated `Ember.create`. + var names = Ember.keys(object); + var i, l, name; + for (i = 0, l = names.length; i < l; i++ ) { + name = names[i]; + if (object.hasOwnProperty(name) && object[name]) { return true; } + } + + return false; +}; + +var didSetProperty = function(record, context) { + if (context.value === context.originalValue) { + delete record._attributes[context.name]; + record.send('propertyWasReset', context.name); + } else if (context.value !== context.oldValue) { + record.send('becomeDirty'); + } + + record.updateRecordArraysLater(); +}; + +// Implementation notes: +// +// Each state has a boolean value for all of the following flags: +// +// * isLoaded: The record has a populated `data` property. When a +// record is loaded via `store.find`, `isLoaded` is false +// until the adapter sets it. When a record is created locally, +// its `isLoaded` property is always true. +// * isDirty: The record has local changes that have not yet been +// saved by the adapter. This includes records that have been +// created (but not yet saved) or deleted. +// * isSaving: The record has been committed, but +// the adapter has not yet acknowledged that the changes have +// been persisted to the backend. +// * isDeleted: The record was marked for deletion. When `isDeleted` +// is true and `isDirty` is true, the record is deleted locally +// but the deletion was not yet persisted. When `isSaving` is +// true, the change is in-flight. When both `isDirty` and +// `isSaving` are false, the change has persisted. +// * isError: The adapter reported that it was unable to save +// local changes to the backend. This may also result in the +// record having its `isValid` property become false if the +// adapter reported that server-side validations failed. +// * isNew: The record was created on the client and the adapter +// did not yet report that it was successfully saved. +// * isValid: No client-side validations have failed and the +// adapter did not report any server-side validation failures. + +// The dirty state is a abstract state whose functionality is +// shared between the `created` and `updated` states. +// +// The deleted state shares the `isDirty` flag with the +// subclasses of `DirtyState`, but with a very different +// implementation. +// +// Dirty states have three child states: +// +// `uncommitted`: the store has not yet handed off the record +// to be saved. +// `inFlight`: the store has handed off the record to be saved, +// but the adapter has not yet acknowledged success. +// `invalid`: the record has invalid information and cannot be +// send to the adapter yet. +var DirtyState = { + initialState: 'uncommitted', + + // FLAGS + isDirty: true, + + // SUBSTATES + + // When a record first becomes dirty, it is `uncommitted`. + // This means that there are local pending changes, but they + // have not yet begun to be saved, and are not invalid. + uncommitted: { + // EVENTS + didSetProperty: didSetProperty, + + propertyWasReset: function(record, name) { + var stillDirty = false; + + for (var prop in record._attributes) { + stillDirty = true; + break; + } + + if (!stillDirty) { record.send('rolledBack'); } + }, + + pushedData: Ember.K, + + becomeDirty: Ember.K, + + willCommit: function(record) { + record.transitionTo('inFlight'); + }, + + reloadRecord: function(record, resolve) { + resolve(get(record, 'store').reloadRecord(record)); + }, + + rolledBack: function(record) { + record.transitionTo('loaded.saved'); + }, + + becameInvalid: function(record) { + record.transitionTo('invalid'); + }, + + rollback: function(record) { + record.rollback(); + } + }, + + // Once a record has been handed off to the adapter to be + // saved, it is in the 'in flight' state. Changes to the + // record cannot be made during this window. + inFlight: { + // FLAGS + isSaving: true, + + // EVENTS + didSetProperty: didSetProperty, + becomeDirty: Ember.K, + pushedData: Ember.K, + + // TODO: More robust semantics around save-while-in-flight + willCommit: Ember.K, + + didCommit: function(record) { + var dirtyType = get(this, 'dirtyType'); + + record.transitionTo('saved'); + record.send('invokeLifecycleCallbacks', dirtyType); + }, + + becameInvalid: function(record) { + record.transitionTo('invalid'); + record.send('invokeLifecycleCallbacks'); + }, + + becameError: function(record) { + record.transitionTo('uncommitted'); + record.triggerLater('becameError', record); + } + }, + + // A record is in the `invalid` state when its client-side + // invalidations have failed, or if the adapter has indicated + // the the record failed server-side invalidations. + invalid: { + // FLAGS + isValid: false, + + // EVENTS + deleteRecord: function(record) { + record.transitionTo('deleted.uncommitted'); + record.clearRelationships(); + }, + + didSetProperty: function(record, context) { + get(record, 'errors').remove(context.name); + + didSetProperty(record, context); + }, + + becomeDirty: Ember.K, + + rolledBack: function(record) { + get(record, 'errors').clear(); + }, + + becameValid: function(record) { + record.transitionTo('uncommitted'); + }, + + invokeLifecycleCallbacks: function(record) { + record.triggerLater('becameInvalid', record); + } + } +}; + +// The created and updated states are created outside the state +// chart so we can reopen their substates and add mixins as +// necessary. + +function deepClone(object) { + var clone = {}, value; + + for (var prop in object) { + value = object[prop]; + if (value && typeof value === 'object') { + clone[prop] = deepClone(value); + } else { + clone[prop] = value; + } + } + + return clone; +} + +function mixin(original, hash) { + for (var prop in hash) { + original[prop] = hash[prop]; + } + + return original; +} + +function dirtyState(options) { + var newState = deepClone(DirtyState); + return mixin(newState, options); +} + +var createdState = dirtyState({ + dirtyType: 'created', + + // FLAGS + isNew: true +}); + +createdState.uncommitted.rolledBack = function(record) { + record.transitionTo('deleted.saved'); +}; + +var updatedState = dirtyState({ + dirtyType: 'updated' +}); + +createdState.uncommitted.deleteRecord = function(record) { + record.clearRelationships(); + record.transitionTo('deleted.saved'); +}; + +createdState.uncommitted.rollback = function(record) { + DirtyState.uncommitted.rollback.apply(this, arguments); + record.transitionTo('deleted.saved'); +}; + +createdState.uncommitted.propertyWasReset = Ember.K; + +updatedState.uncommitted.deleteRecord = function(record) { + record.transitionTo('deleted.uncommitted'); + record.clearRelationships(); +}; + +var RootState = { + // FLAGS + isEmpty: false, + isLoading: false, + isLoaded: false, + isDirty: false, + isSaving: false, + isDeleted: false, + isNew: false, + isValid: true, + + // DEFAULT EVENTS + + // Trying to roll back if you're not in the dirty state + // doesn't change your state. For example, if you're in the + // in-flight state, rolling back the record doesn't move + // you out of the in-flight state. + rolledBack: Ember.K, + + propertyWasReset: Ember.K, + + // SUBSTATES + + // A record begins its lifecycle in the `empty` state. + // If its data will come from the adapter, it will + // transition into the `loading` state. Otherwise, if + // the record is being created on the client, it will + // transition into the `created` state. + empty: { + isEmpty: true, + + // EVENTS + loadingData: function(record, promise) { + record._loadingPromise = promise; + record.transitionTo('loading'); + }, + + loadedData: function(record) { + record.transitionTo('loaded.created.uncommitted'); + + record.suspendRelationshipObservers(function() { + record.notifyPropertyChange('data'); + }); + }, + + pushedData: function(record) { + record.transitionTo('loaded.saved'); + record.triggerLater('didLoad'); + } + }, + + // A record enters this state when the store asks + // the adapter for its data. It remains in this state + // until the adapter provides the requested data. + // + // Usually, this process is asynchronous, using an + // XHR to retrieve the data. + loading: { + // FLAGS + isLoading: true, + + exit: function(record) { + record._loadingPromise = null; + }, + + // EVENTS + pushedData: function(record) { + record.transitionTo('loaded.saved'); + record.triggerLater('didLoad'); + set(record, 'isError', false); + }, + + becameError: function(record) { + record.triggerLater('becameError', record); + }, + + notFound: function(record) { + record.transitionTo('empty'); + } + }, + + // A record enters this state when its data is populated. + // Most of a record's lifecycle is spent inside substates + // of the `loaded` state. + loaded: { + initialState: 'saved', + + // FLAGS + isLoaded: true, + + // SUBSTATES + + // If there are no local changes to a record, it remains + // in the `saved` state. + saved: { + setup: function(record) { + var attrs = record._attributes, + isDirty = false; + + for (var prop in attrs) { + if (attrs.hasOwnProperty(prop)) { + isDirty = true; + break; + } + } + + if (isDirty) { + record.adapterDidDirty(); + } + }, + + // EVENTS + didSetProperty: didSetProperty, + + pushedData: Ember.K, + + becomeDirty: function(record) { + record.transitionTo('updated.uncommitted'); + }, + + willCommit: function(record) { + record.transitionTo('updated.inFlight'); + }, + + reloadRecord: function(record, resolve) { + resolve(get(record, 'store').reloadRecord(record)); + }, + + deleteRecord: function(record) { + record.transitionTo('deleted.uncommitted'); + record.clearRelationships(); + }, + + unloadRecord: function(record) { + // clear relationships before moving to deleted state + // otherwise it fails + record.clearRelationships(); + record.transitionTo('deleted.saved'); + }, + + didCommit: function(record) { + record.send('invokeLifecycleCallbacks', get(record, 'lastDirtyType')); + }, + + // loaded.saved.notFound would be triggered by a failed + // `reload()` on an unchanged record + notFound: Ember.K + + }, + + // A record is in this state after it has been locally + // created but before the adapter has indicated that + // it has been saved. + created: createdState, + + // A record is in this state if it has already been + // saved to the server, but there are new local changes + // that have not yet been saved. + updated: updatedState + }, + + // A record is in this state if it was deleted from the store. + deleted: { + initialState: 'uncommitted', + dirtyType: 'deleted', + + // FLAGS + isDeleted: true, + isLoaded: true, + isDirty: true, + + // TRANSITIONS + setup: function(record) { + record.updateRecordArrays(); + }, + + // SUBSTATES + + // When a record is deleted, it enters the `start` + // state. It will exit this state when the record + // starts to commit. + uncommitted: { + + // EVENTS + + willCommit: function(record) { + record.transitionTo('inFlight'); + }, + + rollback: function(record) { + record.rollback(); + }, + + becomeDirty: Ember.K, + deleteRecord: Ember.K, + + rolledBack: function(record) { + record.transitionTo('loaded.saved'); + } + }, + + // After a record starts committing, but + // before the adapter indicates that the deletion + // has saved to the server, a record is in the + // `inFlight` substate of `deleted`. + inFlight: { + // FLAGS + isSaving: true, + + // EVENTS + + // TODO: More robust semantics around save-while-in-flight + willCommit: Ember.K, + didCommit: function(record) { + record.transitionTo('saved'); + + record.send('invokeLifecycleCallbacks'); + }, + + becameError: function(record) { + record.transitionTo('uncommitted'); + record.triggerLater('becameError', record); + } + }, + + // Once the adapter indicates that the deletion has + // been saved, the record enters the `saved` substate + // of `deleted`. + saved: { + // FLAGS + isDirty: false, + + setup: function(record) { + var store = get(record, 'store'); + store.dematerializeRecord(record); + }, + + invokeLifecycleCallbacks: function(record) { + record.triggerLater('didDelete', record); + record.triggerLater('didCommit', record); + } + } + }, + + invokeLifecycleCallbacks: function(record, dirtyType) { + if (dirtyType === 'created') { + record.triggerLater('didCreate', record); + } else { + record.triggerLater('didUpdate', record); + } + + record.triggerLater('didCommit', record); + } +}; + +function wireState(object, parent, name) { + /*jshint proto:true*/ + // TODO: Use Object.create and copy instead + object = mixin(parent ? Ember.create(parent) : {}, object); + object.parentState = parent; + object.stateName = name; + + for (var prop in object) { + if (!object.hasOwnProperty(prop) || prop === 'parentState' || prop === 'stateName') { continue; } + if (typeof object[prop] === 'object') { + object[prop] = wireState(object[prop], object, name + "." + prop); + } + } + + return object; +} + +RootState = wireState(RootState, null, "root"); + +DS.RootState = RootState; + +})(); + + + +(function() { +var get = Ember.get, isEmpty = Ember.isEmpty; + +/** +@module ember-data +*/ + +/** + Holds validation errors for a given record organized by attribute names. + + @class Errors + @namespace DS + @extends Ember.Object + @uses Ember.Enumerable + @uses Ember.Evented + */ +DS.Errors = Ember.Object.extend(Ember.Enumerable, Ember.Evented, { + /** + Register with target handler + + @method registerHandlers + @param {Object} target + @param {Function} becameInvalid + @param {Function} becameValid + */ + registerHandlers: function(target, becameInvalid, becameValid) { + this.on('becameInvalid', target, becameInvalid); + this.on('becameValid', target, becameValid); + }, + + /** + @property errorsByAttributeName + @type {Ember.MapWithDefault} + @private + */ + errorsByAttributeName: Ember.reduceComputed("content", { + initialValue: function() { + return Ember.MapWithDefault.create({ + defaultValue: function() { + return Ember.A(); + } + }); + }, + + addedItem: function(errors, error) { + errors.get(error.attribute).pushObject(error); + + return errors; + }, + + removedItem: function(errors, error) { + errors.get(error.attribute).removeObject(error); + + return errors; + } + }), + + /** + Returns errors for a given attribute + + @method errorsFor + @param {String} attribute + @returns {Array} + */ + errorsFor: function(attribute) { + return get(this, 'errorsByAttributeName').get(attribute); + }, + + /** + */ + messages: Ember.computed.mapBy('content', 'message'), + + /** + @property content + @type {Array} + @private + */ + content: Ember.computed(function() { + return Ember.A(); + }), + + /** + @method unknownProperty + @private + */ + unknownProperty: function(attribute) { + var errors = this.errorsFor(attribute); + if (isEmpty(errors)) { return null; } + return errors; + }, + + /** + @method nextObject + @private + */ + nextObject: function(index, previousObject, context) { + return get(this, 'content').objectAt(index); + }, + + /** + Total number of errors. + + @property length + @type {Number} + @readOnly + */ + length: Ember.computed.oneWay('content.length').readOnly(), + + /** + @property isEmpty + @type {Boolean} + @readOnly + */ + isEmpty: Ember.computed.not('length').readOnly(), + + /** + Adds error messages to a given attribute and sends + `becameInvalid` event to the record. + + @method add + @param {String} attribute + @param {Array|String} messages + */ + add: function(attribute, messages) { + var wasEmpty = get(this, 'isEmpty'); + + messages = this._findOrCreateMessages(attribute, messages); + get(this, 'content').addObjects(messages); + + this.notifyPropertyChange(attribute); + this.enumerableContentDidChange(); + + if (wasEmpty && !get(this, 'isEmpty')) { + this.trigger('becameInvalid'); + } + }, + + /** + @method _findOrCreateMessages + @private + */ + _findOrCreateMessages: function(attribute, messages) { + var errors = this.errorsFor(attribute); + + return Ember.makeArray(messages).map(function(message) { + return errors.findBy('message', message) || { + attribute: attribute, + message: message + }; + }); + }, + + /** + Removes all error messages from the given attribute and sends + `becameValid` event to the record if there no more errors left. + + @method remove + @param {String} attribute + */ + remove: function(attribute) { + if (get(this, 'isEmpty')) { return; } + + var content = get(this, 'content').rejectBy('attribute', attribute); + get(this, 'content').setObjects(content); + + this.notifyPropertyChange(attribute); + this.enumerableContentDidChange(); + + if (get(this, 'isEmpty')) { + this.trigger('becameValid'); + } + }, + + /** + Removes all error messages and sends `becameValid` event + to the record. + + @method clear + */ + clear: function() { + if (get(this, 'isEmpty')) { return; } + + get(this, 'content').clear(); + this.enumerableContentDidChange(); + + this.trigger('becameValid'); + }, + + /** + Checks if there is error messages for the given attribute. + + @method has + @param {String} attribute + @returns {Boolean} true if there some errors on given attribute + */ + has: function(attribute) { + return !isEmpty(this.errorsFor(attribute)); + } +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set, + merge = Ember.merge; + +var retrieveFromCurrentState = Ember.computed('currentState', function(key, value) { + return get(get(this, 'currentState'), key); +}).readOnly(); + +/** + + The model class that all Ember Data records descend from. + + @class Model + @namespace DS + @extends Ember.Object + @uses Ember.Evented +*/ +DS.Model = Ember.Object.extend(Ember.Evented, { + /** + If this property is `true` the record is in the `empty` + state. Empty is the first state all records enter after they have + been created. Most records created by the store will quickly + transition to the `loading` state if data needs to be fetched from + the server or the `created` state if the record is created on the + client. A record can also enter the empty state if the adapter is + unable to locate the record. + + @property isEmpty + @type {Boolean} + @readOnly + */ + isEmpty: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `loading` state. A + record enters this state when the store asks the adapter for its + data. It remains in this state until the adapter provides the + requested data. + + @property isLoading + @type {Boolean} + @readOnly + */ + isLoading: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `loaded` state. A + record enters this state when its data is populated. Most of a + record's lifecycle is spent inside substates of the `loaded` + state. + + Example + + ```javascript + var record = store.createRecord(App.Model); + record.get('isLoaded'); // true + + store.find('model', 1).then(function(model) { + model.get('isLoaded'); // true + }); + ``` + + @property isLoaded + @type {Boolean} + @readOnly + */ + isLoaded: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `dirty` state. The + record has local changes that have not yet been saved by the + adapter. This includes records that have been created (but not yet + saved) or deleted. + + Example + + ```javascript + var record = store.createRecord(App.Model); + record.get('isDirty'); // true + + store.find('model', 1).then(function(model) { + model.get('isDirty'); // false + model.set('foo', 'some value'); + model.set('isDirty'); // true + }); + ``` + + @property isDirty + @type {Boolean} + @readOnly + */ + isDirty: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `saving` state. A + record enters the saving state when `save` is called, but the + adapter has not yet acknowledged that the changes have been + persisted to the backend. + + Example + + ```javascript + var record = store.createRecord(App.Model); + record.get('isSaving'); // false + var promise = record.save(); + record.get('isSaving'); // true + promise.then(function() { + record.get('isSaving'); // false + }); + ``` + + @property isSaving + @type {Boolean} + @readOnly + */ + isSaving: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `deleted` state + and has been marked for deletion. When `isDeleted` is true and + `isDirty` is true, the record is deleted locally but the deletion + was not yet persisted. When `isSaving` is true, the change is + in-flight. When both `isDirty` and `isSaving` are false, the + change has persisted. + + Example + + ```javascript + var record = store.createRecord(App.Model); + record.get('isDeleted'); // false + record.deleteRecord(); + record.get('isDeleted'); // true + ``` + + @property isDeleted + @type {Boolean} + @readOnly + */ + isDeleted: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `new` state. A + record will be in the `new` state when it has been created on the + client and the adapter has not yet report that it was successfully + saved. + + Example + + ```javascript + var record = store.createRecord(App.Model); + record.get('isNew'); // true + + record.save().then(function(model) { + model.get('isNew'); // false + }); + ``` + + @property isNew + @type {Boolean} + @readOnly + */ + isNew: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `valid` state. A + record will be in the `valid` state when no client-side + validations have failed and the adapter did not report any + server-side validation failures. + + @property isValid + @type {Boolean} + @readOnly + */ + isValid: retrieveFromCurrentState, + /** + If the record is in the dirty state this property will report what + kind of change has caused it to move into the dirty + state. Possible values are: + + - `created` The record has been created by the client and not yet saved to the adapter. + - `updated` The record has been updated by the client and not yet saved to the adapter. + - `deleted` The record has been deleted by the client and not yet saved to the adapter. + + Example + + ```javascript + var record = store.createRecord(App.Model); + record.get('dirtyType'); // 'created' + ``` + + @property dirtyType + @type {String} + @readOnly + */ + dirtyType: retrieveFromCurrentState, + + /** + If `true` the adapter reported that it was unable to save local + changes to the backend. This may also result in the record having + its `isValid` property become false if the adapter reported that + server-side validations failed. + + Example + + ```javascript + record.get('isError'); // false + record.set('foo', 'invalid value'); + record.save().then(null, function() { + record.get('isError'); // true + }); + ``` + + @property isError + @type {Boolean} + @readOnly + */ + isError: false, + /** + If `true` the store is attempting to reload the record form the adapter. + + Example + + ```javascript + record.get('isReloading'); // false + record.reload(); + record.get('isReloading'); // true + ``` + + @property isReloading + @type {Boolean} + @readOnly + */ + isReloading: false, + + /** + The `clientId` property is a transient numerical identifier + generated at runtime by the data store. It is important + primarily because newly created objects may not yet have an + externally generated id. + + @property clientId + @private + @type {Number|String} + */ + clientId: null, + /** + All ember models have an id property. This is an identifier + managed by an external source. These are always coerced to be + strings before being used internally. Note when declaring the + attributes for a model it is an error to declare an id + attribute. + + ```javascript + var record = store.createRecord(App.Model); + record.get('id'); // null + + store.find('model', 1).then(function(model) { + model.get('id'); // '1' + }); + ``` + + @property id + @type {String} + */ + id: null, + transaction: null, + /** + @property currentState + @private + @type {Object} + */ + currentState: null, + /** + When the record is in the `invalid` state this object will contain + any errors returned by the adapter. When present the errors hash + typically contains keys corresponding to the invalid property names + and values which are an array of error messages. + + ```javascript + record.get('errors.length'); // 0 + record.set('foo', 'invalid value'); + record.save().then(null, function() { + record.get('errors').get('foo'); // ['foo should be a number.'] + }); + ``` + + @property errors + @type {Object} + */ + errors: null, + + /** + Create a JSON representation of the record, using the serialization + strategy of the store's adapter. + + `serialize` takes an optional hash as a parameter, currently + supported options are: + + - `includeId`: `true` if the record's ID should be included in the + JSON representation. + + @method serialize + @param {Object} options + @returns {Object} an object whose values are primitive JSON values only + */ + serialize: function(options) { + var store = get(this, 'store'); + return store.serialize(this, options); + }, + + /** + Use [DS.JSONSerializer](DS.JSONSerializer.html) to + get the JSON representation of a record. + + `toJSON` takes an optional hash as a parameter, currently + supported options are: + + - `includeId`: `true` if the record's ID should be included in the + JSON representation. + + @method toJSON + @param {Object} options + @returns {Object} A JSON representation of the object. + */ + toJSON: function(options) { + // container is for lazy transform lookups + var serializer = DS.JSONSerializer.create({ container: this.container }); + return serializer.serialize(this, options); + }, + + /** + Fired when the record is loaded from the server. + + @event didLoad + */ + didLoad: Ember.K, + + /** + Fired when the record is updated. + + @event didUpdate + */ + didUpdate: Ember.K, + + /** + Fired when the record is created. + + @event didCreate + */ + didCreate: Ember.K, + + /** + Fired when the record is deleted. + + @event didDelete + */ + didDelete: Ember.K, + + /** + Fired when the record becomes invalid. + + @event becameInvalid + */ + becameInvalid: Ember.K, + + /** + Fired when the record enters the error state. + + @event becameError + */ + becameError: Ember.K, + + /** + @property data + @private + @type {Object} + */ + data: Ember.computed(function() { + this._data = this._data || {}; + return this._data; + }).property(), + + _data: null, + + init: function() { + set(this, 'currentState', DS.RootState.empty); + var errors = DS.Errors.create(); + errors.registerHandlers(this, function() { + this.send('becameInvalid'); + }, function() { + this.send('becameValid'); + }); + set(this, 'errors', errors); + this._super(); + this._setup(); + }, + + _setup: function() { + this._changesToSync = {}; + this._deferredTriggers = []; + this._data = {}; + this._attributes = {}; + this._inFlightAttributes = {}; + this._relationships = {}; + }, + + /** + @method send + @private + @param {String} name + @param {Object} context + */ + send: function(name, context) { + var currentState = get(this, 'currentState'); + + if (!currentState[name]) { + this._unhandledEvent(currentState, name, context); + } + + return currentState[name](this, context); + }, + + /** + @method transitionTo + @private + @param {String} name + */ + transitionTo: function(name) { + // POSSIBLE TODO: Remove this code and replace with + // always having direct references to state objects + + var pivotName = name.split(".", 1), + currentState = get(this, 'currentState'), + state = currentState; + + do { + if (state.exit) { state.exit(this); } + state = state.parentState; + } while (!state.hasOwnProperty(pivotName)); + + var path = name.split("."); + + var setups = [], enters = [], i, l; + + for (i=0, l=path.length; i')` from " + this.toString(), name !== 'id'); + + meta.name = name; + map.set(name, meta); + } + }); + + return map; + }), + + /** + A map whose keys are the attributes of the model (properties + described by DS.attr) and whose values are type of transformation + applied to each attribute. This map does not include any + attributes that do not have an transformation type. + + Example + + ```javascript + App.Person = DS.Model.extend({ + firstName: attr(), + lastName: attr('string'), + birthday: attr('date') + }); + + var transformedAttributes = Ember.get(App.Person, 'transformedAttributes') + + transformedAttributes.forEach(function(field, type) { + console.log(field, type); + }); + + // prints: + // lastName string + // birthday date + ``` + + @property transformedAttributes + @static + @type {Ember.Map} + @readOnly + */ + transformedAttributes: Ember.computed(function() { + var map = Ember.Map.create(); + + this.eachAttribute(function(key, meta) { + if (meta.type) { + map.set(key, meta.type); + } + }); + + return map; + }), + + /** + Iterates through the attributes of the model, calling the passed function on each + attribute. + + The callback method you provide should have the following signature (all + parameters are optional): + + ```javascript + function(name, meta); + ``` + + - `name` the name of the current property in the iteration + - `meta` the meta object for the attribute property in the iteration + + Note that in addition to a callback, you can also pass an optional target + object that will be set as `this` on the context. + + Example + + ```javascript + App.Person = DS.Model.extend({ + firstName: attr('string'), + lastName: attr('string'), + birthday: attr('date') + }); + + App.Person.eachAttribute(function(name, meta) { + console.log(name, meta); + }); + + // prints: + // firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"} + // lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"} + // birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"} + ``` + + @method eachAttribute + @param {Function} callback The callback to execute + @param {Object} [target] The target object to use + @static + */ + eachAttribute: function(callback, binding) { + get(this, 'attributes').forEach(function(name, meta) { + callback.call(binding, name, meta); + }, binding); + }, + + /** + Iterates through the transformedAttributes of the model, calling + the passed function on each attribute. Note the callback will not be + called for any attributes that do not have an transformation type. + + The callback method you provide should have the following signature (all + parameters are optional): + + ```javascript + function(name, type); + ``` + + - `name` the name of the current property in the iteration + - `type` a string containing the name of the type of transformed + applied to the attribute + + Note that in addition to a callback, you can also pass an optional target + object that will be set as `this` on the context. + + Example + + ```javascript + App.Person = DS.Model.extend({ + firstName: attr(), + lastName: attr('string'), + birthday: attr('date') + }); + + App.Person.eachTransformedAttribute(function(name, type) { + console.log(name, type); + }); + + // prints: + // lastName string + // birthday date + ``` + + @method eachTransformedAttribute + @param {Function} callback The callback to execute + @param {Object} [target] The target object to use + @static + */ + eachTransformedAttribute: function(callback, binding) { + get(this, 'transformedAttributes').forEach(function(name, type) { + callback.call(binding, name, type); + }); + } +}); + + +DS.Model.reopen({ + eachAttribute: function(callback, binding) { + this.constructor.eachAttribute(callback, binding); + } +}); + +function getDefaultValue(record, options, key) { + if (typeof options.defaultValue === "function") { + return options.defaultValue(); + } else { + return options.defaultValue; + } +} + +function hasValue(record, key) { + return record._attributes.hasOwnProperty(key) || + record._inFlightAttributes.hasOwnProperty(key) || + record._data.hasOwnProperty(key); +} + +function getValue(record, key) { + if (record._attributes.hasOwnProperty(key)) { + return record._attributes[key]; + } else if (record._inFlightAttributes.hasOwnProperty(key)) { + return record._inFlightAttributes[key]; + } else { + return record._data[key]; + } +} + +/** + `DS.attr` defines an attribute on a [DS.Model](DS.Model.html). + By default, attributes are passed through as-is, however you can specify an + optional type to have the value automatically transformed. + Ember Data ships with four basic transform types: `string`, `number`, + `boolean` and `date`. You can define your own transforms by subclassing + [DS.Transform](DS.Transform.html). + + `DS.attr` takes an optional hash as a second parameter, currently + supported options are: + + - `defaultValue`: Pass a string or a function to be called to set the attribute + to a default value if none is supplied. + + Example + + ```javascript + var attr = DS.attr; + + App.User = DS.Model.extend({ + username: attr('string'), + email: attr('string'), + verified: attr('boolean', {defaultValue: false}) + }); + ``` + + @namespace + @method attr + @for DS + @param {String} type the attribute type + @param {Object} options a hash of options + @return {Attribute} +*/ + +DS.attr = function(type, options) { + options = options || {}; + + var meta = { + type: type, + isAttribute: true, + options: options + }; + + return Ember.computed(function(key, value) { + 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('')` from " + this.constructor.toString(), key !== 'id'); + var oldValue = this._attributes[key] || this._inFlightAttributes[key] || this._data[key]; + + this.send('didSetProperty', { + name: key, + oldValue: oldValue, + originalValue: this._data[key], + value: value + }); + + this._attributes[key] = value; + return value; + } else if (hasValue(this, key)) { + return getValue(this, key); + } else { + return getDefaultValue(this, options, key); + } + + // `data` is never set directly. However, it may be + // invalidated from the state manager's setData + // event. + }).property('data').meta(meta); +}; + + +})(); + + + +(function() { +/** + @module ember-data +*/ + +})(); + + + +(function() { +/** + @module ember-data +*/ + +/** + An AttributeChange object is created whenever a record's + attribute changes value. It is used to track changes to a + record between transaction commits. + + @class AttributeChange + @namespace DS + @private + @constructor +*/ +var AttributeChange = DS.AttributeChange = function(options) { + this.record = options.record; + this.store = options.store; + this.name = options.name; + this.value = options.value; + this.oldValue = options.oldValue; +}; + +AttributeChange.createChange = function(options) { + return new AttributeChange(options); +}; + +AttributeChange.prototype = { + sync: function() { + if (this.value !== this.oldValue) { + this.record.send('becomeDirty'); + this.record.updateRecordArraysLater(); + } + + // TODO: Use this object in the commit process + this.destroy(); + }, + + /** + If the AttributeChange is destroyed (either by being rolled back + or being committed), remove it from the list of pending changes + on the record. + + @method destroy + */ + destroy: function() { + delete this.record._changesToSync[this.name]; + } +}; + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set; +var forEach = Ember.EnumerableUtils.forEach; + +/** + @class RelationshipChange + @namespace DS + @private + @constructor +*/ +DS.RelationshipChange = function(options) { + this.parentRecord = options.parentRecord; + this.childRecord = options.childRecord; + this.firstRecord = options.firstRecord; + this.firstRecordKind = options.firstRecordKind; + this.firstRecordName = options.firstRecordName; + this.secondRecord = options.secondRecord; + this.secondRecordKind = options.secondRecordKind; + this.secondRecordName = options.secondRecordName; + this.changeType = options.changeType; + this.store = options.store; + + this.committed = {}; +}; + +/** + @class RelationshipChangeAdd + @namespace DS + @private + @constructor +*/ +DS.RelationshipChangeAdd = function(options){ + DS.RelationshipChange.call(this, options); +}; + +/** + @class RelationshipChangeRemove + @namespace DS + @private + @constructor +*/ +DS.RelationshipChangeRemove = function(options){ + DS.RelationshipChange.call(this, options); +}; + +DS.RelationshipChange.create = function(options) { + return new DS.RelationshipChange(options); +}; + +DS.RelationshipChangeAdd.create = function(options) { + return new DS.RelationshipChangeAdd(options); +}; + +DS.RelationshipChangeRemove.create = function(options) { + return new DS.RelationshipChangeRemove(options); +}; + +DS.OneToManyChange = {}; +DS.OneToNoneChange = {}; +DS.ManyToNoneChange = {}; +DS.OneToOneChange = {}; +DS.ManyToManyChange = {}; + +DS.RelationshipChange._createChange = function(options){ + if(options.changeType === "add"){ + return DS.RelationshipChangeAdd.create(options); + } + if(options.changeType === "remove"){ + return DS.RelationshipChangeRemove.create(options); + } +}; + + +DS.RelationshipChange.determineRelationshipType = function(recordType, knownSide){ + var knownKey = knownSide.key, key, otherKind; + var knownKind = knownSide.kind; + + var inverse = recordType.inverseFor(knownKey); + + if (inverse){ + key = inverse.name; + otherKind = inverse.kind; + } + + if (!inverse){ + return knownKind === "belongsTo" ? "oneToNone" : "manyToNone"; + } + else{ + if(otherKind === "belongsTo"){ + return knownKind === "belongsTo" ? "oneToOne" : "manyToOne"; + } + else{ + return knownKind === "belongsTo" ? "oneToMany" : "manyToMany"; + } + } + +}; + +DS.RelationshipChange.createChange = function(firstRecord, secondRecord, store, options){ + // Get the type of the child based on the child's client ID + var firstRecordType = firstRecord.constructor, changeType; + changeType = DS.RelationshipChange.determineRelationshipType(firstRecordType, options); + if (changeType === "oneToMany"){ + return DS.OneToManyChange.createChange(firstRecord, secondRecord, store, options); + } + else if (changeType === "manyToOne"){ + return DS.OneToManyChange.createChange(secondRecord, firstRecord, store, options); + } + else if (changeType === "oneToNone"){ + return DS.OneToNoneChange.createChange(firstRecord, secondRecord, store, options); + } + else if (changeType === "manyToNone"){ + return DS.ManyToNoneChange.createChange(firstRecord, secondRecord, store, options); + } + else if (changeType === "oneToOne"){ + return DS.OneToOneChange.createChange(firstRecord, secondRecord, store, options); + } + else if (changeType === "manyToMany"){ + return DS.ManyToManyChange.createChange(firstRecord, secondRecord, store, options); + } +}; + +DS.OneToNoneChange.createChange = function(childRecord, parentRecord, store, options) { + var key = options.key; + var change = DS.RelationshipChange._createChange({ + parentRecord: parentRecord, + childRecord: childRecord, + firstRecord: childRecord, + store: store, + changeType: options.changeType, + firstRecordName: key, + firstRecordKind: "belongsTo" + }); + + store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); + + return change; +}; + +DS.ManyToNoneChange.createChange = function(childRecord, parentRecord, store, options) { + var key = options.key; + var change = DS.RelationshipChange._createChange({ + parentRecord: childRecord, + childRecord: parentRecord, + secondRecord: childRecord, + store: store, + changeType: options.changeType, + secondRecordName: options.key, + secondRecordKind: "hasMany" + }); + + store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); + return change; +}; + + +DS.ManyToManyChange.createChange = function(childRecord, parentRecord, store, options) { + // 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 + // definition. + var key = options.key; + + var change = DS.RelationshipChange._createChange({ + parentRecord: parentRecord, + childRecord: childRecord, + firstRecord: childRecord, + secondRecord: parentRecord, + firstRecordKind: "hasMany", + secondRecordKind: "hasMany", + store: store, + changeType: options.changeType, + firstRecordName: key + }); + + store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); + + + return change; +}; + +DS.OneToOneChange.createChange = function(childRecord, parentRecord, store, options) { + var 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 + // definition. + if (options.parentType) { + key = options.parentType.inverseFor(options.key).name; + } else if (options.key) { + key = options.key; + } else { + Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent", false); + } + + var change = DS.RelationshipChange._createChange({ + parentRecord: parentRecord, + childRecord: childRecord, + firstRecord: childRecord, + secondRecord: parentRecord, + firstRecordKind: "belongsTo", + secondRecordKind: "belongsTo", + store: store, + changeType: options.changeType, + firstRecordName: key + }); + + store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); + + + return change; +}; + +DS.OneToOneChange.maintainInvariant = function(options, store, childRecord, key){ + if (options.changeType === "add" && store.recordIsMaterialized(childRecord)) { + var oldParent = get(childRecord, key); + if (oldParent){ + var correspondingChange = DS.OneToOneChange.createChange(childRecord, oldParent, store, { + parentType: options.parentType, + hasManyName: options.hasManyName, + changeType: "remove", + key: options.key + }); + store.addRelationshipChangeFor(childRecord, key, options.parentRecord , null, correspondingChange); + correspondingChange.sync(); + } + } +}; + +DS.OneToManyChange.createChange = function(childRecord, parentRecord, store, options) { + var 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 + // definition. + if (options.parentType) { + key = options.parentType.inverseFor(options.key).name; + DS.OneToManyChange.maintainInvariant( options, store, childRecord, key ); + } else if (options.key) { + key = options.key; + } else { + Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent", false); + } + + var change = DS.RelationshipChange._createChange({ + parentRecord: parentRecord, + childRecord: childRecord, + firstRecord: childRecord, + secondRecord: parentRecord, + firstRecordKind: "belongsTo", + secondRecordKind: "hasMany", + store: store, + changeType: options.changeType, + firstRecordName: key + }); + + store.addRelationshipChangeFor(childRecord, key, parentRecord, change.getSecondRecordName(), change); + + + return change; +}; + + +DS.OneToManyChange.maintainInvariant = function(options, store, childRecord, key){ + if (options.changeType === "add" && childRecord) { + var oldParent = get(childRecord, key); + if (oldParent){ + var correspondingChange = DS.OneToManyChange.createChange(childRecord, oldParent, store, { + parentType: options.parentType, + hasManyName: options.hasManyName, + changeType: "remove", + key: options.key + }); + store.addRelationshipChangeFor(childRecord, key, options.parentRecord, correspondingChange.getSecondRecordName(), correspondingChange); + correspondingChange.sync(); + } + } +}; + +/** + @class RelationshipChange + @namespace DS +*/ +DS.RelationshipChange.prototype = { + + getSecondRecordName: function() { + var name = this.secondRecordName, parent; + + if (!name) { + parent = this.secondRecord; + if (!parent) { return; } + + var childType = this.firstRecord.constructor; + var inverse = childType.inverseFor(this.firstRecordName); + this.secondRecordName = inverse.name; + } + + return this.secondRecordName; + }, + + /** + Get the name of the relationship on the belongsTo side. + + @method getFirstRecordName + @return {String} + */ + getFirstRecordName: function() { + var name = this.firstRecordName; + return name; + }, + + /** + @method destroy + @private + */ + destroy: function() { + var childRecord = this.childRecord, + belongsToName = this.getFirstRecordName(), + hasManyName = this.getSecondRecordName(), + store = this.store; + + store.removeRelationshipChangeFor(childRecord, belongsToName, this.parentRecord, hasManyName, this.changeType); + }, + + getSecondRecord: function(){ + return this.secondRecord; + }, + + /** + @method getFirstRecord + @private + */ + getFirstRecord: function() { + return this.firstRecord; + }, + + coalesce: function(){ + var relationshipPairs = this.store.relationshipChangePairsFor(this.firstRecord); + forEach(relationshipPairs, function(pair){ + var addedChange = pair["add"]; + var removedChange = pair["remove"]; + if(addedChange && removedChange) { + addedChange.destroy(); + removedChange.destroy(); + } + }); + } +}; + +DS.RelationshipChangeAdd.prototype = Ember.create(DS.RelationshipChange.create({})); +DS.RelationshipChangeRemove.prototype = Ember.create(DS.RelationshipChange.create({})); + +// the object is a value, and not a promise +function isValue(object) { + return typeof object === 'object' && (!object.then || typeof object.then !== 'function'); +} + +DS.RelationshipChangeAdd.prototype.changeType = "add"; +DS.RelationshipChangeAdd.prototype.sync = function() { + var secondRecordName = this.getSecondRecordName(), + firstRecordName = this.getFirstRecordName(), + firstRecord = this.getFirstRecord(), + secondRecord = this.getSecondRecord(); + + //Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName); + //Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName); + + if (secondRecord instanceof DS.Model && firstRecord instanceof DS.Model) { + if(this.secondRecordKind === "belongsTo"){ + secondRecord.suspendRelationshipObservers(function(){ + set(secondRecord, secondRecordName, firstRecord); + }); + + } + else if(this.secondRecordKind === "hasMany"){ + secondRecord.suspendRelationshipObservers(function(){ + var relationship = get(secondRecord, secondRecordName); + if (isValue(relationship)) { relationship.addObject(firstRecord); } + }); + } + } + + if (firstRecord instanceof DS.Model && secondRecord instanceof DS.Model && get(firstRecord, firstRecordName) !== secondRecord) { + if(this.firstRecordKind === "belongsTo"){ + firstRecord.suspendRelationshipObservers(function(){ + set(firstRecord, firstRecordName, secondRecord); + }); + } + else if(this.firstRecordKind === "hasMany"){ + firstRecord.suspendRelationshipObservers(function(){ + var relationship = get(firstRecord, firstRecordName); + if (isValue(relationship)) { relationship.addObject(secondRecord); } + }); + } + } + + this.coalesce(); +}; + +DS.RelationshipChangeRemove.prototype.changeType = "remove"; +DS.RelationshipChangeRemove.prototype.sync = function() { + var secondRecordName = this.getSecondRecordName(), + firstRecordName = this.getFirstRecordName(), + firstRecord = this.getFirstRecord(), + secondRecord = this.getSecondRecord(); + + //Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName); + //Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName); + + if (secondRecord instanceof DS.Model && firstRecord instanceof DS.Model) { + if(this.secondRecordKind === "belongsTo"){ + secondRecord.suspendRelationshipObservers(function(){ + set(secondRecord, secondRecordName, null); + }); + } + else if(this.secondRecordKind === "hasMany"){ + secondRecord.suspendRelationshipObservers(function(){ + var relationship = get(secondRecord, secondRecordName); + if (isValue(relationship)) { relationship.removeObject(firstRecord); } + }); + } + } + + if (firstRecord instanceof DS.Model && get(firstRecord, firstRecordName)) { + if(this.firstRecordKind === "belongsTo"){ + firstRecord.suspendRelationshipObservers(function(){ + set(firstRecord, firstRecordName, null); + }); + } + else if(this.firstRecordKind === "hasMany"){ + firstRecord.suspendRelationshipObservers(function(){ + var relationship = get(firstRecord, firstRecordName); + if (isValue(relationship)) { relationship.removeObject(secondRecord); } + }); + } + } + + this.coalesce(); +}; + +})(); + + + +(function() { +/** + @module ember-data +*/ + +})(); + + + +(function() { +var get = Ember.get, set = Ember.set, + isNone = Ember.isNone; + +/** + @module ember-data +*/ + +function asyncBelongsTo(type, options, meta) { + return Ember.computed(function(key, value) { + var data = get(this, 'data'), + store = get(this, 'store'), + promiseLabel = "DS: Async belongsTo " + this + " : " + key; + + if (arguments.length === 2) { + Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof store.modelFor(type)); + return value === undefined ? null : DS.PromiseObject.create({ promise: Ember.RSVP.resolve(value, promiseLabel) }); + } + + var link = data.links && data.links[key], + belongsTo = data[key]; + + if(!isNone(belongsTo)) { + var promise = store.fetchRecord(belongsTo) || Ember.RSVP.resolve(belongsTo, promiseLabel); + return DS.PromiseObject.create({ promise: promise}); + } else if (link) { + var resolver = Ember.RSVP.defer("DS: Async belongsTo (link) " + this + " : " + key); + store.findBelongsTo(this, link, meta, resolver); + return DS.PromiseObject.create({ promise: resolver.promise }); + } else { + return null; + } + }).property('data').meta(meta); +} + +/** + `DS.belongsTo` is used to define One-To-One and One-To-Many + relationships on a [DS.Model](DS.Model.html). + + + `DS.belongsTo` takes an optional hash as a second parameter, currently + supported options are: + + - `async`: A boolean value used to explicitly declare this to be an async relationship. + - `inverse`: A string used to identify the inverse property on a + related model in a One-To-Many relationship. See [Explicit Inverses](#toc_explicit-inverses) + + #### One-To-One + To declare a one-to-one relationship between two models, use + `DS.belongsTo`: + + ```javascript + App.User = DS.Model.extend({ + profile: DS.belongsTo('profile') + }); + + App.Profile = DS.Model.extend({ + user: DS.belongsTo('user') + }); + ``` + + #### One-To-Many + To declare a one-to-many relationship between two models, use + `DS.belongsTo` in combination with `DS.hasMany`, like this: + + ```javascript + App.Post = DS.Model.extend({ + comments: DS.hasMany('comment') + }); + + App.Comment = DS.Model.extend({ + post: DS.belongsTo('post') + }); + ``` + + @namespace + @method belongsTo + @for DS + @param {String or DS.Model} type the model type of the relationship + @param {Object} options a hash of options + @return {Ember.computed} relationship +*/ +DS.belongsTo = function(type, options) { + if (typeof type === 'object') { + options = type; + type = undefined; + } else { + Ember.assert("The first argument DS.belongsTo must be a model type or string, like DS.belongsTo(App.Person)", !!type && (typeof type === 'string' || DS.Model.detect(type))); + } + + options = options || {}; + + var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo' }; + + if (options.async) { + return asyncBelongsTo(type, options, meta); + } + + return Ember.computed(function(key, value) { + var data = get(this, 'data'), + store = get(this, 'store'), belongsTo, typeClass; + + if (typeof type === 'string') { + typeClass = store.modelFor(type); + } else { + typeClass = type; + } + + if (arguments.length === 2) { + Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof typeClass); + return value === undefined ? null : value; + } + + belongsTo = data[key]; + + if (isNone(belongsTo)) { return null; } + + store.fetchRecord(belongsTo); + + return belongsTo; + }).property('data').meta(meta); +}; + +/** + These observers observe all `belongsTo` relationships on the record. See + `relationships/ext` to see how these observers get their dependencies. + + @class Model + @namespace DS +*/ +DS.Model.reopen({ + + /** + @method belongsToWillChange + @private + @static + @param record + @param key + */ + belongsToWillChange: Ember.beforeObserver(function(record, key) { + if (get(record, 'isLoaded')) { + var oldParent = get(record, key); + + if (oldParent) { + var store = get(record, 'store'), + change = DS.RelationshipChange.createChange(record, oldParent, store, { key: key, kind: "belongsTo", changeType: "remove" }); + + change.sync(); + this._changesToSync[key] = change; + } + } + }), + + /** + @method belongsToDidChange + @private + @static + @param record + @param key + */ + belongsToDidChange: Ember.immediateObserver(function(record, key) { + if (get(record, 'isLoaded')) { + var newParent = get(record, key); + + if (newParent) { + var store = get(record, 'store'), + change = DS.RelationshipChange.createChange(record, newParent, store, { key: key, kind: "belongsTo", changeType: "add" }); + + change.sync(); + } + } + + delete this._changesToSync[key]; + }) +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set, setProperties = Ember.setProperties; + +function asyncHasMany(type, options, meta) { + return Ember.computed(function(key, value) { + var relationship = this._relationships[key], + promiseLabel = "DS: Async hasMany " + this + " : " + key; + + if (!relationship) { + var resolver = Ember.RSVP.defer(promiseLabel); + relationship = buildRelationship(this, key, options, function(store, data) { + var link = data.links && data.links[key]; + var rel; + if (link) { + rel = store.findHasMany(this, link, meta, resolver); + } else { + rel = store.findMany(this, data[key], meta.type, resolver); + } + // cache the promise so we can use it + // when we come back and don't need to rebuild + // the relationship. + set(rel, 'promise', resolver.promise); + return rel; + }); + } + + var promise = relationship.get('promise').then(function() { + return relationship; + }, null, "DS: Async hasMany records received"); + + return DS.PromiseArray.create({ promise: promise }); + }).property('data').meta(meta); +} + +function buildRelationship(record, key, options, callback) { + var rels = record._relationships; + + if (rels[key]) { return rels[key]; } + + var data = get(record, 'data'), + store = get(record, 'store'); + + var relationship = rels[key] = callback.call(record, store, data); + + return setProperties(relationship, { + owner: record, name: key, isPolymorphic: options.polymorphic + }); +} + +function hasRelationship(type, options) { + options = options || {}; + + var meta = { type: type, isRelationship: true, options: options, kind: 'hasMany' }; + + if (options.async) { + return asyncHasMany(type, options, meta); + } + + return Ember.computed(function(key, value) { + return buildRelationship(this, key, options, function(store, data) { + var records = data[key]; + Ember.assert("You looked up the '" + key + "' relationship on '" + this + "' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`)", Ember.A(records).everyProperty('isEmpty', false)); + return store.findMany(this, data[key], meta.type); + }); + }).property('data').meta(meta); +} + +/** + `DS.hasMany` is used to define One-To-Many and Many-To-Many + relationships on a [DS.Model](DS.Model.html). + + `DS.hasMany` takes an optional hash as a second parameter, currently + supported options are: + + - `async`: A boolean value used to explicitly declare this to be an async relationship. + - `inverse`: A string used to identify the inverse property on a related model. + + #### One-To-Many + To declare a one-to-many relationship between two models, use + `DS.belongsTo` in combination with `DS.hasMany`, like this: + + ```javascript + App.Post = DS.Model.extend({ + comments: DS.hasMany('comment') + }); + + App.Comment = DS.Model.extend({ + post: DS.belongsTo('post') + }); + ``` + + #### Many-To-Many + To declare a many-to-many relationship between two models, use + `DS.hasMany`: + + ```javascript + App.Post = DS.Model.extend({ + tags: DS.hasMany('tag') + }); + + App.Tag = DS.Model.extend({ + posts: DS.hasMany('post') + }); + ``` + + #### Explicit Inverses + + Ember Data will do its best to discover which relationships map to + one another. In the one-to-many code above, for example, Ember Data + can figure out that changing the `comments` relationship should update + the `post` relationship on the inverse because post is the only + relationship to that model. + + However, sometimes you may have multiple `belongsTo`/`hasManys` for the + same type. You can specify which property on the related model is + the inverse using `DS.hasMany`'s `inverse` option: + + ```javascript + var belongsTo = DS.belongsTo, + hasMany = DS.hasMany; + + App.Comment = DS.Model.extend({ + onePost: belongsTo('post'), + twoPost: belongsTo('post'), + redPost: belongsTo('post'), + bluePost: belongsTo('post') + }); + + App.Post = DS.Model.extend({ + comments: hasMany('comment', { + inverse: 'redPost' + }) + }); + ``` + + You can also specify an inverse on a `belongsTo`, which works how + you'd expect. + + @namespace + @method hasMany + @for DS + @param {String or DS.Model} type the model type of the relationship + @param {Object} options a hash of options + @return {Ember.computed} relationship +*/ +DS.hasMany = function(type, options) { + if (typeof type === 'object') { + options = type; + type = undefined; + } + return hasRelationship(type, options); +}; + +})(); + + + +(function() { +var get = Ember.get, set = Ember.set; + +/** + @module ember-data +*/ + +/* + This file defines several extensions to the base `DS.Model` class that + add support for one-to-many relationships. +*/ + +/** + @class Model + @namespace DS +*/ +DS.Model.reopen({ + + /** + This Ember.js hook allows an object to be notified when a property + is defined. + + In this case, we use it to be notified when an Ember Data user defines a + belongs-to relationship. In that case, we need to set up observers for + each one, allowing us to track relationship changes and automatically + reflect changes in the inverse has-many array. + + This hook passes the class being set up, as well as the key and value + being defined. So, for example, when the user does this: + + ```javascript + DS.Model.extend({ + parent: DS.belongsTo('user') + }); + ``` + + This hook would be called with "parent" as the key and the computed + property returned by `DS.belongsTo` as the value. + + @method didDefineProperty + @param proto + @param key + @param value + */ + didDefineProperty: function(proto, key, value) { + // Check if the value being set is a computed property. + if (value instanceof Ember.Descriptor) { + + // If it is, get the metadata for the relationship. This is + // populated by the `DS.belongsTo` helper when it is creating + // the computed property. + var meta = value.meta(); + + if (meta.isRelationship && meta.kind === 'belongsTo') { + Ember.addObserver(proto, key, null, 'belongsToDidChange'); + Ember.addBeforeObserver(proto, key, null, 'belongsToWillChange'); + } + + meta.parentType = proto.constructor; + } + } +}); + +/* + These DS.Model extensions add class methods that provide relationship + introspection abilities about relationships. + + A note about the computed properties contained here: + + **These properties are effectively sealed once called for the first time.** + To avoid repeatedly doing expensive iteration over a model's fields, these + values are computed once and then cached for the remainder of the runtime of + your application. + + If your application needs to modify a class after its initial definition + (for example, using `reopen()` to add additional attributes), make sure you + do it before using your model with the store, which uses these properties + extensively. +*/ + +DS.Model.reopenClass({ + /** + For a given relationship name, returns the model type of the relationship. + + For example, if you define a model like this: + + ```javascript + App.Post = DS.Model.extend({ + comments: DS.hasMany('comment') + }); + ``` + + Calling `App.Post.typeForRelationship('comments')` will return `App.Comment`. + + @method typeForRelationship + @static + @param {String} name the name of the relationship + @return {subclass of DS.Model} the type of the relationship, or undefined + */ + typeForRelationship: function(name) { + var relationship = get(this, 'relationshipsByName').get(name); + return relationship && relationship.type; + }, + + inverseFor: function(name) { + var inverseType = this.typeForRelationship(name); + + if (!inverseType) { return null; } + + var options = this.metaForProperty(name).options; + + if (options.inverse === null) { return null; } + + var inverseName, inverseKind; + + if (options.inverse) { + inverseName = options.inverse; + inverseKind = Ember.get(inverseType, 'relationshipsByName').get(inverseName).kind; + } else { + var possibleRelationships = findPossibleInverses(this, inverseType); + + if (possibleRelationships.length === 0) { return null; } + + Ember.assert("You defined the '" + name + "' relationship on " + this + ", but multiple possible inverse relationships of type " + this + " were found on " + inverseType + ". Look at http://emberjs.com/guides/models/defining-models/#toc_explicit-inverses for how to explicitly specify inverses", possibleRelationships.length === 1); + + inverseName = possibleRelationships[0].name; + inverseKind = possibleRelationships[0].kind; + } + + function findPossibleInverses(type, inverseType, possibleRelationships) { + possibleRelationships = possibleRelationships || []; + + var relationshipMap = get(inverseType, 'relationships'); + if (!relationshipMap) { return; } + + var relationships = relationshipMap.get(type); + if (relationships) { + possibleRelationships.push.apply(possibleRelationships, relationshipMap.get(type)); + } + + if (type.superclass) { + findPossibleInverses(type.superclass, inverseType, possibleRelationships); + } + + return possibleRelationships; + } + + return { + type: inverseType, + name: inverseName, + kind: inverseKind + }; + }, + + /** + The model's relationships as a map, keyed on the type of the + relationship. The value of each entry is an array containing a descriptor + for each relationship with that type, describing the name of the relationship + as well as the type. + + For example, given the following model definition: + + ```javascript + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + posts: DS.hasMany('post') + }); + ``` + + This computed property would return a map describing these + relationships, like this: + + ```javascript + var relationships = Ember.get(App.Blog, 'relationships'); + relationships.get(App.User); + //=> [ { name: 'users', kind: 'hasMany' }, + // { name: 'owner', kind: 'belongsTo' } ] + relationships.get(App.Post); + //=> [ { name: 'posts', kind: 'hasMany' } ] + ``` + + @property relationships + @static + @type Ember.Map + @readOnly + */ + relationships: Ember.computed(function() { + var map = new Ember.MapWithDefault({ + defaultValue: function() { return []; } + }); + + // Loop through each computed property on the class + this.eachComputedProperty(function(name, meta) { + + // If the computed property is a relationship, add + // it to the map. + if (meta.isRelationship) { + if (typeof meta.type === 'string') { + meta.type = this.store.modelFor(meta.type); + } + + var relationshipsForType = map.get(meta.type); + + relationshipsForType.push({ name: name, kind: meta.kind }); + } + }); + + return map; + }), + + /** + A hash containing lists of the model's relationships, grouped + by the relationship kind. For example, given a model with this + definition: + + ```javascript + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + + posts: DS.hasMany('post') + }); + ``` + + This property would contain the following: + + ```javascript + var relationshipNames = Ember.get(App.Blog, 'relationshipNames'); + relationshipNames.hasMany; + //=> ['users', 'posts'] + relationshipNames.belongsTo; + //=> ['owner'] + ``` + + @property relationshipNames + @static + @type Object + @readOnly + */ + relationshipNames: Ember.computed(function() { + var names = { hasMany: [], belongsTo: [] }; + + this.eachComputedProperty(function(name, meta) { + if (meta.isRelationship) { + names[meta.kind].push(name); + } + }); + + return names; + }), + + /** + 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: + + ```javascript + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + + posts: DS.hasMany('post') + }); + ``` + + This property would contain the following: + + ```javascript + var relatedTypes = Ember.get(App.Blog, 'relatedTypes'); + //=> [ App.User, App.Post ] + ``` + + @property relatedTypes + @static + @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) || this.store.modelFor(type); + } + + Ember.assert("You specified a hasMany (" + meta.type + ") on " + meta.parentType + " but " + meta.type + " was not found.", 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. + + For example, given a model with this + definition: + + ```javascript + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + + posts: DS.hasMany('post') + }); + ``` + + This property would contain the following: + + ```javascript + var relationshipsByName = Ember.get(App.Blog, 'relationshipsByName'); + relationshipsByName.get('users'); + //=> { key: 'users', kind: 'hasMany', type: App.User } + relationshipsByName.get('owner'); + //=> { key: 'owner', kind: 'belongsTo', type: App.User } + ``` + + @property relationshipsByName + @static + @type Ember.Map + @readOnly + */ + relationshipsByName: Ember.computed(function() { + var map = Ember.Map.create(), type; + + this.eachComputedProperty(function(name, meta) { + if (meta.isRelationship) { + meta.key = name; + type = meta.type; + + if (!type && meta.kind === 'hasMany') { + type = Ember.String.singularize(name); + } else if (!type) { + type = name; + } + + if (typeof type === 'string') { + meta.type = this.store.modelFor(type); + } + + map.set(name, meta); + } + }); + + return map; + }), + + /** + A map whose keys are the fields of the model and whose values are strings + describing the kind of the field. A model's fields are the union of all of its + attributes and relationships. + + For example: + + ```javascript + + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + + posts: DS.hasMany('post'), + + title: DS.attr('string') + }); + + var fields = Ember.get(App.Blog, 'fields'); + fields.forEach(function(field, kind) { + console.log(field, kind); + }); + + // prints: + // users, hasMany + // owner, belongsTo + // posts, hasMany + // title, attribute + ``` + + @property fields + @static + @type Ember.Map + @readOnly + */ + fields: Ember.computed(function() { + var map = Ember.Map.create(); + + this.eachComputedProperty(function(name, meta) { + if (meta.isRelationship) { + map.set(name, meta.kind); + } else if (meta.isAttribute) { + map.set(name, 'attribute'); + } + }); + + return map; + }), + + /** + Given a callback, iterates over each of the relationships in the model, + invoking the callback with the name of each relationship and its relationship + descriptor. + + @method eachRelationship + @static + @param {Function} callback the callback to invoke + @param {any} binding the value to which the callback's `this` should be bound + */ + eachRelationship: function(callback, binding) { + 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. + + @method eachRelatedType + @static + @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); + }); + } +}); + +DS.Model.reopen({ + /** + Given a callback, iterates over each of the relationships in the model, + invoking the callback with the name of each relationship and its relationship + descriptor. + + @method eachRelationship + @param {Function} callback the callback to invoke + @param {any} binding the value to which the callback's `this` should be bound + */ + eachRelationship: function(callback, binding) { + this.constructor.eachRelationship(callback, binding); + } +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set; +var forEach = Ember.EnumerableUtils.forEach; + +/** + @class RecordArrayManager + @namespace DS + @private + @extends Ember.Object +*/ +DS.RecordArrayManager = Ember.Object.extend({ + init: function() { + this.filteredRecordArrays = Ember.MapWithDefault.create({ + defaultValue: function() { return []; } + }); + + this.changedRecords = []; + }, + + recordDidChange: function(record) { + if (this.changedRecords.push(record) !== 1) { return; } + + Ember.run.schedule('actions', this, this.updateRecordArrays); + }, + + recordArraysForRecord: function(record) { + record._recordArrays = record._recordArrays || Ember.OrderedSet.create(); + return record._recordArrays; + }, + + /** + This method is invoked whenever data is loaded into the store by the + adapter or updated by the adapter, or when a record has changed. + + It updates all record arrays that a record belongs to. + + To avoid thrashing, it only runs at most once per run loop. + + @method updateRecordArrays + @param {Class} type + @param {Number|String} clientId + */ + updateRecordArrays: function() { + forEach(this.changedRecords, function(record) { + if (get(record, 'isDeleted')) { + this._recordWasDeleted(record); + } else { + this._recordWasChanged(record); + } + }, this); + + this.changedRecords.length = 0; + }, + + _recordWasDeleted: function (record) { + var recordArrays = record._recordArrays; + + if (!recordArrays) { return; } + + forEach(recordArrays, function(array) { + array.removeRecord(record); + }); + }, + + _recordWasChanged: function (record) { + var type = record.constructor, + recordArrays = this.filteredRecordArrays.get(type), + filter; + + forEach(recordArrays, function(array) { + filter = get(array, 'filterFunction'); + this.updateRecordArray(array, filter, type, record); + }, this); + + // loop through all manyArrays containing an unloaded copy of this + // clientId and notify them that the record was loaded. + var manyArrays = record._loadingRecordArrays; + + if (manyArrays) { + for (var i=0, l=manyArrays.length; i 'kine' + inflector.singularize('kine') //=> 'cow' + ``` + + Creating an inflector and adding rules later. + + ```javascript + var inflector = Ember.Inflector.inflector; + + inflector.pluralize('advice') // => 'advices' + inflector.uncountable('advice'); + inflector.pluralize('advice') // => 'advice' + + inflector.pluralize('formula') // => 'formulas' + inflector.irregular('formula', 'formulae'); + inflector.pluralize('formula') // => 'formulae' + + // you would not need to add these as they are the default rules + inflector.plural(/$/, 's'); + inflector.singular(/s$/i, ''); + ``` + + Creating an inflector with a nondefault ruleset. + + ```javascript + var rules = { + plurals: [ /$/, 's' ], + singular: [ /\s$/, '' ], + irregularPairs: [ + [ 'cow', 'kine' ] + ], + uncountable: [ 'fish' ] + }; + + var inflector = new Ember.Inflector(rules); + ``` + + @class Inflector + @namespace Ember +*/ +function Inflector(ruleSet) { + ruleSet = ruleSet || {}; + ruleSet.uncountable = ruleSet.uncountable || {}; + ruleSet.irregularPairs = ruleSet.irregularPairs || {}; + + var rules = this.rules = { + plurals: ruleSet.plurals || [], + singular: ruleSet.singular || [], + irregular: {}, + irregularInverse: {}, + uncountable: {} + }; + + loadUncountable(rules, ruleSet.uncountable); + loadIrregular(rules, ruleSet.irregularPairs); +} + +Inflector.prototype = { + /** + @method plural + @param {RegExp} regex + @param {String} string + */ + plural: function(regex, string) { + this.rules.plurals.push([regex, string.toLowerCase()]); + }, + + /** + @method singular + @param {RegExp} regex + @param {String} string + */ + singular: function(regex, string) { + this.rules.singular.push([regex, string.toLowerCase()]); + }, + + /** + @method uncountable + @param {String} regex + */ + uncountable: function(string) { + loadUncountable(this.rules, [string.toLowerCase()]); + }, + + /** + @method irregular + @param {String} singular + @param {String} plural + */ + irregular: function (singular, plural) { + loadIrregular(this.rules, [[singular, plural]]); + }, + + /** + @method pluralize + @param {String} word + */ + pluralize: function(word) { + return this.inflect(word, this.rules.plurals, this.rules.irregular); + }, + + /** + @method singularize + @param {String} word + */ + singularize: function(word) { + return this.inflect(word, this.rules.singular, this.rules.irregularInverse); + }, + + /** + @protected + + @method inflect + @param {String} word + @param {Object} typeRules + @param {Object} irregular + */ + inflect: function(word, typeRules, irregular) { + var inflection, substitution, result, lowercase, isBlank, + isUncountable, isIrregular, isIrregularInverse, rule; + + isBlank = BLANK_REGEX.test(word); + + if (isBlank) { + return word; + } + + lowercase = word.toLowerCase(); + + isUncountable = this.rules.uncountable[lowercase]; + + if (isUncountable) { + return word; + } + + isIrregular = irregular && irregular[lowercase]; + + if (isIrregular) { + return isIrregular; + } + + for (var i = typeRules.length, min = 0; i > min; i--) { + inflection = typeRules[i-1]; + rule = inflection[0]; + + if (rule.test(word)) { + break; + } + } + + inflection = inflection || []; + + rule = inflection[0]; + substitution = inflection[1]; + + result = word.replace(rule, substitution); + + return result; + } +}; + +Ember.Inflector = Inflector; + +})(); + + + +(function() { +Ember.Inflector.defaultRules = { + plurals: [ + [/$/, 's'], + [/s$/i, 's'], + [/^(ax|test)is$/i, '$1es'], + [/(octop|vir)us$/i, '$1i'], + [/(octop|vir)i$/i, '$1i'], + [/(alias|status)$/i, '$1es'], + [/(bu)s$/i, '$1ses'], + [/(buffal|tomat)o$/i, '$1oes'], + [/([ti])um$/i, '$1a'], + [/([ti])a$/i, '$1a'], + [/sis$/i, 'ses'], + [/(?:([^f])fe|([lr])f)$/i, '$1$2ves'], + [/(hive)$/i, '$1s'], + [/([^aeiouy]|qu)y$/i, '$1ies'], + [/(x|ch|ss|sh)$/i, '$1es'], + [/(matr|vert|ind)(?:ix|ex)$/i, '$1ices'], + [/^(m|l)ouse$/i, '$1ice'], + [/^(m|l)ice$/i, '$1ice'], + [/^(ox)$/i, '$1en'], + [/^(oxen)$/i, '$1'], + [/(quiz)$/i, '$1zes'] + ], + + singular: [ + [/s$/i, ''], + [/(ss)$/i, '$1'], + [/(n)ews$/i, '$1ews'], + [/([ti])a$/i, '$1um'], + [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '$1sis'], + [/(^analy)(sis|ses)$/i, '$1sis'], + [/([^f])ves$/i, '$1fe'], + [/(hive)s$/i, '$1'], + [/(tive)s$/i, '$1'], + [/([lr])ves$/i, '$1f'], + [/([^aeiouy]|qu)ies$/i, '$1y'], + [/(s)eries$/i, '$1eries'], + [/(m)ovies$/i, '$1ovie'], + [/(x|ch|ss|sh)es$/i, '$1'], + [/^(m|l)ice$/i, '$1ouse'], + [/(bus)(es)?$/i, '$1'], + [/(o)es$/i, '$1'], + [/(shoe)s$/i, '$1'], + [/(cris|test)(is|es)$/i, '$1is'], + [/^(a)x[ie]s$/i, '$1xis'], + [/(octop|vir)(us|i)$/i, '$1us'], + [/(alias|status)(es)?$/i, '$1'], + [/^(ox)en/i, '$1'], + [/(vert|ind)ices$/i, '$1ex'], + [/(matr)ices$/i, '$1ix'], + [/(quiz)zes$/i, '$1'], + [/(database)s$/i, '$1'] + ], + + irregularPairs: [ + ['person', 'people'], + ['man', 'men'], + ['child', 'children'], + ['sex', 'sexes'], + ['move', 'moves'], + ['cow', 'kine'], + ['zombie', 'zombies'] + ], + + uncountable: [ + 'equipment', + 'information', + 'rice', + 'money', + 'species', + 'series', + 'fish', + 'sheep', + 'jeans', + 'police' + ] +}; + +})(); + + + +(function() { +if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) { + /** + See {{#crossLink "Ember.String/pluralize"}}{{/crossLink}} + + @method pluralize + @for String + */ + String.prototype.pluralize = function() { + return Ember.String.pluralize(this); + }; + + /** + See {{#crossLink "Ember.String/singularize"}}{{/crossLink}} + + @method singularize + @for String + */ + String.prototype.singularize = function() { + return Ember.String.singularize(this); + }; +} + +})(); + + + +(function() { +Ember.Inflector.inflector = new Ember.Inflector(Ember.Inflector.defaultRules); + +})(); + + + +(function() { + +})(); + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, + forEach = Ember.EnumerableUtils.forEach, + camelize = Ember.String.camelize, + capitalize = Ember.String.capitalize, + decamelize = Ember.String.decamelize, + singularize = Ember.String.singularize, + underscore = Ember.String.underscore; + +DS.ActiveModelSerializer = DS.RESTSerializer.extend({ + // SERIALIZE + + /** + Converts camelcased attributes to underscored when serializing. + + @method keyForAttribute + @param {String} attribute + @returns String + */ + keyForAttribute: function(attr) { + return decamelize(attr); + }, + + /** + Underscores relationship names and appends "_id" or "_ids" when serializing + relationship keys. + + @method keyForRelationship + @param {String} key + @param {String} kind + @returns String + */ + keyForRelationship: function(key, kind) { + key = decamelize(key); + if (kind === "belongsTo") { + return key + "_id"; + } else if (kind === "hasMany") { + return singularize(key) + "_ids"; + } else { + return key; + } + }, + + /** + Does not serialize hasMany relationships by default. + */ + serializeHasMany: Ember.K, + + /** + Underscores the JSON root keys when serializing. + + @method serializeIntoHash + @param {Object} hash + @param {subclass of DS.Model} type + @param {DS.Model} record + @param {Object} options + */ + serializeIntoHash: function(data, type, record, options) { + var root = underscore(decamelize(type.typeKey)); + data[root] = this.serialize(record, options); + }, + + /** + Serializes a polymorphic type as a fully capitalized model name. + + @method serializePolymorphicType + @param {DS.Model} record + @param {Object} json + @param relationship + */ + serializePolymorphicType: function(record, json, relationship) { + var key = relationship.key, + belongsTo = get(record, key); + key = this.keyForAttribute(key); + json[key + "_type"] = capitalize(camelize(belongsTo.constructor.typeKey)); + }, + + // EXTRACT + + /** + Extracts the model typeKey from underscored root objects. + + @method typeForRoot + @param {String} root + @returns String the model's typeKey + */ + typeForRoot: function(root) { + var camelized = camelize(root); + return singularize(camelized); + }, + + /** + Add extra step to `DS.RESTSerializer.normalize` so links are + normalized. + + If your payload looks like this + + ```js + { + "post": { + "id": 1, + "title": "Rails is omakase", + "links": { "flagged_comments": "api/comments/flagged" } + } + } + ``` + The normalized version would look like this + + ```js + { + "post": { + "id": 1, + "title": "Rails is omakase", + "links": { "flaggedComments": "api/comments/flagged" } + } + } + ``` + + @method normalize + @param {subclass of DS.Model} type + @param {Object} hash + @param {String} prop + @returns Object + */ + + normalize: function(type, hash, prop) { + this.normalizeLinks(hash); + + return this._super(type, hash, prop); + }, + + /** + Convert `snake_cased` links to `camelCase` + + @method normalizeLinks + @param {Object} hash + */ + + normalizeLinks: function(data){ + if (data.links) { + var links = data.links; + + for (var link in links) { + var camelizedLink = camelize(link); + + if (camelizedLink !== link) { + links[camelizedLink] = links[link]; + delete links[link]; + } + } + } + }, + + /** + Normalize the polymorphic type from the JSON. + + Normalize: + ```js + { + id: "1" + minion: { type: "evil_minion", id: "12"} + } + ``` + + To: + ```js + { + id: "1" + minion: { type: "evilMinion", id: "12"} + } + ``` + + @method normalizeRelationships + @private + */ + normalizeRelationships: function(type, hash) { + var payloadKey, payload; + + if (this.keyForRelationship) { + type.eachRelationship(function(key, relationship) { + if (relationship.options.polymorphic) { + payloadKey = this.keyForAttribute(key); + payload = hash[payloadKey]; + if (payload && payload.type) { + payload.type = this.typeForRoot(payload.type); + } else if (payload && relationship.kind === "hasMany") { + var self = this; + forEach(payload, function(single) { + single.type = self.typeForRoot(single.type); + }); + } + } else { + payloadKey = this.keyForRelationship(key, relationship.kind); + payload = hash[payloadKey]; + } + + hash[key] = payload; + + if (key !== payloadKey) { + delete hash[payloadKey]; + } + }, this); + } + } +}); + +})(); + + + +(function() { +var get = Ember.get; +var forEach = Ember.EnumerableUtils.forEach; + +/** + The EmbeddedRecordsMixin allows you to add embedded record support to your + serializers. + To set up embedded records, you include the mixin into the serializer and then + define your embedded relations. + + ```js + App.PostSerializer = DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, { + attrs: { + comments: {embedded: 'always'} + } + }) + ``` + + Currently only `{embedded: 'always'}` records are supported. + + @class EmbeddedRecordsMixin + @namespace DS +*/ +DS.EmbeddedRecordsMixin = Ember.Mixin.create({ + + /** + Serialize has-may relationship when it is configured as embedded objects. + + @method serializeHasMany + */ + serializeHasMany: function(record, json, relationship) { + var key = relationship.key, + attrs = get(this, 'attrs'), + embed = attrs && attrs[key] && attrs[key].embedded === 'always'; + + if (embed) { + json[this.keyForAttribute(key)] = get(record, key).map(function(relation) { + var data = relation.serialize(), + primaryKey = get(this, 'primaryKey'); + + data[primaryKey] = get(relation, primaryKey); + + return data; + }, this); + } + }, + + /** + Extract embedded objects out of the payload for a single object + and add them as sideloaded objects instead. + + @method extractSingle + */ + extractSingle: function(store, primaryType, payload, recordId, requestType) { + var root = this.keyForAttribute(primaryType.typeKey), + partial = payload[root]; + + updatePayloadWithEmbedded(store, this, primaryType, partial, payload); + + return this._super(store, primaryType, payload, recordId, requestType); + }, + + /** + Extract embedded objects out of a standard payload + and add them as sideloaded objects instead. + + @method extractArray + */ + extractArray: function(store, type, payload) { + var root = this.keyForAttribute(type.typeKey), + partials = payload[Ember.String.pluralize(root)]; + + forEach(partials, function(partial) { + updatePayloadWithEmbedded(store, this, type, partial, payload); + }, this); + + return this._super(store, type, payload); + } +}); + +function updatePayloadWithEmbedded(store, serializer, type, partial, payload) { + var attrs = get(serializer, 'attrs'); + + if (!attrs) { + return; + } + + type.eachRelationship(function(key, relationship) { + var expandedKey, embeddedTypeKey, attribute, ids, + config = attrs[key], + serializer = store.serializerFor(relationship.type.typeKey), + primaryKey = get(serializer, "primaryKey"); + + if (relationship.kind !== "hasMany") { + return; + } + + if (config && (config.embedded === 'always' || config.embedded === 'load')) { + // underscore forces the embedded records to be side loaded. + // it is needed when main type === relationship.type + embeddedTypeKey = '_' + Ember.String.pluralize(relationship.type.typeKey); + expandedKey = this.keyForRelationship(key, relationship.kind); + attribute = this.keyForAttribute(key); + ids = []; + + if (!partial[attribute]) { + return; + } + + payload[embeddedTypeKey] = payload[embeddedTypeKey] || []; + + forEach(partial[attribute], function(data) { + var embeddedType = store.modelFor(relationship.type.typeKey); + updatePayloadWithEmbedded(store, serializer, embeddedType, data, payload); + ids.push(data[primaryKey]); + payload[embeddedTypeKey].push(data); + }); + + partial[expandedKey] = ids; + delete partial[attribute]; + } + }, serializer); +} +})(); + + + +(function() { +/** + @module ember-data +*/ + +var forEach = Ember.EnumerableUtils.forEach; +var decamelize = Ember.String.decamelize, + underscore = Ember.String.underscore, + pluralize = Ember.String.pluralize; + +/** + The ActiveModelAdapter is a subclass of the RESTAdapter designed to integrate + with a JSON API that uses an underscored naming convention instead of camelcasing. + It has been designed to work out of the box with the + [active_model_serializers](http://github.com/rails-api/active_model_serializers) + Ruby gem. + + This adapter extends the DS.RESTAdapter by making consistent use of the camelization, + decamelization and pluralization methods to normalize the serialized JSON into a + format that is compatible with a conventional Rails backend and Ember Data. + + ## JSON Structure + + The ActiveModelAdapter expects the JSON returned from your server to follow + the REST adapter conventions substituting underscored keys for camelcased ones. + + ### Conventional Names + + Attribute names in your JSON payload should be the underscored versions of + the attributes in your Ember.js models. + + For example, if you have a `Person` model: + + ```js + App.FamousPerson = DS.Model.extend({ + firstName: DS.attr('string'), + lastName: DS.attr('string'), + occupation: DS.attr('string') + }); + ``` + + The JSON returned should look like this: + + ```js + { + "famous_person": { + "first_name": "Barack", + "last_name": "Obama", + "occupation": "President" + } + } + ``` + + @class ActiveModelAdapter + @constructor + @namespace DS + @extends DS.Adapter +**/ + +DS.ActiveModelAdapter = DS.RESTAdapter.extend({ + defaultSerializer: '-active-model', + /** + The ActiveModelAdapter overrides the `pathForType` method to build + underscored URLs by decamelizing and pluralizing the object type name. + + ```js + this.pathForType("famousPerson"); + //=> "famous_people" + ``` + + @method pathForType + @param {String} type + @returns String + */ + pathForType: function(type) { + var decamelized = decamelize(type); + var underscored = underscore(decamelized); + return pluralize(underscored); + }, + + /** + The ActiveModelAdapter overrides the `ajaxError` method + to return a DS.InvalidError for all 422 Unprocessable Entity + responses. + + A 422 HTTP response from the server generally implies that the request + was well formed but the API was unable to process it because the + content was not semantically correct or meaningful per the API. + + For more information on 422 HTTP Error code see 11.2 WebDAV RFC 4918 + https://tools.ietf.org/html/rfc4918#section-11.2 + + @method ajaxError + @param jqXHR + @returns error + */ + ajaxError: function(jqXHR) { + var error = this._super(jqXHR); + + if (jqXHR && jqXHR.status === 422) { + var response = Ember.$.parseJSON(jqXHR.responseText), + errors = {}; + + if (response.errors !== undefined) { + var jsonErrors = response.errors; + + forEach(Ember.keys(jsonErrors), function(key) { + errors[Ember.String.camelize(key)] = jsonErrors[key]; + }); + } + + return new DS.InvalidError(errors); + } else { + return error; + } + } +}); + +})(); + + + +(function() { + +})(); + + + +(function() { +Ember.onLoad('Ember.Application', function(Application) { + Application.initializer({ + name: "activeModelAdapter", + + initialize: function(container, application) { + var proxy = new DS.ContainerProxy(container); + proxy.registerDeprecations([ + {deprecated: 'serializer:_ams', valid: 'serializer:-active-model'}, + {deprecated: 'adapter:_ams', valid: 'adapter:-active-model'} + ]); + + application.register('serializer:-active-model', DS.ActiveModelSerializer); + application.register('adapter:-active-model', DS.ActiveModelAdapter); + } + }); +}); + +})(); + + + +(function() { + +})(); + + +})(); diff --git a/ajax/libs/ember-data.js/1.0.0-beta.6/ember-data.min.js b/ajax/libs/ember-data.js/1.0.0-beta.6/ember-data.min.js new file mode 100644 index 000000000..f2a88957d --- /dev/null +++ b/ajax/libs/ember-data.js/1.0.0-beta.6/ember-data.min.js @@ -0,0 +1,10 @@ +/*! + * @overview Ember Data + * @copyright Copyright 2011-2014 Tilde Inc. and contributors. + * Portions Copyright 2011 LivingSocial Inc. + * @license Licensed under MIT license (see license.js) + * @version 1.0.0-beta.6 + */ +!function(){var e,t;!function(){var r={},n={};e=function(e,t,n){r[e]={deps:t,callback:n}},t=function(e){if(n[e])return n[e];n[e]={};var i,a,o,s,d;if(i=r[e],!i)throw new Error("Module '"+e+"' not found.");a=i.deps,o=i.callback,s=[];for(var u=0,c=a.length;c>u;u++)"exports"===a[u]?s.push(d={}):s.push(t(a[u]));var l=o.apply(this,s);return n[e]=d||l}}(),function(){var e;"undefined"==typeof e&&(e=Ember.Namespace.create({VERSION:"1.0.0-beta.6"}),"undefined"!=typeof window&&(window.DS=e),Ember.libraries&&Ember.libraries.registerCoreLibrary("Ember Data",e.VERSION))}(),function(){var e=function(e){this.container=e};e.prototype.aliasedFactory=function(e,t){var r=this;return{create:function(){return t&&t(),r.container.lookup(e)}}},e.prototype.registerAlias=function(e,t,r){var n=this.aliasedFactory(t,r);return this.container.register(e,n)},e.prototype.registerDeprecation=function(e,t){var r=function(){Ember.deprecate("You tried to look up '"+e+"', but this has been deprecated in favor of '"+t+"'.",!1)};return this.registerAlias(e,t,r)},e.prototype.registerDeprecations=function(e){for(var t=e.length;t>0;t--){var r=e[t-1],n=r.deprecated,i=r.valid;this.registerDeprecation(n,i)}},DS.ContainerProxy=e}(),function(){function e(e){return function(){return this[e].apply(this,arguments)}}var t=Ember.get,r=(Ember.set,Ember.isNone);DS.JSONSerializer=Ember.Object.extend({primaryKey:"id",applyTransforms:function(e,t){return e.eachTransformedAttribute(function(e,r){var n=this.transformFor(r);t[e]=n.deserialize(t[e])},this),t},normalize:function(e,t){return t?(this.applyTransforms(e,t),t):t},serialize:function(e,r){var n={};if(r&&r.includeId){var i=t(e,"id");i&&(n[t(this,"primaryKey")]=i)}return e.eachAttribute(function(t,r){this.serializeAttribute(e,n,t,r)},this),e.eachRelationship(function(t,r){"belongsTo"===r.kind?this.serializeBelongsTo(e,n,r):"hasMany"===r.kind&&this.serializeHasMany(e,n,r)},this),n},serializeAttribute:function(e,r,n,i){var a=t(this,"attrs"),o=t(e,n),s=i.type;if(s){var d=this.transformFor(s);o=d.serialize(o)}n=a&&a[n]||(this.keyForAttribute?this.keyForAttribute(n):n),r[n]=o},serializeBelongsTo:function(e,n,i){var a=i.key,o=t(e,a);a=this.keyForRelationship?this.keyForRelationship(a,"belongsTo"):a,n[a]=r(o)?o:t(o,"id"),i.options.polymorphic&&this.serializePolymorphicType(e,n,i)},serializeHasMany:function(e,r,n){var i=n.key,a=DS.RelationshipChange.determineRelationshipType(e.constructor,n);("manyToNone"===a||"manyToMany"===a)&&(r[i]=t(e,i).mapBy("id"))},serializePolymorphicType:Ember.K,extract:function(e,t,r,n,i){this.extractMeta(e,t,r);var a="extract"+i.charAt(0).toUpperCase()+i.substr(1);return this[a](e,t,r,n,i)},extractFindAll:e("extractArray"),extractFindQuery:e("extractArray"),extractFindMany:e("extractArray"),extractFindHasMany:e("extractArray"),extractCreateRecord:e("extractSave"),extractUpdateRecord:e("extractSave"),extractDeleteRecord:e("extractSave"),extractFind:e("extractSingle"),extractFindBelongsTo:e("extractSingle"),extractSave:e("extractSingle"),extractSingle:function(e,t,r){return this.normalize(t,r)},extractArray:function(e,t,r){return this.normalize(t,r)},extractMeta:function(e,t,r){r&&r.meta&&(e.metaForType(t,r.meta),delete r.meta)},transformFor:function(e,t){var r=this.container.lookup("transform:"+e);return Ember.assert("Unable to find transform for '"+e+"'",t||!!r),r}})}(),function(){var e=Ember.get,t=Ember.String.capitalize,r=Ember.String.underscore,n=window.DS;n.DebugAdapter=Ember.DataAdapter.extend({getFilters:function(){return[{name:"isNew",desc:"New"},{name:"isModified",desc:"Modified"},{name:"isClean",desc:"Clean"}]},detect:function(e){return e!==n.Model&&n.Model.detect(e)},columnsForType:function(n){var i=[{name:"id",desc:"Id"}],a=0,o=this;return e(n,"attributes").forEach(function(e){if(a++>o.attributeLimit)return!1;var n=t(r(e).replace("_"," "));i.push({name:e,desc:n})}),i},getRecords:function(e){return this.get("store").all(e)},getRecordColumnValues:function(t){var r=this,n=0,i={id:e(t,"id")};return t.eachAttribute(function(a){if(n++>r.attributeLimit)return!1;var o=e(t,a);i[a]=o}),i},getRecordKeywords:function(t){var r=[],n=Ember.A(["id"]);return t.eachAttribute(function(e){n.push(e)}),n.forEach(function(n){r.push(e(t,n))}),r},getRecordFilterValues:function(e){return{isNew:e.get("isNew"),isModified:e.get("isDirty")&&!e.get("isNew"),isClean:!e.get("isDirty")}},getRecordColor:function(e){var t="black";return e.get("isNew")?t="green":e.get("isDirty")&&(t="blue"),t},observeRecord:function(e,t){var r=Ember.A(),n=this,i=Ember.A(["id","isNew","isDirty"]);e.eachAttribute(function(e){i.push(e)}),i.forEach(function(i){var a=function(){t(n.wrapRecord(e))};Ember.addObserver(e,i,a),r.push(function(){Ember.removeObserver(e,i,a)})});var a=function(){r.forEach(function(e){e()})};return a}})}(),function(){DS.Transform=Ember.Object.extend({serialize:Ember.required(),deserialize:Ember.required()})}(),function(){DS.BooleanTransform=DS.Transform.extend({deserialize:function(e){var t=typeof e;return"boolean"===t?e:"string"===t?null!==e.match(/^true$|^t$|^1$/i):"number"===t?1===e:!1},serialize:function(e){return Boolean(e)}})}(),function(){DS.DateTransform=DS.Transform.extend({deserialize:function(e){var t=typeof e;return"string"===t?new Date(Ember.Date.parse(e)):"number"===t?new Date(e):null===e||void 0===e?e:null},serialize:function(e){return e instanceof Date?Number(e):null}})}(),function(){var e=Ember.isEmpty;DS.NumberTransform=DS.Transform.extend({deserialize:function(t){return e(t)?null:Number(t)},serialize:function(t){return e(t)?null:Number(t)}})}(),function(){var e=Ember.isNone;DS.StringTransform=DS.Transform.extend({deserialize:function(t){return e(t)?null:String(t)},serialize:function(t){return e(t)?null:String(t)}})}(),function(){Ember.set;Ember.onLoad("Ember.Application",function(e){e.initializer({name:"store",initialize:function(e,t){t.register("store:main",t.Store||DS.Store);var r=new DS.ContainerProxy(e);r.registerDeprecations([{deprecated:"serializer:_default",valid:"serializer:-default"},{deprecated:"serializer:_rest",valid:"serializer:-rest"},{deprecated:"adapter:_rest",valid:"adapter:-rest"}]),t.register("serializer:-default",DS.JSONSerializer),t.register("serializer:-rest",DS.RESTSerializer),t.register("adapter:-rest",DS.RESTAdapter),e.lookup("store:main")}}),e.initializer({name:"transforms",before:"store",initialize:function(e,t){t.register("transform:boolean",DS.BooleanTransform),t.register("transform:date",DS.DateTransform),t.register("transform:number",DS.NumberTransform),t.register("transform:string",DS.StringTransform)}}),e.initializer({name:"data-adapter",before:"store",initialize:function(e,t){t.register("data-adapter:main",DS.DebugAdapter)}}),e.initializer({name:"injectStore",before:"store",initialize:function(e,t){t.inject("controller","store","store:main"),t.inject("route","store","store:main"),t.inject("serializer","store","store:main"),t.inject("data-adapter","store","store:main")}})})}(),function(){Ember.Date=Ember.Date||{};var e=Date.parse,t=[1,4,5,6,7,10,11];Ember.Date.parse=function(r){var n,i,a=0;if(i=/^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(r)){for(var o,s=0;o=t[s];++s)i[o]=+i[o]||0;i[2]=(+i[2]||1)-1,i[3]=+i[3]||1,"Z"!==i[8]&&void 0!==i[9]&&(a=60*i[10]+i[11],"+"===i[9]&&(a=0-a)),n=Date.UTC(i[1],i[2],i[3],i[4],i[5]+a,i[6],i[7])}else n=e?e(r):0/0;return n},(Ember.EXTEND_PROTOTYPES===!0||Ember.EXTEND_PROTOTYPES.Date)&&(Date.parse=Ember.Date.parse)}(),function(){{var e=Ember.get;Ember.set}DS.RecordArray=Ember.ArrayProxy.extend(Ember.Evented,{type:null,content:null,isLoaded:!1,isUpdating:!1,store:null,objectAtContent:function(t){var r=e(this,"content");return r.objectAt(t)},update:function(){if(!e(this,"isUpdating")){var t=e(this,"store"),r=e(this,"type");return t.fetchAll(r,this)}},addRecord:function(t){e(this,"content").addObject(t)},removeRecord:function(t){e(this,"content").removeObject(t)},save:function(){var t="DS: RecordArray#save "+e(this,"type"),r=Ember.RSVP.all(this.invoke("save"),t).then(function(e){return Ember.A(e)},null,"DS: RecordArray#save apply Ember.NativeArray");return DS.PromiseArray.create({promise:r})}})}(),function(){var e=Ember.get;DS.FilteredRecordArray=DS.RecordArray.extend({filterFunction:null,isLoaded:!0,replace:function(){var t=e(this,"type").toString();throw new Error("The result of a client-side filter (on "+t+") is immutable.")},updateFilter:Ember.observer(function(){var t=e(this,"manager");t.updateFilter(this,e(this,"type"),e(this,"filterFunction"))},"filterFunction")})}(),function(){{var e=Ember.get;Ember.set}DS.AdapterPopulatedRecordArray=DS.RecordArray.extend({query:null,replace:function(){var t=e(this,"type").toString();throw new Error("The result of a server query (on "+t+") is immutable.")},load:function(t){var r=e(this,"store"),n=e(this,"type"),i=r.pushMany(n,t),a=r.metadataFor(n);this.setProperties({content:Ember.A(i),isLoaded:!0,meta:a}),Ember.run.once(this,"trigger","didLoad")}})}(),function(){var e=Ember.get,t=Ember.set,r=Ember.EnumerableUtils.map;DS.ManyArray=DS.RecordArray.extend({init:function(){this._super.apply(this,arguments),this._changesToSync=Ember.OrderedSet.create()},name:null,owner:null,isPolymorphic:!1,isLoaded:!1,promise:null,loadingRecordsCount:function(e){this.loadingRecordsCount=e},loadedRecord:function(){this.loadingRecordsCount--,0===this.loadingRecordsCount&&(t(this,"isLoaded",!0),this.trigger("didLoad"))},fetch:function(){var t=e(this,"content"),r=e(this,"store"),n=e(this,"owner"),i=Ember.RSVP.defer("DS: ManyArray#fetch "+e(this,"type")),a=t.filterProperty("isEmpty",!0);r.fetchMany(a,n,i)},replaceContent:function(e,t,n){n=r(n,function(e){return Ember.assert("You cannot add '"+e.constructor.typeKey+"' records to this relationship (only '"+this.type.typeKey+"' allowed)",!this.type||e instanceof this.type),e},this),this._super(e,t,n)},arrangedContentDidChange:function(){Ember.run.once(this,"fetch")},arrayContentWillChange:function(t,r){var n=e(this,"owner"),i=e(this,"name");if(!n._suspendedRelationships)for(var a=t;t+r>a;a++){var o=e(this,"content").objectAt(a),s=DS.RelationshipChange.createChange(n,o,e(this,"store"),{parentType:n.constructor,changeType:"remove",kind:"hasMany",key:i});this._changesToSync.add(s)}return this._super.apply(this,arguments)},arrayContentDidChange:function(t,r,n){this._super.apply(this,arguments);var i=e(this,"owner"),a=e(this,"name"),o=e(this,"store");if(!i._suspendedRelationships){for(var s=t;t+n>s;s++){var d=e(this,"content").objectAt(s),u=DS.RelationshipChange.createChange(i,d,o,{parentType:i.constructor,changeType:"add",kind:"hasMany",key:a});u.hasManyName=a,this._changesToSync.add(u)}this._changesToSync.forEach(function(e){e.sync()}),this._changesToSync.clear()}},createRecord:function(t){var r,n=e(this,"owner"),i=e(n,"store"),a=e(this,"type");return Ember.assert("You cannot add '"+a.typeKey+"' records to this polymorphic relationship.",!e(this,"isPolymorphic")),r=i.createRecord.call(i,a,t),this.pushObject(r),r}})}(),function(){function e(e,r,a,o){return r.eachRelationship(function(r,s){if(a.links&&a.links[r])return o&&s.options.async&&(o._relationships[r]=null),void 0;var d=s.kind,u=a[r];null!=u&&("belongsTo"===d?t(e,a,r,s,u):"hasMany"===d&&(n(e,a,r,s,u),i(o,r,u)))}),a}function t(e,t,n,i,a){if(!(R(a)||a instanceof DS.Model)){var o;"number"==typeof a||"string"==typeof a?(o=r(i,n,t),t[n]=e.recordForId(o,a)):"object"==typeof a&&(t[n]=e.recordForId(a.type,a.id))}}function r(e,t,r){return e.options.polymorphic?r[t+"Type"]:e.type}function n(e,r,n,i,a){for(var o=0,s=a.length;s>o;o++)t(e,a,o,i,a[o])}function i(e,t,r){e&&r.pushObjects(e.get(t).filterBy("isNew"))}function a(e){return DS.PromiseObject.create({promise:e})}function o(e){return DS.PromiseArray.create({promise:e})}function s(e){return e&&"function"==typeof e.then}function d(e,t,r){return e.lookup("serializer:"+t)||e.lookup("serializer:application")||e.lookup("serializer:"+r)||e.lookup("serializer:-default")}function u(e){return e.lookup("serializer:application")||e.lookup("serializer:-default")}function c(e,t){var r=e.serializer,n=e.defaultSerializer,i=e.container;return i&&void 0===r&&(r=d(i,t.typeKey,n)),(null===r||void 0===r)&&(r={extract:function(e,t,r){return r}}),r}function l(e,t,r,n){var i=e.find(t,r,n),a=c(e,r);return A(i,"DS: Handle Adapter#find of "+r+" with id: "+n).then(function(e){return Ember.assert("You made a request for a "+r.typeKey+" with id "+n+", but the adapter's response did not have any data",e),e=a.extract(t,r,e,n,"find"),t.push(r,e)},function(e){var i=t.getById(r,n);throw i.notFound(),e},"DS: Extract payload of '"+r+"'")}function h(e,t,r,n,i){var a=e.findMany(t,r,n,i),o=c(e,r);return A(a,"DS: Handle Adapter#findMany of "+r).then(function(e){e=o.extract(t,r,e,null,"findMany"),Ember.assert("The response from a findMany must be an Array, not "+Ember.inspect(e),"array"===Ember.typeOf(e)),t.pushMany(r,e)},null,"DS: Extract payload of "+r)}function p(e,t,r,n,i){var a=e.findHasMany(t,r,n,i),o=c(e,i.type);return A(a,"DS: Handle Adapter#findHasMany of "+r+" : "+i.type).then(function(e){e=o.extract(t,i.type,e,null,"findHasMany"),Ember.assert("The response from a findHasMany must be an Array, not "+Ember.inspect(e),"array"===Ember.typeOf(e));var n=t.pushMany(i.type,e);r.updateHasMany(i.key,n)},null,"DS: Extract payload of "+r+" : hasMany "+i.type)}function f(e,t,r,n,i){var a=e.findBelongsTo(t,r,n,i),o=c(e,i.type);return A(a,"DS: Handle Adapter#findBelongsTo of "+r+" : "+i.type).then(function(e){e=o.extract(t,i.type,e,null,"findBelongsTo");var r=t.push(i.type,e);return r.updateBelongsTo(i.key,r),r},null,"DS: Extract payload of "+r+" : "+i.type)}function m(e,t,r,n){var i=e.findAll(t,r,n),a=c(e,r);return A(i,"DS: Handle Adapter#findAll of "+r).then(function(e){return e=a.extract(t,r,e,null,"findAll"),Ember.assert("The response from a findAll must be an Array, not "+Ember.inspect(e),"array"===Ember.typeOf(e)),t.pushMany(r,e),t.didUpdateAll(r),t.all(r)},null,"DS: Extract payload of findAll "+r)}function y(e,t,r,n,i){var a=e.findQuery(t,r,n,i),o=c(e,r);return A(a,"DS: Handle Adapter#findQuery of "+r).then(function(e){return e=o.extract(t,r,e,null,"findQuery"),Ember.assert("The response from a findQuery must be an Array, not "+Ember.inspect(e),"array"===Ember.typeOf(e)),i.load(e),i},null,"DS: Extract payload of findQuery "+r)}function b(e,t,r,n){var i=n.constructor,a=e[r](t,i,n),o=c(e,i);return Ember.assert("Your adapter's '"+r+"' method must return a promise, but it returned "+a,s(a)),a.then(function(e){return e&&(e=o.extract(t,i,e,g(n,"id"),r)),t.didSaveRecord(n,e),n},function(e){throw e instanceof DS.InvalidError?t.recordWasInvalid(n,e.errors):t.recordWasError(n,e),e},"DS: Extract and notify about "+r+" completion of "+n)}var g=Ember.get,v=Ember.set,E=Ember.run.once,R=Ember.isNone,S=Ember.EnumerableUtils.forEach,D=Ember.EnumerableUtils.indexOf,T=Ember.EnumerableUtils.map,A=Ember.RSVP.resolve,F=Ember.copy,C=function(e){return null==e?null:e+""};DS.Store=Ember.Object.extend({init:function(){this.typeMaps={},this.recordArrayManager=DS.RecordArrayManager.create({store:this}),this._relationshipChanges={},this._pendingSave=[]},adapter:"-rest",serialize:function(e,t){return this.serializerFor(e.constructor.typeKey).serialize(e,t)},defaultAdapter:Ember.computed("adapter",function(){var e=g(this,"adapter");return Ember.assert("You tried to set `adapter` property to an instance of `DS.Adapter`, where it should be a name or a factory",!(e instanceof DS.Adapter)),"string"==typeof e&&(e=this.container.lookup("adapter:"+e)||this.container.lookup("adapter:application")||this.container.lookup("adapter:-rest")),DS.Adapter.detect(e)&&(e=e.create({container:this.container})),e}),createRecord:function(e,t){e=this.modelFor(e),t=F(t)||{},R(t.id)&&(t.id=this._generateId(e)),t.id=C(t.id);var r=this.buildRecord(e,t.id);return r.loadedData(),r.setProperties(t),r},_generateId:function(e){var t=this.adapterFor(e);return t&&t.generateIdForRecord?t.generateIdForRecord(this):null},deleteRecord:function(e){e.deleteRecord()},unloadRecord:function(e){e.unloadRecord()},find:function(e,t){return void 0===t?this.findAll(e):"object"===Ember.typeOf(t)?this.findQuery(e,t):this.findById(e,C(t))},findById:function(e,t){e=this.modelFor(e);var r=this.recordForId(e,t),n=this.fetchRecord(r)||A(r,"DS: Store#findById "+e+" with id: "+t);return a(n)},findByIds:function(e,t){var r=this;return o(Ember.RSVP.all(T(t,function(t){return r.findById(e,t)})).then(Ember.A,null,"DS: Store#findByIds of "+e+" complete"))},fetchRecord:function(e){if(R(e))return null;if(e._loadingPromise)return e._loadingPromise;if(!g(e,"isEmpty"))return null;var t=e.constructor,r=g(e,"id"),n=this.adapterFor(t);Ember.assert("You tried to find a record but you have no adapter (for "+t+")",n),Ember.assert("You tried to find a record but your adapter (for "+t+") does not implement 'find'",n.find);var i=l(n,this,t,r);return e.loadingData(i),i},getById:function(e,t){return this.hasRecordForId(e,t)?this.recordForId(e,t):null},reloadRecord:function(e){var t=e.constructor,r=this.adapterFor(t),n=g(e,"id");return Ember.assert("You cannot reload a record without an ID",n),Ember.assert("You tried to reload a record but you have no adapter (for "+t+")",r),Ember.assert("You tried to reload a record but your adapter does not implement `find`",r.find),l(r,this,t,n)},fetchMany:function(e,t,r){if(e.length){var n=Ember.MapWithDefault.create({defaultValue:function(){return Ember.A()}});S(e,function(e){n.get(e.constructor).push(e)}),S(n,function(e,n){var i=n.mapProperty("id"),a=this.adapterFor(e);Ember.assert("You tried to load many records but you have no adapter (for "+e+")",a),Ember.assert("You tried to load many records but your adapter does not implement `findMany`",a.findMany),r.resolve(h(a,this,e,i,t))},this)}},hasRecordForId:function(e,t){return t=C(t),e=this.modelFor(e),!!this.typeMapFor(e).idToRecord[t]},recordForId:function(e,t){e=this.modelFor(e),t=C(t);var r=this.typeMapFor(e).idToRecord[t];return r||(r=this.buildRecord(e,t)),r},findMany:function(e,t,r,n){r=this.modelFor(r),t=Ember.A(t);var i=t.filterProperty("isEmpty",!0),a=this.recordArrayManager.createManyArray(r,t);return S(i,function(e){e.loadingData()}),a.loadingRecordsCount=i.length,i.length?(S(i,function(e){this.recordArrayManager.registerWaitingRecordArray(e,a)},this),this.fetchMany(i,e,n)):(n&&n.resolve(),a.set("isLoaded",!0),Ember.run.once(a,"trigger","didLoad")),a},findHasMany:function(e,t,r,n){var i=this.adapterFor(e.constructor);Ember.assert("You tried to load a hasMany relationship but you have no adapter (for "+e.constructor+")",i),Ember.assert("You tried to load a hasMany relationship from a specified `link` in the original payload but your adapter does not implement `findHasMany`",i.findHasMany);var a=this.recordArrayManager.createManyArray(r.type,Ember.A([]));return n.resolve(p(i,this,e,t,r)),a},findBelongsTo:function(e,t,r,n){var i=this.adapterFor(e.constructor);Ember.assert("You tried to load a belongsTo relationship but you have no adapter (for "+e.constructor+")",i),Ember.assert("You tried to load a belongsTo relationship from a specified `link` in the original payload but your adapter does not implement `findBelongsTo`",i.findBelongsTo),n.resolve(f(i,this,e,t,r))},findQuery:function(e,t){e=this.modelFor(e);var r=this.recordArrayManager.createAdapterPopulatedRecordArray(e,t),n=this.adapterFor(e),i="DS: Store#findQuery "+e,a=Ember.RSVP.defer(i);return Ember.assert("You tried to load a query but you have no adapter (for "+e+")",n),Ember.assert("You tried to load a query but your adapter does not implement `findQuery`",n.findQuery),a.resolve(y(n,this,e,t,r)),o(a.promise)},findAll:function(e){return e=this.modelFor(e),this.fetchAll(e,this.all(e))},fetchAll:function(e,t){var r=this.adapterFor(e),n=this.typeMapFor(e).metadata.since;return v(t,"isUpdating",!0),Ember.assert("You tried to load all records but you have no adapter (for "+e+")",r),Ember.assert("You tried to load all records but your adapter does not implement `findAll`",r.findAll),o(m(r,this,e,n))},didUpdateAll:function(e){var t=this.typeMapFor(e).findAllCache;v(t,"isUpdating",!1)},all:function(e){e=this.modelFor(e);var t=this.typeMapFor(e),r=t.findAllCache;if(r)return r;var n=this.recordArrayManager.createRecordArray(e);return t.findAllCache=n,n},unloadAll:function(e){e=this.modelFor(e);for(var t,r=this.typeMapFor(e),n=r.records.splice(0);t=n.pop();)t.unloadRecord();r.findAllCache=null},filter:function(e,t,r){var n;3===arguments.length?n=this.findQuery(e,t):2===arguments.length&&(r=t),e=this.modelFor(e);var i=this.recordArrayManager.createFilteredRecordArray(e,r);return n=n||A(i),o(n.then(function(){return i},null,"DS: Store#filter of "+e))},recordIsLoaded:function(e,t){return this.hasRecordForId(e,t)?!g(this.recordForId(e,t),"isEmpty"):!1},metadataFor:function(e){return e=this.modelFor(e),this.typeMapFor(e).metadata},dataWasUpdated:function(e,t){this.recordArrayManager.recordDidChange(t)},scheduleSave:function(e,t){e.adapterWillCommit(),this._pendingSave.push([e,t]),E(this,"flushPendingSave")},flushPendingSave:function(){var e=this._pendingSave.slice();this._pendingSave=[],S(e,function(e){var t,r=e[0],n=e[1],i=this.adapterFor(r.constructor);t=g(r,"isNew")?"createRecord":g(r,"isDeleted")?"deleteRecord":"updateRecord",n.resolve(b(i,this,t,r))},this)},didSaveRecord:function(t,r){r&&(r=e(this,t.constructor,r,t),this.updateId(t,r)),t.adapterDidCommit(r)},recordWasInvalid:function(e,t){e.adapterDidInvalidate(t)},recordWasError:function(e){e.adapterDidError()},updateId:function(e,t){var r=g(e,"id"),n=C(t.id);Ember.assert("An adapter cannot assign a new id to a record that already has an id. "+e+" had id: "+r+" and you tried to update it with "+n+". This likely happened because your server returned data in response to a find or update that had a different id than the one you sent.",null===r||n===r),this.typeMapFor(e.constructor).idToRecord[n]=e,v(e,"id",n)},typeMapFor:function(e){var t,r=g(this,"typeMaps"),n=Ember.guidFor(e);return(t=r[n])?t:(t={idToRecord:{},records:[],metadata:{}},r[n]=t,t)},_load:function(e,t,r){var n=C(t.id),i=this.recordForId(e,n);return i.setupData(t,r),this.recordArrayManager.recordDidChange(i),i},modelFor:function(e){var t;if("string"==typeof e){var r=this.container.normalize("model:"+e);if(t=this.container.lookupFactory(r),!t)throw new Ember.Error("No model was found for '"+e+"'");t.typeKey=r.split(":",2)[1]}else t=e;return t.store=this,t},push:function(t,r,n){return Ember.assert("You must include an `id` in a hash passed to `push`",null!=r.id),t=this.modelFor(t),r=e(this,t,r),this._load(t,r,n),this.recordForId(t,r.id)},pushPayload:function(e,t){var r;t?r=this.serializerFor(e):(t=e,r=u(this.container),Ember.assert("You cannot use `store#pushPayload` without a type unless your default serializer defines `pushPayload`",r.pushPayload)),r.pushPayload(this,t)},update:function(e,t){return Ember.assert("You must include an `id` in a hash passed to `update`",null!=t.id),this.push(e,t,!0)},pushMany:function(e,t){return T(t,function(t){return this.push(e,t)},this)},metaForType:function(e,t){e=this.modelFor(e),Ember.merge(this.typeMapFor(e).metadata,t)},buildRecord:function(e,t,r){var n=this.typeMapFor(e),i=n.idToRecord;Ember.assert("The id "+t+" has already been used with another record of type "+e.toString()+".",!t||!i[t]);var a=e._create({id:t,store:this,container:this.container});return r&&a.setupData(r),t&&(i[t]=a),n.records.push(a),a},dematerializeRecord:function(e){var t=e.constructor,r=this.typeMapFor(t),n=g(e,"id");e.updateRecordArrays(),n&&delete r.idToRecord[n];var i=D(r.records,e);r.records.splice(i,1)},addRelationshipChangeFor:function(e,t,r,n,i){var a=e.clientId,o=r?r:r,s=t+n,d=this._relationshipChanges;a in d||(d[a]={}),o in d[a]||(d[a][o]={}),s in d[a][o]||(d[a][o][s]={}),d[a][o][s][i.changeType]=i},removeRelationshipChangeFor:function(e,t,r,n,i){var a=e.clientId,o=r?r.clientId:r,s=this._relationshipChanges,d=t+n;a in s&&o in s[a]&&d in s[a][o]&&delete s[a][o][d][i]},relationshipChangePairsFor:function(e){var t=[];if(!e)return t;var r=this._relationshipChanges[e.clientId];for(var n in r)if(r.hasOwnProperty(n))for(var i in r[n])r[n].hasOwnProperty(i)&&t.push(r[n][i]);return t},adapterFor:function(e){var t,r=this.container;return r&&(t=r.lookup("adapter:"+e.typeKey)||r.lookup("adapter:application")),t||g(this,"defaultAdapter")},serializerFor:function(e){e=this.modelFor(e);var t=this.adapterFor(e);return d(this.container,e.typeKey,t&&t.defaultSerializer)}}),DS.PromiseArray=Ember.ArrayProxy.extend(Ember.PromiseProxyMixin),DS.PromiseObject=Ember.ObjectProxy.extend(Ember.PromiseProxyMixin)}(),function(){function e(t){var r,n={};for(var i in t)r=t[i],n[i]=r&&"object"==typeof r?e(r):r;return n}function t(e,t){for(var r in t)e[r]=t[r];return e}function r(r){var n=e(s);return t(n,r)}function n(e,r,i){e=t(r?Ember.create(r):{},e),e.parentState=r,e.stateName=i;for(var a in e)e.hasOwnProperty(a)&&"parentState"!==a&&"stateName"!==a&&"object"==typeof e[a]&&(e[a]=n(e[a],e,i+"."+a));return e}var i=Ember.get,a=Ember.set,o=function(e,t){t.value===t.originalValue?(delete e._attributes[t.name],e.send("propertyWasReset",t.name)):t.value!==t.oldValue&&e.send("becomeDirty"),e.updateRecordArraysLater()},s={initialState:"uncommitted",isDirty:!0,uncommitted:{didSetProperty:o,propertyWasReset:function(e){var t=!1;for(var r in e._attributes){t=!0;break}t||e.send("rolledBack")},pushedData:Ember.K,becomeDirty:Ember.K,willCommit:function(e){e.transitionTo("inFlight")},reloadRecord:function(e,t){t(i(e,"store").reloadRecord(e))},rolledBack:function(e){e.transitionTo("loaded.saved")},becameInvalid:function(e){e.transitionTo("invalid")},rollback:function(e){e.rollback()}},inFlight:{isSaving:!0,didSetProperty:o,becomeDirty:Ember.K,pushedData:Ember.K,willCommit:Ember.K,didCommit:function(e){var t=i(this,"dirtyType");e.transitionTo("saved"),e.send("invokeLifecycleCallbacks",t)},becameInvalid:function(e){e.transitionTo("invalid"),e.send("invokeLifecycleCallbacks")},becameError:function(e){e.transitionTo("uncommitted"),e.triggerLater("becameError",e)}},invalid:{isValid:!1,deleteRecord:function(e){e.transitionTo("deleted.uncommitted"),e.clearRelationships()},didSetProperty:function(e,t){i(e,"errors").remove(t.name),o(e,t)},becomeDirty:Ember.K,rolledBack:function(e){i(e,"errors").clear()},becameValid:function(e){e.transitionTo("uncommitted")},invokeLifecycleCallbacks:function(e){e.triggerLater("becameInvalid",e)}}},d=r({dirtyType:"created",isNew:!0});d.uncommitted.rolledBack=function(e){e.transitionTo("deleted.saved")};var u=r({dirtyType:"updated"});d.uncommitted.deleteRecord=function(e){e.clearRelationships(),e.transitionTo("deleted.saved")},d.uncommitted.rollback=function(e){s.uncommitted.rollback.apply(this,arguments),e.transitionTo("deleted.saved")},d.uncommitted.propertyWasReset=Ember.K,u.uncommitted.deleteRecord=function(e){e.transitionTo("deleted.uncommitted"),e.clearRelationships()};var c={isEmpty:!1,isLoading:!1,isLoaded:!1,isDirty:!1,isSaving:!1,isDeleted:!1,isNew:!1,isValid:!0,rolledBack:Ember.K,propertyWasReset:Ember.K,empty:{isEmpty:!0,loadingData:function(e,t){e._loadingPromise=t,e.transitionTo("loading")},loadedData:function(e){e.transitionTo("loaded.created.uncommitted"),e.suspendRelationshipObservers(function(){e.notifyPropertyChange("data")})},pushedData:function(e){e.transitionTo("loaded.saved"),e.triggerLater("didLoad")}},loading:{isLoading:!0,exit:function(e){e._loadingPromise=null},pushedData:function(e){e.transitionTo("loaded.saved"),e.triggerLater("didLoad"),a(e,"isError",!1)},becameError:function(e){e.triggerLater("becameError",e)},notFound:function(e){e.transitionTo("empty")}},loaded:{initialState:"saved",isLoaded:!0,saved:{setup:function(e){var t=e._attributes,r=!1;for(var n in t)if(t.hasOwnProperty(n)){r=!0;break}r&&e.adapterDidDirty()},didSetProperty:o,pushedData:Ember.K,becomeDirty:function(e){e.transitionTo("updated.uncommitted")},willCommit:function(e){e.transitionTo("updated.inFlight")},reloadRecord:function(e,t){t(i(e,"store").reloadRecord(e))},deleteRecord:function(e){e.transitionTo("deleted.uncommitted"),e.clearRelationships()},unloadRecord:function(e){e.clearRelationships(),e.transitionTo("deleted.saved")},didCommit:function(e){e.send("invokeLifecycleCallbacks",i(e,"lastDirtyType"))},notFound:Ember.K},created:d,updated:u},deleted:{initialState:"uncommitted",dirtyType:"deleted",isDeleted:!0,isLoaded:!0,isDirty:!0,setup:function(e){e.updateRecordArrays()},uncommitted:{willCommit:function(e){e.transitionTo("inFlight")},rollback:function(e){e.rollback()},becomeDirty:Ember.K,deleteRecord:Ember.K,rolledBack:function(e){e.transitionTo("loaded.saved")}},inFlight:{isSaving:!0,willCommit:Ember.K,didCommit:function(e){e.transitionTo("saved"),e.send("invokeLifecycleCallbacks")},becameError:function(e){e.transitionTo("uncommitted"),e.triggerLater("becameError",e)}},saved:{isDirty:!1,setup:function(e){var t=i(e,"store");t.dematerializeRecord(e)},invokeLifecycleCallbacks:function(e){e.triggerLater("didDelete",e),e.triggerLater("didCommit",e)}}},invokeLifecycleCallbacks:function(e,t){"created"===t?e.triggerLater("didCreate",e):e.triggerLater("didUpdate",e),e.triggerLater("didCommit",e)}};c=n(c,null,"root"),DS.RootState=c}(),function(){var e=Ember.get,t=Ember.isEmpty;DS.Errors=Ember.Object.extend(Ember.Enumerable,Ember.Evented,{registerHandlers:function(e,t,r){this.on("becameInvalid",e,t),this.on("becameValid",e,r)},errorsByAttributeName:Ember.reduceComputed("content",{initialValue:function(){return Ember.MapWithDefault.create({defaultValue:function(){return Ember.A()}})},addedItem:function(e,t){return e.get(t.attribute).pushObject(t),e},removedItem:function(e,t){return e.get(t.attribute).removeObject(t),e}}),errorsFor:function(t){return e(this,"errorsByAttributeName").get(t)},messages:Ember.computed.mapBy("content","message"),content:Ember.computed(function(){return Ember.A()}),unknownProperty:function(e){var r=this.errorsFor(e);return t(r)?null:r},nextObject:function(t){return e(this,"content").objectAt(t)},length:Ember.computed.oneWay("content.length").readOnly(),isEmpty:Ember.computed.not("length").readOnly(),add:function(t,r){var n=e(this,"isEmpty");r=this._findOrCreateMessages(t,r),e(this,"content").addObjects(r),this.notifyPropertyChange(t),this.enumerableContentDidChange(),n&&!e(this,"isEmpty")&&this.trigger("becameInvalid")},_findOrCreateMessages:function(e,t){var r=this.errorsFor(e);return Ember.makeArray(t).map(function(t){return r.findBy("message",t)||{attribute:e,message:t}})},remove:function(t){if(!e(this,"isEmpty")){var r=e(this,"content").rejectBy("attribute",t);e(this,"content").setObjects(r),this.notifyPropertyChange(t),this.enumerableContentDidChange(),e(this,"isEmpty")&&this.trigger("becameValid")}},clear:function(){e(this,"isEmpty")||(e(this,"content").clear(),this.enumerableContentDidChange(),this.trigger("becameValid"))},has:function(e){return!t(this.errorsFor(e))}})}(),function(){var e=Ember.get,t=Ember.set,r=Ember.merge,n=Ember.computed("currentState",function(t){return e(e(this,"currentState"),t)}).readOnly();DS.Model=Ember.Object.extend(Ember.Evented,{isEmpty:n,isLoading:n,isLoaded:n,isDirty:n,isSaving:n,isDeleted:n,isNew:n,isValid:n,dirtyType:n,isError:!1,isReloading:!1,clientId:null,id:null,transaction:null,currentState:null,errors:null,serialize:function(t){var r=e(this,"store");return r.serialize(this,t)},toJSON:function(e){var t=DS.JSONSerializer.create({container:this.container});return t.serialize(this,e)},didLoad:Ember.K,didUpdate:Ember.K,didCreate:Ember.K,didDelete:Ember.K,becameInvalid:Ember.K,becameError:Ember.K,data:Ember.computed(function(){return this._data=this._data||{},this._data}).property(),_data:null,init:function(){t(this,"currentState",DS.RootState.empty);var e=DS.Errors.create();e.registerHandlers(this,function(){this.send("becameInvalid")},function(){this.send("becameValid")}),t(this,"errors",e),this._super(),this._setup()},_setup:function(){this._changesToSync={},this._deferredTriggers=[],this._data={},this._attributes={},this._inFlightAttributes={},this._relationships={}},send:function(t,r){var n=e(this,"currentState");return n[t]||this._unhandledEvent(n,t,r),n[t](this,r)},transitionTo:function(r){var n=r.split(".",1),i=e(this,"currentState"),a=i;do a.exit&&a.exit(this),a=a.parentState;while(!a.hasOwnProperty(n));var o,s,d=r.split("."),u=[],c=[];for(o=0,s=d.length;s>o;o++)a=a[d[o]],a.enter&&c.push(a),a.setup&&u.push(a);for(o=0,s=c.length;s>o;o++)c[o].enter(this);for(t(this,"currentState",a),o=0,s=u.length;s>o;o++)u[o].setup(this);this.updateRecordArraysLater()},_unhandledEvent:function(e,t,r){var n="Attempted to handle event `"+t+"` ";throw n+="on "+String(this)+" while in state ",n+=e.stateName+". ",void 0!==r&&(n+="Called with "+Ember.inspect(r)+"."),new Ember.Error(n) +},withTransaction:function(t){var r=e(this,"transaction");r&&t(r)},loadingData:function(e){this.send("loadingData",e)},loadedData:function(){this.send("loadedData")},notFound:function(){this.send("notFound")},pushedData:function(){this.send("pushedData")},deleteRecord:function(){this.send("deleteRecord")},destroyRecord:function(){return this.deleteRecord(),this.save()},unloadRecord:function(){Ember.assert("You can only unload a loaded, non-dirty record.",!e(this,"isDirty")),this.send("unloadRecord")},clearRelationships:function(){this.eachRelationship(function(e,r){if("belongsTo"===r.kind)t(this,e,null);else if("hasMany"===r.kind){var n=this._relationships[r.name];n&&n.clear()}},this)},updateRecordArrays:function(){this._updatingRecordArraysLater=!1,e(this,"store").dataWasUpdated(this.constructor,this)},changedAttributes:function(){var t,r=e(this,"_data"),n=e(this,"_attributes"),i={};for(t in n)i[t]=[r[t],n[t]];return i},adapterWillCommit:function(){this.send("willCommit")},adapterDidCommit:function(e){t(this,"isError",!1),e?this._data=e:Ember.mixin(this._data,this._inFlightAttributes),this._inFlightAttributes={},this.send("didCommit"),this.updateRecordArraysLater(),e&&this.suspendRelationshipObservers(function(){this.notifyPropertyChange("data")})},adapterDidDirty:function(){this.send("becomeDirty"),this.updateRecordArraysLater()},dataDidChange:Ember.observer(function(){this.reloadHasManys()},"data"),reloadHasManys:function(){var t=e(this.constructor,"relationshipsByName");this.updateRecordArraysLater(),t.forEach(function(e,t){this._data.links&&this._data.links[e]||"hasMany"===t.kind&&this.hasManyDidChange(t.key)},this)},hasManyDidChange:function(e){var r=this._relationships[e];if(r){var n=this._data[e]||[];t(r,"content",Ember.A(n)),t(r,"isLoaded",!0),r.trigger("didLoad")}},updateRecordArraysLater:function(){this._updatingRecordArraysLater||(this._updatingRecordArraysLater=!0,Ember.run.schedule("actions",this,this.updateRecordArrays))},setupData:function(e,t){t?Ember.merge(this._data,e):this._data=e;var r=this._relationships;this.eachRelationship(function(t,n){e.links&&e.links[t]||n.options.async&&(r[t]=null)}),e&&this.pushedData(),this.suspendRelationshipObservers(function(){this.notifyPropertyChange("data")})},materializeId:function(e){t(this,"id",e)},materializeAttributes:function(e){Ember.assert("Must pass a hash of attributes to materializeAttributes",!!e),r(this._data,e)},materializeAttribute:function(e,t){this._data[e]=t},updateHasMany:function(e,t){this._data[e]=t,this.hasManyDidChange(e)},updateBelongsTo:function(e,t){this._data[e]=t},rollback:function(){this._attributes={},e(this,"isError")&&(this._inFlightAttributes={},t(this,"isError",!1)),e(this,"isValid")||(this._inFlightAttributes={}),this.send("rolledBack"),this.suspendRelationshipObservers(function(){this.notifyPropertyChange("data")})},toStringExtension:function(){return e(this,"id")},suspendRelationshipObservers:function(t,r){var n=e(this.constructor,"relationshipNames").belongsTo,i=this;try{this._suspendedRelationships=!0,Ember._suspendObservers(i,n,null,"belongsToDidChange",function(){Ember._suspendBeforeObservers(i,n,null,"belongsToWillChange",function(){t.call(r||i)})})}finally{this._suspendedRelationships=!1}},save:function(){var e="DS: Model#save "+this,t=Ember.RSVP.defer(e);return this.get("store").scheduleSave(this,t),this._inFlightAttributes=this._attributes,this._attributes={},DS.PromiseObject.create({promise:t.promise})},reload:function(){t(this,"isReloading",!0);var e=this,r="DS: Model#reload of "+this,n=new Ember.RSVP.Promise(function(t){e.send("reloadRecord",t)},r).then(function(){return e.set("isReloading",!1),e.set("isError",!1),e},function(t){throw e.set("isError",!0),t},"DS: Model#reload complete, update flags");return DS.PromiseObject.create({promise:n})},adapterDidUpdateAttribute:function(e,t){void 0!==t?(this._data[e]=t,this.notifyPropertyChange(e)):this._data[e]=this._inFlightAttributes[e],this.updateRecordArraysLater()},adapterDidInvalidate:function(t){function r(e){t[e]&&n.add(e,t[e])}var n=e(this,"errors");this.eachAttribute(r),this.eachRelationship(r)},adapterDidError:function(){this.send("becameError"),t(this,"isError",!0)},trigger:function(e){Ember.tryInvoke(this,e,[].slice.call(arguments,1)),this._super.apply(this,arguments)},triggerLater:function(){1===this._deferredTriggers.push(arguments)&&Ember.run.schedule("actions",this,"_triggerDeferredTriggers")},_triggerDeferredTriggers:function(){for(var e=0,t=this._deferredTriggers.length;t>e;e++)this.trigger.apply(this,this._deferredTriggers[e]);this._deferredTriggers.length=0}}),DS.Model.reopenClass({_create:DS.Model.create,create:function(){throw new Ember.Error("You should not call `create` on a model. Instead, call `store.createRecord` with the attributes you would like to set.")}})}(),function(){function e(e,t){return"function"==typeof t.defaultValue?t.defaultValue():t.defaultValue}function t(e,t){return e._attributes.hasOwnProperty(t)||e._inFlightAttributes.hasOwnProperty(t)||e._data.hasOwnProperty(t)}function r(e,t){return e._attributes.hasOwnProperty(t)?e._attributes[t]:e._inFlightAttributes.hasOwnProperty(t)?e._inFlightAttributes[t]:e._data[t]}var n=Ember.get;DS.Model.reopenClass({attributes:Ember.computed(function(){var e=Ember.Map.create();return this.eachComputedProperty(function(t,r){r.isAttribute&&(Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('')` from "+this.toString(),"id"!==t),r.name=t,e.set(t,r))}),e}),transformedAttributes:Ember.computed(function(){var e=Ember.Map.create();return this.eachAttribute(function(t,r){r.type&&e.set(t,r.type)}),e}),eachAttribute:function(e,t){n(this,"attributes").forEach(function(r,n){e.call(t,r,n)},t)},eachTransformedAttribute:function(e,t){n(this,"transformedAttributes").forEach(function(r,n){e.call(t,r,n)})}}),DS.Model.reopen({eachAttribute:function(e,t){this.constructor.eachAttribute(e,t)}}),DS.attr=function(n,i){i=i||{};var a={type:n,isAttribute:!0,options:i};return Ember.computed(function(n,a){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('')` from "+this.constructor.toString(),"id"!==n);var o=this._attributes[n]||this._inFlightAttributes[n]||this._data[n];return this.send("didSetProperty",{name:n,oldValue:o,originalValue:this._data[n],value:a}),this._attributes[n]=a,a}return t(this,n)?r(this,n):e(this,i,n)}).property("data").meta(a)}}(),function(){var e=DS.AttributeChange=function(e){this.record=e.record,this.store=e.store,this.name=e.name,this.value=e.value,this.oldValue=e.oldValue};e.createChange=function(t){return new e(t)},e.prototype={sync:function(){this.value!==this.oldValue&&(this.record.send("becomeDirty"),this.record.updateRecordArraysLater()),this.destroy()},destroy:function(){delete this.record._changesToSync[this.name]}}}(),function(){function e(e){return"object"==typeof e&&(!e.then||"function"!=typeof e.then)}var t=Ember.get,r=Ember.set,n=Ember.EnumerableUtils.forEach;DS.RelationshipChange=function(e){this.parentRecord=e.parentRecord,this.childRecord=e.childRecord,this.firstRecord=e.firstRecord,this.firstRecordKind=e.firstRecordKind,this.firstRecordName=e.firstRecordName,this.secondRecord=e.secondRecord,this.secondRecordKind=e.secondRecordKind,this.secondRecordName=e.secondRecordName,this.changeType=e.changeType,this.store=e.store,this.committed={}},DS.RelationshipChangeAdd=function(e){DS.RelationshipChange.call(this,e)},DS.RelationshipChangeRemove=function(e){DS.RelationshipChange.call(this,e)},DS.RelationshipChange.create=function(e){return new DS.RelationshipChange(e)},DS.RelationshipChangeAdd.create=function(e){return new DS.RelationshipChangeAdd(e)},DS.RelationshipChangeRemove.create=function(e){return new DS.RelationshipChangeRemove(e)},DS.OneToManyChange={},DS.OneToNoneChange={},DS.ManyToNoneChange={},DS.OneToOneChange={},DS.ManyToManyChange={},DS.RelationshipChange._createChange=function(e){return"add"===e.changeType?DS.RelationshipChangeAdd.create(e):"remove"===e.changeType?DS.RelationshipChangeRemove.create(e):void 0},DS.RelationshipChange.determineRelationshipType=function(e,t){var r,n,i=t.key,a=t.kind,o=e.inverseFor(i);return o&&(r=o.name,n=o.kind),o?"belongsTo"===n?"belongsTo"===a?"oneToOne":"manyToOne":"belongsTo"===a?"oneToMany":"manyToMany":"belongsTo"===a?"oneToNone":"manyToNone"},DS.RelationshipChange.createChange=function(e,t,r,n){var i,a=e.constructor;return i=DS.RelationshipChange.determineRelationshipType(a,n),"oneToMany"===i?DS.OneToManyChange.createChange(e,t,r,n):"manyToOne"===i?DS.OneToManyChange.createChange(t,e,r,n):"oneToNone"===i?DS.OneToNoneChange.createChange(e,t,r,n):"manyToNone"===i?DS.ManyToNoneChange.createChange(e,t,r,n):"oneToOne"===i?DS.OneToOneChange.createChange(e,t,r,n):"manyToMany"===i?DS.ManyToManyChange.createChange(e,t,r,n):void 0},DS.OneToNoneChange.createChange=function(e,t,r,n){var i=n.key,a=DS.RelationshipChange._createChange({parentRecord:t,childRecord:e,firstRecord:e,store:r,changeType:n.changeType,firstRecordName:i,firstRecordKind:"belongsTo"});return r.addRelationshipChangeFor(e,i,t,null,a),a},DS.ManyToNoneChange.createChange=function(e,t,r,n){var i=n.key,a=DS.RelationshipChange._createChange({parentRecord:e,childRecord:t,secondRecord:e,store:r,changeType:n.changeType,secondRecordName:n.key,secondRecordKind:"hasMany"});return r.addRelationshipChangeFor(e,i,t,null,a),a},DS.ManyToManyChange.createChange=function(e,t,r,n){var i=n.key,a=DS.RelationshipChange._createChange({parentRecord:t,childRecord:e,firstRecord:e,secondRecord:t,firstRecordKind:"hasMany",secondRecordKind:"hasMany",store:r,changeType:n.changeType,firstRecordName:i});return r.addRelationshipChangeFor(e,i,t,null,a),a},DS.OneToOneChange.createChange=function(e,t,r,n){var i;n.parentType?i=n.parentType.inverseFor(n.key).name:n.key?i=n.key:Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent",!1);var a=DS.RelationshipChange._createChange({parentRecord:t,childRecord:e,firstRecord:e,secondRecord:t,firstRecordKind:"belongsTo",secondRecordKind:"belongsTo",store:r,changeType:n.changeType,firstRecordName:i});return r.addRelationshipChangeFor(e,i,t,null,a),a},DS.OneToOneChange.maintainInvariant=function(e,r,n,i){if("add"===e.changeType&&r.recordIsMaterialized(n)){var a=t(n,i);if(a){var o=DS.OneToOneChange.createChange(n,a,r,{parentType:e.parentType,hasManyName:e.hasManyName,changeType:"remove",key:e.key});r.addRelationshipChangeFor(n,i,e.parentRecord,null,o),o.sync()}}},DS.OneToManyChange.createChange=function(e,t,r,n){var i;n.parentType?(i=n.parentType.inverseFor(n.key).name,DS.OneToManyChange.maintainInvariant(n,r,e,i)):n.key?i=n.key:Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent",!1);var a=DS.RelationshipChange._createChange({parentRecord:t,childRecord:e,firstRecord:e,secondRecord:t,firstRecordKind:"belongsTo",secondRecordKind:"hasMany",store:r,changeType:n.changeType,firstRecordName:i});return r.addRelationshipChangeFor(e,i,t,a.getSecondRecordName(),a),a},DS.OneToManyChange.maintainInvariant=function(e,r,n,i){if("add"===e.changeType&&n){var a=t(n,i);if(a){var o=DS.OneToManyChange.createChange(n,a,r,{parentType:e.parentType,hasManyName:e.hasManyName,changeType:"remove",key:e.key});r.addRelationshipChangeFor(n,i,e.parentRecord,o.getSecondRecordName(),o),o.sync()}}},DS.RelationshipChange.prototype={getSecondRecordName:function(){var e,t=this.secondRecordName;if(!t){if(e=this.secondRecord,!e)return;var r=this.firstRecord.constructor,n=r.inverseFor(this.firstRecordName);this.secondRecordName=n.name}return this.secondRecordName},getFirstRecordName:function(){var e=this.firstRecordName;return e},destroy:function(){var e=this.childRecord,t=this.getFirstRecordName(),r=this.getSecondRecordName(),n=this.store;n.removeRelationshipChangeFor(e,t,this.parentRecord,r,this.changeType)},getSecondRecord:function(){return this.secondRecord},getFirstRecord:function(){return this.firstRecord},coalesce:function(){var e=this.store.relationshipChangePairsFor(this.firstRecord);n(e,function(e){var t=e.add,r=e.remove;t&&r&&(t.destroy(),r.destroy())})}},DS.RelationshipChangeAdd.prototype=Ember.create(DS.RelationshipChange.create({})),DS.RelationshipChangeRemove.prototype=Ember.create(DS.RelationshipChange.create({})),DS.RelationshipChangeAdd.prototype.changeType="add",DS.RelationshipChangeAdd.prototype.sync=function(){var n=this.getSecondRecordName(),i=this.getFirstRecordName(),a=this.getFirstRecord(),o=this.getSecondRecord();o instanceof DS.Model&&a instanceof DS.Model&&("belongsTo"===this.secondRecordKind?o.suspendRelationshipObservers(function(){r(o,n,a)}):"hasMany"===this.secondRecordKind&&o.suspendRelationshipObservers(function(){var r=t(o,n);e(r)&&r.addObject(a)})),a instanceof DS.Model&&o instanceof DS.Model&&t(a,i)!==o&&("belongsTo"===this.firstRecordKind?a.suspendRelationshipObservers(function(){r(a,i,o)}):"hasMany"===this.firstRecordKind&&a.suspendRelationshipObservers(function(){var r=t(a,i);e(r)&&r.addObject(o)})),this.coalesce()},DS.RelationshipChangeRemove.prototype.changeType="remove",DS.RelationshipChangeRemove.prototype.sync=function(){var n=this.getSecondRecordName(),i=this.getFirstRecordName(),a=this.getFirstRecord(),o=this.getSecondRecord();o instanceof DS.Model&&a instanceof DS.Model&&("belongsTo"===this.secondRecordKind?o.suspendRelationshipObservers(function(){r(o,n,null)}):"hasMany"===this.secondRecordKind&&o.suspendRelationshipObservers(function(){var r=t(o,n);e(r)&&r.removeObject(a)})),a instanceof DS.Model&&t(a,i)&&("belongsTo"===this.firstRecordKind?a.suspendRelationshipObservers(function(){r(a,i,null)}):"hasMany"===this.firstRecordKind&&a.suspendRelationshipObservers(function(){var r=t(a,i);e(r)&&r.removeObject(o)})),this.coalesce()}}(),function(){function e(e,n,i){return Ember.computed(function(n,a){var o=t(this,"data"),s=t(this,"store"),d="DS: Async belongsTo "+this+" : "+n;if(2===arguments.length)return Ember.assert("You can only add a '"+e+"' record to this relationship",!a||a instanceof s.modelFor(e)),void 0===a?null:DS.PromiseObject.create({promise:Ember.RSVP.resolve(a,d)});var u=o.links&&o.links[n],c=o[n];if(r(c)){if(u){var l=Ember.RSVP.defer("DS: Async belongsTo (link) "+this+" : "+n);return s.findBelongsTo(this,u,i,l),DS.PromiseObject.create({promise:l.promise})}return null}var h=s.fetchRecord(c)||Ember.RSVP.resolve(c,d);return DS.PromiseObject.create({promise:h})}).property("data").meta(i)}var t=Ember.get,r=(Ember.set,Ember.isNone);DS.belongsTo=function(n,i){"object"==typeof n?(i=n,n=void 0):Ember.assert("The first argument DS.belongsTo must be a model type or string, like DS.belongsTo(App.Person)",!!n&&("string"==typeof n||DS.Model.detect(n))),i=i||{};var a={type:n,isRelationship:!0,options:i,kind:"belongsTo"};return i.async?e(n,i,a):Ember.computed(function(e,i){var a,o,s=t(this,"data"),d=t(this,"store");return o="string"==typeof n?d.modelFor(n):n,2===arguments.length?(Ember.assert("You can only add a '"+n+"' record to this relationship",!i||i instanceof o),void 0===i?null:i):(a=s[e],r(a)?null:(d.fetchRecord(a),a))}).property("data").meta(a)},DS.Model.reopen({belongsToWillChange:Ember.beforeObserver(function(e,r){if(t(e,"isLoaded")){var n=t(e,r);if(n){var i=t(e,"store"),a=DS.RelationshipChange.createChange(e,n,i,{key:r,kind:"belongsTo",changeType:"remove"});a.sync(),this._changesToSync[r]=a}}}),belongsToDidChange:Ember.immediateObserver(function(e,r){if(t(e,"isLoaded")){var n=t(e,r);if(n){var i=t(e,"store"),a=DS.RelationshipChange.createChange(e,n,i,{key:r,kind:"belongsTo",changeType:"add"});a.sync()}}delete this._changesToSync[r]})})}(),function(){function e(e,r,n){return Ember.computed(function(e){var a=this._relationships[e],o="DS: Async hasMany "+this+" : "+e;if(!a){var s=Ember.RSVP.defer(o);a=t(this,e,r,function(t,r){var a,o=r.links&&r.links[e];return a=o?t.findHasMany(this,o,n,s):t.findMany(this,r[e],n.type,s),i(a,"promise",s.promise),a})}var d=a.get("promise").then(function(){return a},null,"DS: Async hasMany records received");return DS.PromiseArray.create({promise:d})}).property("data").meta(n)}function t(e,t,r,i){var o=e._relationships;if(o[t])return o[t];var s=n(e,"data"),d=n(e,"store"),u=o[t]=i.call(e,d,s);return a(u,{owner:e,name:t,isPolymorphic:r.polymorphic})}function r(r,n){n=n||{};var i={type:r,isRelationship:!0,options:n,kind:"hasMany"};return n.async?e(r,n,i):Ember.computed(function(e){return t(this,e,n,function(t,r){var n=r[e];return Ember.assert("You looked up the '"+e+"' relationship on '"+this+"' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`)",Ember.A(n).everyProperty("isEmpty",!1)),t.findMany(this,r[e],i.type)})}).property("data").meta(i)}var n=Ember.get,i=Ember.set,a=Ember.setProperties;DS.hasMany=function(e,t){return"object"==typeof e&&(t=e,e=void 0),r(e,t)}}(),function(){{var e=Ember.get;Ember.set}DS.Model.reopen({didDefineProperty:function(e,t,r){if(r instanceof Ember.Descriptor){var n=r.meta();n.isRelationship&&"belongsTo"===n.kind&&(Ember.addObserver(e,t,null,"belongsToDidChange"),Ember.addBeforeObserver(e,t,null,"belongsToWillChange")),n.parentType=e.constructor}}}),DS.Model.reopenClass({typeForRelationship:function(t){var r=e(this,"relationshipsByName").get(t);return r&&r.type},inverseFor:function(t){function r(t,n,i){i=i||[];var a=e(n,"relationships");if(a){var o=a.get(t);return o&&i.push.apply(i,a.get(t)),t.superclass&&r(t.superclass,n,i),i}}var n=this.typeForRelationship(t);if(!n)return null;var i=this.metaForProperty(t).options;if(null===i.inverse)return null;var a,o;if(i.inverse)a=i.inverse,o=Ember.get(n,"relationshipsByName").get(a).kind;else{var s=r(this,n);if(0===s.length)return null;Ember.assert("You defined the '"+t+"' relationship on "+this+", but multiple possible inverse relationships of type "+this+" were found on "+n+". Look at http://emberjs.com/guides/models/defining-models/#toc_explicit-inverses for how to explicitly specify inverses",1===s.length),a=s[0].name,o=s[0].kind}return{type:n,name:a,kind:o}},relationships:Ember.computed(function(){var e=new Ember.MapWithDefault({defaultValue:function(){return[]}});return this.eachComputedProperty(function(t,r){if(r.isRelationship){"string"==typeof r.type&&(r.type=this.store.modelFor(r.type));var n=e.get(r.type);n.push({name:t,kind:r.kind})}}),e}),relationshipNames:Ember.computed(function(){var e={hasMany:[],belongsTo:[]};return this.eachComputedProperty(function(t,r){r.isRelationship&&e[r.kind].push(t)}),e}),relatedTypes:Ember.computed(function(){var t,r=Ember.A();return this.eachComputedProperty(function(n,i){i.isRelationship&&(t=i.type,"string"==typeof t&&(t=e(this,t,!1)||this.store.modelFor(t)),Ember.assert("You specified a hasMany ("+i.type+") on "+i.parentType+" but "+i.type+" was not found.",t),r.contains(t)||(Ember.assert("Trying to sideload "+n+" on "+this.toString()+" but the type doesn't exist.",!!t),r.push(t)))}),r}),relationshipsByName:Ember.computed(function(){var e,t=Ember.Map.create();return this.eachComputedProperty(function(r,n){n.isRelationship&&(n.key=r,e=n.type,e||"hasMany"!==n.kind?e||(e=r):e=Ember.String.singularize(r),"string"==typeof e&&(n.type=this.store.modelFor(e)),t.set(r,n))}),t}),fields:Ember.computed(function(){var e=Ember.Map.create();return this.eachComputedProperty(function(t,r){r.isRelationship?e.set(t,r.kind):r.isAttribute&&e.set(t,"attribute")}),e}),eachRelationship:function(t,r){e(this,"relationshipsByName").forEach(function(e,n){t.call(r,e,n)})},eachRelatedType:function(t,r){e(this,"relatedTypes").forEach(function(e){t.call(r,e)})}}),DS.Model.reopen({eachRelationship:function(e,t){this.constructor.eachRelationship(e,t)}})}(),function(){var e=Ember.get,t=(Ember.set,Ember.EnumerableUtils.forEach);DS.RecordArrayManager=Ember.Object.extend({init:function(){this.filteredRecordArrays=Ember.MapWithDefault.create({defaultValue:function(){return[]}}),this.changedRecords=[]},recordDidChange:function(e){1===this.changedRecords.push(e)&&Ember.run.schedule("actions",this,this.updateRecordArrays)},recordArraysForRecord:function(e){return e._recordArrays=e._recordArrays||Ember.OrderedSet.create(),e._recordArrays},updateRecordArrays:function(){t(this.changedRecords,function(t){e(t,"isDeleted")?this._recordWasDeleted(t):this._recordWasChanged(t)},this),this.changedRecords.length=0},_recordWasDeleted:function(e){var r=e._recordArrays;r&&t(r,function(t){t.removeRecord(e)})},_recordWasChanged:function(r){var n,i=r.constructor,a=this.filteredRecordArrays.get(i);t(a,function(t){n=e(t,"filterFunction"),this.updateRecordArray(t,n,i,r)},this);var o=r._loadingRecordArrays;if(o){for(var s=0,d=o.length;d>s;s++)o[s].loadedRecord();r._loadingRecordArrays=[]}},updateRecordArray:function(e,t,r,n){var i;i=t?t(n):!0;var a=this.recordArraysForRecord(n);i?(a.add(e),e.addRecord(n)):i||(a.remove(e),e.removeRecord(n))},updateFilter:function(t,r,n){for(var i,a=this.store.typeMapFor(r),o=a.records,s=0,d=o.length;d>s;s++)i=o[s],e(i,"isDeleted")||e(i,"isEmpty")||this.updateRecordArray(t,n,r,i)},createManyArray:function(e,r){var n=DS.ManyArray.create({type:e,content:r,store:this.store});return t(r,function(e){var t=this.recordArraysForRecord(e);t.add(n)},this),n},createRecordArray:function(e){var t=DS.RecordArray.create({type:e,content:Ember.A(),store:this.store,isLoaded:!0});return this.registerFilteredRecordArray(t,e),t},createFilteredRecordArray:function(e,t){var r=DS.FilteredRecordArray.create({type:e,content:Ember.A(),store:this.store,manager:this,filterFunction:t});return this.registerFilteredRecordArray(r,e,t),r},createAdapterPopulatedRecordArray:function(e,t){return DS.AdapterPopulatedRecordArray.create({type:e,query:t,content:Ember.A(),store:this.store})},registerFilteredRecordArray:function(e,t,r){var n=this.filteredRecordArrays.get(t);n.push(e),this.updateFilter(e,t,r)},registerWaitingRecordArray:function(e,t){var r=e._loadingRecordArrays||[];r.push(t),e._loadingRecordArrays=r}})}(),function(){var e=Ember.get,t=(Ember.set,Ember.ArrayPolyfills.map),r=["description","fileName","lineNumber","message","name","number","stack"];DS.InvalidError=function(e){var t=Error.prototype.constructor.call(this,"The backend rejected the commit because it was invalid: "+Ember.inspect(e));this.errors=e;for(var n=0,i=r.length;i>n;n++)this[r[n]]=t[r[n]]},DS.InvalidError.prototype=Ember.create(Error.prototype),DS.Adapter=Ember.Object.extend({find:Ember.required(Function),findAll:null,findQuery:null,generateIdForRecord:null,serialize:function(t,r){return e(t,"store").serializerFor(t.constructor.typeKey).serialize(t,r)},createRecord:Ember.required(Function),updateRecord:Ember.required(Function),deleteRecord:Ember.required(Function),findMany:function(e,r,n){var i=t.call(n,function(t){return this.find(e,r,t)},this);return Ember.RSVP.all(i)}})}(),function(){var e=Ember.get,t=Ember.String.fmt,r=Ember.EnumerableUtils.indexOf,n=0;DS.FixtureAdapter=DS.Adapter.extend({serializer:null,simulateRemoteResponse:!0,latency:50,fixturesForType:function(e){if(e.FIXTURES){var r=Ember.A(e.FIXTURES);return r.map(function(e){var r=typeof e.id;if("number"!==r&&"string"!==r)throw new Error(t("the id property must be defined as a number or string for fixture %@",[e]));return e.id=e.id+"",e})}return null},queryFixtures:function(){Ember.assert("Not implemented: You must override the DS.FixtureAdapter::queryFixtures method to support querying the fixture store.")},updateFixtures:function(e,t){e.FIXTURES||(e.FIXTURES=[]);var r=e.FIXTURES;this.deleteLoadedFixture(e,t),r.push(t)},mockJSON:function(e,t,r){return e.serializerFor(t).serialize(r,{includeId:!0})},generateIdForRecord:function(){return"fixture-"+n++},find:function(e,t,r){var n,i=this.fixturesForType(t);return Ember.assert("Unable to find fixtures for model type "+t.toString(),i),i&&(n=Ember.A(i).findProperty("id",r)),n?this.simulateRemoteCall(function(){return n},this):void 0},findMany:function(e,t,n){var i=this.fixturesForType(t);return Ember.assert("Unable to find fixtures for model type "+t.toString(),i),i&&(i=i.filter(function(e){return-1!==r(n,e.id)})),i?this.simulateRemoteCall(function(){return i},this):void 0},findAll:function(e,t){var r=this.fixturesForType(t);return Ember.assert("Unable to find fixtures for model type "+t.toString(),r),this.simulateRemoteCall(function(){return r},this)},findQuery:function(e,t,r){var n=this.fixturesForType(t);return Ember.assert("Unable to find fixtures for model type "+t.toString(),n),n=this.queryFixtures(n,r,t),n?this.simulateRemoteCall(function(){return n},this):void 0},createRecord:function(e,t,r){var n=this.mockJSON(e,t,r);return this.updateFixtures(t,n),this.simulateRemoteCall(function(){return n},this)},updateRecord:function(e,t,r){var n=this.mockJSON(e,t,r);return this.updateFixtures(t,n),this.simulateRemoteCall(function(){return n},this)},deleteRecord:function(e,t,r){var n=this.mockJSON(e,t,r);return this.deleteLoadedFixture(t,n),this.simulateRemoteCall(function(){return null})},deleteLoadedFixture:function(e,t){var n=this.findExistingFixture(e,t);if(n){var i=r(e.FIXTURES,n);return e.FIXTURES.splice(i,1),!0}},findExistingFixture:function(t,r){var n=this.fixturesForType(t),i=e(r,"id");return this.findFixtureById(n,i)},findFixtureById:function(t,r){return Ember.A(t).find(function(t){return""+e(t,"id")==""+r?!0:!1})},simulateRemoteCall:function(t,r){var n=this;return new Ember.RSVP.Promise(function(i){e(n,"simulateRemoteResponse")?Ember.run.later(function(){i(t.call(r))},e(n,"latency")):Ember.run.schedule("actions",null,function(){i(t.call(r))})},"DS: FixtureAdapter#simulateRemoteCall")}})}(),function(){function e(e){return null==e?null:e+""}var t=Ember.get,r=(Ember.set,Ember.ArrayPolyfills.forEach),n=Ember.ArrayPolyfills.map;DS.RESTSerializer=DS.JSONSerializer.extend({normalize:function(e,t,r){return this.normalizeId(t),this.normalizeAttributes(e,t),this.normalizeRelationships(e,t),this.normalizeUsingDeclaredMapping(e,t),this.normalizeHash&&this.normalizeHash[r]&&this.normalizeHash[r](t),this._super(e,t,r)},normalizePayload:function(e,t){return t},normalizeId:function(e){var r=t(this,"primaryKey");"id"!==r&&(e.id=e[r],delete e[r])},normalizeUsingDeclaredMapping:function(e,r){var n,i,a=t(this,"attrs");if(a)for(i in a)n=a[i],n&&n.key&&(n=n.key),"string"==typeof n&&(r[i]=r[n],delete r[n])},normalizeAttributes:function(e,t){var r;this.keyForAttribute&&e.eachAttribute(function(e){r=this.keyForAttribute(e),e!==r&&(t[e]=t[r],delete t[r])},this)},normalizeRelationships:function(e,t){var r;this.keyForRelationship&&e.eachRelationship(function(e,n){r=this.keyForRelationship(e,n.kind),e!==r&&(t[e]=t[r],delete t[r])},this)},extractSingle:function(t,n,i,a){i=this.normalizePayload(n,i);var o,s=n.typeKey;for(var d in i){var u=this.typeForRoot(d),c=t.modelFor(u),l=c.typeKey===s;l&&"array"!==Ember.typeOf(i[d])?o=this.normalize(n,i[d],d):r.call(i[d],function(r){var n=this.typeForRoot(d),i=t.modelFor(n),s=t.serializerFor(i);r=s.normalize(i,r,d);var u=l&&!a&&!o,c=l&&e(r.id)===a;u||c?o=r:t.push(n,r)},this)}return o},extractArray:function(e,t,r){r=this.normalizePayload(t,r);var i,a=t.typeKey;for(var o in r){var s=o,d=!1;"_"===o.charAt(0)&&(d=!0,s=o.substr(1));var u=this.typeForRoot(s),c=e.modelFor(u),l=e.serializerFor(c),h=!d&&c.typeKey===a,p=n.call(r[o],function(e){return l.normalize(c,e,o)},this);h?i=p:e.pushMany(u,p)}return i},pushPayload:function(e,t){t=this.normalizePayload(null,t);for(var r in t){var i=this.typeForRoot(r),a=e.modelFor(i),o=n.call(Ember.makeArray(t[r]),function(e){return this.normalize(a,e,r)},this);e.pushMany(i,o)}},typeForRoot:function(e){return Ember.String.singularize(e)},serialize:function(){return this._super.apply(this,arguments)},serializeIntoHash:function(e,t,r,n){var i=Ember.String.camelize(t.typeKey);e[i]=this.serialize(r,n)},serializePolymorphicType:function(e,r,n){var i=n.key,a=t(e,i);i=this.keyForAttribute?this.keyForAttribute(i):i,r[i+"Type"]=Ember.String.camelize(a.constructor.typeKey)}})}(),function(){var e=Ember.get,t=(Ember.set,Ember.ArrayPolyfills.forEach);DS.RESTAdapter=DS.Adapter.extend({defaultSerializer:"-rest",find:function(e,t,r){return this.ajax(this.buildURL(t.typeKey,r),"GET")},findAll:function(e,t,r){var n;return r&&(n={since:r}),this.ajax(this.buildURL(t.typeKey),"GET",{data:n})},findQuery:function(e,t,r){return this.ajax(this.buildURL(t.typeKey),"GET",{data:r})},findMany:function(e,t,r){return this.ajax(this.buildURL(t.typeKey),"GET",{data:{ids:r}})},findHasMany:function(t,r,n){var i=e(this,"host"),a=e(r,"id"),o=r.constructor.typeKey;return i&&"/"===n.charAt(0)&&"/"!==n.charAt(1)&&(n=i+n),this.ajax(this.urlPrefix(n,this.buildURL(o,a)),"GET")},findBelongsTo:function(t,r,n){var i=e(r,"id"),a=r.constructor.typeKey;return this.ajax(this.urlPrefix(n,this.buildURL(a,i)),"GET")},createRecord:function(e,t,r){var n={},i=e.serializerFor(t.typeKey);return i.serializeIntoHash(n,t,r,{includeId:!0}),this.ajax(this.buildURL(t.typeKey),"POST",{data:n})},updateRecord:function(t,r,n){var i={},a=t.serializerFor(r.typeKey);a.serializeIntoHash(i,r,n);var o=e(n,"id");return this.ajax(this.buildURL(r.typeKey,o),"PUT",{data:i})},deleteRecord:function(t,r,n){var i=e(n,"id");return this.ajax(this.buildURL(r.typeKey,i),"DELETE")},buildURL:function(t,r){var n=[],i=e(this,"host"),a=this.urlPrefix();return t&&n.push(this.pathForType(t)),r&&n.push(r),a&&n.unshift(a),n=n.join("/"),!i&&n&&(n="/"+n),n},urlPrefix:function(t,r){var n=e(this,"host"),i=e(this,"namespace"),a=[];return t?"/"===t.charAt(0)?n&&(t=t.slice(1),a.push(n)):/^http(s)?:\/\//.test(t)||a.push(r):(n&&a.push(n),i&&a.push(i)),t&&a.push(t),a.join("/")},pathForType:function(e){var t=Ember.String.camelize(e);return Ember.String.pluralize(t)},ajaxError:function(e){return e&&(e.then=null),e},ajax:function(e,t,r){var n=this;return new Ember.RSVP.Promise(function(i,a){r=n.ajaxOptions(e,t,r),r.success=function(e){Ember.run(null,i,e)},r.error=function(e){Ember.run(null,a,n.ajaxError(e))},Ember.$.ajax(r)},"DS: RestAdapter#ajax "+t+" to "+e)},ajaxOptions:function(e,r,n){if(n=n||{},n.url=e,n.type=r,n.dataType="json",n.context=this,n.data&&"GET"!==r&&(n.contentType="application/json; charset=utf-8",n.data=JSON.stringify(n.data)),void 0!==this.headers){var i=this.headers;n.beforeSend=function(e){t.call(Ember.keys(i),function(t){e.setRequestHeader(t,i[t])})}}return n}})}(),function(){DS.Model.reopen({_debugInfo:function(){var e=["id"],t={belongsTo:[],hasMany:[]},r=[];this.eachAttribute(function(t){e.push(t)},this),this.eachRelationship(function(e,n){t[n.kind].push(e),r.push(e)});var n=[{name:"Attributes",properties:e,expand:!0},{name:"Belongs To",properties:t.belongsTo,expand:!0},{name:"Has Many",properties:t.hasMany,expand:!0},{name:"Flags",properties:["isLoaded","isDirty","isSaving","isDeleted","isError","isNew","isValid"]}];return{propertyInfo:{includeOtherProperties:!0,groups:n,expensiveProperties:r}}}})}(),function(){Ember.String.pluralize=function(e){return Ember.Inflector.inflector.pluralize(e)},Ember.String.singularize=function(e){return Ember.Inflector.inflector.singularize(e)}}(),function(){function e(e,t){for(var r=0,n=t.length;n>r;r++)e.uncountable[t[r].toLowerCase()]=!0}function t(e,t){for(var r,n=0,i=t.length;i>n;n++)r=t[n],e.irregular[r[0].toLowerCase()]=r[1],e.irregularInverse[r[1].toLowerCase()]=r[0]}function r(r){r=r||{},r.uncountable=r.uncountable||{},r.irregularPairs=r.irregularPairs||{};var n=this.rules={plurals:r.plurals||[],singular:r.singular||[],irregular:{},irregularInverse:{},uncountable:{}};e(n,r.uncountable),t(n,r.irregularPairs)}var n=/^\s*$/;r.prototype={plural:function(e,t){this.rules.plurals.push([e,t.toLowerCase()])},singular:function(e,t){this.rules.singular.push([e,t.toLowerCase()])},uncountable:function(t){e(this.rules,[t.toLowerCase()])},irregular:function(e,r){t(this.rules,[[e,r]])},pluralize:function(e){return this.inflect(e,this.rules.plurals,this.rules.irregular)},singularize:function(e){return this.inflect(e,this.rules.singular,this.rules.irregularInverse)},inflect:function(e,t,r){var i,a,o,s,d,u,c,l;if(d=n.test(e))return e;if(s=e.toLowerCase(),u=this.rules.uncountable[s])return e;if(c=r&&r[s])return c;for(var h=t.length,p=0;h>p&&(i=t[h-1],l=i[0],!l.test(e));h--);return i=i||[],l=i[0],a=i[1],o=e.replace(l,a)}},Ember.Inflector=r}(),function(){Ember.Inflector.defaultRules={plurals:[[/$/,"s"],[/s$/i,"s"],[/^(ax|test)is$/i,"$1es"],[/(octop|vir)us$/i,"$1i"],[/(octop|vir)i$/i,"$1i"],[/(alias|status)$/i,"$1es"],[/(bu)s$/i,"$1ses"],[/(buffal|tomat)o$/i,"$1oes"],[/([ti])um$/i,"$1a"],[/([ti])a$/i,"$1a"],[/sis$/i,"ses"],[/(?:([^f])fe|([lr])f)$/i,"$1$2ves"],[/(hive)$/i,"$1s"],[/([^aeiouy]|qu)y$/i,"$1ies"],[/(x|ch|ss|sh)$/i,"$1es"],[/(matr|vert|ind)(?:ix|ex)$/i,"$1ices"],[/^(m|l)ouse$/i,"$1ice"],[/^(m|l)ice$/i,"$1ice"],[/^(ox)$/i,"$1en"],[/^(oxen)$/i,"$1"],[/(quiz)$/i,"$1zes"]],singular:[[/s$/i,""],[/(ss)$/i,"$1"],[/(n)ews$/i,"$1ews"],[/([ti])a$/i,"$1um"],[/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i,"$1sis"],[/(^analy)(sis|ses)$/i,"$1sis"],[/([^f])ves$/i,"$1fe"],[/(hive)s$/i,"$1"],[/(tive)s$/i,"$1"],[/([lr])ves$/i,"$1f"],[/([^aeiouy]|qu)ies$/i,"$1y"],[/(s)eries$/i,"$1eries"],[/(m)ovies$/i,"$1ovie"],[/(x|ch|ss|sh)es$/i,"$1"],[/^(m|l)ice$/i,"$1ouse"],[/(bus)(es)?$/i,"$1"],[/(o)es$/i,"$1"],[/(shoe)s$/i,"$1"],[/(cris|test)(is|es)$/i,"$1is"],[/^(a)x[ie]s$/i,"$1xis"],[/(octop|vir)(us|i)$/i,"$1us"],[/(alias|status)(es)?$/i,"$1"],[/^(ox)en/i,"$1"],[/(vert|ind)ices$/i,"$1ex"],[/(matr)ices$/i,"$1ix"],[/(quiz)zes$/i,"$1"],[/(database)s$/i,"$1"]],irregularPairs:[["person","people"],["man","men"],["child","children"],["sex","sexes"],["move","moves"],["cow","kine"],["zombie","zombies"]],uncountable:["equipment","information","rice","money","species","series","fish","sheep","jeans","police"]} +}(),function(){(Ember.EXTEND_PROTOTYPES===!0||Ember.EXTEND_PROTOTYPES.String)&&(String.prototype.pluralize=function(){return Ember.String.pluralize(this)},String.prototype.singularize=function(){return Ember.String.singularize(this)})}(),function(){Ember.Inflector.inflector=new Ember.Inflector(Ember.Inflector.defaultRules)}(),function(){var e=Ember.get,t=Ember.EnumerableUtils.forEach,r=Ember.String.camelize,n=Ember.String.capitalize,i=Ember.String.decamelize,a=Ember.String.singularize,o=Ember.String.underscore;DS.ActiveModelSerializer=DS.RESTSerializer.extend({keyForAttribute:function(e){return i(e)},keyForRelationship:function(e,t){return e=i(e),"belongsTo"===t?e+"_id":"hasMany"===t?a(e)+"_ids":e},serializeHasMany:Ember.K,serializeIntoHash:function(e,t,r,n){var a=o(i(t.typeKey));e[a]=this.serialize(r,n)},serializePolymorphicType:function(t,i,a){var o=a.key,s=e(t,o);o=this.keyForAttribute(o),i[o+"_type"]=n(r(s.constructor.typeKey))},typeForRoot:function(e){var t=r(e);return a(t)},normalize:function(e,t,r){return this.normalizeLinks(t),this._super(e,t,r)},normalizeLinks:function(e){if(e.links){var t=e.links;for(var n in t){var i=r(n);i!==n&&(t[i]=t[n],delete t[n])}}},normalizeRelationships:function(e,r){var n,i;this.keyForRelationship&&e.eachRelationship(function(e,a){if(a.options.polymorphic){if(n=this.keyForAttribute(e),i=r[n],i&&i.type)i.type=this.typeForRoot(i.type);else if(i&&"hasMany"===a.kind){var o=this;t(i,function(e){e.type=o.typeForRoot(e.type)})}}else n=this.keyForRelationship(e,a.kind),i=r[n];r[e]=i,e!==n&&delete r[n]},this)}})}(),function(){function e(n,i,a,o,s){var d=t(i,"attrs");d&&a.eachRelationship(function(i,a){var u,c,l,h,p=d[i],f=n.serializerFor(a.type.typeKey),m=t(f,"primaryKey");if("hasMany"===a.kind&&p&&("always"===p.embedded||"load"===p.embedded)){if(c="_"+Ember.String.pluralize(a.type.typeKey),u=this.keyForRelationship(i,a.kind),l=this.keyForAttribute(i),h=[],!o[l])return;s[c]=s[c]||[],r(o[l],function(t){var r=n.modelFor(a.type.typeKey);e(n,f,r,t,s),h.push(t[m]),s[c].push(t)}),o[u]=h,delete o[l]}},i)}var t=Ember.get,r=Ember.EnumerableUtils.forEach;DS.EmbeddedRecordsMixin=Ember.Mixin.create({serializeHasMany:function(e,r,n){var i=n.key,a=t(this,"attrs"),o=a&&a[i]&&"always"===a[i].embedded;o&&(r[this.keyForAttribute(i)]=t(e,i).map(function(e){var r=e.serialize(),n=t(this,"primaryKey");return r[n]=t(e,n),r},this))},extractSingle:function(t,r,n,i,a){var o=this.keyForAttribute(r.typeKey),s=n[o];return e(t,this,r,s,n),this._super(t,r,n,i,a)},extractArray:function(t,n,i){var a=this.keyForAttribute(n.typeKey),o=i[Ember.String.pluralize(a)];return r(o,function(r){e(t,this,n,r,i)},this),this._super(t,n,i)}})}(),function(){var e=Ember.EnumerableUtils.forEach,t=Ember.String.decamelize,r=Ember.String.underscore,n=Ember.String.pluralize;DS.ActiveModelAdapter=DS.RESTAdapter.extend({defaultSerializer:"-active-model",pathForType:function(e){var i=t(e),a=r(i);return n(a)},ajaxError:function(t){var r=this._super(t);if(t&&422===t.status){var n=Ember.$.parseJSON(t.responseText),i={};if(void 0!==n.errors){var a=n.errors;e(Ember.keys(a),function(e){i[Ember.String.camelize(e)]=a[e]})}return new DS.InvalidError(i)}return r}})}(),function(){Ember.onLoad("Ember.Application",function(e){e.initializer({name:"activeModelAdapter",initialize:function(e,t){var r=new DS.ContainerProxy(e);r.registerDeprecations([{deprecated:"serializer:_ams",valid:"serializer:-active-model"},{deprecated:"adapter:_ams",valid:"adapter:-active-model"}]),t.register("serializer:-active-model",DS.ActiveModelSerializer),t.register("adapter:-active-model",DS.ActiveModelAdapter)}})})}()}(),"undefined"==typeof location||"localhost"!==location.hostname&&"127.0.0.1"!==location.hostname||Ember.Logger.warn("You are running a production build of Ember on localhost and won't receive detailed error messages. If you want full error messages please use the non-minified build provided on the Ember website."); \ No newline at end of file diff --git a/ajax/libs/ember-data.js/1.0.0-beta.6/ember-data.prod.js b/ajax/libs/ember-data.js/1.0.0-beta.6/ember-data.prod.js new file mode 100644 index 000000000..1a0fdda9b --- /dev/null +++ b/ajax/libs/ember-data.js/1.0.0-beta.6/ember-data.prod.js @@ -0,0 +1,10626 @@ +/*! + * @overview Ember Data + * @copyright Copyright 2011-2014 Tilde Inc. and contributors. + * Portions Copyright 2011 LivingSocial Inc. + * @license Licensed under MIT license (see license.js) + * @version 1.0.0-beta.6 + */ + + +(function() { +var define, requireModule; + +(function() { + var registry = {}, seen = {}; + + define = function(name, deps, callback) { + registry[name] = { deps: deps, callback: callback }; + }; + + requireModule = function(name) { + if (seen[name]) { return seen[name]; } + seen[name] = {}; + + var mod, deps, callback, reified , exports; + + mod = registry[name]; + + if (!mod) { + throw new Error("Module '" + name + "' not found."); + } + + deps = mod.deps; + callback = mod.callback; + reified = []; + exports; + + for (var i=0, l=deps.length; i 0; i--) { + var proxyPair = proxyPairs[i - 1], + deprecated = proxyPair['deprecated'], + valid = proxyPair['valid']; + + this.registerDeprecation(deprecated, valid); + } +}; + +DS.ContainerProxy = ContainerProxy; + +})(); + + + +(function() { +var get = Ember.get, set = Ember.set, isNone = Ember.isNone; + +// Simple dispatcher to support overriding the aliased +// method in subclasses. +function aliasMethod(methodName) { + return function() { + return this[methodName].apply(this, arguments); + }; +} + +/** + In Ember Data a Serializer is used to serialize and deserialize + records when they are transferred in and out of an external source. + This process involves normalizing property names, transforming + attribute values and serializing relationships. + + For maximum performance Ember Data recommends you use the + [RESTSerializer](DS.RESTSerializer.html) or one of its subclasses. + + `JSONSerializer` is useful for simpler or legacy backends that may + not support the http://jsonapi.org/ spec. + + @class JSONSerializer + @namespace DS +*/ +DS.JSONSerializer = Ember.Object.extend({ + /** + The primaryKey is used when serializing and deserializing + data. Ember Data always uses the `id` property to store the id of + the record. The external source may not always follow this + convention. In these cases it is useful to override the + primaryKey property to match the primaryKey of your external + store. + + Example + + ```javascript + App.ApplicationSerializer = DS.JSONSerializer.extend({ + primaryKey: '_id' + }); + ``` + + @property primaryKey + @type {String} + @default 'id' + */ + primaryKey: 'id', + + /** + Given a subclass of `DS.Model` and a JSON object this method will + iterate through each attribute of the `DS.Model` and invoke the + `DS.Transform#deserialize` method on the matching property of the + JSON object. This method is typically called after the + serializer's `normalize` method. + + @method applyTransforms + @private + @param {subclass of DS.Model} type + @param {Object} data The data to transform + @return {Object} data The transformed data object + */ + applyTransforms: function(type, data) { + type.eachTransformedAttribute(function(key, type) { + var transform = this.transformFor(type); + data[key] = transform.deserialize(data[key]); + }, this); + + return data; + }, + + /** + Normalizes a part of the JSON payload returned by + the server. You should override this method, munge the hash + and call super if you have generic normalization to do. + + It takes the type of the record that is being normalized + (as a DS.Model class), the property where the hash was + originally found, and the hash to normalize. + + You can use this method, for example, to normalize underscored keys to camelized + or other general-purpose normalizations. + + Example + + ```javascript + App.ApplicationSerializer = DS.JSONSerializer.extend({ + normalize: function(type, hash) { + var fields = Ember.get(type, 'fields'); + fields.forEach(function(field) { + var payloadField = Ember.String.underscore(field); + if (field === payloadField) { return; } + + hash[field] = hash[payloadField]; + delete hash[payloadField]; + }); + return this._super.apply(this, arguments); + } + }); + ``` + + @method normalize + @param {subclass of DS.Model} type + @param {Object} hash + @return {Object} + */ + normalize: function(type, hash) { + if (!hash) { return hash; } + + this.applyTransforms(type, hash); + return hash; + }, + + // SERIALIZE + /** + Called when a record is saved in order to convert the + record into JSON. + + By default, it creates a JSON object with a key for + each attribute and belongsTo relationship. + + For example, consider this model: + + ```javascript + App.Comment = DS.Model.extend({ + title: DS.attr(), + body: DS.attr(), + + author: DS.belongsTo('user') + }); + ``` + + The default serialization would create a JSON object like: + + ```javascript + { + "title": "Rails is unagi", + "body": "Rails? Omakase? O_O", + "author": 12 + } + ``` + + By default, attributes are passed through as-is, unless + you specified an attribute type (`DS.attr('date')`). If + you specify a transform, the JavaScript value will be + serialized when inserted into the JSON hash. + + By default, belongs-to relationships are converted into + IDs when inserted into the JSON hash. + + ## IDs + + `serialize` takes an options hash with a single option: + `includeId`. If this option is `true`, `serialize` will, + by default include the ID in the JSON object it builds. + + The adapter passes in `includeId: true` when serializing + a record for `createRecord`, but not for `updateRecord`. + + ## Customization + + Your server may expect a different JSON format than the + built-in serialization format. + + In that case, you can implement `serialize` yourself and + return a JSON hash of your choosing. + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + serialize: function(post, options) { + var json = { + POST_TTL: post.get('title'), + POST_BDY: post.get('body'), + POST_CMS: post.get('comments').mapProperty('id') + } + + if (options.includeId) { + json.POST_ID_ = post.get('id'); + } + + return json; + } + }); + ``` + + ## Customizing an App-Wide Serializer + + If you want to define a serializer for your entire + application, you'll probably want to use `eachAttribute` + and `eachRelationship` on the record. + + ```javascript + App.ApplicationSerializer = DS.JSONSerializer.extend({ + serialize: function(record, options) { + var json = {}; + + record.eachAttribute(function(name) { + json[serverAttributeName(name)] = record.get(name); + }) + + record.eachRelationship(function(name, relationship) { + if (relationship.kind === 'hasMany') { + json[serverHasManyName(name)] = record.get(name).mapBy('id'); + } + }); + + if (options.includeId) { + json.ID_ = record.get('id'); + } + + return json; + } + }); + + function serverAttributeName(attribute) { + return attribute.underscore().toUpperCase(); + } + + function serverHasManyName(name) { + return serverAttributeName(name.singularize()) + "_IDS"; + } + ``` + + This serializer will generate JSON that looks like this: + + ```javascript + { + "TITLE": "Rails is omakase", + "BODY": "Yep. Omakase.", + "COMMENT_IDS": [ 1, 2, 3 ] + } + ``` + + ## Tweaking the Default JSON + + If you just want to do some small tweaks on the default JSON, + you can call super first and make the tweaks on the returned + JSON. + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + serialize: function(record, options) { + var json = this._super.apply(this, arguments); + + json.subject = json.title; + delete json.title; + + return json; + } + }); + ``` + + @method serialize + @param {subclass of DS.Model} record + @param {Object} options + @return {Object} json + */ + serialize: function(record, options) { + var json = {}; + + if (options && options.includeId) { + var id = get(record, 'id'); + + if (id) { + json[get(this, 'primaryKey')] = id; + } + } + + record.eachAttribute(function(key, attribute) { + this.serializeAttribute(record, json, key, attribute); + }, this); + + record.eachRelationship(function(key, relationship) { + if (relationship.kind === 'belongsTo') { + this.serializeBelongsTo(record, json, relationship); + } else if (relationship.kind === 'hasMany') { + this.serializeHasMany(record, json, relationship); + } + }, this); + + return json; + }, + + /** + `serializeAttribute` can be used to customize how `DS.attr` + properties are serialized + + For example if you wanted to ensure all you attributes were always + serialized as properties on an `attributes` object you could + write: + + ```javascript + App.ApplicationSerializer = DS.JSONSerializer.extend({ + serializeAttribute: function(record, json, key, attributes) { + json.attributes = json.attributes || {}; + this._super(record, json.attributes, key, attributes); + } + }); + ``` + + @method serializeAttribute + @param {DS.Model} record + @param {Object} json + @param {String} key + @param {Object} attribute + */ + serializeAttribute: function(record, json, key, attribute) { + var attrs = get(this, 'attrs'); + var value = get(record, key), type = attribute.type; + + if (type) { + var transform = this.transformFor(type); + value = transform.serialize(value); + } + + // if provided, use the mapping provided by `attrs` in + // the serializer + key = attrs && attrs[key] || (this.keyForAttribute ? this.keyForAttribute(key) : key); + + json[key] = value; + }, + + /** + `serializeBelongsTo` can be used to customize how `DS.belongsTo` + properties are serialized. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + serializeBelongsTo: function(record, json, relationship) { + var key = relationship.key; + + var belongsTo = get(record, key); + + key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key; + + json[key] = Ember.isNone(belongsTo) ? belongsTo : belongsTo.toJSON(); + } + }); + ``` + + @method serializeBelongsTo + @param {DS.Model} record + @param {Object} json + @param {Object} relationship + */ + serializeBelongsTo: function(record, json, relationship) { + var key = relationship.key; + + var belongsTo = get(record, key); + + key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key; + + if (isNone(belongsTo)) { + json[key] = belongsTo; + } else { + json[key] = get(belongsTo, 'id'); + } + + if (relationship.options.polymorphic) { + this.serializePolymorphicType(record, json, relationship); + } + }, + + /** + `serializeHasMany` can be used to customize how `DS.hasMany` + properties are serialized. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + serializeHasMany: function(record, json, relationship) { + var key = relationship.key; + if (key === 'comments') { + return; + } else { + this._super.apply(this, arguments); + } + } + }); + ``` + + @method serializeHasMany + @param {DS.Model} record + @param {Object} json + @param {Object} relationship + */ + serializeHasMany: function(record, json, relationship) { + var key = relationship.key; + + var relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship); + + if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') { + json[key] = get(record, key).mapBy('id'); + // TODO support for polymorphic manyToNone and manyToMany relationships + } + }, + + /** + You can use this method to customize how polymorphic objects are + serialized. Objects are considered to be polymorphic if + `{polymorphic: true}` is pass as the second argument to the + `DS.belongsTo` function. + + Example + + ```javascript + App.CommentSerializer = DS.JSONSerializer.extend({ + serializePolymorphicType: function(record, json, relationship) { + var key = relationship.key, + belongsTo = get(record, key); + key = this.keyForAttribute ? this.keyForAttribute(key) : key; + json[key + "_type"] = belongsTo.constructor.typeKey; + } + }); + ``` + + @method serializePolymorphicType + @param {DS.Model} record + @param {Object} json + @param {Object} relationship + */ + serializePolymorphicType: Ember.K, + + // EXTRACT + + /** + The `extract` method is used to deserialize payload data from the + server. By default the `JSONSerializer` does not push the records + into the store. However records that subclass `JSONSerializer` + such as the `RESTSerializer` may push records into the store as + part of the extract call. + + This method delegates to a more specific extract method based on + the `requestType`. + + Example + + ```javascript + var get = Ember.get; + socket.on('message', function(message) { + var modelName = message.model; + var data = message.data; + var type = store.modelFor(modelName); + var serializer = store.serializerFor(type.typeKey); + var record = serializer.extract(store, type, data, get(data, 'id'), 'single'); + store.push(modelName, record); + }); + ``` + + @method extract + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @param {String or Number} id + @param {String} requestType + @return {Object} json The deserialized payload + */ + extract: function(store, type, payload, id, requestType) { + this.extractMeta(store, type, payload); + + var specificExtract = "extract" + requestType.charAt(0).toUpperCase() + requestType.substr(1); + return this[specificExtract](store, type, payload, id, requestType); + }, + + /** + `extractFindAll` is a hook into the extract method used when a + call is made to `DS.Store#findAll`. By default this method is an + alias for [extractArray](#method_extractArray). + + @method extractFindAll + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractFindAll: aliasMethod('extractArray'), + /** + `extractFindQuery` is a hook into the extract method used when a + call is made to `DS.Store#findQuery`. By default this method is an + alias for [extractArray](#method_extractArray). + + @method extractFindQuery + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractFindQuery: aliasMethod('extractArray'), + /** + `extractFindMany` is a hook into the extract method used when a + call is made to `DS.Store#findMany`. By default this method is + alias for [extractArray](#method_extractArray). + + @method extractFindMany + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractFindMany: aliasMethod('extractArray'), + /** + `extractFindHasMany` is a hook into the extract method used when a + call is made to `DS.Store#findHasMany`. By default this method is + alias for [extractArray](#method_extractArray). + + @method extractFindHasMany + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractFindHasMany: aliasMethod('extractArray'), + + /** + `extractCreateRecord` is a hook into the extract method used when a + call is made to `DS.Store#createRecord`. By default this method is + alias for [extractSave](#method_extractSave). + + @method extractCreateRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractCreateRecord: aliasMethod('extractSave'), + /** + `extractUpdateRecord` is a hook into the extract method used when + a call is made to `DS.Store#update`. By default this method is alias + for [extractSave](#method_extractSave). + + @method extractUpdateRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractUpdateRecord: aliasMethod('extractSave'), + /** + `extractDeleteRecord` is a hook into the extract method used when + a call is made to `DS.Store#deleteRecord`. By default this method is + alias for [extractSave](#method_extractSave). + + @method extractDeleteRecord + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractDeleteRecord: aliasMethod('extractSave'), + + /** + `extractFind` is a hook into the extract method used when + a call is made to `DS.Store#find`. By default this method is + alias for [extractSingle](#method_extractSingle). + + @method extractFind + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractFind: aliasMethod('extractSingle'), + /** + `extractFindBelongsTo` is a hook into the extract method used when + a call is made to `DS.Store#findBelongsTo`. By default this method is + alias for [extractSingle](#method_extractSingle). + + @method extractFindBelongsTo + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractFindBelongsTo: aliasMethod('extractSingle'), + /** + `extractSave` is a hook into the extract method used when a call + is made to `DS.Model#save`. By default this method is alias + for [extractSingle](#method_extractSingle). + + @method extractSave + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractSave: aliasMethod('extractSingle'), + + /** + `extractSingle` is used to deserialize a single record returned + from the adapter. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + extractSingle: function(store, type, payload) { + payload.comments = payload._embedded.comment; + delete payload._embedded; + + return this._super(store, type, payload); + }, + }); + ``` + + @method extractSingle + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Object} json The deserialized payload + */ + extractSingle: function(store, type, payload) { + return this.normalize(type, payload); + }, + + /** + `extractArray` is used to deserialize an array of records + returned from the adapter. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + extractArray: function(store, type, payload) { + return payload.map(function(json) { + return this.extractSingle(json); + }, this); + } + }); + ``` + + @method extractArray + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + @return {Array} array An array of deserialized objects + */ + extractArray: function(store, type, payload) { + return this.normalize(type, payload); + }, + + /** + `extractMeta` is used to deserialize any meta information in the + adapter payload. By default Ember Data expects meta information to + be located on the `meta` property of the payload object. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + extractMeta: function(store, type, payload) { + if (payload && payload._pagination) { + store.metaForType(type, payload._pagination); + delete payload._pagination; + } + } + }); + ``` + + @method extractMeta + @param {DS.Store} store + @param {subclass of DS.Model} type + @param {Object} payload + */ + extractMeta: function(store, type, payload) { + if (payload && payload.meta) { + store.metaForType(type, payload.meta); + delete payload.meta; + } + }, + + /** + `keyForAttribute` can be used to define rules for how to convert an + attribute name in your model to a key in your JSON. + + Example + + ```javascript + App.ApplicationSerializer = DS.RESTSerializer.extend({ + keyForAttribute: function(attr) { + return Ember.String.underscore(attr).toUpperCase(); + } + }); + ``` + + @method keyForAttribute + @param {String} key + @return {String} normalized key + */ + + + /** + `keyForRelationship` can be used to define a custom key when + serializing relationship properties. By default `JSONSerializer` + does not provide an implementation of this method. + + Example + + ```javascript + App.PostSerializer = DS.JSONSerializer.extend({ + keyForRelationship: function(key, relationship) { + return 'rel_' + Ember.String.underscore(key); + } + }); + ``` + + @method keyForRelationship + @param {String} key + @param {String} relationship type + @return {String} normalized key + */ + + // HELPERS + + /** + @method transformFor + @private + @param {String} attributeType + @param {Boolean} skipAssertion + @return {DS.Transform} transform + */ + transformFor: function(attributeType, skipAssertion) { + var transform = this.container.lookup('transform:' + attributeType); + Ember.assert("Unable to find transform for '" + attributeType + "'", skipAssertion || !!transform); + return transform; + } +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ +var get = Ember.get, capitalize = Ember.String.capitalize, underscore = Ember.String.underscore, DS = window.DS ; + +/** + Extend `Ember.DataAdapter` with ED specific code. + + @class DebugAdapter + @namespace DS + @extends Ember.DataAdapter + @private +*/ +DS.DebugAdapter = Ember.DataAdapter.extend({ + getFilters: function() { + return [ + { name: 'isNew', desc: 'New' }, + { name: 'isModified', desc: 'Modified' }, + { name: 'isClean', desc: 'Clean' } + ]; + }, + + detect: function(klass) { + return klass !== DS.Model && DS.Model.detect(klass); + }, + + columnsForType: function(type) { + var columns = [{ name: 'id', desc: 'Id' }], count = 0, self = this; + get(type, 'attributes').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) { + return this.get('store').all(type); + }, + + getRecordColumnValues: function(record) { + var self = this, count = 0, + columnValues = { id: get(record, 'id') }; + + record.eachAttribute(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.eachAttribute(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.eachAttribute(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; + } + +}); + +})(); + + + +(function() { +/** + The `DS.Transform` class is used to serialize and deserialize model + attributes when they are saved or loaded from an + adapter. Subclassing `DS.Transform` is useful for creating custom + attributes. All subclasses of `DS.Transform` must implement a + `serialize` and a `deserialize` method. + + Example + + ```javascript + App.RawTransform = DS.Transform.extend({ + deserialize: function(serialized) { + return serialized; + }, + serialize: function(deserialized) { + return deserialized; + } + }); + ``` + + Usage + + ```javascript + var attr = DS.attr; + App.Requirement = DS.Model.extend({ + name: attr('string'), + optionsArray: attr('raw') + }); + ``` + + @class Transform + @namespace DS + */ +DS.Transform = Ember.Object.extend({ + /** + When given a deserialized value from a record attribute this + method must return the serialized value. + + Example + + ```javascript + serialize: function(deserialized) { + return Ember.isEmpty(deserialized) ? null : Number(deserialized); + } + ``` + + @method serialize + @param deserialized The deserialized value + @return The serialized value + */ + serialize: Ember.required(), + + /** + When given a serialize value from a JSON object this method must + return the deserialized value for the record attribute. + + Example + + ```javascript + deserialize: function(serialized) { + return empty(serialized) ? null : Number(serialized); + } + ``` + + @method deserialize + @param serialized The serialized value + @return The deserialized value + */ + deserialize: Ember.required() + +}); + +})(); + + + +(function() { + +/** + The `DS.BooleanTransform` class is used to serialize and deserialize + boolean attributes on Ember Data record objects. This transform is + used when `boolean` is passed as the type parameter to the + [DS.attr](../../data#method_attr) function. + + Usage + + ```javascript + var attr = DS.attr; + App.User = DS.Model.extend({ + isAdmin: attr('boolean'), + name: attr('string'), + email: attr('string') + }); + ``` + + @class BooleanTransform + @extends DS.Transform + @namespace DS + */ +DS.BooleanTransform = DS.Transform.extend({ + deserialize: function(serialized) { + var type = typeof serialized; + + if (type === "boolean") { + return serialized; + } else if (type === "string") { + return serialized.match(/^true$|^t$|^1$/i) !== null; + } else if (type === "number") { + return serialized === 1; + } else { + return false; + } + }, + + serialize: function(deserialized) { + return Boolean(deserialized); + } +}); + +})(); + + + +(function() { +/** + The `DS.DateTransform` class is used to serialize and deserialize + date attributes on Ember Data record objects. This transform is used + when `date` is passed as the type parameter to the + [DS.attr](../../data#method_attr) function. + + ```javascript + var attr = DS.attr; + App.Score = DS.Model.extend({ + value: attr('number'), + player: DS.belongsTo('player'), + date: attr('date') + }); + ``` + + @class DateTransform + @extends DS.Transform + @namespace DS + */ +DS.DateTransform = DS.Transform.extend({ + + deserialize: function(serialized) { + var type = typeof serialized; + + if (type === "string") { + return new Date(Ember.Date.parse(serialized)); + } else if (type === "number") { + return new Date(serialized); + } else if (serialized === null || serialized === undefined) { + // if the value is not present in the data, + // return undefined, not null. + return serialized; + } else { + return null; + } + }, + + serialize: function(date) { + if (date instanceof Date) { + // Serialize it as a number to maintain millisecond precision + return Number(date); + } else { + return null; + } + } + +}); + +})(); + + + +(function() { +var empty = Ember.isEmpty; +/** + The `DS.NumberTransform` class is used to serialize and deserialize + numeric attributes on Ember Data record objects. This transform is + used when `number` is passed as the type parameter to the + [DS.attr](../../data#method_attr) function. + + Usage + + ```javascript + var attr = DS.attr; + App.Score = DS.Model.extend({ + value: attr('number'), + player: DS.belongsTo('player'), + date: attr('date') + }); + ``` + + @class NumberTransform + @extends DS.Transform + @namespace DS + */ +DS.NumberTransform = DS.Transform.extend({ + + deserialize: function(serialized) { + return empty(serialized) ? null : Number(serialized); + }, + + serialize: function(deserialized) { + return empty(deserialized) ? null : Number(deserialized); + } +}); + +})(); + + + +(function() { +var none = Ember.isNone; + +/** + The `DS.StringTransform` class is used to serialize and deserialize + string attributes on Ember Data record objects. This transform is + used when `string` is passed as the type parameter to the + [DS.attr](../../data#method_attr) function. + + Usage + + ```javascript + var attr = DS.attr; + App.User = DS.Model.extend({ + isAdmin: attr('boolean'), + name: attr('string'), + email: attr('string') + }); + ``` + + @class StringTransform + @extends DS.Transform + @namespace DS + */ +DS.StringTransform = DS.Transform.extend({ + + deserialize: function(serialized) { + return none(serialized) ? null : String(serialized); + }, + + serialize: function(deserialized) { + return none(deserialized) ? null : String(deserialized); + } + +}); + +})(); + + + +(function() { + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var set = Ember.set; + +/* + This code registers an injection for Ember.Application. + + If an Ember.js developer defines a subclass of DS.Store on their application, + this code will automatically instantiate it and make it available on the + router. + + Additionally, after an application's controllers have been injected, they will + each have the store made available to them. + + For example, imagine an Ember.js application with the following classes: + + App.Store = DS.Store.extend({ + adapter: 'custom' + }); + + App.PostsController = Ember.ArrayController.extend({ + // ... + }); + + When the application is initialized, `App.Store` will automatically be + instantiated, and the instance of `App.PostsController` will have its `store` + property set to that instance. + + Note that this code will only be run if the `ember-application` package is + loaded. If Ember Data is being used in an environment other than a + typical application (e.g., node.js where only `ember-runtime` is available), + this code will be ignored. +*/ + +Ember.onLoad('Ember.Application', function(Application) { + Application.initializer({ + name: "store", + + initialize: function(container, application) { + application.register('store:main', application.Store || DS.Store); + + // allow older names to be looked up + + var proxy = new DS.ContainerProxy(container); + proxy.registerDeprecations([ + {deprecated: 'serializer:_default', valid: 'serializer:-default'}, + {deprecated: 'serializer:_rest', valid: 'serializer:-rest'}, + {deprecated: 'adapter:_rest', valid: 'adapter:-rest'} + ]); + + // new go forward paths + application.register('serializer:-default', DS.JSONSerializer); + application.register('serializer:-rest', DS.RESTSerializer); + application.register('adapter:-rest', DS.RESTAdapter); + + // Eagerly generate the store so defaultStore is populated. + // TODO: Do this in a finisher hook + container.lookup('store:main'); + } + }); + + Application.initializer({ + name: "transforms", + before: "store", + + initialize: function(container, application) { + application.register('transform:boolean', DS.BooleanTransform); + application.register('transform:date', DS.DateTransform); + application.register('transform:number', DS.NumberTransform); + application.register('transform:string', DS.StringTransform); + } + }); + + Application.initializer({ + name: "data-adapter", + before: "store", + + initialize: function(container, application) { + application.register('data-adapter:main', DS.DebugAdapter); + } + }); + + Application.initializer({ + name: "injectStore", + before: "store", + + initialize: function(container, application) { + application.inject('controller', 'store', 'store:main'); + application.inject('route', 'store', 'store:main'); + application.inject('serializer', 'store', 'store:main'); + application.inject('data-adapter', 'store', 'store:main'); + } + }); + +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +/** + Date.parse with progressive enhancement for ISO 8601 + + © 2011 Colin Snover + + Released under MIT license. + + @class Date + @namespace Ember + @static +*/ +Ember.Date = Ember.Date || {}; + +var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ]; + +/** + @method parse + @param date +*/ +Ember.Date.parse = function (date) { + var timestamp, struct, minutesOffset = 0; + + // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string + // before falling back to any implementation-specific date parsing, so that’s what we do, even if native + // implementations could be faster + // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm + if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) { + // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC + for (var i = 0, k; (k = numericKeys[i]); ++i) { + struct[k] = +struct[k] || 0; + } + + // allow undefined days and months + struct[2] = (+struct[2] || 1) - 1; + struct[3] = +struct[3] || 1; + + if (struct[8] !== 'Z' && struct[9] !== undefined) { + minutesOffset = struct[10] * 60 + struct[11]; + + if (struct[9] === '+') { + minutesOffset = 0 - minutesOffset; + } + } + + timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); + } + else { + timestamp = origParse ? origParse(date) : NaN; + } + + return timestamp; +}; + +if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Date) { + Date.parse = Ember.Date.parse; +} + +})(); + + + +(function() { + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set; + +/** + A record array is an array that contains records of a certain type. The record + array materializes records as needed when they are retrieved for the first + time. You should not create record arrays yourself. Instead, an instance of + `DS.RecordArray` or its subclasses will be returned by your application's store + in response to queries. + + @class RecordArray + @namespace DS + @extends Ember.ArrayProxy + @uses Ember.Evented +*/ + +DS.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, { + /** + The model type contained by this record array. + + @property type + @type DS.Model + */ + type: null, + + /** + The array of client ids backing the record array. When a + record is requested from the record array, the record + for the client id at the same index is materialized, if + necessary, by the store. + + @property content + @private + @type Ember.Array + */ + content: null, + + /** + The flag to signal a `RecordArray` is currently loading data. + + Example + + ```javascript + var people = store.all(App.Person); + people.get('isLoaded'); // true + ``` + + @property isLoaded + @type Boolean + */ + isLoaded: false, + /** + The flag to signal a `RecordArray` is currently loading data. + + Example + + ```javascript + var people = store.all(App.Person); + people.get('isUpdating'); // false + people.update(); + people.get('isUpdating'); // true + ``` + + @property isUpdating + @type Boolean + */ + isUpdating: false, + + /** + The store that created this record array. + + @property store + @private + @type DS.Store + */ + store: null, + + /** + Retrieves an object from the content by index. + + @method objectAtContent + @private + @param {Number} index + @return {DS.Model} record + */ + objectAtContent: function(index) { + var content = get(this, 'content'); + + return content.objectAt(index); + }, + + /** + Used to get the latest version of all of the records in this array + from the adapter. + + Example + + ```javascript + var people = store.all(App.Person); + people.get('isUpdating'); // false + people.update(); + people.get('isUpdating'); // true + ``` + + @method update + */ + update: function() { + if (get(this, 'isUpdating')) { return; } + + var store = get(this, 'store'), + type = get(this, 'type'); + + return store.fetchAll(type, this); + }, + + /** + Adds a record to the `RecordArray`. + + @method addRecord + @private + @param {DS.Model} record + */ + addRecord: function(record) { + get(this, 'content').addObject(record); + }, + + /** + Removes a record to the `RecordArray`. + + @method removeRecord + @private + @param {DS.Model} record + */ + removeRecord: function(record) { + get(this, 'content').removeObject(record); + }, + + /** + Saves all of the records in the `RecordArray`. + + Example + + ```javascript + var messages = store.all(App.Message); + messages.forEach(function(message) { + message.set('hasBeenSeen', true); + }); + messages.save(); + ``` + + @method save + @return {DS.PromiseArray} promise + */ + save: function() { + var promiseLabel = "DS: RecordArray#save " + get(this, 'type'); + var promise = Ember.RSVP.all(this.invoke("save"), promiseLabel).then(function(array) { + return Ember.A(array); + }, null, "DS: RecordArray#save apply Ember.NativeArray"); + + return DS.PromiseArray.create({ promise: promise }); + } +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get; + +/** + Represents a list of records whose membership is determined by the + store. As records are created, loaded, or modified, the store + evaluates them to determine if they should be part of the record + array. + + @class FilteredRecordArray + @namespace DS + @extends DS.RecordArray +*/ +DS.FilteredRecordArray = DS.RecordArray.extend({ + /** + The filterFunction is a function used to test records from the store to + determine if they should be part of the record array. + + Example + + ```javascript + var allPeople = store.all('person'); + allPeople.mapBy('name'); // ["Tom Dale", "Yehuda Katz", "Trek Glowacki"] + + var people = store.filter('person', function(person) { + if (person.get('name').match(/Katz$/)) { return true; } + }); + people.mapBy('name'); // ["Yehuda Katz"] + + var notKatzFilter = function(person) { + return !person.get('name').match(/Katz$/); + }; + people.set('filterFunction', notKatzFilter); + people.mapBy('name'); // ["Tom Dale", "Trek Glowacki"] + ``` + + @method filterFunction + @param {DS.Model} record + @return {Boolean} `true` if the record should be in the array + */ + filterFunction: null, + isLoaded: true, + + replace: function() { + var type = get(this, 'type').toString(); + throw new Error("The result of a client-side filter (on " + type + ") is immutable."); + }, + + /** + @method updateFilter + @private + */ + updateFilter: Ember.observer(function() { + var manager = get(this, 'manager'); + manager.updateFilter(this, get(this, 'type'), get(this, 'filterFunction')); + }, 'filterFunction') +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set; + +/** + Represents an ordered list of records whose order and membership is + determined by the adapter. For example, a query sent to the adapter + may trigger a search on the server, whose results would be loaded + into an instance of the `AdapterPopulatedRecordArray`. + + @class AdapterPopulatedRecordArray + @namespace DS + @extends DS.RecordArray +*/ +DS.AdapterPopulatedRecordArray = DS.RecordArray.extend({ + query: null, + + replace: function() { + var type = get(this, 'type').toString(); + throw new Error("The result of a server query (on " + type + ") is immutable."); + }, + + /** + @method load + @private + @param {Array} data + */ + load: function(data) { + var store = get(this, 'store'), + type = get(this, 'type'), + records = store.pushMany(type, data), + meta = store.metadataFor(type); + + this.setProperties({ + content: Ember.A(records), + isLoaded: true, + meta: meta + }); + + // TODO: does triggering didLoad event should be the last action of the runLoop? + Ember.run.once(this, 'trigger', 'didLoad'); + } +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set; +var map = Ember.EnumerableUtils.map; + +/** + A `ManyArray` is a `RecordArray` that represents the contents of a has-many + relationship. + + The `ManyArray` is instantiated lazily the first time the relationship is + requested. + + ### Inverses + + Often, the relationships in Ember Data applications will have + an inverse. For example, imagine the following models are + defined: + + ```javascript + App.Post = DS.Model.extend({ + comments: DS.hasMany('comment') + }); + + App.Comment = DS.Model.extend({ + post: DS.belongsTo('post') + }); + ``` + + If you created a new instance of `App.Post` and added + a `App.Comment` record to its `comments` has-many + relationship, you would expect the comment's `post` + property to be set to the post that contained + the has-many. + + We call the record to which a relationship belongs the + relationship's _owner_. + + @class ManyArray + @namespace DS + @extends DS.RecordArray +*/ +DS.ManyArray = DS.RecordArray.extend({ + init: function() { + this._super.apply(this, arguments); + this._changesToSync = Ember.OrderedSet.create(); + }, + + /** + The property name of the relationship + + @property {String} name + @private + */ + name: null, + + /** + The record to which this relationship belongs. + + @property {DS.Model} owner + @private + */ + owner: null, + + /** + `true` if the relationship is polymorphic, `false` otherwise. + + @property {Boolean} isPolymorphic + @private + */ + isPolymorphic: false, + + // LOADING STATE + + isLoaded: false, + + /** + Used for async `hasMany` arrays + to keep track of when they will resolve. + + @property {Ember.RSVP.Promise} promise + @private + */ + promise: null, + + /** + @method loadingRecordsCount + @param {Number} count + @private + */ + loadingRecordsCount: function(count) { + this.loadingRecordsCount = count; + }, + + /** + @method loadedRecord + @private + */ + loadedRecord: function() { + this.loadingRecordsCount--; + if (this.loadingRecordsCount === 0) { + set(this, 'isLoaded', true); + this.trigger('didLoad'); + } + }, + + /** + @method fetch + @private + */ + fetch: function() { + var records = get(this, 'content'), + store = get(this, 'store'), + owner = get(this, 'owner'), + resolver = Ember.RSVP.defer("DS: ManyArray#fetch " + get(this, 'type')); + + var unloadedRecords = records.filterProperty('isEmpty', true); + store.fetchMany(unloadedRecords, owner, resolver); + }, + + // Overrides Ember.Array's replace method to implement + replaceContent: function(index, removed, added) { + // Map the array of record objects into an array of client ids. + added = map(added, function(record) { + Ember.assert("You cannot add '" + record.constructor.typeKey + "' records to this relationship (only '" + this.type.typeKey + "' allowed)", !this.type || record instanceof this.type); + return record; + }, this); + + this._super(index, removed, added); + }, + + arrangedContentDidChange: function() { + Ember.run.once(this, 'fetch'); + }, + + arrayContentWillChange: function(index, removed, added) { + var owner = get(this, 'owner'), + name = get(this, 'name'); + + if (!owner._suspendedRelationships) { + // This code is the first half of code that continues inside + // of arrayContentDidChange. It gets or creates a change from + // the child object, adds the current owner as the old + // parent if this is the first time the object was removed + // from a ManyArray, and sets `newParent` to null. + // + // Later, if the object is added to another ManyArray, + // the `arrayContentDidChange` will set `newParent` on + // the change. + for (var i=index; i "root.created.uncommitted" + ``` + + The hierarchy of valid states that ship with ember data looks like + this: + + ```text + * root + * deleted + * saved + * uncommitted + * inFlight + * empty + * loaded + * created + * uncommitted + * inFlight + * saved + * updated + * uncommitted + * inFlight + * loading + ``` + + The `DS.Model` states are themselves stateless. What we mean is + that, the hierarchical states that each of *those* points to is a + shared data structure. For performance reasons, instead of each + record getting its own copy of the hierarchy of states, each record + points to this global, immutable shared instance. How does a state + know which record it should be acting on? We pass the record + instance into the state's event handlers as the first argument. + + The record passed as the first parameter is where you should stash + state about the record if needed; you should never store data on the state + object itself. + + ### Events and Flags + + A state may implement zero or more events and flags. + + #### Events + + Events are named functions that are invoked when sent to a record. The + record will first look for a method with the given name on the + current state. If no method is found, it will search the current + state's parent, and then its grandparent, and so on until reaching + the top of the hierarchy. If the root is reached without an event + handler being found, an exception will be raised. This can be very + helpful when debugging new features. + + Here's an example implementation of a state with a `myEvent` event handler: + + ```javascript + aState: DS.State.create({ + myEvent: function(manager, param) { + console.log("Received myEvent with", param); + } + }) + ``` + + To trigger this event: + + ```javascript + record.send('myEvent', 'foo'); + //=> "Received myEvent with foo" + ``` + + Note that an optional parameter can be sent to a record's `send()` method, + which will be passed as the second parameter to the event handler. + + Events should transition to a different state if appropriate. This can be + done by calling the record's `transitionTo()` method with a path to the + desired state. The state manager will attempt to resolve the state path + relative to the current state. If no state is found at that path, it will + attempt to resolve it relative to the current state's parent, and then its + parent, and so on until the root is reached. For example, imagine a hierarchy + like this: + + * created + * uncommitted <-- currentState + * inFlight + * updated + * inFlight + + If we are currently in the `uncommitted` state, calling + `transitionTo('inFlight')` would transition to the `created.inFlight` state, + while calling `transitionTo('updated.inFlight')` would transition to + the `updated.inFlight` state. + + Remember that *only events* should ever cause a state transition. You should + never call `transitionTo()` from outside a state's event handler. If you are + tempted to do so, create a new event and send that to the state manager. + + #### Flags + + Flags are Boolean values that can be used to introspect a record's current + state in a more user-friendly way than examining its state path. For example, + instead of doing this: + + ```javascript + var statePath = record.get('stateManager.currentPath'); + if (statePath === 'created.inFlight') { + doSomething(); + } + ``` + + You can say: + + ```javascript + if (record.get('isNew') && record.get('isSaving')) { + doSomething(); + } + ``` + + If your state does not set a value for a given flag, the value will + be inherited from its parent (or the first place in the state hierarchy + where it is defined). + + The current set of flags are defined below. If you want to add a new flag, + in addition to the area below, you will also need to declare it in the + `DS.Model` class. + + + * [isEmpty](DS.Model.html#property_isEmpty) + * [isLoading](DS.Model.html#property_isLoading) + * [isLoaded](DS.Model.html#property_isLoaded) + * [isDirty](DS.Model.html#property_isDirty) + * [isSaving](DS.Model.html#property_isSaving) + * [isDeleted](DS.Model.html#property_isDeleted) + * [isNew](DS.Model.html#property_isNew) + * [isValid](DS.Model.html#property_isValid) + + @namespace DS + @class RootState +*/ + +var hasDefinedProperties = function(object) { + // Ignore internal property defined by simulated `Ember.create`. + var names = Ember.keys(object); + var i, l, name; + for (i = 0, l = names.length; i < l; i++ ) { + name = names[i]; + if (object.hasOwnProperty(name) && object[name]) { return true; } + } + + return false; +}; + +var didSetProperty = function(record, context) { + if (context.value === context.originalValue) { + delete record._attributes[context.name]; + record.send('propertyWasReset', context.name); + } else if (context.value !== context.oldValue) { + record.send('becomeDirty'); + } + + record.updateRecordArraysLater(); +}; + +// Implementation notes: +// +// Each state has a boolean value for all of the following flags: +// +// * isLoaded: The record has a populated `data` property. When a +// record is loaded via `store.find`, `isLoaded` is false +// until the adapter sets it. When a record is created locally, +// its `isLoaded` property is always true. +// * isDirty: The record has local changes that have not yet been +// saved by the adapter. This includes records that have been +// created (but not yet saved) or deleted. +// * isSaving: The record has been committed, but +// the adapter has not yet acknowledged that the changes have +// been persisted to the backend. +// * isDeleted: The record was marked for deletion. When `isDeleted` +// is true and `isDirty` is true, the record is deleted locally +// but the deletion was not yet persisted. When `isSaving` is +// true, the change is in-flight. When both `isDirty` and +// `isSaving` are false, the change has persisted. +// * isError: The adapter reported that it was unable to save +// local changes to the backend. This may also result in the +// record having its `isValid` property become false if the +// adapter reported that server-side validations failed. +// * isNew: The record was created on the client and the adapter +// did not yet report that it was successfully saved. +// * isValid: No client-side validations have failed and the +// adapter did not report any server-side validation failures. + +// The dirty state is a abstract state whose functionality is +// shared between the `created` and `updated` states. +// +// The deleted state shares the `isDirty` flag with the +// subclasses of `DirtyState`, but with a very different +// implementation. +// +// Dirty states have three child states: +// +// `uncommitted`: the store has not yet handed off the record +// to be saved. +// `inFlight`: the store has handed off the record to be saved, +// but the adapter has not yet acknowledged success. +// `invalid`: the record has invalid information and cannot be +// send to the adapter yet. +var DirtyState = { + initialState: 'uncommitted', + + // FLAGS + isDirty: true, + + // SUBSTATES + + // When a record first becomes dirty, it is `uncommitted`. + // This means that there are local pending changes, but they + // have not yet begun to be saved, and are not invalid. + uncommitted: { + // EVENTS + didSetProperty: didSetProperty, + + propertyWasReset: function(record, name) { + var stillDirty = false; + + for (var prop in record._attributes) { + stillDirty = true; + break; + } + + if (!stillDirty) { record.send('rolledBack'); } + }, + + pushedData: Ember.K, + + becomeDirty: Ember.K, + + willCommit: function(record) { + record.transitionTo('inFlight'); + }, + + reloadRecord: function(record, resolve) { + resolve(get(record, 'store').reloadRecord(record)); + }, + + rolledBack: function(record) { + record.transitionTo('loaded.saved'); + }, + + becameInvalid: function(record) { + record.transitionTo('invalid'); + }, + + rollback: function(record) { + record.rollback(); + } + }, + + // Once a record has been handed off to the adapter to be + // saved, it is in the 'in flight' state. Changes to the + // record cannot be made during this window. + inFlight: { + // FLAGS + isSaving: true, + + // EVENTS + didSetProperty: didSetProperty, + becomeDirty: Ember.K, + pushedData: Ember.K, + + // TODO: More robust semantics around save-while-in-flight + willCommit: Ember.K, + + didCommit: function(record) { + var dirtyType = get(this, 'dirtyType'); + + record.transitionTo('saved'); + record.send('invokeLifecycleCallbacks', dirtyType); + }, + + becameInvalid: function(record) { + record.transitionTo('invalid'); + record.send('invokeLifecycleCallbacks'); + }, + + becameError: function(record) { + record.transitionTo('uncommitted'); + record.triggerLater('becameError', record); + } + }, + + // A record is in the `invalid` state when its client-side + // invalidations have failed, or if the adapter has indicated + // the the record failed server-side invalidations. + invalid: { + // FLAGS + isValid: false, + + // EVENTS + deleteRecord: function(record) { + record.transitionTo('deleted.uncommitted'); + record.clearRelationships(); + }, + + didSetProperty: function(record, context) { + get(record, 'errors').remove(context.name); + + didSetProperty(record, context); + }, + + becomeDirty: Ember.K, + + rolledBack: function(record) { + get(record, 'errors').clear(); + }, + + becameValid: function(record) { + record.transitionTo('uncommitted'); + }, + + invokeLifecycleCallbacks: function(record) { + record.triggerLater('becameInvalid', record); + } + } +}; + +// The created and updated states are created outside the state +// chart so we can reopen their substates and add mixins as +// necessary. + +function deepClone(object) { + var clone = {}, value; + + for (var prop in object) { + value = object[prop]; + if (value && typeof value === 'object') { + clone[prop] = deepClone(value); + } else { + clone[prop] = value; + } + } + + return clone; +} + +function mixin(original, hash) { + for (var prop in hash) { + original[prop] = hash[prop]; + } + + return original; +} + +function dirtyState(options) { + var newState = deepClone(DirtyState); + return mixin(newState, options); +} + +var createdState = dirtyState({ + dirtyType: 'created', + + // FLAGS + isNew: true +}); + +createdState.uncommitted.rolledBack = function(record) { + record.transitionTo('deleted.saved'); +}; + +var updatedState = dirtyState({ + dirtyType: 'updated' +}); + +createdState.uncommitted.deleteRecord = function(record) { + record.clearRelationships(); + record.transitionTo('deleted.saved'); +}; + +createdState.uncommitted.rollback = function(record) { + DirtyState.uncommitted.rollback.apply(this, arguments); + record.transitionTo('deleted.saved'); +}; + +createdState.uncommitted.propertyWasReset = Ember.K; + +updatedState.uncommitted.deleteRecord = function(record) { + record.transitionTo('deleted.uncommitted'); + record.clearRelationships(); +}; + +var RootState = { + // FLAGS + isEmpty: false, + isLoading: false, + isLoaded: false, + isDirty: false, + isSaving: false, + isDeleted: false, + isNew: false, + isValid: true, + + // DEFAULT EVENTS + + // Trying to roll back if you're not in the dirty state + // doesn't change your state. For example, if you're in the + // in-flight state, rolling back the record doesn't move + // you out of the in-flight state. + rolledBack: Ember.K, + + propertyWasReset: Ember.K, + + // SUBSTATES + + // A record begins its lifecycle in the `empty` state. + // If its data will come from the adapter, it will + // transition into the `loading` state. Otherwise, if + // the record is being created on the client, it will + // transition into the `created` state. + empty: { + isEmpty: true, + + // EVENTS + loadingData: function(record, promise) { + record._loadingPromise = promise; + record.transitionTo('loading'); + }, + + loadedData: function(record) { + record.transitionTo('loaded.created.uncommitted'); + + record.suspendRelationshipObservers(function() { + record.notifyPropertyChange('data'); + }); + }, + + pushedData: function(record) { + record.transitionTo('loaded.saved'); + record.triggerLater('didLoad'); + } + }, + + // A record enters this state when the store asks + // the adapter for its data. It remains in this state + // until the adapter provides the requested data. + // + // Usually, this process is asynchronous, using an + // XHR to retrieve the data. + loading: { + // FLAGS + isLoading: true, + + exit: function(record) { + record._loadingPromise = null; + }, + + // EVENTS + pushedData: function(record) { + record.transitionTo('loaded.saved'); + record.triggerLater('didLoad'); + set(record, 'isError', false); + }, + + becameError: function(record) { + record.triggerLater('becameError', record); + }, + + notFound: function(record) { + record.transitionTo('empty'); + } + }, + + // A record enters this state when its data is populated. + // Most of a record's lifecycle is spent inside substates + // of the `loaded` state. + loaded: { + initialState: 'saved', + + // FLAGS + isLoaded: true, + + // SUBSTATES + + // If there are no local changes to a record, it remains + // in the `saved` state. + saved: { + setup: function(record) { + var attrs = record._attributes, + isDirty = false; + + for (var prop in attrs) { + if (attrs.hasOwnProperty(prop)) { + isDirty = true; + break; + } + } + + if (isDirty) { + record.adapterDidDirty(); + } + }, + + // EVENTS + didSetProperty: didSetProperty, + + pushedData: Ember.K, + + becomeDirty: function(record) { + record.transitionTo('updated.uncommitted'); + }, + + willCommit: function(record) { + record.transitionTo('updated.inFlight'); + }, + + reloadRecord: function(record, resolve) { + resolve(get(record, 'store').reloadRecord(record)); + }, + + deleteRecord: function(record) { + record.transitionTo('deleted.uncommitted'); + record.clearRelationships(); + }, + + unloadRecord: function(record) { + // clear relationships before moving to deleted state + // otherwise it fails + record.clearRelationships(); + record.transitionTo('deleted.saved'); + }, + + didCommit: function(record) { + record.send('invokeLifecycleCallbacks', get(record, 'lastDirtyType')); + }, + + // loaded.saved.notFound would be triggered by a failed + // `reload()` on an unchanged record + notFound: Ember.K + + }, + + // A record is in this state after it has been locally + // created but before the adapter has indicated that + // it has been saved. + created: createdState, + + // A record is in this state if it has already been + // saved to the server, but there are new local changes + // that have not yet been saved. + updated: updatedState + }, + + // A record is in this state if it was deleted from the store. + deleted: { + initialState: 'uncommitted', + dirtyType: 'deleted', + + // FLAGS + isDeleted: true, + isLoaded: true, + isDirty: true, + + // TRANSITIONS + setup: function(record) { + record.updateRecordArrays(); + }, + + // SUBSTATES + + // When a record is deleted, it enters the `start` + // state. It will exit this state when the record + // starts to commit. + uncommitted: { + + // EVENTS + + willCommit: function(record) { + record.transitionTo('inFlight'); + }, + + rollback: function(record) { + record.rollback(); + }, + + becomeDirty: Ember.K, + deleteRecord: Ember.K, + + rolledBack: function(record) { + record.transitionTo('loaded.saved'); + } + }, + + // After a record starts committing, but + // before the adapter indicates that the deletion + // has saved to the server, a record is in the + // `inFlight` substate of `deleted`. + inFlight: { + // FLAGS + isSaving: true, + + // EVENTS + + // TODO: More robust semantics around save-while-in-flight + willCommit: Ember.K, + didCommit: function(record) { + record.transitionTo('saved'); + + record.send('invokeLifecycleCallbacks'); + }, + + becameError: function(record) { + record.transitionTo('uncommitted'); + record.triggerLater('becameError', record); + } + }, + + // Once the adapter indicates that the deletion has + // been saved, the record enters the `saved` substate + // of `deleted`. + saved: { + // FLAGS + isDirty: false, + + setup: function(record) { + var store = get(record, 'store'); + store.dematerializeRecord(record); + }, + + invokeLifecycleCallbacks: function(record) { + record.triggerLater('didDelete', record); + record.triggerLater('didCommit', record); + } + } + }, + + invokeLifecycleCallbacks: function(record, dirtyType) { + if (dirtyType === 'created') { + record.triggerLater('didCreate', record); + } else { + record.triggerLater('didUpdate', record); + } + + record.triggerLater('didCommit', record); + } +}; + +function wireState(object, parent, name) { + /*jshint proto:true*/ + // TODO: Use Object.create and copy instead + object = mixin(parent ? Ember.create(parent) : {}, object); + object.parentState = parent; + object.stateName = name; + + for (var prop in object) { + if (!object.hasOwnProperty(prop) || prop === 'parentState' || prop === 'stateName') { continue; } + if (typeof object[prop] === 'object') { + object[prop] = wireState(object[prop], object, name + "." + prop); + } + } + + return object; +} + +RootState = wireState(RootState, null, "root"); + +DS.RootState = RootState; + +})(); + + + +(function() { +var get = Ember.get, isEmpty = Ember.isEmpty; + +/** +@module ember-data +*/ + +/** + Holds validation errors for a given record organized by attribute names. + + @class Errors + @namespace DS + @extends Ember.Object + @uses Ember.Enumerable + @uses Ember.Evented + */ +DS.Errors = Ember.Object.extend(Ember.Enumerable, Ember.Evented, { + /** + Register with target handler + + @method registerHandlers + @param {Object} target + @param {Function} becameInvalid + @param {Function} becameValid + */ + registerHandlers: function(target, becameInvalid, becameValid) { + this.on('becameInvalid', target, becameInvalid); + this.on('becameValid', target, becameValid); + }, + + /** + @property errorsByAttributeName + @type {Ember.MapWithDefault} + @private + */ + errorsByAttributeName: Ember.reduceComputed("content", { + initialValue: function() { + return Ember.MapWithDefault.create({ + defaultValue: function() { + return Ember.A(); + } + }); + }, + + addedItem: function(errors, error) { + errors.get(error.attribute).pushObject(error); + + return errors; + }, + + removedItem: function(errors, error) { + errors.get(error.attribute).removeObject(error); + + return errors; + } + }), + + /** + Returns errors for a given attribute + + @method errorsFor + @param {String} attribute + @returns {Array} + */ + errorsFor: function(attribute) { + return get(this, 'errorsByAttributeName').get(attribute); + }, + + /** + */ + messages: Ember.computed.mapBy('content', 'message'), + + /** + @property content + @type {Array} + @private + */ + content: Ember.computed(function() { + return Ember.A(); + }), + + /** + @method unknownProperty + @private + */ + unknownProperty: function(attribute) { + var errors = this.errorsFor(attribute); + if (isEmpty(errors)) { return null; } + return errors; + }, + + /** + @method nextObject + @private + */ + nextObject: function(index, previousObject, context) { + return get(this, 'content').objectAt(index); + }, + + /** + Total number of errors. + + @property length + @type {Number} + @readOnly + */ + length: Ember.computed.oneWay('content.length').readOnly(), + + /** + @property isEmpty + @type {Boolean} + @readOnly + */ + isEmpty: Ember.computed.not('length').readOnly(), + + /** + Adds error messages to a given attribute and sends + `becameInvalid` event to the record. + + @method add + @param {String} attribute + @param {Array|String} messages + */ + add: function(attribute, messages) { + var wasEmpty = get(this, 'isEmpty'); + + messages = this._findOrCreateMessages(attribute, messages); + get(this, 'content').addObjects(messages); + + this.notifyPropertyChange(attribute); + this.enumerableContentDidChange(); + + if (wasEmpty && !get(this, 'isEmpty')) { + this.trigger('becameInvalid'); + } + }, + + /** + @method _findOrCreateMessages + @private + */ + _findOrCreateMessages: function(attribute, messages) { + var errors = this.errorsFor(attribute); + + return Ember.makeArray(messages).map(function(message) { + return errors.findBy('message', message) || { + attribute: attribute, + message: message + }; + }); + }, + + /** + Removes all error messages from the given attribute and sends + `becameValid` event to the record if there no more errors left. + + @method remove + @param {String} attribute + */ + remove: function(attribute) { + if (get(this, 'isEmpty')) { return; } + + var content = get(this, 'content').rejectBy('attribute', attribute); + get(this, 'content').setObjects(content); + + this.notifyPropertyChange(attribute); + this.enumerableContentDidChange(); + + if (get(this, 'isEmpty')) { + this.trigger('becameValid'); + } + }, + + /** + Removes all error messages and sends `becameValid` event + to the record. + + @method clear + */ + clear: function() { + if (get(this, 'isEmpty')) { return; } + + get(this, 'content').clear(); + this.enumerableContentDidChange(); + + this.trigger('becameValid'); + }, + + /** + Checks if there is error messages for the given attribute. + + @method has + @param {String} attribute + @returns {Boolean} true if there some errors on given attribute + */ + has: function(attribute) { + return !isEmpty(this.errorsFor(attribute)); + } +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set, + merge = Ember.merge; + +var retrieveFromCurrentState = Ember.computed('currentState', function(key, value) { + return get(get(this, 'currentState'), key); +}).readOnly(); + +/** + + The model class that all Ember Data records descend from. + + @class Model + @namespace DS + @extends Ember.Object + @uses Ember.Evented +*/ +DS.Model = Ember.Object.extend(Ember.Evented, { + /** + If this property is `true` the record is in the `empty` + state. Empty is the first state all records enter after they have + been created. Most records created by the store will quickly + transition to the `loading` state if data needs to be fetched from + the server or the `created` state if the record is created on the + client. A record can also enter the empty state if the adapter is + unable to locate the record. + + @property isEmpty + @type {Boolean} + @readOnly + */ + isEmpty: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `loading` state. A + record enters this state when the store asks the adapter for its + data. It remains in this state until the adapter provides the + requested data. + + @property isLoading + @type {Boolean} + @readOnly + */ + isLoading: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `loaded` state. A + record enters this state when its data is populated. Most of a + record's lifecycle is spent inside substates of the `loaded` + state. + + Example + + ```javascript + var record = store.createRecord(App.Model); + record.get('isLoaded'); // true + + store.find('model', 1).then(function(model) { + model.get('isLoaded'); // true + }); + ``` + + @property isLoaded + @type {Boolean} + @readOnly + */ + isLoaded: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `dirty` state. The + record has local changes that have not yet been saved by the + adapter. This includes records that have been created (but not yet + saved) or deleted. + + Example + + ```javascript + var record = store.createRecord(App.Model); + record.get('isDirty'); // true + + store.find('model', 1).then(function(model) { + model.get('isDirty'); // false + model.set('foo', 'some value'); + model.set('isDirty'); // true + }); + ``` + + @property isDirty + @type {Boolean} + @readOnly + */ + isDirty: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `saving` state. A + record enters the saving state when `save` is called, but the + adapter has not yet acknowledged that the changes have been + persisted to the backend. + + Example + + ```javascript + var record = store.createRecord(App.Model); + record.get('isSaving'); // false + var promise = record.save(); + record.get('isSaving'); // true + promise.then(function() { + record.get('isSaving'); // false + }); + ``` + + @property isSaving + @type {Boolean} + @readOnly + */ + isSaving: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `deleted` state + and has been marked for deletion. When `isDeleted` is true and + `isDirty` is true, the record is deleted locally but the deletion + was not yet persisted. When `isSaving` is true, the change is + in-flight. When both `isDirty` and `isSaving` are false, the + change has persisted. + + Example + + ```javascript + var record = store.createRecord(App.Model); + record.get('isDeleted'); // false + record.deleteRecord(); + record.get('isDeleted'); // true + ``` + + @property isDeleted + @type {Boolean} + @readOnly + */ + isDeleted: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `new` state. A + record will be in the `new` state when it has been created on the + client and the adapter has not yet report that it was successfully + saved. + + Example + + ```javascript + var record = store.createRecord(App.Model); + record.get('isNew'); // true + + record.save().then(function(model) { + model.get('isNew'); // false + }); + ``` + + @property isNew + @type {Boolean} + @readOnly + */ + isNew: retrieveFromCurrentState, + /** + If this property is `true` the record is in the `valid` state. A + record will be in the `valid` state when no client-side + validations have failed and the adapter did not report any + server-side validation failures. + + @property isValid + @type {Boolean} + @readOnly + */ + isValid: retrieveFromCurrentState, + /** + If the record is in the dirty state this property will report what + kind of change has caused it to move into the dirty + state. Possible values are: + + - `created` The record has been created by the client and not yet saved to the adapter. + - `updated` The record has been updated by the client and not yet saved to the adapter. + - `deleted` The record has been deleted by the client and not yet saved to the adapter. + + Example + + ```javascript + var record = store.createRecord(App.Model); + record.get('dirtyType'); // 'created' + ``` + + @property dirtyType + @type {String} + @readOnly + */ + dirtyType: retrieveFromCurrentState, + + /** + If `true` the adapter reported that it was unable to save local + changes to the backend. This may also result in the record having + its `isValid` property become false if the adapter reported that + server-side validations failed. + + Example + + ```javascript + record.get('isError'); // false + record.set('foo', 'invalid value'); + record.save().then(null, function() { + record.get('isError'); // true + }); + ``` + + @property isError + @type {Boolean} + @readOnly + */ + isError: false, + /** + If `true` the store is attempting to reload the record form the adapter. + + Example + + ```javascript + record.get('isReloading'); // false + record.reload(); + record.get('isReloading'); // true + ``` + + @property isReloading + @type {Boolean} + @readOnly + */ + isReloading: false, + + /** + The `clientId` property is a transient numerical identifier + generated at runtime by the data store. It is important + primarily because newly created objects may not yet have an + externally generated id. + + @property clientId + @private + @type {Number|String} + */ + clientId: null, + /** + All ember models have an id property. This is an identifier + managed by an external source. These are always coerced to be + strings before being used internally. Note when declaring the + attributes for a model it is an error to declare an id + attribute. + + ```javascript + var record = store.createRecord(App.Model); + record.get('id'); // null + + store.find('model', 1).then(function(model) { + model.get('id'); // '1' + }); + ``` + + @property id + @type {String} + */ + id: null, + transaction: null, + /** + @property currentState + @private + @type {Object} + */ + currentState: null, + /** + When the record is in the `invalid` state this object will contain + any errors returned by the adapter. When present the errors hash + typically contains keys corresponding to the invalid property names + and values which are an array of error messages. + + ```javascript + record.get('errors.length'); // 0 + record.set('foo', 'invalid value'); + record.save().then(null, function() { + record.get('errors').get('foo'); // ['foo should be a number.'] + }); + ``` + + @property errors + @type {Object} + */ + errors: null, + + /** + Create a JSON representation of the record, using the serialization + strategy of the store's adapter. + + `serialize` takes an optional hash as a parameter, currently + supported options are: + + - `includeId`: `true` if the record's ID should be included in the + JSON representation. + + @method serialize + @param {Object} options + @returns {Object} an object whose values are primitive JSON values only + */ + serialize: function(options) { + var store = get(this, 'store'); + return store.serialize(this, options); + }, + + /** + Use [DS.JSONSerializer](DS.JSONSerializer.html) to + get the JSON representation of a record. + + `toJSON` takes an optional hash as a parameter, currently + supported options are: + + - `includeId`: `true` if the record's ID should be included in the + JSON representation. + + @method toJSON + @param {Object} options + @returns {Object} A JSON representation of the object. + */ + toJSON: function(options) { + // container is for lazy transform lookups + var serializer = DS.JSONSerializer.create({ container: this.container }); + return serializer.serialize(this, options); + }, + + /** + Fired when the record is loaded from the server. + + @event didLoad + */ + didLoad: Ember.K, + + /** + Fired when the record is updated. + + @event didUpdate + */ + didUpdate: Ember.K, + + /** + Fired when the record is created. + + @event didCreate + */ + didCreate: Ember.K, + + /** + Fired when the record is deleted. + + @event didDelete + */ + didDelete: Ember.K, + + /** + Fired when the record becomes invalid. + + @event becameInvalid + */ + becameInvalid: Ember.K, + + /** + Fired when the record enters the error state. + + @event becameError + */ + becameError: Ember.K, + + /** + @property data + @private + @type {Object} + */ + data: Ember.computed(function() { + this._data = this._data || {}; + return this._data; + }).property(), + + _data: null, + + init: function() { + set(this, 'currentState', DS.RootState.empty); + var errors = DS.Errors.create(); + errors.registerHandlers(this, function() { + this.send('becameInvalid'); + }, function() { + this.send('becameValid'); + }); + set(this, 'errors', errors); + this._super(); + this._setup(); + }, + + _setup: function() { + this._changesToSync = {}; + this._deferredTriggers = []; + this._data = {}; + this._attributes = {}; + this._inFlightAttributes = {}; + this._relationships = {}; + }, + + /** + @method send + @private + @param {String} name + @param {Object} context + */ + send: function(name, context) { + var currentState = get(this, 'currentState'); + + if (!currentState[name]) { + this._unhandledEvent(currentState, name, context); + } + + return currentState[name](this, context); + }, + + /** + @method transitionTo + @private + @param {String} name + */ + transitionTo: function(name) { + // POSSIBLE TODO: Remove this code and replace with + // always having direct references to state objects + + var pivotName = name.split(".", 1), + currentState = get(this, 'currentState'), + state = currentState; + + do { + if (state.exit) { state.exit(this); } + state = state.parentState; + } while (!state.hasOwnProperty(pivotName)); + + var path = name.split("."); + + var setups = [], enters = [], i, l; + + for (i=0, l=path.length; i')` from " + this.toString(), name !== 'id'); + + meta.name = name; + map.set(name, meta); + } + }); + + return map; + }), + + /** + A map whose keys are the attributes of the model (properties + described by DS.attr) and whose values are type of transformation + applied to each attribute. This map does not include any + attributes that do not have an transformation type. + + Example + + ```javascript + App.Person = DS.Model.extend({ + firstName: attr(), + lastName: attr('string'), + birthday: attr('date') + }); + + var transformedAttributes = Ember.get(App.Person, 'transformedAttributes') + + transformedAttributes.forEach(function(field, type) { + console.log(field, type); + }); + + // prints: + // lastName string + // birthday date + ``` + + @property transformedAttributes + @static + @type {Ember.Map} + @readOnly + */ + transformedAttributes: Ember.computed(function() { + var map = Ember.Map.create(); + + this.eachAttribute(function(key, meta) { + if (meta.type) { + map.set(key, meta.type); + } + }); + + return map; + }), + + /** + Iterates through the attributes of the model, calling the passed function on each + attribute. + + The callback method you provide should have the following signature (all + parameters are optional): + + ```javascript + function(name, meta); + ``` + + - `name` the name of the current property in the iteration + - `meta` the meta object for the attribute property in the iteration + + Note that in addition to a callback, you can also pass an optional target + object that will be set as `this` on the context. + + Example + + ```javascript + App.Person = DS.Model.extend({ + firstName: attr('string'), + lastName: attr('string'), + birthday: attr('date') + }); + + App.Person.eachAttribute(function(name, meta) { + console.log(name, meta); + }); + + // prints: + // firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"} + // lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"} + // birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"} + ``` + + @method eachAttribute + @param {Function} callback The callback to execute + @param {Object} [target] The target object to use + @static + */ + eachAttribute: function(callback, binding) { + get(this, 'attributes').forEach(function(name, meta) { + callback.call(binding, name, meta); + }, binding); + }, + + /** + Iterates through the transformedAttributes of the model, calling + the passed function on each attribute. Note the callback will not be + called for any attributes that do not have an transformation type. + + The callback method you provide should have the following signature (all + parameters are optional): + + ```javascript + function(name, type); + ``` + + - `name` the name of the current property in the iteration + - `type` a string containing the name of the type of transformed + applied to the attribute + + Note that in addition to a callback, you can also pass an optional target + object that will be set as `this` on the context. + + Example + + ```javascript + App.Person = DS.Model.extend({ + firstName: attr(), + lastName: attr('string'), + birthday: attr('date') + }); + + App.Person.eachTransformedAttribute(function(name, type) { + console.log(name, type); + }); + + // prints: + // lastName string + // birthday date + ``` + + @method eachTransformedAttribute + @param {Function} callback The callback to execute + @param {Object} [target] The target object to use + @static + */ + eachTransformedAttribute: function(callback, binding) { + get(this, 'transformedAttributes').forEach(function(name, type) { + callback.call(binding, name, type); + }); + } +}); + + +DS.Model.reopen({ + eachAttribute: function(callback, binding) { + this.constructor.eachAttribute(callback, binding); + } +}); + +function getDefaultValue(record, options, key) { + if (typeof options.defaultValue === "function") { + return options.defaultValue(); + } else { + return options.defaultValue; + } +} + +function hasValue(record, key) { + return record._attributes.hasOwnProperty(key) || + record._inFlightAttributes.hasOwnProperty(key) || + record._data.hasOwnProperty(key); +} + +function getValue(record, key) { + if (record._attributes.hasOwnProperty(key)) { + return record._attributes[key]; + } else if (record._inFlightAttributes.hasOwnProperty(key)) { + return record._inFlightAttributes[key]; + } else { + return record._data[key]; + } +} + +/** + `DS.attr` defines an attribute on a [DS.Model](DS.Model.html). + By default, attributes are passed through as-is, however you can specify an + optional type to have the value automatically transformed. + Ember Data ships with four basic transform types: `string`, `number`, + `boolean` and `date`. You can define your own transforms by subclassing + [DS.Transform](DS.Transform.html). + + `DS.attr` takes an optional hash as a second parameter, currently + supported options are: + + - `defaultValue`: Pass a string or a function to be called to set the attribute + to a default value if none is supplied. + + Example + + ```javascript + var attr = DS.attr; + + App.User = DS.Model.extend({ + username: attr('string'), + email: attr('string'), + verified: attr('boolean', {defaultValue: false}) + }); + ``` + + @namespace + @method attr + @for DS + @param {String} type the attribute type + @param {Object} options a hash of options + @return {Attribute} +*/ + +DS.attr = function(type, options) { + options = options || {}; + + var meta = { + type: type, + isAttribute: true, + options: options + }; + + return Ember.computed(function(key, value) { + 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('')` from " + this.constructor.toString(), key !== 'id'); + var oldValue = this._attributes[key] || this._inFlightAttributes[key] || this._data[key]; + + this.send('didSetProperty', { + name: key, + oldValue: oldValue, + originalValue: this._data[key], + value: value + }); + + this._attributes[key] = value; + return value; + } else if (hasValue(this, key)) { + return getValue(this, key); + } else { + return getDefaultValue(this, options, key); + } + + // `data` is never set directly. However, it may be + // invalidated from the state manager's setData + // event. + }).property('data').meta(meta); +}; + + +})(); + + + +(function() { +/** + @module ember-data +*/ + +})(); + + + +(function() { +/** + @module ember-data +*/ + +/** + An AttributeChange object is created whenever a record's + attribute changes value. It is used to track changes to a + record between transaction commits. + + @class AttributeChange + @namespace DS + @private + @constructor +*/ +var AttributeChange = DS.AttributeChange = function(options) { + this.record = options.record; + this.store = options.store; + this.name = options.name; + this.value = options.value; + this.oldValue = options.oldValue; +}; + +AttributeChange.createChange = function(options) { + return new AttributeChange(options); +}; + +AttributeChange.prototype = { + sync: function() { + if (this.value !== this.oldValue) { + this.record.send('becomeDirty'); + this.record.updateRecordArraysLater(); + } + + // TODO: Use this object in the commit process + this.destroy(); + }, + + /** + If the AttributeChange is destroyed (either by being rolled back + or being committed), remove it from the list of pending changes + on the record. + + @method destroy + */ + destroy: function() { + delete this.record._changesToSync[this.name]; + } +}; + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set; +var forEach = Ember.EnumerableUtils.forEach; + +/** + @class RelationshipChange + @namespace DS + @private + @constructor +*/ +DS.RelationshipChange = function(options) { + this.parentRecord = options.parentRecord; + this.childRecord = options.childRecord; + this.firstRecord = options.firstRecord; + this.firstRecordKind = options.firstRecordKind; + this.firstRecordName = options.firstRecordName; + this.secondRecord = options.secondRecord; + this.secondRecordKind = options.secondRecordKind; + this.secondRecordName = options.secondRecordName; + this.changeType = options.changeType; + this.store = options.store; + + this.committed = {}; +}; + +/** + @class RelationshipChangeAdd + @namespace DS + @private + @constructor +*/ +DS.RelationshipChangeAdd = function(options){ + DS.RelationshipChange.call(this, options); +}; + +/** + @class RelationshipChangeRemove + @namespace DS + @private + @constructor +*/ +DS.RelationshipChangeRemove = function(options){ + DS.RelationshipChange.call(this, options); +}; + +DS.RelationshipChange.create = function(options) { + return new DS.RelationshipChange(options); +}; + +DS.RelationshipChangeAdd.create = function(options) { + return new DS.RelationshipChangeAdd(options); +}; + +DS.RelationshipChangeRemove.create = function(options) { + return new DS.RelationshipChangeRemove(options); +}; + +DS.OneToManyChange = {}; +DS.OneToNoneChange = {}; +DS.ManyToNoneChange = {}; +DS.OneToOneChange = {}; +DS.ManyToManyChange = {}; + +DS.RelationshipChange._createChange = function(options){ + if(options.changeType === "add"){ + return DS.RelationshipChangeAdd.create(options); + } + if(options.changeType === "remove"){ + return DS.RelationshipChangeRemove.create(options); + } +}; + + +DS.RelationshipChange.determineRelationshipType = function(recordType, knownSide){ + var knownKey = knownSide.key, key, otherKind; + var knownKind = knownSide.kind; + + var inverse = recordType.inverseFor(knownKey); + + if (inverse){ + key = inverse.name; + otherKind = inverse.kind; + } + + if (!inverse){ + return knownKind === "belongsTo" ? "oneToNone" : "manyToNone"; + } + else{ + if(otherKind === "belongsTo"){ + return knownKind === "belongsTo" ? "oneToOne" : "manyToOne"; + } + else{ + return knownKind === "belongsTo" ? "oneToMany" : "manyToMany"; + } + } + +}; + +DS.RelationshipChange.createChange = function(firstRecord, secondRecord, store, options){ + // Get the type of the child based on the child's client ID + var firstRecordType = firstRecord.constructor, changeType; + changeType = DS.RelationshipChange.determineRelationshipType(firstRecordType, options); + if (changeType === "oneToMany"){ + return DS.OneToManyChange.createChange(firstRecord, secondRecord, store, options); + } + else if (changeType === "manyToOne"){ + return DS.OneToManyChange.createChange(secondRecord, firstRecord, store, options); + } + else if (changeType === "oneToNone"){ + return DS.OneToNoneChange.createChange(firstRecord, secondRecord, store, options); + } + else if (changeType === "manyToNone"){ + return DS.ManyToNoneChange.createChange(firstRecord, secondRecord, store, options); + } + else if (changeType === "oneToOne"){ + return DS.OneToOneChange.createChange(firstRecord, secondRecord, store, options); + } + else if (changeType === "manyToMany"){ + return DS.ManyToManyChange.createChange(firstRecord, secondRecord, store, options); + } +}; + +DS.OneToNoneChange.createChange = function(childRecord, parentRecord, store, options) { + var key = options.key; + var change = DS.RelationshipChange._createChange({ + parentRecord: parentRecord, + childRecord: childRecord, + firstRecord: childRecord, + store: store, + changeType: options.changeType, + firstRecordName: key, + firstRecordKind: "belongsTo" + }); + + store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); + + return change; +}; + +DS.ManyToNoneChange.createChange = function(childRecord, parentRecord, store, options) { + var key = options.key; + var change = DS.RelationshipChange._createChange({ + parentRecord: childRecord, + childRecord: parentRecord, + secondRecord: childRecord, + store: store, + changeType: options.changeType, + secondRecordName: options.key, + secondRecordKind: "hasMany" + }); + + store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); + return change; +}; + + +DS.ManyToManyChange.createChange = function(childRecord, parentRecord, store, options) { + // 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 + // definition. + var key = options.key; + + var change = DS.RelationshipChange._createChange({ + parentRecord: parentRecord, + childRecord: childRecord, + firstRecord: childRecord, + secondRecord: parentRecord, + firstRecordKind: "hasMany", + secondRecordKind: "hasMany", + store: store, + changeType: options.changeType, + firstRecordName: key + }); + + store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); + + + return change; +}; + +DS.OneToOneChange.createChange = function(childRecord, parentRecord, store, options) { + var 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 + // definition. + if (options.parentType) { + key = options.parentType.inverseFor(options.key).name; + } else if (options.key) { + key = options.key; + } else { + Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent", false); + } + + var change = DS.RelationshipChange._createChange({ + parentRecord: parentRecord, + childRecord: childRecord, + firstRecord: childRecord, + secondRecord: parentRecord, + firstRecordKind: "belongsTo", + secondRecordKind: "belongsTo", + store: store, + changeType: options.changeType, + firstRecordName: key + }); + + store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); + + + return change; +}; + +DS.OneToOneChange.maintainInvariant = function(options, store, childRecord, key){ + if (options.changeType === "add" && store.recordIsMaterialized(childRecord)) { + var oldParent = get(childRecord, key); + if (oldParent){ + var correspondingChange = DS.OneToOneChange.createChange(childRecord, oldParent, store, { + parentType: options.parentType, + hasManyName: options.hasManyName, + changeType: "remove", + key: options.key + }); + store.addRelationshipChangeFor(childRecord, key, options.parentRecord , null, correspondingChange); + correspondingChange.sync(); + } + } +}; + +DS.OneToManyChange.createChange = function(childRecord, parentRecord, store, options) { + var 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 + // definition. + if (options.parentType) { + key = options.parentType.inverseFor(options.key).name; + DS.OneToManyChange.maintainInvariant( options, store, childRecord, key ); + } else if (options.key) { + key = options.key; + } else { + Ember.assert("You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent", false); + } + + var change = DS.RelationshipChange._createChange({ + parentRecord: parentRecord, + childRecord: childRecord, + firstRecord: childRecord, + secondRecord: parentRecord, + firstRecordKind: "belongsTo", + secondRecordKind: "hasMany", + store: store, + changeType: options.changeType, + firstRecordName: key + }); + + store.addRelationshipChangeFor(childRecord, key, parentRecord, change.getSecondRecordName(), change); + + + return change; +}; + + +DS.OneToManyChange.maintainInvariant = function(options, store, childRecord, key){ + if (options.changeType === "add" && childRecord) { + var oldParent = get(childRecord, key); + if (oldParent){ + var correspondingChange = DS.OneToManyChange.createChange(childRecord, oldParent, store, { + parentType: options.parentType, + hasManyName: options.hasManyName, + changeType: "remove", + key: options.key + }); + store.addRelationshipChangeFor(childRecord, key, options.parentRecord, correspondingChange.getSecondRecordName(), correspondingChange); + correspondingChange.sync(); + } + } +}; + +/** + @class RelationshipChange + @namespace DS +*/ +DS.RelationshipChange.prototype = { + + getSecondRecordName: function() { + var name = this.secondRecordName, parent; + + if (!name) { + parent = this.secondRecord; + if (!parent) { return; } + + var childType = this.firstRecord.constructor; + var inverse = childType.inverseFor(this.firstRecordName); + this.secondRecordName = inverse.name; + } + + return this.secondRecordName; + }, + + /** + Get the name of the relationship on the belongsTo side. + + @method getFirstRecordName + @return {String} + */ + getFirstRecordName: function() { + var name = this.firstRecordName; + return name; + }, + + /** + @method destroy + @private + */ + destroy: function() { + var childRecord = this.childRecord, + belongsToName = this.getFirstRecordName(), + hasManyName = this.getSecondRecordName(), + store = this.store; + + store.removeRelationshipChangeFor(childRecord, belongsToName, this.parentRecord, hasManyName, this.changeType); + }, + + getSecondRecord: function(){ + return this.secondRecord; + }, + + /** + @method getFirstRecord + @private + */ + getFirstRecord: function() { + return this.firstRecord; + }, + + coalesce: function(){ + var relationshipPairs = this.store.relationshipChangePairsFor(this.firstRecord); + forEach(relationshipPairs, function(pair){ + var addedChange = pair["add"]; + var removedChange = pair["remove"]; + if(addedChange && removedChange) { + addedChange.destroy(); + removedChange.destroy(); + } + }); + } +}; + +DS.RelationshipChangeAdd.prototype = Ember.create(DS.RelationshipChange.create({})); +DS.RelationshipChangeRemove.prototype = Ember.create(DS.RelationshipChange.create({})); + +// the object is a value, and not a promise +function isValue(object) { + return typeof object === 'object' && (!object.then || typeof object.then !== 'function'); +} + +DS.RelationshipChangeAdd.prototype.changeType = "add"; +DS.RelationshipChangeAdd.prototype.sync = function() { + var secondRecordName = this.getSecondRecordName(), + firstRecordName = this.getFirstRecordName(), + firstRecord = this.getFirstRecord(), + secondRecord = this.getSecondRecord(); + + //Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName); + //Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName); + + if (secondRecord instanceof DS.Model && firstRecord instanceof DS.Model) { + if(this.secondRecordKind === "belongsTo"){ + secondRecord.suspendRelationshipObservers(function(){ + set(secondRecord, secondRecordName, firstRecord); + }); + + } + else if(this.secondRecordKind === "hasMany"){ + secondRecord.suspendRelationshipObservers(function(){ + var relationship = get(secondRecord, secondRecordName); + if (isValue(relationship)) { relationship.addObject(firstRecord); } + }); + } + } + + if (firstRecord instanceof DS.Model && secondRecord instanceof DS.Model && get(firstRecord, firstRecordName) !== secondRecord) { + if(this.firstRecordKind === "belongsTo"){ + firstRecord.suspendRelationshipObservers(function(){ + set(firstRecord, firstRecordName, secondRecord); + }); + } + else if(this.firstRecordKind === "hasMany"){ + firstRecord.suspendRelationshipObservers(function(){ + var relationship = get(firstRecord, firstRecordName); + if (isValue(relationship)) { relationship.addObject(secondRecord); } + }); + } + } + + this.coalesce(); +}; + +DS.RelationshipChangeRemove.prototype.changeType = "remove"; +DS.RelationshipChangeRemove.prototype.sync = function() { + var secondRecordName = this.getSecondRecordName(), + firstRecordName = this.getFirstRecordName(), + firstRecord = this.getFirstRecord(), + secondRecord = this.getSecondRecord(); + + //Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName); + //Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName); + + if (secondRecord instanceof DS.Model && firstRecord instanceof DS.Model) { + if(this.secondRecordKind === "belongsTo"){ + secondRecord.suspendRelationshipObservers(function(){ + set(secondRecord, secondRecordName, null); + }); + } + else if(this.secondRecordKind === "hasMany"){ + secondRecord.suspendRelationshipObservers(function(){ + var relationship = get(secondRecord, secondRecordName); + if (isValue(relationship)) { relationship.removeObject(firstRecord); } + }); + } + } + + if (firstRecord instanceof DS.Model && get(firstRecord, firstRecordName)) { + if(this.firstRecordKind === "belongsTo"){ + firstRecord.suspendRelationshipObservers(function(){ + set(firstRecord, firstRecordName, null); + }); + } + else if(this.firstRecordKind === "hasMany"){ + firstRecord.suspendRelationshipObservers(function(){ + var relationship = get(firstRecord, firstRecordName); + if (isValue(relationship)) { relationship.removeObject(secondRecord); } + }); + } + } + + this.coalesce(); +}; + +})(); + + + +(function() { +/** + @module ember-data +*/ + +})(); + + + +(function() { +var get = Ember.get, set = Ember.set, + isNone = Ember.isNone; + +/** + @module ember-data +*/ + +function asyncBelongsTo(type, options, meta) { + return Ember.computed(function(key, value) { + var data = get(this, 'data'), + store = get(this, 'store'), + promiseLabel = "DS: Async belongsTo " + this + " : " + key; + + if (arguments.length === 2) { + Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof store.modelFor(type)); + return value === undefined ? null : DS.PromiseObject.create({ promise: Ember.RSVP.resolve(value, promiseLabel) }); + } + + var link = data.links && data.links[key], + belongsTo = data[key]; + + if(!isNone(belongsTo)) { + var promise = store.fetchRecord(belongsTo) || Ember.RSVP.resolve(belongsTo, promiseLabel); + return DS.PromiseObject.create({ promise: promise}); + } else if (link) { + var resolver = Ember.RSVP.defer("DS: Async belongsTo (link) " + this + " : " + key); + store.findBelongsTo(this, link, meta, resolver); + return DS.PromiseObject.create({ promise: resolver.promise }); + } else { + return null; + } + }).property('data').meta(meta); +} + +/** + `DS.belongsTo` is used to define One-To-One and One-To-Many + relationships on a [DS.Model](DS.Model.html). + + + `DS.belongsTo` takes an optional hash as a second parameter, currently + supported options are: + + - `async`: A boolean value used to explicitly declare this to be an async relationship. + - `inverse`: A string used to identify the inverse property on a + related model in a One-To-Many relationship. See [Explicit Inverses](#toc_explicit-inverses) + + #### One-To-One + To declare a one-to-one relationship between two models, use + `DS.belongsTo`: + + ```javascript + App.User = DS.Model.extend({ + profile: DS.belongsTo('profile') + }); + + App.Profile = DS.Model.extend({ + user: DS.belongsTo('user') + }); + ``` + + #### One-To-Many + To declare a one-to-many relationship between two models, use + `DS.belongsTo` in combination with `DS.hasMany`, like this: + + ```javascript + App.Post = DS.Model.extend({ + comments: DS.hasMany('comment') + }); + + App.Comment = DS.Model.extend({ + post: DS.belongsTo('post') + }); + ``` + + @namespace + @method belongsTo + @for DS + @param {String or DS.Model} type the model type of the relationship + @param {Object} options a hash of options + @return {Ember.computed} relationship +*/ +DS.belongsTo = function(type, options) { + if (typeof type === 'object') { + options = type; + type = undefined; + } else { + Ember.assert("The first argument DS.belongsTo must be a model type or string, like DS.belongsTo(App.Person)", !!type && (typeof type === 'string' || DS.Model.detect(type))); + } + + options = options || {}; + + var meta = { type: type, isRelationship: true, options: options, kind: 'belongsTo' }; + + if (options.async) { + return asyncBelongsTo(type, options, meta); + } + + return Ember.computed(function(key, value) { + var data = get(this, 'data'), + store = get(this, 'store'), belongsTo, typeClass; + + if (typeof type === 'string') { + typeClass = store.modelFor(type); + } else { + typeClass = type; + } + + if (arguments.length === 2) { + Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof typeClass); + return value === undefined ? null : value; + } + + belongsTo = data[key]; + + if (isNone(belongsTo)) { return null; } + + store.fetchRecord(belongsTo); + + return belongsTo; + }).property('data').meta(meta); +}; + +/** + These observers observe all `belongsTo` relationships on the record. See + `relationships/ext` to see how these observers get their dependencies. + + @class Model + @namespace DS +*/ +DS.Model.reopen({ + + /** + @method belongsToWillChange + @private + @static + @param record + @param key + */ + belongsToWillChange: Ember.beforeObserver(function(record, key) { + if (get(record, 'isLoaded')) { + var oldParent = get(record, key); + + if (oldParent) { + var store = get(record, 'store'), + change = DS.RelationshipChange.createChange(record, oldParent, store, { key: key, kind: "belongsTo", changeType: "remove" }); + + change.sync(); + this._changesToSync[key] = change; + } + } + }), + + /** + @method belongsToDidChange + @private + @static + @param record + @param key + */ + belongsToDidChange: Ember.immediateObserver(function(record, key) { + if (get(record, 'isLoaded')) { + var newParent = get(record, key); + + if (newParent) { + var store = get(record, 'store'), + change = DS.RelationshipChange.createChange(record, newParent, store, { key: key, kind: "belongsTo", changeType: "add" }); + + change.sync(); + } + } + + delete this._changesToSync[key]; + }) +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set, setProperties = Ember.setProperties; + +function asyncHasMany(type, options, meta) { + return Ember.computed(function(key, value) { + var relationship = this._relationships[key], + promiseLabel = "DS: Async hasMany " + this + " : " + key; + + if (!relationship) { + var resolver = Ember.RSVP.defer(promiseLabel); + relationship = buildRelationship(this, key, options, function(store, data) { + var link = data.links && data.links[key]; + var rel; + if (link) { + rel = store.findHasMany(this, link, meta, resolver); + } else { + rel = store.findMany(this, data[key], meta.type, resolver); + } + // cache the promise so we can use it + // when we come back and don't need to rebuild + // the relationship. + set(rel, 'promise', resolver.promise); + return rel; + }); + } + + var promise = relationship.get('promise').then(function() { + return relationship; + }, null, "DS: Async hasMany records received"); + + return DS.PromiseArray.create({ promise: promise }); + }).property('data').meta(meta); +} + +function buildRelationship(record, key, options, callback) { + var rels = record._relationships; + + if (rels[key]) { return rels[key]; } + + var data = get(record, 'data'), + store = get(record, 'store'); + + var relationship = rels[key] = callback.call(record, store, data); + + return setProperties(relationship, { + owner: record, name: key, isPolymorphic: options.polymorphic + }); +} + +function hasRelationship(type, options) { + options = options || {}; + + var meta = { type: type, isRelationship: true, options: options, kind: 'hasMany' }; + + if (options.async) { + return asyncHasMany(type, options, meta); + } + + return Ember.computed(function(key, value) { + return buildRelationship(this, key, options, function(store, data) { + var records = data[key]; + Ember.assert("You looked up the '" + key + "' relationship on '" + this + "' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`)", Ember.A(records).everyProperty('isEmpty', false)); + return store.findMany(this, data[key], meta.type); + }); + }).property('data').meta(meta); +} + +/** + `DS.hasMany` is used to define One-To-Many and Many-To-Many + relationships on a [DS.Model](DS.Model.html). + + `DS.hasMany` takes an optional hash as a second parameter, currently + supported options are: + + - `async`: A boolean value used to explicitly declare this to be an async relationship. + - `inverse`: A string used to identify the inverse property on a related model. + + #### One-To-Many + To declare a one-to-many relationship between two models, use + `DS.belongsTo` in combination with `DS.hasMany`, like this: + + ```javascript + App.Post = DS.Model.extend({ + comments: DS.hasMany('comment') + }); + + App.Comment = DS.Model.extend({ + post: DS.belongsTo('post') + }); + ``` + + #### Many-To-Many + To declare a many-to-many relationship between two models, use + `DS.hasMany`: + + ```javascript + App.Post = DS.Model.extend({ + tags: DS.hasMany('tag') + }); + + App.Tag = DS.Model.extend({ + posts: DS.hasMany('post') + }); + ``` + + #### Explicit Inverses + + Ember Data will do its best to discover which relationships map to + one another. In the one-to-many code above, for example, Ember Data + can figure out that changing the `comments` relationship should update + the `post` relationship on the inverse because post is the only + relationship to that model. + + However, sometimes you may have multiple `belongsTo`/`hasManys` for the + same type. You can specify which property on the related model is + the inverse using `DS.hasMany`'s `inverse` option: + + ```javascript + var belongsTo = DS.belongsTo, + hasMany = DS.hasMany; + + App.Comment = DS.Model.extend({ + onePost: belongsTo('post'), + twoPost: belongsTo('post'), + redPost: belongsTo('post'), + bluePost: belongsTo('post') + }); + + App.Post = DS.Model.extend({ + comments: hasMany('comment', { + inverse: 'redPost' + }) + }); + ``` + + You can also specify an inverse on a `belongsTo`, which works how + you'd expect. + + @namespace + @method hasMany + @for DS + @param {String or DS.Model} type the model type of the relationship + @param {Object} options a hash of options + @return {Ember.computed} relationship +*/ +DS.hasMany = function(type, options) { + if (typeof type === 'object') { + options = type; + type = undefined; + } + return hasRelationship(type, options); +}; + +})(); + + + +(function() { +var get = Ember.get, set = Ember.set; + +/** + @module ember-data +*/ + +/* + This file defines several extensions to the base `DS.Model` class that + add support for one-to-many relationships. +*/ + +/** + @class Model + @namespace DS +*/ +DS.Model.reopen({ + + /** + This Ember.js hook allows an object to be notified when a property + is defined. + + In this case, we use it to be notified when an Ember Data user defines a + belongs-to relationship. In that case, we need to set up observers for + each one, allowing us to track relationship changes and automatically + reflect changes in the inverse has-many array. + + This hook passes the class being set up, as well as the key and value + being defined. So, for example, when the user does this: + + ```javascript + DS.Model.extend({ + parent: DS.belongsTo('user') + }); + ``` + + This hook would be called with "parent" as the key and the computed + property returned by `DS.belongsTo` as the value. + + @method didDefineProperty + @param proto + @param key + @param value + */ + didDefineProperty: function(proto, key, value) { + // Check if the value being set is a computed property. + if (value instanceof Ember.Descriptor) { + + // If it is, get the metadata for the relationship. This is + // populated by the `DS.belongsTo` helper when it is creating + // the computed property. + var meta = value.meta(); + + if (meta.isRelationship && meta.kind === 'belongsTo') { + Ember.addObserver(proto, key, null, 'belongsToDidChange'); + Ember.addBeforeObserver(proto, key, null, 'belongsToWillChange'); + } + + meta.parentType = proto.constructor; + } + } +}); + +/* + These DS.Model extensions add class methods that provide relationship + introspection abilities about relationships. + + A note about the computed properties contained here: + + **These properties are effectively sealed once called for the first time.** + To avoid repeatedly doing expensive iteration over a model's fields, these + values are computed once and then cached for the remainder of the runtime of + your application. + + If your application needs to modify a class after its initial definition + (for example, using `reopen()` to add additional attributes), make sure you + do it before using your model with the store, which uses these properties + extensively. +*/ + +DS.Model.reopenClass({ + /** + For a given relationship name, returns the model type of the relationship. + + For example, if you define a model like this: + + ```javascript + App.Post = DS.Model.extend({ + comments: DS.hasMany('comment') + }); + ``` + + Calling `App.Post.typeForRelationship('comments')` will return `App.Comment`. + + @method typeForRelationship + @static + @param {String} name the name of the relationship + @return {subclass of DS.Model} the type of the relationship, or undefined + */ + typeForRelationship: function(name) { + var relationship = get(this, 'relationshipsByName').get(name); + return relationship && relationship.type; + }, + + inverseFor: function(name) { + var inverseType = this.typeForRelationship(name); + + if (!inverseType) { return null; } + + var options = this.metaForProperty(name).options; + + if (options.inverse === null) { return null; } + + var inverseName, inverseKind; + + if (options.inverse) { + inverseName = options.inverse; + inverseKind = Ember.get(inverseType, 'relationshipsByName').get(inverseName).kind; + } else { + var possibleRelationships = findPossibleInverses(this, inverseType); + + if (possibleRelationships.length === 0) { return null; } + + Ember.assert("You defined the '" + name + "' relationship on " + this + ", but multiple possible inverse relationships of type " + this + " were found on " + inverseType + ". Look at http://emberjs.com/guides/models/defining-models/#toc_explicit-inverses for how to explicitly specify inverses", possibleRelationships.length === 1); + + inverseName = possibleRelationships[0].name; + inverseKind = possibleRelationships[0].kind; + } + + function findPossibleInverses(type, inverseType, possibleRelationships) { + possibleRelationships = possibleRelationships || []; + + var relationshipMap = get(inverseType, 'relationships'); + if (!relationshipMap) { return; } + + var relationships = relationshipMap.get(type); + if (relationships) { + possibleRelationships.push.apply(possibleRelationships, relationshipMap.get(type)); + } + + if (type.superclass) { + findPossibleInverses(type.superclass, inverseType, possibleRelationships); + } + + return possibleRelationships; + } + + return { + type: inverseType, + name: inverseName, + kind: inverseKind + }; + }, + + /** + The model's relationships as a map, keyed on the type of the + relationship. The value of each entry is an array containing a descriptor + for each relationship with that type, describing the name of the relationship + as well as the type. + + For example, given the following model definition: + + ```javascript + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + posts: DS.hasMany('post') + }); + ``` + + This computed property would return a map describing these + relationships, like this: + + ```javascript + var relationships = Ember.get(App.Blog, 'relationships'); + relationships.get(App.User); + //=> [ { name: 'users', kind: 'hasMany' }, + // { name: 'owner', kind: 'belongsTo' } ] + relationships.get(App.Post); + //=> [ { name: 'posts', kind: 'hasMany' } ] + ``` + + @property relationships + @static + @type Ember.Map + @readOnly + */ + relationships: Ember.computed(function() { + var map = new Ember.MapWithDefault({ + defaultValue: function() { return []; } + }); + + // Loop through each computed property on the class + this.eachComputedProperty(function(name, meta) { + + // If the computed property is a relationship, add + // it to the map. + if (meta.isRelationship) { + if (typeof meta.type === 'string') { + meta.type = this.store.modelFor(meta.type); + } + + var relationshipsForType = map.get(meta.type); + + relationshipsForType.push({ name: name, kind: meta.kind }); + } + }); + + return map; + }), + + /** + A hash containing lists of the model's relationships, grouped + by the relationship kind. For example, given a model with this + definition: + + ```javascript + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + + posts: DS.hasMany('post') + }); + ``` + + This property would contain the following: + + ```javascript + var relationshipNames = Ember.get(App.Blog, 'relationshipNames'); + relationshipNames.hasMany; + //=> ['users', 'posts'] + relationshipNames.belongsTo; + //=> ['owner'] + ``` + + @property relationshipNames + @static + @type Object + @readOnly + */ + relationshipNames: Ember.computed(function() { + var names = { hasMany: [], belongsTo: [] }; + + this.eachComputedProperty(function(name, meta) { + if (meta.isRelationship) { + names[meta.kind].push(name); + } + }); + + return names; + }), + + /** + 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: + + ```javascript + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + + posts: DS.hasMany('post') + }); + ``` + + This property would contain the following: + + ```javascript + var relatedTypes = Ember.get(App.Blog, 'relatedTypes'); + //=> [ App.User, App.Post ] + ``` + + @property relatedTypes + @static + @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) || this.store.modelFor(type); + } + + Ember.assert("You specified a hasMany (" + meta.type + ") on " + meta.parentType + " but " + meta.type + " was not found.", 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. + + For example, given a model with this + definition: + + ```javascript + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + + posts: DS.hasMany('post') + }); + ``` + + This property would contain the following: + + ```javascript + var relationshipsByName = Ember.get(App.Blog, 'relationshipsByName'); + relationshipsByName.get('users'); + //=> { key: 'users', kind: 'hasMany', type: App.User } + relationshipsByName.get('owner'); + //=> { key: 'owner', kind: 'belongsTo', type: App.User } + ``` + + @property relationshipsByName + @static + @type Ember.Map + @readOnly + */ + relationshipsByName: Ember.computed(function() { + var map = Ember.Map.create(), type; + + this.eachComputedProperty(function(name, meta) { + if (meta.isRelationship) { + meta.key = name; + type = meta.type; + + if (!type && meta.kind === 'hasMany') { + type = Ember.String.singularize(name); + } else if (!type) { + type = name; + } + + if (typeof type === 'string') { + meta.type = this.store.modelFor(type); + } + + map.set(name, meta); + } + }); + + return map; + }), + + /** + A map whose keys are the fields of the model and whose values are strings + describing the kind of the field. A model's fields are the union of all of its + attributes and relationships. + + For example: + + ```javascript + + App.Blog = DS.Model.extend({ + users: DS.hasMany('user'), + owner: DS.belongsTo('user'), + + posts: DS.hasMany('post'), + + title: DS.attr('string') + }); + + var fields = Ember.get(App.Blog, 'fields'); + fields.forEach(function(field, kind) { + console.log(field, kind); + }); + + // prints: + // users, hasMany + // owner, belongsTo + // posts, hasMany + // title, attribute + ``` + + @property fields + @static + @type Ember.Map + @readOnly + */ + fields: Ember.computed(function() { + var map = Ember.Map.create(); + + this.eachComputedProperty(function(name, meta) { + if (meta.isRelationship) { + map.set(name, meta.kind); + } else if (meta.isAttribute) { + map.set(name, 'attribute'); + } + }); + + return map; + }), + + /** + Given a callback, iterates over each of the relationships in the model, + invoking the callback with the name of each relationship and its relationship + descriptor. + + @method eachRelationship + @static + @param {Function} callback the callback to invoke + @param {any} binding the value to which the callback's `this` should be bound + */ + eachRelationship: function(callback, binding) { + 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. + + @method eachRelatedType + @static + @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); + }); + } +}); + +DS.Model.reopen({ + /** + Given a callback, iterates over each of the relationships in the model, + invoking the callback with the name of each relationship and its relationship + descriptor. + + @method eachRelationship + @param {Function} callback the callback to invoke + @param {any} binding the value to which the callback's `this` should be bound + */ + eachRelationship: function(callback, binding) { + this.constructor.eachRelationship(callback, binding); + } +}); + +})(); + + + +(function() { +/** + @module ember-data +*/ + +})(); + + + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, set = Ember.set; +var forEach = Ember.EnumerableUtils.forEach; + +/** + @class RecordArrayManager + @namespace DS + @private + @extends Ember.Object +*/ +DS.RecordArrayManager = Ember.Object.extend({ + init: function() { + this.filteredRecordArrays = Ember.MapWithDefault.create({ + defaultValue: function() { return []; } + }); + + this.changedRecords = []; + }, + + recordDidChange: function(record) { + if (this.changedRecords.push(record) !== 1) { return; } + + Ember.run.schedule('actions', this, this.updateRecordArrays); + }, + + recordArraysForRecord: function(record) { + record._recordArrays = record._recordArrays || Ember.OrderedSet.create(); + return record._recordArrays; + }, + + /** + This method is invoked whenever data is loaded into the store by the + adapter or updated by the adapter, or when a record has changed. + + It updates all record arrays that a record belongs to. + + To avoid thrashing, it only runs at most once per run loop. + + @method updateRecordArrays + @param {Class} type + @param {Number|String} clientId + */ + updateRecordArrays: function() { + forEach(this.changedRecords, function(record) { + if (get(record, 'isDeleted')) { + this._recordWasDeleted(record); + } else { + this._recordWasChanged(record); + } + }, this); + + this.changedRecords.length = 0; + }, + + _recordWasDeleted: function (record) { + var recordArrays = record._recordArrays; + + if (!recordArrays) { return; } + + forEach(recordArrays, function(array) { + array.removeRecord(record); + }); + }, + + _recordWasChanged: function (record) { + var type = record.constructor, + recordArrays = this.filteredRecordArrays.get(type), + filter; + + forEach(recordArrays, function(array) { + filter = get(array, 'filterFunction'); + this.updateRecordArray(array, filter, type, record); + }, this); + + // loop through all manyArrays containing an unloaded copy of this + // clientId and notify them that the record was loaded. + var manyArrays = record._loadingRecordArrays; + + if (manyArrays) { + for (var i=0, l=manyArrays.length; i 'kine' + inflector.singularize('kine') //=> 'cow' + ``` + + Creating an inflector and adding rules later. + + ```javascript + var inflector = Ember.Inflector.inflector; + + inflector.pluralize('advice') // => 'advices' + inflector.uncountable('advice'); + inflector.pluralize('advice') // => 'advice' + + inflector.pluralize('formula') // => 'formulas' + inflector.irregular('formula', 'formulae'); + inflector.pluralize('formula') // => 'formulae' + + // you would not need to add these as they are the default rules + inflector.plural(/$/, 's'); + inflector.singular(/s$/i, ''); + ``` + + Creating an inflector with a nondefault ruleset. + + ```javascript + var rules = { + plurals: [ /$/, 's' ], + singular: [ /\s$/, '' ], + irregularPairs: [ + [ 'cow', 'kine' ] + ], + uncountable: [ 'fish' ] + }; + + var inflector = new Ember.Inflector(rules); + ``` + + @class Inflector + @namespace Ember +*/ +function Inflector(ruleSet) { + ruleSet = ruleSet || {}; + ruleSet.uncountable = ruleSet.uncountable || {}; + ruleSet.irregularPairs = ruleSet.irregularPairs || {}; + + var rules = this.rules = { + plurals: ruleSet.plurals || [], + singular: ruleSet.singular || [], + irregular: {}, + irregularInverse: {}, + uncountable: {} + }; + + loadUncountable(rules, ruleSet.uncountable); + loadIrregular(rules, ruleSet.irregularPairs); +} + +Inflector.prototype = { + /** + @method plural + @param {RegExp} regex + @param {String} string + */ + plural: function(regex, string) { + this.rules.plurals.push([regex, string.toLowerCase()]); + }, + + /** + @method singular + @param {RegExp} regex + @param {String} string + */ + singular: function(regex, string) { + this.rules.singular.push([regex, string.toLowerCase()]); + }, + + /** + @method uncountable + @param {String} regex + */ + uncountable: function(string) { + loadUncountable(this.rules, [string.toLowerCase()]); + }, + + /** + @method irregular + @param {String} singular + @param {String} plural + */ + irregular: function (singular, plural) { + loadIrregular(this.rules, [[singular, plural]]); + }, + + /** + @method pluralize + @param {String} word + */ + pluralize: function(word) { + return this.inflect(word, this.rules.plurals, this.rules.irregular); + }, + + /** + @method singularize + @param {String} word + */ + singularize: function(word) { + return this.inflect(word, this.rules.singular, this.rules.irregularInverse); + }, + + /** + @protected + + @method inflect + @param {String} word + @param {Object} typeRules + @param {Object} irregular + */ + inflect: function(word, typeRules, irregular) { + var inflection, substitution, result, lowercase, isBlank, + isUncountable, isIrregular, isIrregularInverse, rule; + + isBlank = BLANK_REGEX.test(word); + + if (isBlank) { + return word; + } + + lowercase = word.toLowerCase(); + + isUncountable = this.rules.uncountable[lowercase]; + + if (isUncountable) { + return word; + } + + isIrregular = irregular && irregular[lowercase]; + + if (isIrregular) { + return isIrregular; + } + + for (var i = typeRules.length, min = 0; i > min; i--) { + inflection = typeRules[i-1]; + rule = inflection[0]; + + if (rule.test(word)) { + break; + } + } + + inflection = inflection || []; + + rule = inflection[0]; + substitution = inflection[1]; + + result = word.replace(rule, substitution); + + return result; + } +}; + +Ember.Inflector = Inflector; + +})(); + + + +(function() { +Ember.Inflector.defaultRules = { + plurals: [ + [/$/, 's'], + [/s$/i, 's'], + [/^(ax|test)is$/i, '$1es'], + [/(octop|vir)us$/i, '$1i'], + [/(octop|vir)i$/i, '$1i'], + [/(alias|status)$/i, '$1es'], + [/(bu)s$/i, '$1ses'], + [/(buffal|tomat)o$/i, '$1oes'], + [/([ti])um$/i, '$1a'], + [/([ti])a$/i, '$1a'], + [/sis$/i, 'ses'], + [/(?:([^f])fe|([lr])f)$/i, '$1$2ves'], + [/(hive)$/i, '$1s'], + [/([^aeiouy]|qu)y$/i, '$1ies'], + [/(x|ch|ss|sh)$/i, '$1es'], + [/(matr|vert|ind)(?:ix|ex)$/i, '$1ices'], + [/^(m|l)ouse$/i, '$1ice'], + [/^(m|l)ice$/i, '$1ice'], + [/^(ox)$/i, '$1en'], + [/^(oxen)$/i, '$1'], + [/(quiz)$/i, '$1zes'] + ], + + singular: [ + [/s$/i, ''], + [/(ss)$/i, '$1'], + [/(n)ews$/i, '$1ews'], + [/([ti])a$/i, '$1um'], + [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '$1sis'], + [/(^analy)(sis|ses)$/i, '$1sis'], + [/([^f])ves$/i, '$1fe'], + [/(hive)s$/i, '$1'], + [/(tive)s$/i, '$1'], + [/([lr])ves$/i, '$1f'], + [/([^aeiouy]|qu)ies$/i, '$1y'], + [/(s)eries$/i, '$1eries'], + [/(m)ovies$/i, '$1ovie'], + [/(x|ch|ss|sh)es$/i, '$1'], + [/^(m|l)ice$/i, '$1ouse'], + [/(bus)(es)?$/i, '$1'], + [/(o)es$/i, '$1'], + [/(shoe)s$/i, '$1'], + [/(cris|test)(is|es)$/i, '$1is'], + [/^(a)x[ie]s$/i, '$1xis'], + [/(octop|vir)(us|i)$/i, '$1us'], + [/(alias|status)(es)?$/i, '$1'], + [/^(ox)en/i, '$1'], + [/(vert|ind)ices$/i, '$1ex'], + [/(matr)ices$/i, '$1ix'], + [/(quiz)zes$/i, '$1'], + [/(database)s$/i, '$1'] + ], + + irregularPairs: [ + ['person', 'people'], + ['man', 'men'], + ['child', 'children'], + ['sex', 'sexes'], + ['move', 'moves'], + ['cow', 'kine'], + ['zombie', 'zombies'] + ], + + uncountable: [ + 'equipment', + 'information', + 'rice', + 'money', + 'species', + 'series', + 'fish', + 'sheep', + 'jeans', + 'police' + ] +}; + +})(); + + + +(function() { +if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) { + /** + See {{#crossLink "Ember.String/pluralize"}}{{/crossLink}} + + @method pluralize + @for String + */ + String.prototype.pluralize = function() { + return Ember.String.pluralize(this); + }; + + /** + See {{#crossLink "Ember.String/singularize"}}{{/crossLink}} + + @method singularize + @for String + */ + String.prototype.singularize = function() { + return Ember.String.singularize(this); + }; +} + +})(); + + + +(function() { +Ember.Inflector.inflector = new Ember.Inflector(Ember.Inflector.defaultRules); + +})(); + + + +(function() { + +})(); + +(function() { +/** + @module ember-data +*/ + +var get = Ember.get, + forEach = Ember.EnumerableUtils.forEach, + camelize = Ember.String.camelize, + capitalize = Ember.String.capitalize, + decamelize = Ember.String.decamelize, + singularize = Ember.String.singularize, + underscore = Ember.String.underscore; + +DS.ActiveModelSerializer = DS.RESTSerializer.extend({ + // SERIALIZE + + /** + Converts camelcased attributes to underscored when serializing. + + @method keyForAttribute + @param {String} attribute + @returns String + */ + keyForAttribute: function(attr) { + return decamelize(attr); + }, + + /** + Underscores relationship names and appends "_id" or "_ids" when serializing + relationship keys. + + @method keyForRelationship + @param {String} key + @param {String} kind + @returns String + */ + keyForRelationship: function(key, kind) { + key = decamelize(key); + if (kind === "belongsTo") { + return key + "_id"; + } else if (kind === "hasMany") { + return singularize(key) + "_ids"; + } else { + return key; + } + }, + + /** + Does not serialize hasMany relationships by default. + */ + serializeHasMany: Ember.K, + + /** + Underscores the JSON root keys when serializing. + + @method serializeIntoHash + @param {Object} hash + @param {subclass of DS.Model} type + @param {DS.Model} record + @param {Object} options + */ + serializeIntoHash: function(data, type, record, options) { + var root = underscore(decamelize(type.typeKey)); + data[root] = this.serialize(record, options); + }, + + /** + Serializes a polymorphic type as a fully capitalized model name. + + @method serializePolymorphicType + @param {DS.Model} record + @param {Object} json + @param relationship + */ + serializePolymorphicType: function(record, json, relationship) { + var key = relationship.key, + belongsTo = get(record, key); + key = this.keyForAttribute(key); + json[key + "_type"] = capitalize(camelize(belongsTo.constructor.typeKey)); + }, + + // EXTRACT + + /** + Extracts the model typeKey from underscored root objects. + + @method typeForRoot + @param {String} root + @returns String the model's typeKey + */ + typeForRoot: function(root) { + var camelized = camelize(root); + return singularize(camelized); + }, + + /** + Add extra step to `DS.RESTSerializer.normalize` so links are + normalized. + + If your payload looks like this + + ```js + { + "post": { + "id": 1, + "title": "Rails is omakase", + "links": { "flagged_comments": "api/comments/flagged" } + } + } + ``` + The normalized version would look like this + + ```js + { + "post": { + "id": 1, + "title": "Rails is omakase", + "links": { "flaggedComments": "api/comments/flagged" } + } + } + ``` + + @method normalize + @param {subclass of DS.Model} type + @param {Object} hash + @param {String} prop + @returns Object + */ + + normalize: function(type, hash, prop) { + this.normalizeLinks(hash); + + return this._super(type, hash, prop); + }, + + /** + Convert `snake_cased` links to `camelCase` + + @method normalizeLinks + @param {Object} hash + */ + + normalizeLinks: function(data){ + if (data.links) { + var links = data.links; + + for (var link in links) { + var camelizedLink = camelize(link); + + if (camelizedLink !== link) { + links[camelizedLink] = links[link]; + delete links[link]; + } + } + } + }, + + /** + Normalize the polymorphic type from the JSON. + + Normalize: + ```js + { + id: "1" + minion: { type: "evil_minion", id: "12"} + } + ``` + + To: + ```js + { + id: "1" + minion: { type: "evilMinion", id: "12"} + } + ``` + + @method normalizeRelationships + @private + */ + normalizeRelationships: function(type, hash) { + var payloadKey, payload; + + if (this.keyForRelationship) { + type.eachRelationship(function(key, relationship) { + if (relationship.options.polymorphic) { + payloadKey = this.keyForAttribute(key); + payload = hash[payloadKey]; + if (payload && payload.type) { + payload.type = this.typeForRoot(payload.type); + } else if (payload && relationship.kind === "hasMany") { + var self = this; + forEach(payload, function(single) { + single.type = self.typeForRoot(single.type); + }); + } + } else { + payloadKey = this.keyForRelationship(key, relationship.kind); + payload = hash[payloadKey]; + } + + hash[key] = payload; + + if (key !== payloadKey) { + delete hash[payloadKey]; + } + }, this); + } + } +}); + +})(); + + + +(function() { +var get = Ember.get; +var forEach = Ember.EnumerableUtils.forEach; + +/** + The EmbeddedRecordsMixin allows you to add embedded record support to your + serializers. + To set up embedded records, you include the mixin into the serializer and then + define your embedded relations. + + ```js + App.PostSerializer = DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, { + attrs: { + comments: {embedded: 'always'} + } + }) + ``` + + Currently only `{embedded: 'always'}` records are supported. + + @class EmbeddedRecordsMixin + @namespace DS +*/ +DS.EmbeddedRecordsMixin = Ember.Mixin.create({ + + /** + Serialize has-may relationship when it is configured as embedded objects. + + @method serializeHasMany + */ + serializeHasMany: function(record, json, relationship) { + var key = relationship.key, + attrs = get(this, 'attrs'), + embed = attrs && attrs[key] && attrs[key].embedded === 'always'; + + if (embed) { + json[this.keyForAttribute(key)] = get(record, key).map(function(relation) { + var data = relation.serialize(), + primaryKey = get(this, 'primaryKey'); + + data[primaryKey] = get(relation, primaryKey); + + return data; + }, this); + } + }, + + /** + Extract embedded objects out of the payload for a single object + and add them as sideloaded objects instead. + + @method extractSingle + */ + extractSingle: function(store, primaryType, payload, recordId, requestType) { + var root = this.keyForAttribute(primaryType.typeKey), + partial = payload[root]; + + updatePayloadWithEmbedded(store, this, primaryType, partial, payload); + + return this._super(store, primaryType, payload, recordId, requestType); + }, + + /** + Extract embedded objects out of a standard payload + and add them as sideloaded objects instead. + + @method extractArray + */ + extractArray: function(store, type, payload) { + var root = this.keyForAttribute(type.typeKey), + partials = payload[Ember.String.pluralize(root)]; + + forEach(partials, function(partial) { + updatePayloadWithEmbedded(store, this, type, partial, payload); + }, this); + + return this._super(store, type, payload); + } +}); + +function updatePayloadWithEmbedded(store, serializer, type, partial, payload) { + var attrs = get(serializer, 'attrs'); + + if (!attrs) { + return; + } + + type.eachRelationship(function(key, relationship) { + var expandedKey, embeddedTypeKey, attribute, ids, + config = attrs[key], + serializer = store.serializerFor(relationship.type.typeKey), + primaryKey = get(serializer, "primaryKey"); + + if (relationship.kind !== "hasMany") { + return; + } + + if (config && (config.embedded === 'always' || config.embedded === 'load')) { + // underscore forces the embedded records to be side loaded. + // it is needed when main type === relationship.type + embeddedTypeKey = '_' + Ember.String.pluralize(relationship.type.typeKey); + expandedKey = this.keyForRelationship(key, relationship.kind); + attribute = this.keyForAttribute(key); + ids = []; + + if (!partial[attribute]) { + return; + } + + payload[embeddedTypeKey] = payload[embeddedTypeKey] || []; + + forEach(partial[attribute], function(data) { + var embeddedType = store.modelFor(relationship.type.typeKey); + updatePayloadWithEmbedded(store, serializer, embeddedType, data, payload); + ids.push(data[primaryKey]); + payload[embeddedTypeKey].push(data); + }); + + partial[expandedKey] = ids; + delete partial[attribute]; + } + }, serializer); +} +})(); + + + +(function() { +/** + @module ember-data +*/ + +var forEach = Ember.EnumerableUtils.forEach; +var decamelize = Ember.String.decamelize, + underscore = Ember.String.underscore, + pluralize = Ember.String.pluralize; + +/** + The ActiveModelAdapter is a subclass of the RESTAdapter designed to integrate + with a JSON API that uses an underscored naming convention instead of camelcasing. + It has been designed to work out of the box with the + [active_model_serializers](http://github.com/rails-api/active_model_serializers) + Ruby gem. + + This adapter extends the DS.RESTAdapter by making consistent use of the camelization, + decamelization and pluralization methods to normalize the serialized JSON into a + format that is compatible with a conventional Rails backend and Ember Data. + + ## JSON Structure + + The ActiveModelAdapter expects the JSON returned from your server to follow + the REST adapter conventions substituting underscored keys for camelcased ones. + + ### Conventional Names + + Attribute names in your JSON payload should be the underscored versions of + the attributes in your Ember.js models. + + For example, if you have a `Person` model: + + ```js + App.FamousPerson = DS.Model.extend({ + firstName: DS.attr('string'), + lastName: DS.attr('string'), + occupation: DS.attr('string') + }); + ``` + + The JSON returned should look like this: + + ```js + { + "famous_person": { + "first_name": "Barack", + "last_name": "Obama", + "occupation": "President" + } + } + ``` + + @class ActiveModelAdapter + @constructor + @namespace DS + @extends DS.Adapter +**/ + +DS.ActiveModelAdapter = DS.RESTAdapter.extend({ + defaultSerializer: '-active-model', + /** + The ActiveModelAdapter overrides the `pathForType` method to build + underscored URLs by decamelizing and pluralizing the object type name. + + ```js + this.pathForType("famousPerson"); + //=> "famous_people" + ``` + + @method pathForType + @param {String} type + @returns String + */ + pathForType: function(type) { + var decamelized = decamelize(type); + var underscored = underscore(decamelized); + return pluralize(underscored); + }, + + /** + The ActiveModelAdapter overrides the `ajaxError` method + to return a DS.InvalidError for all 422 Unprocessable Entity + responses. + + A 422 HTTP response from the server generally implies that the request + was well formed but the API was unable to process it because the + content was not semantically correct or meaningful per the API. + + For more information on 422 HTTP Error code see 11.2 WebDAV RFC 4918 + https://tools.ietf.org/html/rfc4918#section-11.2 + + @method ajaxError + @param jqXHR + @returns error + */ + ajaxError: function(jqXHR) { + var error = this._super(jqXHR); + + if (jqXHR && jqXHR.status === 422) { + var response = Ember.$.parseJSON(jqXHR.responseText), + errors = {}; + + if (response.errors !== undefined) { + var jsonErrors = response.errors; + + forEach(Ember.keys(jsonErrors), function(key) { + errors[Ember.String.camelize(key)] = jsonErrors[key]; + }); + } + + return new DS.InvalidError(errors); + } else { + return error; + } + } +}); + +})(); + + + +(function() { + +})(); + + + +(function() { +Ember.onLoad('Ember.Application', function(Application) { + Application.initializer({ + name: "activeModelAdapter", + + initialize: function(container, application) { + var proxy = new DS.ContainerProxy(container); + proxy.registerDeprecations([ + {deprecated: 'serializer:_ams', valid: 'serializer:-active-model'}, + {deprecated: 'adapter:_ams', valid: 'adapter:-active-model'} + ]); + + application.register('serializer:-active-model', DS.ActiveModelSerializer); + application.register('adapter:-active-model', DS.ActiveModelAdapter); + } + }); +}); + +})(); + + + +(function() { + +})(); + + +})(); + + +if (typeof location !== 'undefined' && (location.hostname === 'localhost' || location.hostname === '127.0.0.1')) { + Ember.Logger.warn("You are running a production build of Ember on localhost and won't receive detailed error messages. "+ + "If you want full error messages please use the non-minified build provided on the Ember website."); +} diff --git a/ajax/libs/ember-data.js/package.json b/ajax/libs/ember-data.js/package.json index 5fe81af6c..6a9442752 100755 --- a/ajax/libs/ember-data.js/package.json +++ b/ajax/libs/ember-data.js/package.json @@ -1,7 +1,7 @@ { "name": "ember-data.js", "filename": "ember-data.min.js", - "version": "1.0.0-beta.5", + "version": "1.0.0-beta.6", "description": "A data persistence library for Ember.js.", "homepage": "https://github.com/emberjs/data", "keywords": [