| /*! |
| * @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.8.2a68c63a |
| */ |
| (function(global) { |
| var define, requireModule, require, requirejs; |
| |
| (function() { |
| var registry = {}, seen = {}; |
| |
| define = function(name, deps, callback) { |
| registry[name] = { deps: deps, callback: callback }; |
| }; |
| |
| requirejs = require = requireModule = function(name) { |
| requirejs._eak_seen = registry; |
| |
| if (seen[name]) { return seen[name]; } |
| seen[name] = {}; |
| |
| if (!registry[name]) { |
| throw new Error("Could not find module " + name); |
| } |
| |
| var mod = registry[name], |
| deps = mod.deps, |
| callback = mod.callback, |
| reified = [], |
| exports; |
| |
| for (var i=0, l=deps.length; i<l; i++) { |
| if (deps[i] === 'exports') { |
| reified.push(exports = {}); |
| } else { |
| reified.push(requireModule(resolve(deps[i]))); |
| } |
| } |
| |
| var value = callback.apply(this, reified); |
| return seen[name] = exports || value; |
| |
| function resolve(child) { |
| if (child.charAt(0) !== '.') { return child; } |
| var parts = child.split("/"); |
| var parentBase = name.split("/").slice(0, -1); |
| |
| for (var i=0, l=parts.length; i<l; i++) { |
| var part = parts[i]; |
| |
| if (part === '..') { parentBase.pop(); } |
| else if (part === '.') { continue; } |
| else { parentBase.push(part); } |
| } |
| |
| return parentBase.join("/"); |
| } |
| }; |
| })(); |
| |
| define("activemodel-adapter/lib/main", |
| ["./system","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var ActiveModelAdapter = __dependency1__.ActiveModelAdapter; |
| var ActiveModelSerializer = __dependency1__.ActiveModelSerializer; |
| var EmbeddedRecordsMixin = __dependency1__.EmbeddedRecordsMixin; |
| |
| __exports__.ActiveModelAdapter = ActiveModelAdapter; |
| __exports__.ActiveModelSerializer = ActiveModelSerializer; |
| __exports__.EmbeddedRecordsMixin = EmbeddedRecordsMixin; |
| }); |
| define("activemodel-adapter/lib/setup-container", |
| ["../../ember-data/lib/system/container_proxy","./system/active_model_serializer","./system/active_model_adapter","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var ContainerProxy = __dependency1__["default"]; |
| var ActiveModelSerializer = __dependency2__["default"]; |
| var ActiveModelAdapter = __dependency3__["default"]; |
| |
| __exports__["default"] = function setupActiveModelAdapter(container, application){ |
| var proxy = new ContainerProxy(container); |
| proxy.registerDeprecations([ |
| {deprecated: 'serializer:_ams', valid: 'serializer:-active-model'}, |
| {deprecated: 'adapter:_ams', valid: 'adapter:-active-model'} |
| ]); |
| |
| container.register('serializer:-active-model', ActiveModelSerializer); |
| container.register('adapter:-active-model', ActiveModelAdapter); |
| }; |
| }); |
| define("activemodel-adapter/lib/system", |
| ["./system/embedded_records_mixin","./system/active_model_adapter","./system/active_model_serializer","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var EmbeddedRecordsMixin = __dependency1__["default"]; |
| var ActiveModelAdapter = __dependency2__["default"]; |
| var ActiveModelSerializer = __dependency3__["default"]; |
| |
| __exports__.EmbeddedRecordsMixin = EmbeddedRecordsMixin; |
| __exports__.ActiveModelAdapter = ActiveModelAdapter; |
| __exports__.ActiveModelSerializer = ActiveModelSerializer; |
| }); |
| define("activemodel-adapter/lib/system/active_model_adapter", |
| ["../../../ember-data/lib/adapters","../../../ember-data/lib/system/adapter","../../../ember-inflector/lib/main","./active_model_serializer","./embedded_records_mixin","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { |
| "use strict"; |
| var RESTAdapter = __dependency1__.RESTAdapter; |
| var InvalidError = __dependency2__.InvalidError; |
| var pluralize = __dependency3__.pluralize; |
| var ActiveModelSerializer = __dependency4__["default"]; |
| var EmbeddedRecordsMixin = __dependency5__["default"]; |
| |
| /** |
| @module ember-data |
| */ |
| |
| var forEach = Ember.EnumerableUtils.forEach; |
| var decamelize = Ember.String.decamelize, |
| underscore = Ember.String.underscore; |
| |
| /** |
| 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 expects specific settings using ActiveModel::Serializers, |
| `embed :ids, include: true` which sideloads the records. |
| |
| 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.RESTAdapter |
| **/ |
| |
| var ActiveModelAdapter = 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 |
| @return 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 |
| @return 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 InvalidError(errors); |
| } else { |
| return error; |
| } |
| } |
| }); |
| |
| __exports__["default"] = ActiveModelAdapter; |
| }); |
| define("activemodel-adapter/lib/system/active_model_serializer", |
| ["../../../ember-inflector/lib/main","../../../ember-data/lib/serializers/rest_serializer","exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var singularize = __dependency1__.singularize; |
| var RESTSerializer = __dependency2__["default"]; |
| /** |
| @module ember-data |
| */ |
| |
| var get = Ember.get, |
| forEach = Ember.EnumerableUtils.forEach, |
| camelize = Ember.String.camelize, |
| capitalize = Ember.String.capitalize, |
| decamelize = Ember.String.decamelize, |
| underscore = Ember.String.underscore; |
| /** |
| The ActiveModelSerializer is a subclass of the RESTSerializer 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 Serializer expects specific settings using ActiveModel::Serializers, |
| `embed :ids, include: true` which sideloads the records. |
| |
| This serializer extends the DS.RESTSerializer 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 ActiveModelSerializer 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 ActiveModelSerializer |
| @namespace DS |
| @extends DS.RESTSerializer |
| */ |
| var ActiveModelSerializer = RESTSerializer.extend({ |
| // SERIALIZE |
| |
| /** |
| Converts camelCased attributes to underscored when serializing. |
| |
| @method keyForAttribute |
| @param {String} attribute |
| @return 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 |
| @return 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); |
| |
| if (belongsTo) { |
| key = this.keyForAttribute(key); |
| json[key + "_type"] = capitalize(belongsTo.constructor.typeKey); |
| } |
| }, |
| |
| // EXTRACT |
| |
| /** |
| Add extra step to `DS.RESTSerializer.normalize` so links are normalized. |
| |
| If your payload looks like: |
| |
| ```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 |
| @return 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} data |
| */ |
| |
| 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); |
| } |
| } |
| }); |
| |
| __exports__["default"] = ActiveModelSerializer; |
| }); |
| define("activemodel-adapter/lib/system/embedded_records_mixin", |
| ["../../../ember-inflector/lib/main","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var get = Ember.get; |
| var forEach = Ember.EnumerableUtils.forEach; |
| var camelize = Ember.String.camelize; |
| |
| var pluralize = __dependency1__.pluralize; |
| |
| /** |
| ## Using Embedded Records |
| |
| `DS.EmbeddedRecordsMixin` supports serializing embedded records. |
| |
| To set up embedded records, include the mixin when extending a serializer |
| then define and configure embedded (model) relationships. |
| |
| Below is an example of a per-type serializer ('post' type). |
| |
| ```js |
| App.PostSerializer = DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, { |
| attrs: { |
| author: {embedded: 'always'}, |
| comments: {serialize: 'ids'} |
| } |
| }) |
| ``` |
| |
| The `attrs` option for a resource `{embedded: 'always'}` is shorthand for: |
| |
| ```js |
| {serialize: 'records', deserialize: 'records'} |
| ``` |
| |
| ### Configuring Attrs |
| |
| A resource's `attrs` option may be set to use `ids`, `records` or `no` for the |
| `serialize` and `deserialize` settings. |
| |
| The `attrs` property can be set on the ApplicationSerializer or a per-type |
| serializer. |
| |
| In the case where embedded JSON is expected while extracting a payoad (reading) |
| the setting is `deserialize: 'records'`, there is no need to use `ids` when |
| extracting as that is the default behavior without this mixin if you are using |
| the vanilla ActiveModelAdapter. Likewise, to embed JSON in the payload while |
| serializing `serialize: 'records'` is the setting to use. There is an option of |
| not embedding JSON in the serialized payload by using `serialize: 'ids'`. If you |
| do not want the relationship sent at all, you can use `serialize: 'no'`. |
| |
| |
| ### ActiveModelSerializer defaults |
| If you do not overwrite `attrs` for a specific relationship, the `ActiveModelSerializer` |
| will behave in the following way: |
| |
| BelongsTo: `{serialize:'id', deserialize:'id'}` |
| HasMany: `{serialize:no, deserialize:'ids'}` |
| |
| ### Model Relationships |
| |
| Embedded records must have a model defined to be extracted and serialized. |
| |
| To successfully extract and serialize embedded records the model relationships |
| must be setup correcty See the |
| [defining relationships](/guides/models/defining-models/#toc_defining-relationships) |
| section of the **Defining Models** guide page. |
| |
| Records without an `id` property are not considered embedded records, model |
| instances must have an `id` property to be used with Ember Data. |
| |
| ### Example JSON payloads, Models and Serializers |
| |
| **When customizing a serializer it is imporant to grok what the cusomizations |
| are, please read the docs for the methods this mixin provides, in case you need |
| to modify to fit your specific needs.** |
| |
| For example review the docs for each method of this mixin: |
| |
| * [extractArray](/api/data/classes/DS.EmbeddedRecordsMixin.html#method_extractArray) |
| * [extractSingle](/api/data/classes/DS.EmbeddedRecordsMixin.html#method_extractSingle) |
| * [serializeBelongsTo](/api/data/classes/DS.EmbeddedRecordsMixin.html#method_serializeBelongsTo) |
| * [serializeHasMany](/api/data/classes/DS.EmbeddedRecordsMixin.html#method_serializeHasMany) |
| |
| @class EmbeddedRecordsMixin |
| @namespace DS |
| */ |
| var EmbeddedRecordsMixin = Ember.Mixin.create({ |
| |
| /** |
| Serialize `belongsTo` relationship when it is configured as an embedded object. |
| |
| This example of an author model belongs to a post model: |
| |
| ```js |
| Post = DS.Model.extend({ |
| title: DS.attr('string'), |
| body: DS.attr('string'), |
| author: DS.belongsTo('author') |
| }); |
| |
| Author = DS.Model.extend({ |
| name: DS.attr('string'), |
| post: DS.belongsTo('post') |
| }); |
| ``` |
| |
| Use a custom (type) serializer for the post model to configure embedded author |
| |
| ```js |
| App.PostSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, { |
| attrs: { |
| author: {embedded: 'always'} |
| } |
| }) |
| ``` |
| |
| A payload with an attribute configured for embedded records can serialize |
| the records together under the root attribute's payload: |
| |
| ```js |
| { |
| "post": { |
| "id": "1" |
| "title": "Rails is omakase", |
| "author": { |
| "id": "2" |
| "name": "dhh" |
| } |
| } |
| } |
| ``` |
| |
| @method serializeBelongsTo |
| @param {DS.Model} record |
| @param {Object} json |
| @param {Object} relationship |
| */ |
| serializeBelongsTo: function(record, json, relationship) { |
| var attr = relationship.key; |
| var attrs = this.get('attrs'); |
| if (noSerializeOptionSpecified(attrs, attr)) { |
| this._super(record, json, relationship); |
| return; |
| } |
| var includeIds = hasSerializeIdsOption(attrs, attr); |
| var includeRecords = hasSerializeRecordsOption(attrs, attr); |
| var embeddedRecord = record.get(attr); |
| if (includeIds) { |
| key = this.keyForRelationship(attr, relationship.kind); |
| if (!embeddedRecord) { |
| json[key] = null; |
| } else { |
| json[key] = get(embeddedRecord, 'id'); |
| } |
| } else if (includeRecords) { |
| var key = this.keyForRelationship(attr); |
| if (!embeddedRecord) { |
| json[key] = null; |
| } else { |
| json[key] = embeddedRecord.serialize({includeId: true}); |
| this.removeEmbeddedForeignKey(record, embeddedRecord, relationship, json[key]); |
| } |
| } |
| }, |
| |
| /** |
| Serialize `hasMany` relationship when it is configured as embedded objects. |
| |
| This example of a post model has many comments: |
| |
| ```js |
| Post = DS.Model.extend({ |
| title: DS.attr('string'), |
| body: DS.attr('string'), |
| comments: DS.hasMany('comment') |
| }); |
| |
| Comment = DS.Model.extend({ |
| body: DS.attr('string'), |
| post: DS.belongsTo('post') |
| }); |
| ``` |
| |
| Use a custom (type) serializer for the post model to configure embedded comments |
| |
| ```js |
| App.PostSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, { |
| attrs: { |
| comments: {embedded: 'always'} |
| } |
| }) |
| ``` |
| |
| A payload with an attribute configured for embedded records can serialize |
| the records together under the root attribute's payload: |
| |
| ```js |
| { |
| "post": { |
| "id": "1" |
| "title": "Rails is omakase", |
| "body": "I want this for my ORM, I want that for my template language..." |
| "comments": [{ |
| "id": "1", |
| "body": "Rails is unagi" |
| }, { |
| "id": "2", |
| "body": "Omakase O_o" |
| }] |
| } |
| } |
| ``` |
| |
| The attrs options object can use more specific instruction for extracting and |
| serializing. When serializing, an option to embed `ids` or `records` can be set. |
| When extracting the only option is `records`. |
| |
| So `{embedded: 'always'}` is shorthand for: |
| `{serialize: 'records', deserialize: 'records'}` |
| |
| To embed the `ids` for a related object (using a hasMany relationship): |
| |
| ```js |
| App.PostSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, { |
| attrs: { |
| comments: {serialize: 'ids', deserialize: 'records'} |
| } |
| }) |
| ``` |
| |
| ```js |
| { |
| "post": { |
| "id": "1" |
| "title": "Rails is omakase", |
| "body": "I want this for my ORM, I want that for my template language..." |
| "comments": ["1", "2"] |
| } |
| } |
| ``` |
| |
| @method serializeHasMany |
| @param {DS.Model} record |
| @param {Object} json |
| @param {Object} relationship |
| */ |
| serializeHasMany: function(record, json, relationship) { |
| var attr = relationship.key; |
| var attrs = this.get('attrs'); |
| if (noSerializeOptionSpecified(attrs, attr)) { |
| this._super(record, json, relationship); |
| return; |
| } |
| var includeIds = hasSerializeIdsOption(attrs, attr); |
| var includeRecords = hasSerializeRecordsOption(attrs, attr); |
| var key; |
| if (includeIds) { |
| key = this.keyForRelationship(attr, relationship.kind); |
| json[key] = get(record, attr).mapBy('id'); |
| } else if (includeRecords) { |
| key = this.keyForAttribute(attr); |
| json[key] = get(record, attr).map(function(embeddedRecord) { |
| var serializedEmbeddedRecord = embeddedRecord.serialize({includeId: true}); |
| this.removeEmbeddedForeignKey(record, embeddedRecord, relationship, serializedEmbeddedRecord); |
| return serializedEmbeddedRecord; |
| }, this); |
| } |
| }, |
| |
| /** |
| When serializing an embedded record, modify the property (in the json payload) |
| that refers to the parent record (foreign key for relationship). |
| |
| Serializing a `belongsTo` relationship removes the property that refers to the |
| parent record |
| |
| Serializing a `hasMany` relationship does not remove the property that refers to |
| the parent record. |
| |
| @method removeEmbeddedForeignKey |
| @param {DS.Model} record |
| @param {DS.Model} embeddedRecord |
| @param {Object} relationship |
| @param {Object} json |
| */ |
| removeEmbeddedForeignKey: function (record, embeddedRecord, relationship, json) { |
| if (relationship.kind === 'hasMany') { |
| return; |
| } else if (relationship.kind === 'belongsTo') { |
| var parentRecord = record.constructor.inverseFor(relationship.key); |
| if (parentRecord) { |
| var name = parentRecord.name; |
| var embeddedSerializer = this.store.serializerFor(embeddedRecord.constructor); |
| var parentKey = embeddedSerializer.keyForRelationship(name, parentRecord.kind); |
| if (parentKey) { |
| delete json[parentKey]; |
| } |
| } |
| } |
| }, |
| |
| /** |
| Extract an embedded object from the payload for a single object |
| and add the object in the compound document (side-loaded) format instead. |
| |
| A payload with an attribute configured for embedded records needs to be extracted: |
| |
| ```js |
| { |
| "post": { |
| "id": 1 |
| "title": "Rails is omakase", |
| "author": { |
| "id": 2 |
| "name": "dhh" |
| } |
| "comments": [] |
| } |
| } |
| ``` |
| |
| Ember Data is expecting a payload with a compound document (side-loaded) like: |
| |
| ```js |
| { |
| "post": { |
| "id": "1" |
| "title": "Rails is omakase", |
| "author": "2" |
| "comments": [] |
| }, |
| "authors": [{ |
| "id": "2" |
| "post": "1" |
| "name": "dhh" |
| }] |
| "comments": [] |
| } |
| ``` |
| |
| The payload's `author` attribute represents an object with a `belongsTo` relationship. |
| The `post` attribute under `author` is the foreign key with the id for the post |
| |
| @method extractSingle |
| @param {DS.Store} store |
| @param {subclass of DS.Model} primaryType |
| @param {Object} payload |
| @param {String} recordId |
| @return Object the primary response to the original request |
| */ |
| extractSingle: function(store, primaryType, payload, recordId) { |
| var root = this.keyForAttribute(primaryType.typeKey), |
| partial = payload[root]; |
| |
| updatePayloadWithEmbedded(this, store, primaryType, payload, partial); |
| |
| return this._super(store, primaryType, payload, recordId); |
| }, |
| |
| /** |
| Extract embedded objects in an array when an attr is configured for embedded, |
| and add them as side-loaded objects instead. |
| |
| A payload with an attr configured for embedded records needs to be extracted: |
| |
| ```js |
| { |
| "post": { |
| "id": "1" |
| "title": "Rails is omakase", |
| "comments": [{ |
| "id": "1", |
| "body": "Rails is unagi" |
| }, { |
| "id": "2", |
| "body": "Omakase O_o" |
| }] |
| } |
| } |
| ``` |
| |
| Ember Data is expecting a payload with compound document (side-loaded) like: |
| |
| ```js |
| { |
| "post": { |
| "id": "1" |
| "title": "Rails is omakase", |
| "comments": ["1", "2"] |
| }, |
| "comments": [{ |
| "id": "1", |
| "body": "Rails is unagi" |
| }, { |
| "id": "2", |
| "body": "Omakase O_o" |
| }] |
| } |
| ``` |
| |
| The payload's `comments` attribute represents records in a `hasMany` relationship |
| |
| @method extractArray |
| @param {DS.Store} store |
| @param {subclass of DS.Model} primaryType |
| @param {Object} payload |
| @return {Array<Object>} The primary array that was returned in response |
| to the original query. |
| */ |
| extractArray: function(store, primaryType, payload) { |
| var root = this.keyForAttribute(primaryType.typeKey), |
| partials = payload[pluralize(root)]; |
| |
| forEach(partials, function(partial) { |
| updatePayloadWithEmbedded(this, store, primaryType, payload, partial); |
| }, this); |
| |
| return this._super(store, primaryType, payload); |
| } |
| }); |
| |
| // checks config for attrs option to embedded (always) - serialize and deserialize |
| function hasEmbeddedAlwaysOption(attrs, attr) { |
| var option = attrsOption(attrs, attr); |
| return option && option.embedded === 'always'; |
| } |
| |
| // checks config for attrs option to serialize ids |
| function hasSerializeRecordsOption(attrs, attr) { |
| var alwaysEmbed = hasEmbeddedAlwaysOption(attrs, attr); |
| var option = attrsOption(attrs, attr); |
| return alwaysEmbed || (option && (option.serialize === 'records')); |
| } |
| |
| // checks config for attrs option to serialize records |
| function hasSerializeIdsOption(attrs, attr) { |
| var option = attrsOption(attrs, attr); |
| return option && (option.serialize === 'ids' || option.serialize === 'id'); |
| } |
| |
| // checks config for attrs option to serialize records |
| function noSerializeOptionSpecified(attrs, attr) { |
| var option = attrsOption(attrs, attr); |
| var serializeRecords = hasSerializeRecordsOption(attrs, attr); |
| var serializeIds = hasSerializeIdsOption(attrs, attr); |
| return !(option && (option.serialize || option.embedded)); |
| } |
| |
| // checks config for attrs option to deserialize records |
| // a defined option object for a resource is treated the same as |
| // `deserialize: 'records'` |
| function hasDeserializeRecordsOption(attrs, attr) { |
| var alwaysEmbed = hasEmbeddedAlwaysOption(attrs, attr); |
| var option = attrsOption(attrs, attr); |
| var hasSerializingOption = option && (option.deserialize || option.serialize); |
| return alwaysEmbed || hasSerializingOption /* option.deserialize === 'records' */; |
| } |
| |
| function attrsOption(attrs, attr) { |
| return attrs && (attrs[Ember.String.camelize(attr)] || attrs[attr]); |
| } |
| |
| // chooses a relationship kind to branch which function is used to update payload |
| // does not change payload if attr is not embedded |
| function updatePayloadWithEmbedded(serializer, store, type, payload, partial) { |
| var attrs = get(serializer, 'attrs'); |
| |
| if (!attrs) { |
| return; |
| } |
| type.eachRelationship(function(key, relationship) { |
| if (hasDeserializeRecordsOption(attrs, key)) { |
| if (relationship.kind === "hasMany") { |
| updatePayloadWithEmbeddedHasMany(serializer, store, key, relationship, payload, partial); |
| } |
| if (relationship.kind === "belongsTo") { |
| updatePayloadWithEmbeddedBelongsTo(serializer, store, key, relationship, payload, partial); |
| } |
| } |
| }); |
| } |
| |
| // handles embedding for `hasMany` relationship |
| function updatePayloadWithEmbeddedHasMany(serializer, store, primaryType, relationship, payload, partial) { |
| var embeddedSerializer = store.serializerFor(relationship.type.typeKey); |
| var primaryKey = get(serializer, 'primaryKey'); |
| var attr = relationship.type.typeKey; |
| // underscore forces the embedded records to be side loaded. |
| // it is needed when main type === relationship.type |
| var embeddedTypeKey = '_' + serializer.typeForRoot(relationship.type.typeKey); |
| var expandedKey = serializer.keyForRelationship(primaryType, relationship.kind); |
| var attribute = serializer.keyForAttribute(primaryType); |
| var ids = []; |
| |
| if (!partial[attribute]) { |
| return; |
| } |
| |
| payload[embeddedTypeKey] = payload[embeddedTypeKey] || []; |
| |
| forEach(partial[attribute], function(data) { |
| var embeddedType = store.modelFor(attr); |
| updatePayloadWithEmbedded(embeddedSerializer, store, embeddedType, payload, data); |
| ids.push(data[primaryKey]); |
| payload[embeddedTypeKey].push(data); |
| }); |
| |
| partial[expandedKey] = ids; |
| delete partial[attribute]; |
| } |
| |
| // handles embedding for `belongsTo` relationship |
| function updatePayloadWithEmbeddedBelongsTo(serializer, store, primaryType, relationship, payload, partial) { |
| var attrs = serializer.get('attrs'); |
| |
| if (!attrs || |
| !(hasDeserializeRecordsOption(attrs, Ember.String.camelize(primaryType)) || |
| hasDeserializeRecordsOption(attrs, primaryType))) { |
| return; |
| } |
| var attr = relationship.type.typeKey; |
| var _serializer = store.serializerFor(relationship.type.typeKey); |
| var primaryKey = get(_serializer, 'primaryKey'); |
| var embeddedTypeKey = Ember.String.pluralize(attr); // TODO don't use pluralize |
| var expandedKey = _serializer.keyForRelationship(primaryType, relationship.kind); |
| var attribute = _serializer.keyForAttribute(primaryType); |
| |
| if (!partial[attribute]) { |
| return; |
| } |
| payload[embeddedTypeKey] = payload[embeddedTypeKey] || []; |
| var embeddedType = store.modelFor(relationship.type.typeKey); |
| // Recursive call for nested record |
| updatePayloadWithEmbedded(_serializer, store, embeddedType, payload, partial[attribute]); |
| partial[expandedKey] = partial[attribute].id; |
| // Need to move an embedded `belongsTo` object into a pluralized collection |
| payload[embeddedTypeKey].push(partial[attribute]); |
| // Need a reference to the parent so relationship works between both `belongsTo` records |
| partial[attribute][relationship.parentType.typeKey + '_id'] = partial.id; |
| delete partial[attribute]; |
| } |
| |
| __exports__["default"] = EmbeddedRecordsMixin; |
| }); |
| define("ember-data/lib/adapters", |
| ["./adapters/fixture_adapter","./adapters/rest_adapter","exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var FixtureAdapter = __dependency1__["default"]; |
| var RESTAdapter = __dependency2__["default"]; |
| |
| __exports__.RESTAdapter = RESTAdapter; |
| __exports__.FixtureAdapter = FixtureAdapter; |
| }); |
| define("ember-data/lib/adapters/fixture_adapter", |
| ["../system/adapter","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var get = Ember.get, fmt = Ember.String.fmt, |
| indexOf = Ember.EnumerableUtils.indexOf; |
| |
| var counter = 0; |
| |
| var Adapter = __dependency1__["default"]; |
| |
| /** |
| `DS.FixtureAdapter` is an adapter that loads records from memory. |
| It's primarily used for development and testing. You can also use |
| `DS.FixtureAdapter` while working on the API but are not ready to |
| integrate yet. It is a fully functioning adapter. All CRUD methods |
| are implemented. You can also implement query logic that a remote |
| system would do. It's possible to develop your entire application |
| with `DS.FixtureAdapter`. |
| |
| For information on how to use the `FixtureAdapter` in your |
| application please see the [FixtureAdapter |
| guide](/guides/models/the-fixture-adapter/). |
| |
| @class FixtureAdapter |
| @namespace DS |
| @extends DS.Adapter |
| */ |
| var FixtureAdapter = Adapter.extend({ |
| // by default, fixtures are already in normalized form |
| serializer: null, |
| |
| /** |
| If `simulateRemoteResponse` is `true` the `FixtureAdapter` will |
| wait a number of milliseconds before resolving promises with the |
| fixture values. The wait time can be configured via the `latency` |
| property. |
| |
| @property simulateRemoteResponse |
| @type {Boolean} |
| @default true |
| */ |
| simulateRemoteResponse: true, |
| |
| /** |
| By default the `FixtureAdapter` will simulate a wait of the |
| `latency` milliseconds before resolving promises with the fixture |
| values. This behavior can be turned off via the |
| `simulateRemoteResponse` property. |
| |
| @property latency |
| @type {Number} |
| @default 50 |
| */ |
| latency: 50, |
| |
| /** |
| Implement this method in order to provide data associated with a type |
| |
| @method fixturesForType |
| @param {Subclass of DS.Model} type |
| @return {Array} |
| */ |
| fixturesForType: function(type) { |
| if (type.FIXTURES) { |
| var fixtures = Ember.A(type.FIXTURES); |
| return fixtures.map(function(fixture){ |
| var fixtureIdType = typeof fixture.id; |
| if(fixtureIdType !== "number" && fixtureIdType !== "string"){ |
| throw new Error(fmt('the id property must be defined as a number or string for fixture %@', [fixture])); |
| } |
| fixture.id = fixture.id + ''; |
| return fixture; |
| }); |
| } |
| return null; |
| }, |
| |
| /** |
| Implement this method in order to query fixtures data |
| |
| @method queryFixtures |
| @param {Array} fixture |
| @param {Object} query |
| @param {Subclass of DS.Model} type |
| @return {Promise|Array} |
| */ |
| queryFixtures: function(fixtures, query, type) { |
| Ember.assert('Not implemented: You must override the DS.FixtureAdapter::queryFixtures method to support querying the fixture store.'); |
| }, |
| |
| /** |
| @method updateFixtures |
| @param {Subclass of DS.Model} type |
| @param {Array} fixture |
| */ |
| updateFixtures: function(type, fixture) { |
| if(!type.FIXTURES) { |
| type.FIXTURES = []; |
| } |
| |
| var fixtures = type.FIXTURES; |
| |
| this.deleteLoadedFixture(type, fixture); |
| |
| fixtures.push(fixture); |
| }, |
| |
| /** |
| Implement this method in order to provide json for CRUD methods |
| |
| @method mockJSON |
| @param {Subclass of DS.Model} type |
| @param {DS.Model} record |
| */ |
| mockJSON: function(store, type, record) { |
| return store.serializerFor(type).serialize(record, { includeId: true }); |
| }, |
| |
| /** |
| @method generateIdForRecord |
| @param {DS.Store} store |
| @param {DS.Model} record |
| @return {String} id |
| */ |
| generateIdForRecord: function(store) { |
| return "fixture-" + counter++; |
| }, |
| |
| /** |
| @method find |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {String} id |
| @return {Promise} promise |
| */ |
| find: function(store, type, id) { |
| var fixtures = this.fixturesForType(type), |
| fixture; |
| |
| Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures); |
| |
| if (fixtures) { |
| fixture = Ember.A(fixtures).findProperty('id', id); |
| } |
| |
| if (fixture) { |
| return this.simulateRemoteCall(function() { |
| return fixture; |
| }, this); |
| } |
| }, |
| |
| /** |
| @method findMany |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {Array} ids |
| @return {Promise} promise |
| */ |
| findMany: function(store, type, ids) { |
| var fixtures = this.fixturesForType(type); |
| |
| Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures); |
| |
| if (fixtures) { |
| fixtures = fixtures.filter(function(item) { |
| return indexOf(ids, item.id) !== -1; |
| }); |
| } |
| |
| if (fixtures) { |
| return this.simulateRemoteCall(function() { |
| return fixtures; |
| }, this); |
| } |
| }, |
| |
| /** |
| @private |
| @method findAll |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {String} sinceToken |
| @return {Promise} promise |
| */ |
| findAll: function(store, type) { |
| var fixtures = this.fixturesForType(type); |
| |
| Ember.assert("Unable to find fixtures for model type "+type.toString(), fixtures); |
| |
| return this.simulateRemoteCall(function() { |
| return fixtures; |
| }, this); |
| }, |
| |
| /** |
| @private |
| @method findQuery |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {Object} query |
| @param {DS.AdapterPopulatedRecordArray} recordArray |
| @return {Promise} promise |
| */ |
| findQuery: function(store, type, query, array) { |
| var fixtures = this.fixturesForType(type); |
| |
| Ember.assert("Unable to find fixtures for model type " + type.toString(), fixtures); |
| |
| fixtures = this.queryFixtures(fixtures, query, type); |
| |
| if (fixtures) { |
| return this.simulateRemoteCall(function() { |
| return fixtures; |
| }, this); |
| } |
| }, |
| |
| /** |
| @method createRecord |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {DS.Model} record |
| @return {Promise} promise |
| */ |
| createRecord: function(store, type, record) { |
| var fixture = this.mockJSON(store, type, record); |
| |
| this.updateFixtures(type, fixture); |
| |
| return this.simulateRemoteCall(function() { |
| return fixture; |
| }, this); |
| }, |
| |
| /** |
| @method updateRecord |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {DS.Model} record |
| @return {Promise} promise |
| */ |
| updateRecord: function(store, type, record) { |
| var fixture = this.mockJSON(store, type, record); |
| |
| this.updateFixtures(type, fixture); |
| |
| return this.simulateRemoteCall(function() { |
| return fixture; |
| }, this); |
| }, |
| |
| /** |
| @method deleteRecord |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {DS.Model} record |
| @return {Promise} promise |
| */ |
| deleteRecord: function(store, type, record) { |
| var fixture = this.mockJSON(store, type, record); |
| |
| this.deleteLoadedFixture(type, fixture); |
| |
| return this.simulateRemoteCall(function() { |
| // no payload in a deletion |
| return null; |
| }); |
| }, |
| |
| /* |
| @method deleteLoadedFixture |
| @private |
| @param type |
| @param record |
| */ |
| deleteLoadedFixture: function(type, record) { |
| var existingFixture = this.findExistingFixture(type, record); |
| |
| if(existingFixture) { |
| var index = indexOf(type.FIXTURES, existingFixture); |
| type.FIXTURES.splice(index, 1); |
| return true; |
| } |
| }, |
| |
| /* |
| @method findExistingFixture |
| @private |
| @param type |
| @param record |
| */ |
| findExistingFixture: function(type, record) { |
| var fixtures = this.fixturesForType(type); |
| var id = get(record, 'id'); |
| |
| return this.findFixtureById(fixtures, id); |
| }, |
| |
| /* |
| @method findFixtureById |
| @private |
| @param fixtures |
| @param id |
| */ |
| findFixtureById: function(fixtures, id) { |
| return Ember.A(fixtures).find(function(r) { |
| if(''+get(r, 'id') === ''+id) { |
| return true; |
| } else { |
| return false; |
| } |
| }); |
| }, |
| |
| /* |
| @method simulateRemoteCall |
| @private |
| @param callback |
| @param context |
| */ |
| simulateRemoteCall: function(callback, context) { |
| var adapter = this; |
| |
| return new Ember.RSVP.Promise(function(resolve) { |
| if (get(adapter, 'simulateRemoteResponse')) { |
| // Schedule with setTimeout |
| Ember.run.later(function() { |
| resolve(callback.call(context)); |
| }, get(adapter, 'latency')); |
| } else { |
| // Asynchronous, but at the of the runloop with zero latency |
| Ember.run.schedule('actions', null, function() { |
| resolve(callback.call(context)); |
| }); |
| } |
| }, "DS: FixtureAdapter#simulateRemoteCall"); |
| } |
| }); |
| |
| __exports__["default"] = FixtureAdapter; |
| }); |
| define("ember-data/lib/adapters/rest_adapter", |
| ["../system/adapter","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var Adapter = __dependency1__["default"]; |
| var get = Ember.get, set = Ember.set; |
| var forEach = Ember.ArrayPolyfills.forEach; |
| |
| /** |
| The REST adapter allows your store to communicate with an HTTP server by |
| transmitting JSON via XHR. Most Ember.js apps that consume a JSON API |
| should use the REST adapter. |
| |
| This adapter is designed around the idea that the JSON exchanged with |
| the server should be conventional. |
| |
| ## JSON Structure |
| |
| The REST adapter expects the JSON returned from your server to follow |
| these conventions. |
| |
| ### Object Root |
| |
| The JSON payload should be an object that contains the record inside a |
| root property. For example, in response to a `GET` request for |
| `/posts/1`, the JSON should look like this: |
| |
| ```js |
| { |
| "post": { |
| "title": "I'm Running to Reform the W3C's Tag", |
| "author": "Yehuda Katz" |
| } |
| } |
| ``` |
| |
| ### Conventional Names |
| |
| Attribute names in your JSON payload should be the camelCased versions of |
| the attributes in your Ember.js models. |
| |
| For example, if you have a `Person` model: |
| |
| ```js |
| App.Person = DS.Model.extend({ |
| firstName: DS.attr('string'), |
| lastName: DS.attr('string'), |
| occupation: DS.attr('string') |
| }); |
| ``` |
| |
| The JSON returned should look like this: |
| |
| ```js |
| { |
| "person": { |
| "firstName": "Barack", |
| "lastName": "Obama", |
| "occupation": "President" |
| } |
| } |
| ``` |
| |
| ## Customization |
| |
| ### Endpoint path customization |
| |
| Endpoint paths can be prefixed with a `namespace` by setting the namespace |
| property on the adapter: |
| |
| ```js |
| DS.RESTAdapter.reopen({ |
| namespace: 'api/1' |
| }); |
| ``` |
| Requests for `App.Person` would now target `/api/1/people/1`. |
| |
| ### Host customization |
| |
| An adapter can target other hosts by setting the `host` property. |
| |
| ```js |
| DS.RESTAdapter.reopen({ |
| host: 'https://api.example.com' |
| }); |
| ``` |
| |
| ### Headers customization |
| |
| Some APIs require HTTP headers, e.g. to provide an API key. Arbitrary |
| headers can be set as key/value pairs on the `RESTAdapter`'s `headers` |
| object and Ember Data will send them along with each ajax request. |
| |
| |
| ```js |
| App.ApplicationAdapter = DS.RESTAdapter.extend({ |
| headers: { |
| "API_KEY": "secret key", |
| "ANOTHER_HEADER": "Some header value" |
| } |
| }); |
| ``` |
| |
| `headers` can also be used as a computed property to support dynamic |
| headers. In the example below, the `session` object has been |
| injected into an adapter by Ember's container. |
| |
| ```js |
| App.ApplicationAdapter = DS.RESTAdapter.extend({ |
| headers: function() { |
| return { |
| "API_KEY": this.get("session.authToken"), |
| "ANOTHER_HEADER": "Some header value" |
| }; |
| }.property("session.authToken") |
| }); |
| ``` |
| |
| In some cases, your dynamic headers may require data from some |
| object outside of Ember's observer system (for example |
| `document.cookie`). You can use the |
| [volatile](/api/classes/Ember.ComputedProperty.html#method_volatile) |
| function to set the property into a non-chached mode causing the headers to |
| be recomputed with every request. |
| |
| ```js |
| App.ApplicationAdapter = DS.RESTAdapter.extend({ |
| headers: function() { |
| return { |
| "API_KEY": Ember.get(document.cookie.match(/apiKey\=([^;]*)/), "1"), |
| "ANOTHER_HEADER": "Some header value" |
| }; |
| }.property().volatile(); |
| }); |
| ``` |
| |
| @class RESTAdapter |
| @constructor |
| @namespace DS |
| @extends DS.Adapter |
| */ |
| var RESTAdapter = Adapter.extend({ |
| defaultSerializer: '-rest', |
| /** |
| Endpoint paths can be prefixed with a `namespace` by setting the namespace |
| property on the adapter: |
| |
| ```javascript |
| DS.RESTAdapter.reopen({ |
| namespace: 'api/1' |
| }); |
| ``` |
| |
| Requests for `App.Post` would now target `/api/1/post/`. |
| |
| @property namespace |
| @type {String} |
| */ |
| |
| /** |
| An adapter can target other hosts by setting the `host` property. |
| |
| ```javascript |
| DS.RESTAdapter.reopen({ |
| host: 'https://api.example.com' |
| }); |
| ``` |
| |
| Requests for `App.Post` would now target `https://api.example.com/post/`. |
| |
| @property host |
| @type {String} |
| */ |
| |
| /** |
| Some APIs require HTTP headers, e.g. to provide an API |
| key. Arbitrary headers can be set as key/value pairs on the |
| `RESTAdapter`'s `headers` object and Ember Data will send them |
| along with each ajax request. For dynamic headers see [headers |
| customization](/api/data/classes/DS.RESTAdapter.html#toc_headers-customization). |
| |
| ```javascript |
| App.ApplicationAdapter = DS.RESTAdapter.extend({ |
| headers: { |
| "API_KEY": "secret key", |
| "ANOTHER_HEADER": "Some header value" |
| } |
| }); |
| ``` |
| |
| @property headers |
| @type {Object} |
| */ |
| |
| /** |
| Called by the store in order to fetch the JSON for a given |
| type and ID. |
| |
| The `find` method makes an Ajax request to a URL computed by `buildURL`, and returns a |
| promise for the resulting payload. |
| |
| This method performs an HTTP `GET` request with the id provided as part of the query string. |
| |
| @method find |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {String} id |
| @return {Promise} promise |
| */ |
| find: function(store, type, id) { |
| return this.ajax(this.buildURL(type.typeKey, id), 'GET'); |
| }, |
| |
| /** |
| Called by the store in order to fetch a JSON array for all |
| of the records for a given type. |
| |
| The `findAll` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a |
| promise for the resulting payload. |
| |
| @private |
| @method findAll |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {String} sinceToken |
| @return {Promise} promise |
| */ |
| findAll: function(store, type, sinceToken) { |
| var query; |
| |
| if (sinceToken) { |
| query = { since: sinceToken }; |
| } |
| |
| return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query }); |
| }, |
| |
| /** |
| Called by the store in order to fetch a JSON array for |
| the records that match a particular query. |
| |
| The `findQuery` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a |
| promise for the resulting payload. |
| |
| The `query` argument is a simple JavaScript object that will be passed directly |
| to the server as parameters. |
| |
| @private |
| @method findQuery |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {Object} query |
| @return {Promise} promise |
| */ |
| findQuery: function(store, type, query) { |
| return this.ajax(this.buildURL(type.typeKey), 'GET', { data: query }); |
| }, |
| |
| /** |
| Called by the store in order to fetch a JSON array for |
| the unloaded records in a has-many relationship that were originally |
| specified as IDs. |
| |
| For example, if the original payload looks like: |
| |
| ```js |
| { |
| "id": 1, |
| "title": "Rails is omakase", |
| "comments": [ 1, 2, 3 ] |
| } |
| ``` |
| |
| The IDs will be passed as a URL-encoded Array of IDs, in this form: |
| |
| ``` |
| ids[]=1&ids[]=2&ids[]=3 |
| ``` |
| |
| Many servers, such as Rails and PHP, will automatically convert this URL-encoded array |
| into an Array for you on the server-side. If you want to encode the |
| IDs, differently, just override this (one-line) method. |
| |
| The `findMany` method makes an Ajax (HTTP GET) request to a URL computed by `buildURL`, and returns a |
| promise for the resulting payload. |
| |
| @method findMany |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {Array} ids |
| @return {Promise} promise |
| */ |
| findMany: function(store, type, ids) { |
| return this.ajax(this.buildURL(type.typeKey), 'GET', { data: { ids: ids } }); |
| }, |
| |
| /** |
| Called by the store in order to fetch a JSON array for |
| the unloaded records in a has-many relationship that were originally |
| specified as a URL (inside of `links`). |
| |
| For example, if your original payload looks like this: |
| |
| ```js |
| { |
| "post": { |
| "id": 1, |
| "title": "Rails is omakase", |
| "links": { "comments": "/posts/1/comments" } |
| } |
| } |
| ``` |
| |
| This method will be called with the parent record and `/posts/1/comments`. |
| |
| The `findHasMany` method will make an Ajax (HTTP GET) request to the originally specified URL. |
| If the URL is host-relative (starting with a single slash), the |
| request will use the host specified on the adapter (if any). |
| |
| @method findHasMany |
| @param {DS.Store} store |
| @param {DS.Model} record |
| @param {String} url |
| @return {Promise} promise |
| */ |
| findHasMany: function(store, record, url) { |
| var host = get(this, 'host'), |
| id = get(record, 'id'), |
| type = record.constructor.typeKey; |
| |
| if (host && url.charAt(0) === '/' && url.charAt(1) !== '/') { |
| url = host + url; |
| } |
| |
| return this.ajax(this.urlPrefix(url, this.buildURL(type, id)), 'GET'); |
| }, |
| |
| /** |
| Called by the store in order to fetch a JSON array for |
| the unloaded records in a belongs-to relationship that were originally |
| specified as a URL (inside of `links`). |
| |
| For example, if your original payload looks like this: |
| |
| ```js |
| { |
| "person": { |
| "id": 1, |
| "name": "Tom Dale", |
| "links": { "group": "/people/1/group" } |
| } |
| } |
| ``` |
| |
| This method will be called with the parent record and `/people/1/group`. |
| |
| The `findBelongsTo` method will make an Ajax (HTTP GET) request to the originally specified URL. |
| |
| @method findBelongsTo |
| @param {DS.Store} store |
| @param {DS.Model} record |
| @param {String} url |
| @return {Promise} promise |
| */ |
| findBelongsTo: function(store, record, url) { |
| var id = get(record, 'id'), |
| type = record.constructor.typeKey; |
| |
| return this.ajax(this.urlPrefix(url, this.buildURL(type, id)), 'GET'); |
| }, |
| |
| /** |
| Called by the store when a newly created record is |
| saved via the `save` method on a model record instance. |
| |
| The `createRecord` method serializes the record and makes an Ajax (HTTP POST) request |
| to a URL computed by `buildURL`. |
| |
| See `serialize` for information on how to customize the serialized form |
| of a record. |
| |
| @method createRecord |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {DS.Model} record |
| @return {Promise} promise |
| */ |
| createRecord: function(store, type, record) { |
| var data = {}; |
| var serializer = store.serializerFor(type.typeKey); |
| |
| serializer.serializeIntoHash(data, type, record, { includeId: true }); |
| |
| return this.ajax(this.buildURL(type.typeKey), "POST", { data: data }); |
| }, |
| |
| /** |
| Called by the store when an existing record is saved |
| via the `save` method on a model record instance. |
| |
| The `updateRecord` method serializes the record and makes an Ajax (HTTP PUT) request |
| to a URL computed by `buildURL`. |
| |
| See `serialize` for information on how to customize the serialized form |
| of a record. |
| |
| @method updateRecord |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {DS.Model} record |
| @return {Promise} promise |
| */ |
| updateRecord: function(store, type, record) { |
| var data = {}; |
| var serializer = store.serializerFor(type.typeKey); |
| |
| serializer.serializeIntoHash(data, type, record); |
| |
| var id = get(record, 'id'); |
| |
| return this.ajax(this.buildURL(type.typeKey, id), "PUT", { data: data }); |
| }, |
| |
| /** |
| Called by the store when a record is deleted. |
| |
| The `deleteRecord` method makes an Ajax (HTTP DELETE) request to a URL computed by `buildURL`. |
| |
| @method deleteRecord |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {DS.Model} record |
| @return {Promise} promise |
| */ |
| deleteRecord: function(store, type, record) { |
| var id = get(record, 'id'); |
| |
| return this.ajax(this.buildURL(type.typeKey, id), "DELETE"); |
| }, |
| |
| /** |
| Builds a URL for a given type and optional ID. |
| |
| By default, it pluralizes the type's name (for example, 'post' |
| becomes 'posts' and 'person' becomes 'people'). To override the |
| pluralization see [pathForType](#method_pathForType). |
| |
| If an ID is specified, it adds the ID to the path generated |
| for the type, separated by a `/`. |
| |
| @method buildURL |
| @param {String} type |
| @param {String} id |
| @return {String} url |
| */ |
| buildURL: function(type, id) { |
| var url = [], |
| host = get(this, 'host'), |
| prefix = this.urlPrefix(); |
| |
| if (type) { url.push(this.pathForType(type)); } |
| if (id) { url.push(id); } |
| |
| if (prefix) { url.unshift(prefix); } |
| |
| url = url.join('/'); |
| if (!host && url) { url = '/' + url; } |
| |
| return url; |
| }, |
| |
| /** |
| @method urlPrefix |
| @private |
| @param {String} path |
| @param {String} parentUrl |
| @return {String} urlPrefix |
| */ |
| urlPrefix: function(path, parentURL) { |
| var host = get(this, 'host'), |
| namespace = get(this, 'namespace'), |
| url = []; |
| |
| if (path) { |
| // Absolute path |
| if (path.charAt(0) === '/') { |
| if (host) { |
| path = path.slice(1); |
| url.push(host); |
| } |
| // Relative path |
| } else if (!/^http(s)?:\/\//.test(path)) { |
| url.push(parentURL); |
| } |
| } else { |
| if (host) { url.push(host); } |
| if (namespace) { url.push(namespace); } |
| } |
| |
| if (path) { |
| url.push(path); |
| } |
| |
| return url.join('/'); |
| }, |
| |
| /** |
| Determines the pathname for a given type. |
| |
| By default, it pluralizes the type's name (for example, |
| 'post' becomes 'posts' and 'person' becomes 'people'). |
| |
| ### Pathname customization |
| |
| For example if you have an object LineItem with an |
| endpoint of "/line_items/". |
| |
| ```js |
| App.ApplicationAdapter = DS.RESTAdapter.extend({ |
| pathForType: function(type) { |
| var decamelized = Ember.String.decamelize(type); |
| return Ember.String.pluralize(decamelized); |
| } |
| }); |
| ``` |
| |
| @method pathForType |
| @param {String} type |
| @return {String} path |
| **/ |
| pathForType: function(type) { |
| var camelized = Ember.String.camelize(type); |
| return Ember.String.pluralize(camelized); |
| }, |
| |
| /** |
| Takes an ajax response, and returns a relevant error. |
| |
| Returning a `DS.InvalidError` from this method will cause the |
| record to transition into the `invalid` state and make the |
| `errors` object available on the record. |
| |
| ```javascript |
| App.ApplicationAdapter = DS.RESTAdapter.extend({ |
| ajaxError: function(jqXHR) { |
| var error = this._super(jqXHR); |
| |
| if (jqXHR && jqXHR.status === 422) { |
| var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"]; |
| |
| return new DS.InvalidError(jsonErrors); |
| } else { |
| return error; |
| } |
| } |
| }); |
| ``` |
| |
| Note: As a correctness optimization, the default implementation of |
| the `ajaxError` method strips out the `then` method from jquery's |
| ajax response (jqXHR). This is important because the jqXHR's |
| `then` method fulfills the promise with itself resulting in a |
| circular "thenable" chain which may cause problems for some |
| promise libraries. |
| |
| @method ajaxError |
| @param {Object} jqXHR |
| @return {Object} jqXHR |
| */ |
| ajaxError: function(jqXHR) { |
| if (jqXHR && typeof jqXHR === 'object') { |
| jqXHR.then = null; |
| } |
| |
| return jqXHR; |
| }, |
| |
| /** |
| Takes a URL, an HTTP method and a hash of data, and makes an |
| HTTP request. |
| |
| When the server responds with a payload, Ember Data will call into `extractSingle` |
| or `extractArray` (depending on whether the original query was for one record or |
| many records). |
| |
| By default, `ajax` method has the following behavior: |
| |
| * It sets the response `dataType` to `"json"` |
| * If the HTTP method is not `"GET"`, it sets the `Content-Type` to be |
| `application/json; charset=utf-8` |
| * If the HTTP method is not `"GET"`, it stringifies the data passed in. The |
| data is the serialized record in the case of a save. |
| * Registers success and failure handlers. |
| |
| @method ajax |
| @private |
| @param {String} url |
| @param {String} type The request type GET, POST, PUT, DELETE etc. |
| @param {Object} hash |
| @return {Promise} promise |
| */ |
| ajax: function(url, type, hash) { |
| var adapter = this; |
| |
| return new Ember.RSVP.Promise(function(resolve, reject) { |
| hash = adapter.ajaxOptions(url, type, hash); |
| |
| hash.success = function(json) { |
| Ember.run(null, resolve, json); |
| }; |
| |
| hash.error = function(jqXHR, textStatus, errorThrown) { |
| Ember.run(null, reject, adapter.ajaxError(jqXHR)); |
| }; |
| |
| Ember.$.ajax(hash); |
| }, "DS: RestAdapter#ajax " + type + " to " + url); |
| }, |
| |
| /** |
| @method ajaxOptions |
| @private |
| @param {String} url |
| @param {String} type The request type GET, POST, PUT, DELETE etc. |
| @param {Object} hash |
| @return {Object} hash |
| */ |
| ajaxOptions: function(url, type, hash) { |
| hash = hash || {}; |
| hash.url = url; |
| hash.type = type; |
| hash.dataType = 'json'; |
| hash.context = this; |
| |
| if (hash.data && type !== 'GET') { |
| hash.contentType = 'application/json; charset=utf-8'; |
| hash.data = JSON.stringify(hash.data); |
| } |
| |
| var headers = get(this, 'headers'); |
| if (headers !== undefined) { |
| hash.beforeSend = function (xhr) { |
| forEach.call(Ember.keys(headers), function(key) { |
| xhr.setRequestHeader(key, headers[key]); |
| }); |
| }; |
| } |
| |
| |
| return hash; |
| } |
| |
| }); |
| |
| __exports__["default"] = RESTAdapter; |
| }); |
| define("ember-data/lib/core", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| /** |
| All Ember Data methods and functions are defined inside of this namespace. |
| |
| @class DS |
| @static |
| */ |
| var DS; |
| if ('undefined' === typeof DS) { |
| /** |
| @property VERSION |
| @type String |
| @default '1.0.0-beta.8.2a68c63a' |
| @static |
| */ |
| DS = Ember.Namespace.create({ |
| VERSION: '1.0.0-beta.8.2a68c63a' |
| }); |
| |
| if (Ember.libraries) { |
| Ember.libraries.registerCoreLibrary('Ember Data', DS.VERSION); |
| } |
| } |
| |
| __exports__["default"] = DS; |
| }); |
| define("ember-data/lib/ember-initializer", |
| ["./setup-container"], |
| function(__dependency1__) { |
| "use strict"; |
| var setupContainer = __dependency1__["default"]; |
| |
| var K = Ember.K; |
| |
| /** |
| @module ember-data |
| */ |
| |
| /** |
| |
| This code initializes Ember-Data onto an Ember application. |
| |
| If an Ember.js developer defines a subclass of DS.Store on their application, |
| as `App.ApplicationStore` (or via a module system that resolves to `store: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.ApplicationStore = DS.Store.extend({ |
| adapter: 'custom' |
| }); |
| |
| App.PostsController = Ember.ArrayController.extend({ |
| // ... |
| }); |
| |
| When the application is initialized, `App.ApplicationStore` 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: "ember-data", |
| initialize: setupContainer |
| }); |
| |
| // Deprecated initializers to satisfy old code that depended on them |
| |
| Application.initializer({ |
| name: "store", |
| after: "ember-data", |
| initialize: K |
| }); |
| |
| Application.initializer({ |
| name: "activeModelAdapter", |
| before: "store", |
| initialize: K |
| }); |
| |
| Application.initializer({ |
| name: "transforms", |
| before: "store", |
| initialize: K |
| }); |
| |
| Application.initializer({ |
| name: "data-adapter", |
| before: "store", |
| initialize: K |
| }); |
| |
| Application.initializer({ |
| name: "injectStore", |
| before: "store", |
| initialize: K |
| }); |
| }); |
| }); |
| define("ember-data/lib/ext/date", |
| [], |
| function() { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| /** |
| Date.parse with progressive enhancement for ISO 8601 <https://github.com/csnover/js-iso8601> |
| |
| © 2011 Colin Snover <http://zetafleet.com> |
| |
| 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; |
| } |
| }); |
| define("ember-data/lib/initializers/data_adapter", |
| ["../system/debug/debug_adapter","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var DebugAdapter = __dependency1__["default"]; |
| |
| /** |
| Configures a container with injections on Ember applications |
| for the Ember-Data store. Accepts an optional namespace argument. |
| |
| @method initializeStoreInjections |
| @param {Ember.Container} container |
| */ |
| __exports__["default"] = function initializeDebugAdapter(container){ |
| container.register('data-adapter:main', DebugAdapter); |
| }; |
| }); |
| define("ember-data/lib/initializers/store", |
| ["../serializers","../adapters","../system/container_proxy","../system/store","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { |
| "use strict"; |
| var JSONSerializer = __dependency1__.JSONSerializer; |
| var RESTSerializer = __dependency1__.RESTSerializer; |
| var RESTAdapter = __dependency2__.RESTAdapter; |
| var ContainerProxy = __dependency3__["default"]; |
| var Store = __dependency4__["default"]; |
| |
| /** |
| Configures a container for use with an Ember-Data |
| store. Accepts an optional namespace argument. |
| |
| @method initializeStore |
| @param {Ember.Container} container |
| @param {Object} [application] an application namespace |
| */ |
| __exports__["default"] = function initializeStore(container, application){ |
| Ember.deprecate('Specifying a custom Store for Ember Data on your global namespace as `App.Store` ' + |
| 'has been deprecated. Please use `App.ApplicationStore` instead.', !(application && application.Store)); |
| |
| container.register('store:main', container.lookupFactory('store:application') || (application && application.Store) || Store); |
| |
| // allow older names to be looked up |
| |
| var proxy = new 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 |
| container.register('serializer:-default', JSONSerializer); |
| container.register('serializer:-rest', RESTSerializer); |
| container.register('adapter:-rest', RESTAdapter); |
| |
| // Eagerly generate the store so defaultStore is populated. |
| // TODO: Do this in a finisher hook |
| container.lookup('store:main'); |
| }; |
| }); |
| define("ember-data/lib/initializers/store_injections", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| /** |
| Configures a container with injections on Ember applications |
| for the Ember-Data store. Accepts an optional namespace argument. |
| |
| @method initializeStoreInjections |
| @param {Ember.Container} container |
| */ |
| __exports__["default"] = function initializeStoreInjections(container){ |
| container.injection('controller', 'store', 'store:main'); |
| container.injection('route', 'store', 'store:main'); |
| container.injection('serializer', 'store', 'store:main'); |
| container.injection('data-adapter', 'store', 'store:main'); |
| }; |
| }); |
| define("ember-data/lib/initializers/transforms", |
| ["../transforms","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var BooleanTransform = __dependency1__.BooleanTransform; |
| var DateTransform = __dependency1__.DateTransform; |
| var StringTransform = __dependency1__.StringTransform; |
| var NumberTransform = __dependency1__.NumberTransform; |
| |
| /** |
| Configures a container for use with Ember-Data |
| transforms. |
| |
| @method initializeTransforms |
| @param {Ember.Container} container |
| */ |
| __exports__["default"] = function initializeTransforms(container){ |
| container.register('transform:boolean', BooleanTransform); |
| container.register('transform:date', DateTransform); |
| container.register('transform:number', NumberTransform); |
| container.register('transform:string', StringTransform); |
| }; |
| }); |
| define("ember-data/lib/main", |
| ["./core","./ext/date","./system/store","./system/model","./system/changes","./system/adapter","./system/debug","./system/record_arrays","./system/record_array_manager","./adapters","./serializers/json_serializer","./serializers/rest_serializer","../../ember-inflector/lib/main","../../activemodel-adapter/lib/main","./transforms","./system/relationships","./ember-initializer","./setup-container","./system/container_proxy","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __exports__) { |
| "use strict"; |
| /** |
| Ember Data |
| |
| @module ember-data |
| @main ember-data |
| */ |
| |
| // support RSVP 2.x via resolve, but prefer RSVP 3.x's Promise.cast |
| Ember.RSVP.Promise.cast = Ember.RSVP.Promise.cast || Ember.RSVP.resolve; |
| |
| var DS = __dependency1__["default"]; |
| |
| var Store = __dependency3__.Store; |
| var PromiseArray = __dependency3__.PromiseArray; |
| var PromiseObject = __dependency3__.PromiseObject; |
| var Model = __dependency4__.Model; |
| var Errors = __dependency4__.Errors; |
| var RootState = __dependency4__.RootState; |
| var attr = __dependency4__.attr; |
| var AttributeChange = __dependency5__.AttributeChange; |
| var RelationshipChange = __dependency5__.RelationshipChange; |
| var RelationshipChangeAdd = __dependency5__.RelationshipChangeAdd; |
| var RelationshipChangeRemove = __dependency5__.RelationshipChangeRemove; |
| var OneToManyChange = __dependency5__.OneToManyChange; |
| var ManyToNoneChange = __dependency5__.ManyToNoneChange; |
| var OneToOneChange = __dependency5__.OneToOneChange; |
| var ManyToManyChange = __dependency5__.ManyToManyChange; |
| var InvalidError = __dependency6__.InvalidError; |
| var Adapter = __dependency6__.Adapter; |
| var DebugAdapter = __dependency7__["default"]; |
| var RecordArray = __dependency8__.RecordArray; |
| var FilteredRecordArray = __dependency8__.FilteredRecordArray; |
| var AdapterPopulatedRecordArray = __dependency8__.AdapterPopulatedRecordArray; |
| var ManyArray = __dependency8__.ManyArray; |
| var RecordArrayManager = __dependency9__["default"]; |
| var RESTAdapter = __dependency10__.RESTAdapter; |
| var FixtureAdapter = __dependency10__.FixtureAdapter; |
| var JSONSerializer = __dependency11__["default"]; |
| var RESTSerializer = __dependency12__["default"]; |
| var ActiveModelAdapter = __dependency14__.ActiveModelAdapter; |
| var ActiveModelSerializer = __dependency14__.ActiveModelSerializer; |
| var EmbeddedRecordsMixin = __dependency14__.EmbeddedRecordsMixin; |
| |
| var Transform = __dependency15__.Transform; |
| var DateTransform = __dependency15__.DateTransform; |
| var NumberTransform = __dependency15__.NumberTransform; |
| var StringTransform = __dependency15__.StringTransform; |
| var BooleanTransform = __dependency15__.BooleanTransform; |
| |
| var hasMany = __dependency16__.hasMany; |
| var belongsTo = __dependency16__.belongsTo; |
| var setupContainer = __dependency18__["default"]; |
| |
| var ContainerProxy = __dependency19__["default"]; |
| |
| DS.Store = Store; |
| DS.PromiseArray = PromiseArray; |
| DS.PromiseObject = PromiseObject; |
| |
| DS.Model = Model; |
| DS.RootState = RootState; |
| DS.attr = attr; |
| DS.Errors = Errors; |
| |
| DS.AttributeChange = AttributeChange; |
| DS.RelationshipChange = RelationshipChange; |
| DS.RelationshipChangeAdd = RelationshipChangeAdd; |
| DS.OneToManyChange = OneToManyChange; |
| DS.ManyToNoneChange = OneToManyChange; |
| DS.OneToOneChange = OneToOneChange; |
| DS.ManyToManyChange = ManyToManyChange; |
| |
| DS.Adapter = Adapter; |
| DS.InvalidError = InvalidError; |
| |
| DS.DebugAdapter = DebugAdapter; |
| |
| DS.RecordArray = RecordArray; |
| DS.FilteredRecordArray = FilteredRecordArray; |
| DS.AdapterPopulatedRecordArray = AdapterPopulatedRecordArray; |
| DS.ManyArray = ManyArray; |
| |
| DS.RecordArrayManager = RecordArrayManager; |
| |
| DS.RESTAdapter = RESTAdapter; |
| DS.FixtureAdapter = FixtureAdapter; |
| |
| DS.RESTSerializer = RESTSerializer; |
| DS.JSONSerializer = JSONSerializer; |
| |
| DS.Transform = Transform; |
| DS.DateTransform = DateTransform; |
| DS.StringTransform = StringTransform; |
| DS.NumberTransform = NumberTransform; |
| DS.BooleanTransform = BooleanTransform; |
| |
| DS.ActiveModelAdapter = ActiveModelAdapter; |
| DS.ActiveModelSerializer = ActiveModelSerializer; |
| DS.EmbeddedRecordsMixin = EmbeddedRecordsMixin; |
| |
| DS.belongsTo = belongsTo; |
| DS.hasMany = hasMany; |
| |
| DS.ContainerProxy = ContainerProxy; |
| |
| DS._setupContainer = setupContainer; |
| |
| Ember.lookup.DS = DS; |
| |
| __exports__["default"] = DS; |
| }); |
| define("ember-data/lib/serializers", |
| ["./serializers/json_serializer","./serializers/rest_serializer","exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var JSONSerializer = __dependency1__["default"]; |
| var RESTSerializer = __dependency2__["default"]; |
| |
| __exports__.JSONSerializer = JSONSerializer; |
| __exports__.RESTSerializer = RESTSerializer; |
| }); |
| define("ember-data/lib/serializers/json_serializer", |
| ["../system/changes","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var RelationshipChange = __dependency1__.RelationshipChange; |
| var get = Ember.get, set = Ember.set, isNone = Ember.isNone, |
| map = Ember.ArrayPolyfills.map; |
| |
| /** |
| 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 |
| */ |
| var 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', |
| |
| /** |
| The `attrs` object can be used to declare a simple mapping between |
| property names on `DS.Model` records and payload keys in the |
| serialized JSON object representing the record. An object with the |
| propery `key` can also be used to designate the attribute's key on |
| the response payload. |
| |
| Example |
| |
| ```javascript |
| App.Person = DS.Model.extend({ |
| firstName: DS.attr('string'), |
| lastName: DS.attr('string'), |
| occupation: DS.attr('string'), |
| admin: DS.attr('boolean') |
| }); |
| |
| App.PersonSerializer = DS.JSONSerializer.extend({ |
| attrs: { |
| admin: 'is_admin', |
| occupation: {key: 'career'} |
| } |
| }); |
| ``` |
| |
| @property attrs |
| @type {Object} |
| */ |
| |
| /** |
| 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.normalizeId(hash); |
| this.normalizeUsingDeclaredMapping(type, hash); |
| this.applyTransforms(type, hash); |
| return hash; |
| }, |
| |
| /** |
| @method normalizeUsingDeclaredMapping |
| @private |
| */ |
| normalizeUsingDeclaredMapping: function(type, hash) { |
| var attrs = get(this, 'attrs'), payloadKey, key; |
| |
| if (attrs) { |
| for (key in attrs) { |
| payloadKey = attrs[key]; |
| if (payloadKey && payloadKey.key) { |
| payloadKey = payloadKey.key; |
| } |
| if (typeof payloadKey === 'string') { |
| hash[key] = hash[payloadKey]; |
| delete hash[payloadKey]; |
| } |
| } |
| } |
| }, |
| /** |
| @method normalizeId |
| @private |
| */ |
| normalizeId: function(hash) { |
| var primaryKey = get(this, 'primaryKey'); |
| |
| if (primaryKey === 'id') { return; } |
| |
| hash.id = hash[primaryKey]; |
| delete hash[primaryKey]; |
| }, |
| |
| // 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 your 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 payloadKey = this.keyForRelationship ? this.keyForRelationship(key, "hasMany") : key; |
| var relationshipType = RelationshipChange.determineRelationshipType(record.constructor, relationship); |
| |
| if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') { |
| json[payloadKey] = 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: function(store, type, payload){ |
| return this.extractArray(store, type, payload); |
| }, |
| /** |
| `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: function(store, type, payload){ |
| return this.extractArray(store, type, payload); |
| }, |
| /** |
| `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: function(store, type, payload){ |
| return this.extractArray(store, type, payload); |
| }, |
| /** |
| `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: function(store, type, payload){ |
| return this.extractArray(store, type, payload); |
| }, |
| |
| /** |
| `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: function(store, type, payload) { |
| return this.extractSave(store, type, payload); |
| }, |
| /** |
| `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: function(store, type, payload) { |
| return this.extractSave(store, type, payload); |
| }, |
| /** |
| `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: function(store, type, payload) { |
| return this.extractSave(store, type, payload); |
| }, |
| |
| /** |
| `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: function(store, type, payload) { |
| return this.extractSingle(store, type, payload); |
| }, |
| /** |
| `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: function(store, type, payload) { |
| return this.extractSingle(store, type, payload); |
| }, |
| /** |
| `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: function(store, type, payload) { |
| return this.extractSingle(store, type, payload); |
| }, |
| |
| /** |
| `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(store, type, 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, arrayPayload) { |
| var serializer = this; |
| return map.call(arrayPayload, function(singlePayload) { |
| return serializer.normalize(type, singlePayload); |
| }); |
| }, |
| |
| /** |
| `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; |
| } |
| }); |
| |
| __exports__["default"] = JSONSerializer; |
| }); |
| define("ember-data/lib/serializers/rest_serializer", |
| ["./json_serializer","ember-inflector/lib/system/string","exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var JSONSerializer = __dependency1__["default"]; |
| var get = Ember.get, set = Ember.set; |
| var forEach = Ember.ArrayPolyfills.forEach; |
| var map = Ember.ArrayPolyfills.map; |
| |
| var singularize = __dependency2__.singularize; |
| var camelize = Ember.String.camelize; |
| |
| function coerceId(id) { |
| return id == null ? null : id+''; |
| } |
| |
| /** |
| Normally, applications will use the `RESTSerializer` by implementing |
| the `normalize` method and individual normalizations under |
| `normalizeHash`. |
| |
| This allows you to do whatever kind of munging you need, and is |
| especially useful if your server is inconsistent and you need to |
| do munging differently for many different kinds of responses. |
| |
| See the `normalize` documentation for more information. |
| |
| ## Across the Board Normalization |
| |
| There are also a number of hooks that you might find useful to defined |
| across-the-board rules for your payload. These rules will be useful |
| if your server is consistent, or if you're building an adapter for |
| an infrastructure service, like Parse, and want to encode service |
| conventions. |
| |
| For example, if all of your keys are underscored and all-caps, but |
| otherwise consistent with the names you use in your models, you |
| can implement across-the-board rules for how to convert an attribute |
| name in your model to a key in your JSON. |
| |
| ```js |
| App.ApplicationSerializer = DS.RESTSerializer.extend({ |
| keyForAttribute: function(attr) { |
| return Ember.String.underscore(attr).toUpperCase(); |
| } |
| }); |
| ``` |
| |
| You can also implement `keyForRelationship`, which takes the name |
| of the relationship as the first parameter, and the kind of |
| relationship (`hasMany` or `belongsTo`) as the second parameter. |
| |
| @class RESTSerializer |
| @namespace DS |
| @extends DS.JSONSerializer |
| */ |
| var RESTSerializer = JSONSerializer.extend({ |
| /** |
| If you want to do normalizations specific to some part of the payload, you |
| can specify those under `normalizeHash`. |
| |
| For example, given the following json where the the `IDs` under |
| `"comments"` are provided as `_id` instead of `id`. |
| |
| ```javascript |
| { |
| "post": { |
| "id": 1, |
| "title": "Rails is omakase", |
| "comments": [ 1, 2 ] |
| }, |
| "comments": [{ |
| "_id": 1, |
| "body": "FIRST" |
| }, { |
| "_id": 2, |
| "body": "Rails is unagi" |
| }] |
| } |
| ``` |
| |
| You use `normalizeHash` to normalize just the comments: |
| |
| ```javascript |
| App.PostSerializer = DS.RESTSerializer.extend({ |
| normalizeHash: { |
| comments: function(hash) { |
| hash.id = hash._id; |
| delete hash._id; |
| return hash; |
| } |
| } |
| }); |
| ``` |
| |
| The key under `normalizeHash` is usually just the original key |
| that was in the original payload. However, key names will be |
| impacted by any modifications done in the `normalizePayload` |
| method. The `DS.RESTSerializer`'s default implementation makes no |
| changes to the payload keys. |
| |
| @property normalizeHash |
| @type {Object} |
| @default undefined |
| */ |
| |
| /** |
| 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. |
| |
| For example, if you have a payload that looks like this: |
| |
| ```js |
| { |
| "post": { |
| "id": 1, |
| "title": "Rails is omakase", |
| "comments": [ 1, 2 ] |
| }, |
| "comments": [{ |
| "id": 1, |
| "body": "FIRST" |
| }, { |
| "id": 2, |
| "body": "Rails is unagi" |
| }] |
| } |
| ``` |
| |
| The `normalize` method will be called three times: |
| |
| * With `App.Post`, `"posts"` and `{ id: 1, title: "Rails is omakase", ... }` |
| * With `App.Comment`, `"comments"` and `{ id: 1, body: "FIRST" }` |
| * With `App.Comment`, `"comments"` and `{ id: 2, body: "Rails is unagi" }` |
| |
| You can use this method, for example, to normalize underscored keys to camelized |
| or other general-purpose normalizations. |
| |
| If you want to do normalizations specific to some part of the payload, you |
| can specify those under `normalizeHash`. |
| |
| For example, if the `IDs` under `"comments"` are provided as `_id` instead of |
| `id`, you can specify how to normalize just the comments: |
| |
| ```js |
| App.PostSerializer = DS.RESTSerializer.extend({ |
| normalizeHash: { |
| comments: function(hash) { |
| hash.id = hash._id; |
| delete hash._id; |
| return hash; |
| } |
| } |
| }); |
| ``` |
| |
| The key under `normalizeHash` is just the original key that was in the original |
| payload. |
| |
| @method normalize |
| @param {subclass of DS.Model} type |
| @param {Object} hash |
| @param {String} prop |
| @return {Object} |
| */ |
| normalize: function(type, hash, prop) { |
| this.normalizeId(hash); |
| this.normalizeAttributes(type, hash); |
| this.normalizeRelationships(type, hash); |
| |
| this.normalizeUsingDeclaredMapping(type, hash); |
| |
| if (this.normalizeHash && this.normalizeHash[prop]) { |
| this.normalizeHash[prop](hash); |
| } |
| |
| this.applyTransforms(type, hash); |
| return hash; |
| }, |
| |
| /** |
| You can use this method to normalize all payloads, regardless of whether they |
| represent single records or an array. |
| |
| For example, you might want to remove some extraneous data from the payload: |
| |
| ```js |
| App.ApplicationSerializer = DS.RESTSerializer.extend({ |
| normalizePayload: function(payload) { |
| delete payload.version; |
| delete payload.status; |
| return payload; |
| } |
| }); |
| ``` |
| |
| @method normalizePayload |
| @param {Object} payload |
| @return {Object} the normalized payload |
| */ |
| normalizePayload: function(payload) { |
| return payload; |
| }, |
| |
| /** |
| @method normalizeAttributes |
| @private |
| */ |
| normalizeAttributes: function(type, hash) { |
| var payloadKey, key; |
| |
| if (this.keyForAttribute) { |
| type.eachAttribute(function(key) { |
| payloadKey = this.keyForAttribute(key); |
| if (key === payloadKey) { return; } |
| |
| hash[key] = hash[payloadKey]; |
| delete hash[payloadKey]; |
| }, this); |
| } |
| }, |
| |
| /** |
| @method normalizeRelationships |
| @private |
| */ |
| normalizeRelationships: function(type, hash) { |
| var payloadKey, key; |
| |
| if (this.keyForRelationship) { |
| type.eachRelationship(function(key, relationship) { |
| payloadKey = this.keyForRelationship(key, relationship.kind); |
| if (key === payloadKey) { return; } |
| |
| hash[key] = hash[payloadKey]; |
| delete hash[payloadKey]; |
| }, this); |
| } |
| }, |
| |
| /** |
| Called when the server has returned a payload representing |
| a single record, such as in response to a `find` or `save`. |
| |
| It is your opportunity to clean up the server's response into the normalized |
| form expected by Ember Data. |
| |
| If you want, you can just restructure the top-level of your payload, and |
| do more fine-grained normalization in the `normalize` method. |
| |
| For example, if you have a payload like this in response to a request for |
| post 1: |
| |
| ```js |
| { |
| "id": 1, |
| "title": "Rails is omakase", |
| |
| "_embedded": { |
| "comment": [{ |
| "_id": 1, |
| "comment_title": "FIRST" |
| }, { |
| "_id": 2, |
| "comment_title": "Rails is unagi" |
| }] |
| } |
| } |
| ``` |
| |
| You could implement a serializer that looks like this to get your payload |
| into shape: |
| |
| ```js |
| App.PostSerializer = DS.RESTSerializer.extend({ |
| // First, restructure the top-level so it's organized by type |
| extractSingle: function(store, type, payload, id) { |
| var comments = payload._embedded.comment; |
| delete payload._embedded; |
| |
| payload = { comments: comments, post: payload }; |
| return this._super(store, type, payload, id); |
| }, |
| |
| normalizeHash: { |
| // Next, normalize individual comments, which (after `extract`) |
| // are now located under `comments` |
| comments: function(hash) { |
| hash.id = hash._id; |
| hash.title = hash.comment_title; |
| delete hash._id; |
| delete hash.comment_title; |
| return hash; |
| } |
| } |
| }) |
| ``` |
| |
| When you call super from your own implementation of `extractSingle`, the |
| built-in implementation will find the primary record in your normalized |
| payload and push the remaining records into the store. |
| |
| The primary record is the single hash found under `post` or the first |
| element of the `posts` array. |
| |
| The primary record has special meaning when the record is being created |
| for the first time or updated (`createRecord` or `updateRecord`). In |
| particular, it will update the properties of the record that was saved. |
| |
| @method extractSingle |
| @param {DS.Store} store |
| @param {subclass of DS.Model} primaryType |
| @param {Object} payload |
| @param {String} recordId |
| @return {Object} the primary response to the original request |
| */ |
| extractSingle: function(store, primaryType, payload, recordId) { |
| payload = this.normalizePayload(payload); |
| var primaryTypeName = primaryType.typeKey, |
| primaryRecord; |
| |
| for (var prop in payload) { |
| var typeName = this.typeForRoot(prop), |
| type = store.modelFor(typeName), |
| isPrimary = type.typeKey === primaryTypeName; |
| |
| // legacy support for singular resources |
| if (isPrimary && Ember.typeOf(payload[prop]) !== "array" ) { |
| primaryRecord = this.normalize(primaryType, payload[prop], prop); |
| continue; |
| } |
| |
| /*jshint loopfunc:true*/ |
| forEach.call(payload[prop], function(hash) { |
| var typeName = this.typeForRoot(prop), |
| type = store.modelFor(typeName), |
| typeSerializer = store.serializerFor(type); |
| |
| hash = typeSerializer.normalize(type, hash, prop); |
| |
| var isFirstCreatedRecord = isPrimary && !recordId && !primaryRecord, |
| isUpdatedRecord = isPrimary && coerceId(hash.id) === recordId; |
| |
| // find the primary record. |
| // |
| // It's either: |
| // * the record with the same ID as the original request |
| // * in the case of a newly created record that didn't have an ID, the first |
| // record in the Array |
| if (isFirstCreatedRecord || isUpdatedRecord) { |
| primaryRecord = hash; |
| } else { |
| store.push(typeName, hash); |
| } |
| }, this); |
| } |
| |
| return primaryRecord; |
| }, |
| |
| /** |
| Called when the server has returned a payload representing |
| multiple records, such as in response to a `findAll` or `findQuery`. |
| |
| It is your opportunity to clean up the server's response into the normalized |
| form expected by Ember Data. |
| |
| If you want, you can just restructure the top-level of your payload, and |
| do more fine-grained normalization in the `normalize` method. |
| |
| For example, if you have a payload like this in response to a request for |
| all posts: |
| |
| ```js |
| { |
| "_embedded": { |
| "post": [{ |
| "id": 1, |
| "title": "Rails is omakase" |
| }, { |
| "id": 2, |
| "title": "The Parley Letter" |
| }], |
| "comment": [{ |
| "_id": 1, |
| "comment_title": "Rails is unagi" |
| "post_id": 1 |
| }, { |
| "_id": 2, |
| "comment_title": "Don't tread on me", |
| "post_id": 2 |
| }] |
| } |
| } |
| ``` |
| |
| You could implement a serializer that looks like this to get your payload |
| into shape: |
| |
| ```js |
| App.PostSerializer = DS.RESTSerializer.extend({ |
| // First, restructure the top-level so it's organized by type |
| // and the comments are listed under a post's `comments` key. |
| extractArray: function(store, type, payload) { |
| var posts = payload._embedded.post; |
| var comments = []; |
| var postCache = {}; |
| |
| posts.forEach(function(post) { |
| post.comments = []; |
| postCache[post.id] = post; |
| }); |
| |
| payload._embedded.comment.forEach(function(comment) { |
| comments.push(comment); |
| postCache[comment.post_id].comments.push(comment); |
| delete comment.post_id; |
| } |
| |
| payload = { comments: comments, posts: payload }; |
| |
| return this._super(store, type, payload); |
| }, |
| |
| normalizeHash: { |
| // Next, normalize individual comments, which (after `extract`) |
| // are now located under `comments` |
| comments: function(hash) { |
| hash.id = hash._id; |
| hash.title = hash.comment_title; |
| delete hash._id; |
| delete hash.comment_title; |
| return hash; |
| } |
| } |
| }) |
| ``` |
| |
| When you call super from your own implementation of `extractArray`, the |
| built-in implementation will find the primary array in your normalized |
| payload and push the remaining records into the store. |
| |
| The primary array is the array found under `posts`. |
| |
| The primary record has special meaning when responding to `findQuery` |
| or `findHasMany`. In particular, the primary array will become the |
| list of records in the record array that kicked off the request. |
| |
| If your primary array contains secondary (embedded) records of the same type, |
| you cannot place these into the primary array `posts`. Instead, place the |
| secondary items into an underscore prefixed property `_posts`, which will |
| push these items into the store and will not affect the resulting query. |
| |
| @method extractArray |
| @param {DS.Store} store |
| @param {subclass of DS.Model} primaryType |
| @param {Object} payload |
| @return {Array} The primary array that was returned in response |
| to the original query. |
| */ |
| extractArray: function(store, primaryType, payload) { |
| payload = this.normalizePayload(payload); |
| |
| var primaryTypeName = primaryType.typeKey, |
| primaryArray; |
| |
| for (var prop in payload) { |
| var typeKey = prop, |
| forcedSecondary = false; |
| |
| if (prop.charAt(0) === '_') { |
| forcedSecondary = true; |
| typeKey = prop.substr(1); |
| } |
| |
| var typeName = this.typeForRoot(typeKey), |
| type = store.modelFor(typeName), |
| typeSerializer = store.serializerFor(type), |
| isPrimary = (!forcedSecondary && (type.typeKey === primaryTypeName)); |
| |
| /*jshint loopfunc:true*/ |
| var normalizedArray = map.call(payload[prop], function(hash) { |
| return typeSerializer.normalize(type, hash, prop); |
| }, this); |
| |
| if (isPrimary) { |
| primaryArray = normalizedArray; |
| } else { |
| store.pushMany(typeName, normalizedArray); |
| } |
| } |
| |
| return primaryArray; |
| }, |
| |
| /** |
| This method allows you to push a payload containing top-level |
| collections of records organized per type. |
| |
| ```js |
| { |
| "posts": [{ |
| "id": "1", |
| "title": "Rails is omakase", |
| "author", "1", |
| "comments": [ "1" ] |
| }], |
| "comments": [{ |
| "id": "1", |
| "body": "FIRST" |
| }], |
| "users": [{ |
| "id": "1", |
| "name": "@d2h" |
| }] |
| } |
| ``` |
| |
| It will first normalize the payload, so you can use this to push |
| in data streaming in from your server structured the same way |
| that fetches and saves are structured. |
| |
| @method pushPayload |
| @param {DS.Store} store |
| @param {Object} payload |
| */ |
| pushPayload: function(store, payload) { |
| payload = this.normalizePayload(payload); |
| |
| for (var prop in payload) { |
| var typeName = this.typeForRoot(prop), |
| type = store.modelFor(typeName), |
| typeSerializer = store.serializerFor(type); |
| |
| /*jshint loopfunc:true*/ |
| var normalizedArray = map.call(Ember.makeArray(payload[prop]), function(hash) { |
| return typeSerializer.normalize(type, hash, prop); |
| }, this); |
| |
| store.pushMany(typeName, normalizedArray); |
| } |
| }, |
| |
| /** |
| This method is used to convert each JSON root key in the payload |
| into a typeKey that it can use to look up the appropriate model for |
| that part of the payload. By default the typeKey for a model is its |
| name in camelCase, so if your JSON root key is 'fast-car' you would |
| use typeForRoot to convert it to 'fastCar' so that Ember Data finds |
| the `FastCar` model. |
| |
| If you diverge from this norm you should also consider changes to |
| store._normalizeTypeKey as well. |
| |
| For example, your server may return prefixed root keys like so: |
| |
| ```js |
| { |
| "response-fast-car": { |
| "id": "1", |
| "name": "corvette" |
| } |
| } |
| ``` |
| |
| In order for Ember Data to know that the model corresponding to |
| the 'response-fast-car' hash is `FastCar` (typeKey: 'fastCar'), |
| you can override typeForRoot to convert 'response-fast-car' to |
| 'fastCar' like so: |
| |
| ```js |
| App.ApplicationSerializer = DS.RESTSerializer.extend({ |
| typeForRoot: function(root) { |
| // 'response-fast-car' should become 'fast-car' |
| var subRoot = root.substring(9); |
| |
| // _super normalizes 'fast-car' to 'fastCar' |
| return this._super(subRoot); |
| } |
| }); |
| ``` |
| |
| @method typeForRoot |
| @param {String} key |
| @return {String} the model's typeKey |
| */ |
| typeForRoot: function(key) { |
| return camelize(singularize(key)); |
| }, |
| |
| // 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: |
| |
| ```js |
| App.Comment = DS.Model.extend({ |
| title: DS.attr(), |
| body: DS.attr(), |
| |
| author: DS.belongsTo('user') |
| }); |
| ``` |
| |
| The default serialization would create a JSON object like: |
| |
| ```js |
| { |
| "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. |
| |
| ```js |
| App.PostSerializer = DS.RESTSerializer.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. |
| |
| ```js |
| App.ApplicationSerializer = DS.RESTSerializer.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: |
| |
| ```js |
| { |
| "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. |
| |
| ```js |
| App.PostSerializer = DS.RESTSerializer.extend({ |
| serialize: function(record, options) { |
| var json = this._super(record, options); |
| |
| json.subject = json.title; |
| delete json.title; |
| |
| return json; |
| } |
| }); |
| ``` |
| |
| @method serialize |
| @param record |
| @param options |
| */ |
| serialize: function(record, options) { |
| return this._super.apply(this, arguments); |
| }, |
| |
| /** |
| You can use this method to customize the root keys serialized into the JSON. |
| By default the REST Serializer sends the typeKey of a model, whih is a camelized |
| version of the name. |
| |
| For example, your server may expect underscored root objects. |
| |
| ```js |
| App.ApplicationSerializer = DS.RESTSerializer.extend({ |
| serializeIntoHash: function(data, type, record, options) { |
| var root = Ember.String.decamelize(type.typeKey); |
| data[root] = this.serialize(record, options); |
| } |
| }); |
| ``` |
| |
| @method serializeIntoHash |
| @param {Object} hash |
| @param {subclass of DS.Model} type |
| @param {DS.Model} record |
| @param {Object} options |
| */ |
| serializeIntoHash: function(hash, type, record, options) { |
| hash[type.typeKey] = this.serialize(record, options); |
| }, |
| |
| /** |
| You can use this method to customize how polymorphic objects are serialized. |
| By default the JSON Serializer creates the key by appending `Type` to |
| the attribute and value from the model's camelcased model name. |
| |
| @method serializePolymorphicType |
| @param {DS.Model} record |
| @param {Object} json |
| @param {Object} relationship |
| */ |
| 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; |
| } |
| }); |
| |
| __exports__["default"] = RESTSerializer; |
| }); |
| define("ember-data/lib/setup-container", |
| ["./initializers/store","./initializers/transforms","./initializers/store_injections","./initializers/data_adapter","../../../activemodel-adapter/lib/setup-container","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { |
| "use strict"; |
| var initializeStore = __dependency1__["default"]; |
| var initializeTransforms = __dependency2__["default"]; |
| var initializeStoreInjections = __dependency3__["default"]; |
| var initializeDataAdapter = __dependency4__["default"]; |
| var setupActiveModelContainer = __dependency5__["default"]; |
| |
| __exports__["default"] = function setupContainer(container, application){ |
| // application is not a required argument. This ensures |
| // testing setups can setup a container without booting an |
| // entire ember application. |
| |
| initializeDataAdapter(container, application); |
| initializeTransforms(container, application); |
| initializeStoreInjections(container, application); |
| initializeStore(container, application); |
| setupActiveModelContainer(container, application); |
| }; |
| }); |
| define("ember-data/lib/system/adapter", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var get = Ember.get, set = Ember.set; |
| var map = Ember.ArrayPolyfills.map; |
| |
| var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; |
| |
| /** |
| A `DS.InvalidError` is used by an adapter to signal the external API |
| was unable to process a request because the content was not |
| semantically correct or meaningful per the API. Usually this means a |
| record failed some form of server side validation. When a promise |
| from an adapter is rejected with a `DS.InvalidError` the record will |
| transition to the `invalid` state and the errors will be set to the |
| `errors` property on the record. |
| |
| Example |
| |
| ```javascript |
| App.ApplicationAdapter = DS.RESTAdapter.extend({ |
| ajaxError: function(jqXHR) { |
| var error = this._super(jqXHR); |
| |
| if (jqXHR && jqXHR.status === 422) { |
| var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"]; |
| return new DS.InvalidError(jsonErrors); |
| } else { |
| return error; |
| } |
| } |
| }); |
| ``` |
| |
| The `DS.InvalidError` must be constructed with a single object whose |
| keys are the invalid model properties, and whose values are the |
| corresponding error messages. For example: |
| |
| ```javascript |
| return new DS.InvalidError({ |
| length: 'Must be less than 15', |
| name: 'Must not be blank |
| }); |
| ``` |
| |
| @class InvalidError |
| @namespace DS |
| */ |
| var InvalidError = function(errors) { |
| var tmp = Error.prototype.constructor.call(this, "The backend rejected the commit because it was invalid: " + Ember.inspect(errors)); |
| this.errors = errors; |
| |
| for (var i=0, l=errorProps.length; i<l; i++) { |
| this[errorProps[i]] = tmp[errorProps[i]]; |
| } |
| }; |
| InvalidError.prototype = Ember.create(Error.prototype); |
| |
| /** |
| An adapter is an object that receives requests from a store and |
| translates them into the appropriate action to take against your |
| persistence layer. The persistence layer is usually an HTTP API, but |
| may be anything, such as the browser's local storage. Typically the |
| adapter is not invoked directly instead its functionality is accessed |
| through the `store`. |
| |
| ### Creating an Adapter |
| |
| Create a new subclass of `DS.Adapter`, then assign |
| it to the `ApplicationAdapter` property of the application. |
| |
| ```javascript |
| var MyAdapter = DS.Adapter.extend({ |
| // ...your code here |
| }); |
| |
| App.ApplicationAdapter = MyAdapter; |
| ``` |
| |
| Model-specific adapters can be created by assigning your adapter |
| class to the `ModelName` + `Adapter` property of the application. |
| |
| ```javascript |
| var MyPostAdapter = DS.Adapter.extend({ |
| // ...Post-specific adapter code goes here |
| }); |
| |
| App.PostAdapter = MyPostAdapter; |
| ``` |
| |
| `DS.Adapter` is an abstract base class that you should override in your |
| application to customize it for your backend. The minimum set of methods |
| that you should implement is: |
| |
| * `find()` |
| * `createRecord()` |
| * `updateRecord()` |
| * `deleteRecord()` |
| * `findAll()` |
| * `findQuery()` |
| |
| To improve the network performance of your application, you can optimize |
| your adapter by overriding these lower-level methods: |
| |
| * `findMany()` |
| |
| |
| For an example implementation, see `DS.RESTAdapter`, the |
| included REST adapter. |
| |
| @class Adapter |
| @namespace DS |
| @extends Ember.Object |
| */ |
| |
| var Adapter = Ember.Object.extend({ |
| |
| /** |
| If you would like your adapter to use a custom serializer you can |
| set the `defaultSerializer` property to be the name of the custom |
| serializer. |
| |
| Note the `defaultSerializer` serializer has a lower priority then |
| a model specific serializer (i.e. `PostSerializer`) or the |
| `application` serializer. |
| |
| ```javascript |
| var DjangoAdapter = DS.Adapter.extend({ |
| defaultSerializer: 'django' |
| }); |
| ``` |
| |
| @property defaultSerializer |
| @type {String} |
| */ |
| |
| /** |
| The `find()` method is invoked when the store is asked for a record that |
| has not previously been loaded. In response to `find()` being called, you |
| should query your persistence layer for a record with the given ID. Once |
| found, you can asynchronously call the store's `push()` method to push |
| the record into the store. |
| |
| Here is an example `find` implementation: |
| |
| ```javascript |
| App.ApplicationAdapter = DS.Adapter.extend({ |
| find: function(store, type, id) { |
| var url = [type, id].join('/'); |
| |
| return new Ember.RSVP.Promise(function(resolve, reject) { |
| jQuery.getJSON(url).then(function(data) { |
| Ember.run(null, resolve, data); |
| }, function(jqXHR) { |
| jqXHR.then = null; // tame jQuery's ill mannered promises |
| Ember.run(null, reject, jqXHR); |
| }); |
| }); |
| } |
| }); |
| ``` |
| |
| @method find |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {String} id |
| @return {Promise} promise |
| */ |
| find: Ember.required(Function), |
| |
| /** |
| The `findAll()` method is called when you call `find` on the store |
| without an ID (i.e. `store.find('post')`). |
| |
| Example |
| |
| ```javascript |
| App.ApplicationAdapter = DS.Adapter.extend({ |
| findAll: function(store, type, sinceToken) { |
| var url = type; |
| var query = { since: sinceToken }; |
| return new Ember.RSVP.Promise(function(resolve, reject) { |
| jQuery.getJSON(url, query).then(function(data) { |
| Ember.run(null, resolve, data); |
| }, function(jqXHR) { |
| jqXHR.then = null; // tame jQuery's ill mannered promises |
| Ember.run(null, reject, jqXHR); |
| }); |
| }); |
| } |
| }); |
| ``` |
| |
| @private |
| @method findAll |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {String} sinceToken |
| @return {Promise} promise |
| */ |
| findAll: null, |
| |
| /** |
| This method is called when you call `find` on the store with a |
| query object as the second parameter (i.e. `store.find('person', { |
| page: 1 })`). |
| |
| Example |
| |
| ```javascript |
| App.ApplicationAdapter = DS.Adapter.extend({ |
| findQuery: function(store, type, query) { |
| var url = type; |
| return new Ember.RSVP.Promise(function(resolve, reject) { |
| jQuery.getJSON(url, query).then(function(data) { |
| Ember.run(null, resolve, data); |
| }, function(jqXHR) { |
| jqXHR.then = null; // tame jQuery's ill mannered promises |
| Ember.run(null, reject, jqXHR); |
| }); |
| }); |
| } |
| }); |
| ``` |
| |
| @private |
| @method findQuery |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type |
| @param {Object} query |
| @param {DS.AdapterPopulatedRecordArray} recordArray |
| @return {Promise} promise |
| */ |
| findQuery: null, |
| |
| /** |
| If the globally unique IDs for your records should be generated on the client, |
| implement the `generateIdForRecord()` method. This method will be invoked |
| each time you create a new record, and the value returned from it will be |
| assigned to the record's `primaryKey`. |
| |
| Most traditional REST-like HTTP APIs will not use this method. Instead, the ID |
| of the record will be set by the server, and your adapter will update the store |
| with the new ID when it calls `didCreateRecord()`. Only implement this method if |
| you intend to generate record IDs on the client-side. |
| |
| The `generateIdForRecord()` method will be invoked with the requesting store as |
| the first parameter and the newly created record as the second parameter: |
| |
| ```javascript |
| generateIdForRecord: function(store, record) { |
| var uuid = App.generateUUIDWithStatisticallyLowOddsOfCollision(); |
| return uuid; |
| } |
| ``` |
| |
| @method generateIdForRecord |
| @param {DS.Store} store |
| @param {DS.Model} record |
| @return {String|Number} id |
| */ |
| generateIdForRecord: null, |
| |
| /** |
| Proxies to the serializer's `serialize` method. |
| |
| Example |
| |
| ```javascript |
| App.ApplicationAdapter = DS.Adapter.extend({ |
| createRecord: function(store, type, record) { |
| var data = this.serialize(record, { includeId: true }); |
| var url = type; |
| |
| // ... |
| } |
| }); |
| ``` |
| |
| @method serialize |
| @param {DS.Model} record |
| @param {Object} options |
| @return {Object} serialized record |
| */ |
| serialize: function(record, options) { |
| return get(record, 'store').serializerFor(record.constructor.typeKey).serialize(record, options); |
| }, |
| |
| /** |
| Implement this method in a subclass to handle the creation of |
| new records. |
| |
| Serializes the record and send it to the server. |
| |
| Example |
| |
| ```javascript |
| App.ApplicationAdapter = DS.Adapter.extend({ |
| createRecord: function(store, type, record) { |
| var data = this.serialize(record, { includeId: true }); |
| var url = type; |
| |
| return new Ember.RSVP.Promise(function(resolve, reject) { |
| jQuery.ajax({ |
| type: 'POST', |
| url: url, |
| dataType: 'json', |
| data: data |
| }).then(function(data) { |
| Ember.run(null, resolve, data); |
| }, function(jqXHR) { |
| jqXHR.then = null; // tame jQuery's ill mannered promises |
| Ember.run(null, reject, jqXHR); |
| }); |
| }); |
| } |
| }); |
| ``` |
| |
| @method createRecord |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type the DS.Model class of the record |
| @param {DS.Model} record |
| @return {Promise} promise |
| */ |
| createRecord: Ember.required(Function), |
| |
| /** |
| Implement this method in a subclass to handle the updating of |
| a record. |
| |
| Serializes the record update and send it to the server. |
| |
| Example |
| |
| ```javascript |
| App.ApplicationAdapter = DS.Adapter.extend({ |
| updateRecord: function(store, type, record) { |
| var data = this.serialize(record, { includeId: true }); |
| var id = record.get('id'); |
| var url = [type, id].join('/'); |
| |
| return new Ember.RSVP.Promise(function(resolve, reject) { |
| jQuery.ajax({ |
| type: 'PUT', |
| url: url, |
| dataType: 'json', |
| data: data |
| }).then(function(data) { |
| Ember.run(null, resolve, data); |
| }, function(jqXHR) { |
| jqXHR.then = null; // tame jQuery's ill mannered promises |
| Ember.run(null, reject, jqXHR); |
| }); |
| }); |
| } |
| }); |
| ``` |
| |
| @method updateRecord |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type the DS.Model class of the record |
| @param {DS.Model} record |
| @return {Promise} promise |
| */ |
| updateRecord: Ember.required(Function), |
| |
| /** |
| Implement this method in a subclass to handle the deletion of |
| a record. |
| |
| Sends a delete request for the record to the server. |
| |
| Example |
| |
| ```javascript |
| App.ApplicationAdapter = DS.Adapter.extend({ |
| deleteRecord: function(store, type, record) { |
| var data = this.serialize(record, { includeId: true }); |
| var id = record.get('id'); |
| var url = [type, id].join('/'); |
| |
| return new Ember.RSVP.Promise(function(resolve, reject) { |
| jQuery.ajax({ |
| type: 'DELETE', |
| url: url, |
| dataType: 'json', |
| data: data |
| }).then(function(data) { |
| Ember.run(null, resolve, data); |
| }, function(jqXHR) { |
| jqXHR.then = null; // tame jQuery's ill mannered promises |
| Ember.run(null, reject, jqXHR); |
| }); |
| }); |
| } |
| }); |
| ``` |
| |
| @method deleteRecord |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type the DS.Model class of the record |
| @param {DS.Model} record |
| @return {Promise} promise |
| */ |
| deleteRecord: Ember.required(Function), |
| |
| /** |
| Find multiple records at once. |
| |
| By default, it loops over the provided ids and calls `find` on each. |
| May be overwritten to improve performance and reduce the number of |
| server requests. |
| |
| Example |
| |
| ```javascript |
| App.ApplicationAdapter = DS.Adapter.extend({ |
| findMany: function(store, type, ids) { |
| var url = type; |
| return new Ember.RSVP.Promise(function(resolve, reject) { |
| jQuery.getJSON(url, {ids: ids}).then(function(data) { |
| Ember.run(null, resolve, data); |
| }, function(jqXHR) { |
| jqXHR.then = null; // tame jQuery's ill mannered promises |
| Ember.run(null, reject, jqXHR); |
| }); |
| }); |
| } |
| }); |
| ``` |
| |
| @method findMany |
| @param {DS.Store} store |
| @param {subclass of DS.Model} type the DS.Model class of the records |
| @param {Array} ids |
| @return {Promise} promise |
| */ |
| findMany: function(store, type, ids) { |
| var promises = map.call(ids, function(id) { |
| return this.find(store, type, id); |
| }, this); |
| |
| return Ember.RSVP.all(promises); |
| } |
| }); |
| |
| __exports__.InvalidError = InvalidError; |
| __exports__.Adapter = Adapter; |
| __exports__["default"] = Adapter; |
| }); |
| define("ember-data/lib/system/changes", |
| ["./changes/relationship_change","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var RelationshipChange = __dependency1__.RelationshipChange; |
| var RelationshipChangeAdd = __dependency1__.RelationshipChangeAdd; |
| var RelationshipChangeRemove = __dependency1__.RelationshipChangeRemove; |
| var OneToManyChange = __dependency1__.OneToManyChange; |
| var ManyToNoneChange = __dependency1__.ManyToNoneChange; |
| var OneToOneChange = __dependency1__.OneToOneChange; |
| var ManyToManyChange = __dependency1__.ManyToManyChange; |
| |
| __exports__.RelationshipChange = RelationshipChange; |
| __exports__.RelationshipChangeAdd = RelationshipChangeAdd; |
| __exports__.RelationshipChangeRemove = RelationshipChangeRemove; |
| __exports__.OneToManyChange = OneToManyChange; |
| __exports__.ManyToNoneChange = ManyToNoneChange; |
| __exports__.OneToOneChange = OneToOneChange; |
| __exports__.ManyToManyChange = ManyToManyChange; |
| }); |
| define("ember-data/lib/system/changes/relationship_change", |
| ["../model","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var Model = __dependency1__.Model; |
| |
| var get = Ember.get, set = Ember.set; |
| var forEach = Ember.EnumerableUtils.forEach; |
| |
| /** |
| @class RelationshipChange |
| @namespace DS |
| @private |
| @constructor |
| */ |
| var 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 |
| */ |
| var RelationshipChangeAdd = function(options){ |
| RelationshipChange.call(this, options); |
| }; |
| |
| /** |
| @class RelationshipChangeRemove |
| @namespace DS |
| @private |
| @constructor |
| */ |
| var RelationshipChangeRemove = function(options){ |
| RelationshipChange.call(this, options); |
| }; |
| |
| RelationshipChange.create = function(options) { |
| return new RelationshipChange(options); |
| }; |
| |
| RelationshipChangeAdd.create = function(options) { |
| return new RelationshipChangeAdd(options); |
| }; |
| |
| RelationshipChangeRemove.create = function(options) { |
| return new RelationshipChangeRemove(options); |
| }; |
| |
| var OneToManyChange = {}; |
| var OneToNoneChange = {}; |
| var ManyToNoneChange = {}; |
| var OneToOneChange = {}; |
| var ManyToManyChange = {}; |
| |
| RelationshipChange._createChange = function(options){ |
| if(options.changeType === "add"){ |
| return RelationshipChangeAdd.create(options); |
| } |
| if(options.changeType === "remove"){ |
| return RelationshipChangeRemove.create(options); |
| } |
| }; |
| |
| |
| 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"; |
| } |
| } |
| |
| }; |
| |
| 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 = RelationshipChange.determineRelationshipType(firstRecordType, options); |
| if (changeType === "oneToMany"){ |
| return OneToManyChange.createChange(firstRecord, secondRecord, store, options); |
| } |
| else if (changeType === "manyToOne"){ |
| return OneToManyChange.createChange(secondRecord, firstRecord, store, options); |
| } |
| else if (changeType === "oneToNone"){ |
| return OneToNoneChange.createChange(firstRecord, secondRecord, store, options); |
| } |
| else if (changeType === "manyToNone"){ |
| return ManyToNoneChange.createChange(firstRecord, secondRecord, store, options); |
| } |
| else if (changeType === "oneToOne"){ |
| return OneToOneChange.createChange(firstRecord, secondRecord, store, options); |
| } |
| else if (changeType === "manyToMany"){ |
| return ManyToManyChange.createChange(firstRecord, secondRecord, store, options); |
| } |
| }; |
| |
| OneToNoneChange.createChange = function(childRecord, parentRecord, store, options) { |
| var key = options.key; |
| var change = 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; |
| }; |
| |
| ManyToNoneChange.createChange = function(childRecord, parentRecord, store, options) { |
| var key = options.key; |
| var change = 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; |
| }; |
| |
| |
| 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 = 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; |
| }; |
| |
| 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 = 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; |
| }; |
| |
| OneToOneChange.maintainInvariant = function(options, store, childRecord, key){ |
| if (options.changeType === "add" && store.recordIsMaterialized(childRecord)) { |
| var oldParent = get(childRecord, key); |
| if (oldParent){ |
| var correspondingChange = 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(); |
| } |
| } |
| }; |
| |
| 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; |
| 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 = 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; |
| }; |
| |
| |
| OneToManyChange.maintainInvariant = function(options, store, childRecord, key){ |
| if (options.changeType === "add" && childRecord) { |
| var oldParent = get(childRecord, key); |
| if (oldParent){ |
| var correspondingChange = 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 |
| */ |
| 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(); |
| } |
| }); |
| } |
| }; |
| |
| RelationshipChangeAdd.prototype = Ember.create(RelationshipChange.create({})); |
| RelationshipChangeRemove.prototype = Ember.create(RelationshipChange.create({})); |
| |
| // the object is a value, and not a promise |
| function isValue(object) { |
| return typeof object === 'object' && (!object.then || typeof object.then !== 'function'); |
| } |
| |
| RelationshipChangeAdd.prototype.changeType = "add"; |
| 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 Model && firstRecord instanceof 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 Model && secondRecord instanceof 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(); |
| }; |
| |
| RelationshipChangeRemove.prototype.changeType = "remove"; |
| 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 Model && firstRecord instanceof 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 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(); |
| }; |
| |
| __exports__.RelationshipChange = RelationshipChange; |
| __exports__.RelationshipChangeAdd = RelationshipChangeAdd; |
| __exports__.RelationshipChangeRemove = RelationshipChangeRemove; |
| __exports__.OneToManyChange = OneToManyChange; |
| __exports__.ManyToNoneChange = ManyToNoneChange; |
| __exports__.OneToOneChange = OneToOneChange; |
| __exports__.ManyToManyChange = ManyToManyChange; |
| }); |
| define("ember-data/lib/system/container_proxy", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| /** |
| This is used internally to enable deprecation of container paths and provide |
| a decent message to the user indicating how to fix the issue. |
| |
| @class ContainerProxy |
| @namespace DS |
| @private |
| */ |
| var ContainerProxy = function (container){ |
| this.container = container; |
| }; |
| |
| ContainerProxy.prototype.aliasedFactory = function(path, preLookup) { |
| var _this = this; |
| |
| return {create: function(){ |
| if (preLookup) { preLookup(); } |
| |
| return _this.container.lookup(path); |
| }}; |
| }; |
| |
| ContainerProxy.prototype.registerAlias = function(source, dest, preLookup) { |
| var factory = this.aliasedFactory(dest, preLookup); |
| |
| return this.container.register(source, factory); |
| }; |
| |
| ContainerProxy.prototype.registerDeprecation = function(deprecated, valid) { |
| var preLookupCallback = function(){ |
| Ember.deprecate("You tried to look up '" + deprecated + "', " + |
| "but this has been deprecated in favor of '" + valid + "'.", false); |
| }; |
| |
| return this.registerAlias(deprecated, valid, preLookupCallback); |
| }; |
| |
| ContainerProxy.prototype.registerDeprecations = function(proxyPairs) { |
| for (var i = proxyPairs.length; i > 0; i--) { |
| var proxyPair = proxyPairs[i - 1], |
| deprecated = proxyPair['deprecated'], |
| valid = proxyPair['valid']; |
| |
| this.registerDeprecation(deprecated, valid); |
| } |
| }; |
| |
| __exports__["default"] = ContainerProxy; |
| }); |
| define("ember-data/lib/system/debug", |
| ["./debug/debug_info","./debug/debug_adapter","exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var DebugAdapter = __dependency2__["default"]; |
| |
| __exports__["default"] = DebugAdapter; |
| }); |
| define("ember-data/lib/system/debug/debug_adapter", |
| ["../model","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| var Model = __dependency1__.Model; |
| var get = Ember.get, capitalize = Ember.String.capitalize, underscore = Ember.String.underscore; |
| |
| /** |
| Extend `Ember.DataAdapter` with ED specific code. |
| |
| @class DebugAdapter |
| @namespace DS |
| @extends Ember.DataAdapter |
| @private |
| */ |
| var DebugAdapter = Ember.DataAdapter.extend({ |
| getFilters: function() { |
| return [ |
| { name: 'isNew', desc: 'New' }, |
| { name: 'isModified', desc: 'Modified' }, |
| { name: 'isClean', desc: 'Clean' } |
| ]; |
| }, |
| |
| detect: function(klass) { |
| return klass !== Model && 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; |
| } |
| |
| }); |
| |
| __exports__["default"] = DebugAdapter; |
| }); |
| define("ember-data/lib/system/debug/debug_info", |
| ["../model","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var Model = __dependency1__.Model; |
| |
| Model.reopen({ |
| |
| /** |
| Provides info about the model for debugging purposes |
| by grouping the properties into more semantic groups. |
| |
| Meant to be used by debugging tools such as the Chrome Ember Extension. |
| |
| - Groups all attributes in "Attributes" group. |
| - Groups all belongsTo relationships in "Belongs To" group. |
| - Groups all hasMany relationships in "Has Many" group. |
| - Groups all flags in "Flags" group. |
| - Flags relationship CPs as expensive properties. |
| |
| @method _debugInfo |
| @for DS.Model |
| @private |
| */ |
| _debugInfo: function() { |
| var attributes = ['id'], |
| relationships = { belongsTo: [], hasMany: [] }, |
| expensiveProperties = []; |
| |
| this.eachAttribute(function(name, meta) { |
| attributes.push(name); |
| }, this); |
| |
| this.eachRelationship(function(name, relationship) { |
| relationships[relationship.kind].push(name); |
| expensiveProperties.push(name); |
| }); |
| |
| var groups = [ |
| { |
| name: 'Attributes', |
| properties: attributes, |
| expand: true |
| }, |
| { |
| name: 'Belongs To', |
| properties: relationships.belongsTo, |
| expand: true |
| }, |
| { |
| name: 'Has Many', |
| properties: relationships.hasMany, |
| expand: true |
| }, |
| { |
| name: 'Flags', |
| properties: ['isLoaded', 'isDirty', 'isSaving', 'isDeleted', 'isError', 'isNew', 'isValid'] |
| } |
| ]; |
| |
| return { |
| propertyInfo: { |
| // include all other mixins / properties (not just the grouped ones) |
| includeOtherProperties: true, |
| groups: groups, |
| // don't pre-calculate unless cached |
| expensiveProperties: expensiveProperties |
| } |
| }; |
| } |
| }); |
| |
| __exports__["default"] = Model; |
| }); |
| define("ember-data/lib/system/model", |
| ["./model/model","./model/attributes","./model/states","./model/errors","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var Model = __dependency1__["default"]; |
| var attr = __dependency2__["default"]; |
| var RootState = __dependency3__["default"]; |
| var Errors = __dependency4__["default"]; |
| |
| __exports__.Model = Model; |
| __exports__.RootState = RootState; |
| __exports__.attr = attr; |
| __exports__.Errors = Errors; |
| }); |
| define("ember-data/lib/system/model/attributes", |
| ["./model","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var Model = __dependency1__["default"]; |
| |
| /** |
| @module ember-data |
| */ |
| |
| var get = Ember.get; |
| |
| /** |
| @class Model |
| @namespace DS |
| */ |
| Model.reopenClass({ |
| /** |
| A map whose keys are the attributes of the model (properties |
| described by DS.attr) and whose values are the meta object for the |
| property. |
| |
| Example |
| |
| ```javascript |
| |
| App.Person = DS.Model.extend({ |
| firstName: attr('string'), |
| lastName: attr('string'), |
| birthday: attr('date') |
| }); |
| |
| var attributes = Ember.get(App.Person, 'attributes') |
| |
| attributes.forEach(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"} |
| ``` |
| |
| @property attributes |
| @static |
| @type {Ember.Map} |
| @readOnly |
| */ |
| attributes: Ember.computed(function() { |
| var map = Ember.Map.create(); |
| |
| this.eachComputedProperty(function(name, meta) { |
| if (meta.isAttribute) { |
| Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('<type>')` from " + this.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); |
| }); |
| } |
| }); |
| |
| |
| Model.reopen({ |
| eachAttribute: function(callback, binding) { |
| this.constructor.eachAttribute(callback, binding); |
| } |
| }); |
| |
| function getDefaultValue(record, options, key) { |
| if (typeof options.defaultValue === "function") { |
| return options.defaultValue.apply(null, arguments); |
| } 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](/api/data/classes/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](/api/data/classes/DS.Transform.html). |
| |
| Note that you cannot use `attr` to define an attribute of `id`. |
| |
| `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} |
| */ |
| |
| function attr(type, options) { |
| options = options || {}; |
| |
| var meta = { |
| type: type, |
| isAttribute: true, |
| options: options |
| }; |
| |
| return Ember.computed('data', 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('<type>')` from " + this.constructor.toString(), key !== 'id'); |
| var oldValue = getValue(this, key); |
| |
| if (value !== oldValue) { |
| // Add the new value to the changed attributes hash; it will get deleted by |
| // the 'didSetProperty' handler if it is no different from the original value |
| this._attributes[key] = value; |
| |
| this.send('didSetProperty', { |
| name: key, |
| oldValue: oldValue, |
| originalValue: this._data[key], |
| value: 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. |
| }).meta(meta); |
| } |
| |
| __exports__["default"] = attr; |
| }); |
| define("ember-data/lib/system/model/errors", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| var get = Ember.get, isEmpty = Ember.isEmpty; |
| var map = Ember.EnumerableUtils.map; |
| |
| /** |
| @module ember-data |
| */ |
| |
| /** |
| Holds validation errors for a given record organized by attribute names. |
| |
| Every DS.Model has an `errors` property that is an instance of |
| `DS.Errors`. This can be used to display validation error |
| messages returned from the server when a `record.save()` rejects. |
| |
| For Example, if you had an `User` model that looked like this: |
| |
| ```javascript |
| App.User = DS.Model.extend({ |
| username: attr('string'), |
| email: attr('string') |
| }); |
| ``` |
| And you attempted to save a record that did not validate on the backend. |
| |
| ```javascript |
| var user = store.createRecord('user', { |
| username: 'tomster', |
| email: 'invalidEmail' |
| }); |
| user.save(); |
| ``` |
| |
| Your backend data store might return a response that looks like |
| this. This response will be used to populate the error object. |
| |
| ```javascript |
| { |
| "errors": { |
| "username": ["This username is already taken!"], |
| "email": ["Doesn't look like a valid email."] |
| } |
| } |
| ``` |
| |
| Errors can be displayed to the user by accessing their property name |
| or using the `messages` property to get an array of all errors. |
| |
| ```handlebars |
| {{#each errors.messages}} |
| <div class="error"> |
| {{message}} |
| </div> |
| {{/each}} |
| |
| <label>Username: {{input value=username}} </label> |
| {{#each errors.username}} |
| <div class="error"> |
| {{message}} |
| </div> |
| {{/each}} |
| |
| <label>Email: {{input value=email}} </label> |
| {{#each errors.email}} |
| <div class="error"> |
| {{message}} |
| </div> |
| {{/each}} |
| ``` |
| |
| @class Errors |
| @namespace DS |
| @extends Ember.Object |
| @uses Ember.Enumerable |
| @uses Ember.Evented |
| */ |
| var 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 |
| |
| ```javascript |
| var user = store.createRecord('user', { |
| username: 'tomster', |
| email: 'invalidEmail' |
| }); |
| user.save().catch(function(){ |
| user.get('errors').errorsFor('email'); // ["Doesn't look like a valid email."] |
| }); |
| ``` |
| |
| @method errorsFor |
| @param {String} attribute |
| @return {Array} |
| */ |
| errorsFor: function(attribute) { |
| return get(this, 'errorsByAttributeName').get(attribute); |
| }, |
| |
| /** |
| An array containing all of the error messages for this |
| record. This is useful for displaying all errors to the user. |
| |
| ```handlebars |
| {{#each errors.messages}} |
| <div class="error"> |
| {{message}} |
| </div> |
| {{/each}} |
| ``` |
| |
| @property messages |
| @type {Array} |
| */ |
| 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. |
| |
| Example: |
| |
| ```javascript |
| if (!user.get('username') { |
| user.get('errors').add('username', 'This field is required'); |
| } |
| ``` |
| |
| @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 map(Ember.makeArray(messages), 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. |
| |
| Example: |
| |
| ```javascript |
| App.User = DS.Model.extend({ |
| email: DS.attr('string'), |
| twoFactorAuth: DS.attr('boolean'), |
| phone: DS.attr('string') |
| }); |
| |
| App.UserEditRoute = Ember.Route.extend({ |
| actions: { |
| save: function(user) { |
| if (!user.get('twoFactorAuth')) { |
| user.get('errors').remove('phone'); |
| } |
| user.save(); |
| } |
| } |
| }); |
| ``` |
| |
| @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. |
| |
| Example: |
| |
| ```javascript |
| App.UserEditRoute = Ember.Route.extend({ |
| actions: { |
| retrySave: function(user) { |
| user.get('errors').clear(); |
| user.save(); |
| } |
| } |
| }); |
| ``` |
| |
| @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. |
| |
| ```javascript |
| App.UserEditRoute = Ember.Route.extend({ |
| actions: { |
| save: function(user) { |
| if (user.get('errors').has('email')) { |
| return alert('Please update your email before attempting to save.'); |
| } |
| user.save(); |
| } |
| } |
| }); |
| ``` |
| |
| @method has |
| @param {String} attribute |
| @return {Boolean} true if there some errors on given attribute |
| */ |
| has: function(attribute) { |
| return !isEmpty(this.errorsFor(attribute)); |
| } |
| }); |
| |
| __exports__["default"] = Errors; |
| }); |
| define("ember-data/lib/system/model/model", |
| ["./states","./errors","../store","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var RootState = __dependency1__["default"]; |
| var Errors = __dependency2__["default"]; |
| var PromiseObject = __dependency3__.PromiseObject; |
| /** |
| @module ember-data |
| */ |
| |
| var get = Ember.get, set = Ember.set, |
| merge = Ember.merge, |
| Promise = Ember.RSVP.Promise; |
| |
| var JSONSerializer; |
| 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 |
| */ |
| var Model = Ember.Object.extend(Ember.Evented, { |
| _recordArrays: undefined, |
| _relationships: undefined, |
| _loadingRecordArrays: undefined, |
| /** |
| 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('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('model'); |
| record.get('isDirty'); // true |
| |
| store.find('model', 1).then(function(model) { |
| model.get('isDirty'); // false |
| model.set('foo', 'some value'); |
| model.get('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('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('model'); |
| record.get('isDeleted'); // false |
| record.deleteRecord(); |
| |
| // Locally deleted |
| record.get('isDeleted'); // true |
| record.get('isDirty'); // true |
| record.get('isSaving'); // false |
| |
| // Persisting the deletion |
| var promise = record.save(); |
| record.get('isDeleted'); // true |
| record.get('isSaving'); // true |
| |
| // Deletion Persisted |
| promise.then(function() { |
| record.get('isDeleted'); // true |
| record.get('isSaving'); // false |
| record.get('isDirty'); // false |
| }); |
| ``` |
| |
| @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('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 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('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 for any reason other than a server-side |
| validation error. |
| |
| Example |
| |
| ```javascript |
| record.get('isError'); // false |
| record.set('foo', 'valid 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('model'); |
| record.get('id'); // null |
| |
| store.find('model', 1).then(function(model) { |
| model.get('id'); // '1' |
| }); |
| ``` |
| |
| @property id |
| @type {String} |
| */ |
| id: null, |
| |
| /** |
| @property currentState |
| @private |
| @type {Object} |
| */ |
| currentState: RootState.empty, |
| |
| /** |
| 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 {DS.Errors} |
| */ |
| errors: Ember.computed(function() { |
| var errors = Errors.create(); |
| |
| errors.registerHandlers(this, function() { |
| this.send('becameInvalid'); |
| }, function() { |
| this.send('becameValid'); |
| }); |
| |
| return errors; |
| }).readOnly(), |
| |
| /** |
| 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 |
| @return {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 |
| @return {Object} A JSON representation of the object. |
| */ |
| toJSON: function(options) { |
| if (!JSONSerializer) { JSONSerializer = requireModule("ember-data/lib/serializers/json_serializer")["default"]; } |
| // container is for lazy transform lookups |
| var serializer = 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; |
| }).readOnly(), |
| |
| _data: null, |
| |
| init: function() { |
| 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<l; i++) { |
| state = state[path[i]]; |
| |
| if (state.enter) { enters.push(state); } |
| if (state.setup) { setups.push(state); } |
| } |
| |
| for (i=0, l=enters.length; i<l; i++) { |
| enters[i].enter(this); |
| } |
| |
| set(this, 'currentState', state); |
| |
| for (i=0, l=setups.length; i<l; i++) { |
| setups[i].setup(this); |
| } |
| |
| this.updateRecordArraysLater(); |
| }, |
| |
| _unhandledEvent: function(state, name, context) { |
| var errorMessage = "Attempted to handle event `" + name + "` "; |
| errorMessage += "on " + String(this) + " while in state "; |
| errorMessage += state.stateName + ". "; |
| |
| if (context !== undefined) { |
| errorMessage += "Called with " + Ember.inspect(context) + "."; |
| } |
| |
| throw new Ember.Error(errorMessage); |
| }, |
| |
| withTransaction: function(fn) { |
| var transaction = get(this, 'transaction'); |
| if (transaction) { fn(transaction); } |
| }, |
| |
| /** |
| @method loadingData |
| @private |
| @param {Promise} promise |
| */ |
| loadingData: function(promise) { |
| this.send('loadingData', promise); |
| }, |
| |
| /** |
| @method loadedData |
| @private |
| */ |
| loadedData: function() { |
| this.send('loadedData'); |
| }, |
| |
| /** |
| @method notFound |
| @private |
| */ |
| notFound: function() { |
| this.send('notFound'); |
| }, |
| |
| /** |
| @method pushedData |
| @private |
| */ |
| pushedData: function() { |
| this.send('pushedData'); |
| }, |
| |
| /** |
| Marks the record as deleted but does not save it. You must call |
| `save` afterwards if you want to persist it. You might use this |
| method if you want to allow the user to still `rollback()` a |
| delete after it was made. |
| |
| Example |
| |
| ```javascript |
| App.ModelDeleteRoute = Ember.Route.extend({ |
| actions: { |
| softDelete: function() { |
| this.get('model').deleteRecord(); |
| }, |
| confirm: function() { |
| this.get('model').save(); |
| }, |
| undo: function() { |
| this.get('model').rollback(); |
| } |
| } |
| }); |
| ``` |
| |
| @method deleteRecord |
| */ |
| deleteRecord: function() { |
| this.send('deleteRecord'); |
| }, |
| |
| /** |
| Same as `deleteRecord`, but saves the record immediately. |
| |
| Example |
| |
| ```javascript |
| App.ModelDeleteRoute = Ember.Route.extend({ |
| actions: { |
| delete: function() { |
| var controller = this.controller; |
| this.get('model').destroyRecord().then(function() { |
| controller.transitionToRoute('model.index'); |
| }); |
| } |
| } |
| }); |
| ``` |
| |
| @method destroyRecord |
| @return {Promise} a promise that will be resolved when the adapter returns |
| successfully or rejected if the adapter returns with an error. |
| */ |
| destroyRecord: function() { |
| this.deleteRecord(); |
| return this.save(); |
| }, |
| |
| /** |
| @method unloadRecord |
| @private |
| */ |
| unloadRecord: function() { |
| if (this.isDestroyed) { return; } |
| |
| this.send('unloadRecord'); |
| }, |
| |
| /** |
| @method clearRelationships |
| @private |
| */ |
| clearRelationships: function() { |
| this.eachRelationship(function(name, relationship) { |
| if (relationship.kind === 'belongsTo') { |
| set(this, name, null); |
| } else if (relationship.kind === 'hasMany') { |
| var hasMany = this._relationships[name]; |
| if (hasMany) { // relationships are created lazily |
| hasMany.destroy(); |
| } |
| } |
| }, this); |
| }, |
| |
| /** |
| @method updateRecordArrays |
| @private |
| */ |
| updateRecordArrays: function() { |
| this._updatingRecordArraysLater = false; |
| get(this, 'store').dataWasUpdated(this.constructor, this); |
| }, |
| |
| /** |
| Returns an object, whose keys are changed properties, and value is |
| an [oldProp, newProp] array. |
| |
| Example |
| |
| ```javascript |
| App.Mascot = DS.Model.extend({ |
| name: attr('string') |
| }); |
| |
| var person = store.createRecord('person'); |
| person.changedAttributes(); // {} |
| person.set('name', 'Tomster'); |
| person.changedAttributes(); // {name: [undefined, 'Tomster']} |
| ``` |
| |
| @method changedAttributes |
| @return {Object} an object, whose keys are changed properties, |
| and value is an [oldProp, newProp] array. |
| */ |
| changedAttributes: function() { |
| var oldData = get(this, '_data'), |
| newData = get(this, '_attributes'), |
| diffData = {}, |
| prop; |
| |
| for (prop in newData) { |
| diffData[prop] = [oldData[prop], newData[prop]]; |
| } |
| |
| return diffData; |
| }, |
| |
| /** |
| @method adapterWillCommit |
| @private |
| */ |
| adapterWillCommit: function() { |
| this.send('willCommit'); |
| }, |
| |
| /** |
| If the adapter did not return a hash in response to a commit, |
| merge the changed attributes and relationships into the existing |
| saved data. |
| |
| @method adapterDidCommit |
| */ |
| adapterDidCommit: function(data) { |
| set(this, 'isError', false); |
| |
| if (data) { |
| this._data = data; |
| } else { |
| Ember.mixin(this._data, this._inFlightAttributes); |
| } |
| |
| this._inFlightAttributes = {}; |
| |
| this.send('didCommit'); |
| this.updateRecordArraysLater(); |
| |
| if (!data) { return; } |
| |
| this.suspendRelationshipObservers(function() { |
| this.notifyPropertyChange('data'); |
| }); |
| }, |
| |
| /** |
| @method adapterDidDirty |
| @private |
| */ |
| adapterDidDirty: function() { |
| this.send('becomeDirty'); |
| this.updateRecordArraysLater(); |
| }, |
| |
| dataDidChange: Ember.observer(function() { |
| this.reloadHasManys(); |
| }, 'data'), |
| |
| reloadHasManys: function() { |
| var relationships = get(this.constructor, 'relationshipsByName'); |
| this.updateRecordArraysLater(); |
| relationships.forEach(function(name, relationship) { |
| if (this._data.links && this._data.links[name]) { return; } |
| if (relationship.kind === 'hasMany') { |
| this.hasManyDidChange(relationship.key); |
| } |
| }, this); |
| }, |
| |
| hasManyDidChange: function(key) { |
| var hasMany = this._relationships[key]; |
| |
| if (hasMany) { |
| var records = this._data[key] || []; |
| |
| set(hasMany, 'content', Ember.A(records)); |
| set(hasMany, 'isLoaded', true); |
| hasMany.trigger('didLoad'); |
| } |
| }, |
| |
| /** |
| @method updateRecordArraysLater |
| @private |
| */ |
| updateRecordArraysLater: function() { |
| // quick hack (something like this could be pushed into run.once |
| if (this._updatingRecordArraysLater) { return; } |
| this._updatingRecordArraysLater = true; |
| |
| Ember.run.schedule('actions', this, this.updateRecordArrays); |
| }, |
| |
| /** |
| @method setupData |
| @private |
| @param {Object} data |
| @param {Boolean} partial the data should be merged into |
| the existing data, not replace it. |
| */ |
| setupData: function(data, partial) { |
| if (partial) { |
| Ember.merge(this._data, data); |
| } else { |
| this._data = data; |
| } |
| |
| var relationships = this._relationships; |
| |
| this.eachRelationship(function(name, rel) { |
| if (data.links && data.links[name]) { return; } |
| if (rel.options.async) { relationships[name] = null; } |
| }); |
| |
| if (data) { this.pushedData(); } |
| |
| this.suspendRelationshipObservers(function() { |
| this.notifyPropertyChange('data'); |
| }); |
| }, |
| |
| materializeId: function(id) { |
| set(this, 'id', id); |
| }, |
| |
| materializeAttributes: function(attributes) { |
| Ember.assert("Must pass a hash of attributes to materializeAttributes", !!attributes); |
| merge(this._data, attributes); |
| }, |
| |
| materializeAttribute: function(name, value) { |
| this._data[name] = value; |
| }, |
| |
| /** |
| @method updateHasMany |
| @private |
| @param {String} name |
| @param {Array} records |
| */ |
| updateHasMany: function(name, records) { |
| this._data[name] = records; |
| this.hasManyDidChange(name); |
| }, |
| |
| /** |
| @method updateBelongsTo |
| @private |
| @param {String} name |
| @param {DS.Model} record |
| */ |
| updateBelongsTo: function(name, record) { |
| this._data[name] = record; |
| }, |
| |
| /** |
| If the model `isDirty` this function will discard any unsaved |
| changes |
| |
| Example |
| |
| ```javascript |
| record.get('name'); // 'Untitled Document' |
| record.set('name', 'Doc 1'); |
| record.get('name'); // 'Doc 1' |
| record.rollback(); |
| record.get('name'); // 'Untitled Document' |
| ``` |
| |
| @method rollback |
| */ |
| rollback: function() { |
| this._attributes = {}; |
| |
| if (get(this, 'isError')) { |
| this._inFlightAttributes = {}; |
| set(this, 'isError', false); |
| } |
| |
| if (!get(this, 'isValid')) { |
| this._inFlightAttributes = {}; |
| } |
| |
| this.send('rolledBack'); |
| |
| this.suspendRelationshipObservers(function() { |
| this.notifyPropertyChange('data'); |
| }); |
| }, |
| |
| toStringExtension: function() { |
| return get(this, 'id'); |
| }, |
| |
| /** |
| The goal of this method is to temporarily disable specific observers |
| that take action in response to application changes. |
| |
| This allows the system to make changes (such as materialization and |
| rollback) that should not trigger secondary behavior (such as setting an |
| inverse relationship or marking records as dirty). |
| |
| The specific implementation will likely change as Ember proper provides |
| better infrastructure for suspending groups of observers, and if Array |
| observation becomes more unified with regular observers. |
| |
| @method suspendRelationshipObservers |
| @private |
| @param callback |
| @param binding |
| */ |
| suspendRelationshipObservers: function(callback, binding) { |
| var observers = get(this.constructor, 'relationshipNames').belongsTo; |
| var self = this; |
| |
| try { |
| this._suspendedRelationships = true; |
| Ember._suspendObservers(self, observers, null, 'belongsToDidChange', function() { |
| Ember._suspendBeforeObservers(self, observers, null, 'belongsToWillChange', function() { |
| callback.call(binding || self); |
| }); |
| }); |
| } finally { |
| this._suspendedRelationships = false; |
| } |
| }, |
| |
| /** |
| Save the record and persist any changes to the record to an |
| extenal source via the adapter. |
| |
| Example |
| |
| ```javascript |
| record.set('name', 'Tomster'); |
| record.save().then(function(){ |
| // Success callback |
| }, function() { |
| // Error callback |
| }); |
| ``` |
| @method save |
| @return {Promise} a promise that will be resolved when the adapter returns |
| successfully or rejected if the adapter returns with an error. |
| */ |
| save: function() { |
| var promiseLabel = "DS: Model#save " + this; |
| var resolver = Ember.RSVP.defer(promiseLabel); |
| |
| this.get('store').scheduleSave(this, resolver); |
| this._inFlightAttributes = this._attributes; |
| this._attributes = {}; |
| |
| return PromiseObject.create({ promise: resolver.promise }); |
| }, |
| |
| /** |
| Reload the record from the adapter. |
| |
| This will only work if the record has already finished loading |
| and has not yet been modified (`isLoaded` but not `isDirty`, |
| or `isSaving`). |
| |
| Example |
| |
| ```javascript |
| App.ModelViewRoute = Ember.Route.extend({ |
| actions: { |
| reload: function() { |
| this.get('model').reload(); |
| } |
| } |
| }); |
| ``` |
| |
| @method reload |
| @return {Promise} a promise that will be resolved with the record when the |
| adapter returns successfully or rejected if the adapter returns |
| with an error. |
| */ |
| reload: function() { |
| set(this, 'isReloading', true); |
| |
| var record = this; |
| |
| var promiseLabel = "DS: Model#reload of " + this; |
| var promise = new Promise(function(resolve){ |
| record.send('reloadRecord', resolve); |
| }, promiseLabel).then(function() { |
| record.set('isReloading', false); |
| record.set('isError', false); |
| return record; |
| }, function(reason) { |
| record.set('isError', true); |
| throw reason; |
| }, "DS: Model#reload complete, update flags"); |
| |
| return PromiseObject.create({ promise: promise }); |
| }, |
| |
| // FOR USE DURING COMMIT PROCESS |
| |
| adapterDidUpdateAttribute: function(attributeName, value) { |
| |
| // If a value is passed in, update the internal attributes and clear |
| // the attribute cache so it picks up the new value. Otherwise, |
| // collapse the current value into the internal attributes because |
| // the adapter has acknowledged it. |
| if (value !== undefined) { |
| this._data[attributeName] = value; |
| this.notifyPropertyChange(attributeName); |
| } else { |
| this._data[attributeName] = this._inFlightAttributes[attributeName]; |
| } |
| |
| this.updateRecordArraysLater(); |
| }, |
| |
| /** |
| @method adapterDidInvalidate |
| @private |
| */ |
| adapterDidInvalidate: function(errors) { |
| var recordErrors = get(this, 'errors'); |
| function addError(name) { |
| if (errors[name]) { |
| recordErrors.add(name, errors[name]); |
| } |
| } |
| |
| this.eachAttribute(addError); |
| this.eachRelationship(addError); |
| }, |
| |
| /** |
| @method adapterDidError |
| @private |
| */ |
| adapterDidError: function() { |
| this.send('becameError'); |
| set(this, 'isError', true); |
| }, |
| |
| /** |
| Override the default event firing from Ember.Evented to |
| also call methods with the given name. |
| |
| @method trigger |
| @private |
| @param name |
| */ |
| trigger: function(name) { |
| Ember.tryInvoke(this, name, [].slice.call(arguments, 1)); |
| this._super.apply(this, arguments); |
| }, |
| |
| triggerLater: function() { |
| if (this._deferredTriggers.push(arguments) !== 1) { return; } |
| Ember.run.schedule('actions', this, '_triggerDeferredTriggers'); |
| }, |
| |
| _triggerDeferredTriggers: function() { |
| for (var i=0, l=this._deferredTriggers.length; i<l; i++) { |
| this.trigger.apply(this, this._deferredTriggers[i]); |
| } |
| |
| this._deferredTriggers.length = 0; |
| }, |
| |
| willDestroy: function() { |
| this._super(); |
| this.clearRelationships(); |
| } |
| }); |
| |
| Model.reopenClass({ |
| |
| /** |
| Alias DS.Model's `create` method to `_create`. This allows us to create DS.Model |
| instances from within the store, but if end users accidentally call `create()` |
| (instead of `createRecord()`), we can raise an error. |
| |
| @method _create |
| @private |
| @static |
| */ |
| _create: Model.create, |
| |
| /** |
| Override the class' `create()` method to raise an error. This |
| prevents end users from inadvertently calling `create()` instead |
| of `createRecord()`. The store is still able to create instances |
| by calling the `_create()` method. To create an instance of a |
| `DS.Model` use [store.createRecord](DS.Store.html#method_createRecord). |
| |
| @method create |
| @private |
| @static |
| */ |
| 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."); |
| } |
| }); |
| |
| __exports__["default"] = Model; |
| }); |
| define("ember-data/lib/system/model/states", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var get = Ember.get, set = Ember.set; |
| /* |
| This file encapsulates the various states that a record can transition |
| through during its lifecycle. |
| */ |
| /** |
| ### State |
| |
| Each record has a `currentState` property that explicitly tracks what |
| state a record is in at any given time. For instance, if a record is |
| newly created and has not yet been sent to the adapter to be saved, |
| it would be in the `root.loaded.created.uncommitted` state. If a |
| record has had local modifications made to it that are in the |
| process of being saved, the record would be in the |
| `root.loaded.updated.inFlight` state. (These state paths will be |
| explained in more detail below.) |
| |
| Events are sent by the record or its store to the record's |
| `currentState` property. How the state reacts to these events is |
| dependent on which state it is in. In some states, certain events |
| will be invalid and will cause an exception to be raised. |
| |
| States are hierarchical and every state is a substate of the |
| `RootState`. For example, a record can be in the |
| `root.deleted.uncommitted` state, then transition into the |
| `root.deleted.inFlight` state. If a child state does not implement |
| an event handler, the state manager will attempt to invoke the event |
| on all parent states until the root state is reached. The state |
| hierarchy of a record is described in terms of a path string. You |
| can determine a record's current state by getting the state's |
| `stateName` property: |
| |
| ```javascript |
| record.get('currentState.stateName'); |
| //=> "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 |
| */ |
| |
| function hasDefinedProperties(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; |
| } |
| |
| function didSetProperty(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: 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, |
| |
| unloadRecord: function(record) { |
| Ember.assert("You can only unload a record which is not inFlight. `" + Ember.inspect(record) + " `", false); |
| }, |
| |
| // 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` 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, |
| |
| willCommit: function(record) { |
| get(record, 'errors').clear(); |
| record.transitionTo('inFlight'); |
| }, |
| |
| rolledBack: function(record) { |
| get(record, 'errors').clear(); |
| }, |
| |
| becameValid: function(record) { |
| record.transitionTo('uncommitted'); |
| }, |
| |
| invokeLifecycleCallbacks: function(record) { |
| record.triggerLater('becameInvalid', record); |
| }, |
| |
| exit: function(record) { |
| record._inFlightAttributes = {}; |
| } |
| } |
| }; |
| |
| // 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; |
| |
| function assertAgainstUnloadRecord(record) { |
| Ember.assert("You can only unload a record which is not inFlight. `" + Ember.inspect(record) + "`", false); |
| } |
| |
| updatedState.inFlight.unloadRecord = assertAgainstUnloadRecord; |
| |
| 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, |
| unloadRecord: function(record) { |
| // clear relationships before moving to deleted state |
| // otherwise it fails |
| record.clearRelationships(); |
| record.transitionTo('deleted.saved'); |
| }, |
| |
| |
| 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 |
| |
| unloadRecord: assertAgainstUnloadRecord, |
| |
| // 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); |
| }, |
| |
| willCommit: Ember.K, |
| |
| didCommit: Ember.K |
| } |
| }, |
| |
| 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"); |
| |
| __exports__["default"] = RootState; |
| }); |
| define("ember-data/lib/system/record_array_manager", |
| ["./record_arrays","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var RecordArray = __dependency1__.RecordArray; |
| var FilteredRecordArray = __dependency1__.FilteredRecordArray; |
| var AdapterPopulatedRecordArray = __dependency1__.AdapterPopulatedRecordArray; |
| var ManyArray = __dependency1__.ManyArray; |
| var get = Ember.get, set = Ember.set; |
| var forEach = Ember.EnumerableUtils.forEach; |
| |
| /** |
| @class RecordArrayManager |
| @namespace DS |
| @private |
| @extends Ember.Object |
| */ |
| var RecordArrayManager = Ember.Object.extend({ |
| init: function() { |
| this.filteredRecordArrays = Ember.MapWithDefault.create({ |
| defaultValue: function() { return []; } |
| }); |
| |
| this.changedRecords = []; |
| this._adapterPopulatedRecordArrays = []; |
| }, |
| |
| 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<l; i++) { |
| manyArrays[i].loadedRecord(); |
| } |
| |
| record._loadingRecordArrays = []; |
| } |
| }, |
| |
| /** |
| Update an individual filter. |
| |
| @method updateRecordArray |
| @param {DS.FilteredRecordArray} array |
| @param {Function} filter |
| @param {Class} type |
| @param {Number|String} clientId |
| */ |
| updateRecordArray: function(array, filter, type, record) { |
| var shouldBeInArray; |
| |
| if (!filter) { |
| shouldBeInArray = true; |
| } else { |
| shouldBeInArray = filter(record); |
| } |
| |
| var recordArrays = this.recordArraysForRecord(record); |
| |
| if (shouldBeInArray) { |
| recordArrays.add(array); |
| array.addRecord(record); |
| } else if (!shouldBeInArray) { |
| recordArrays.remove(array); |
| array.removeRecord(record); |
| } |
| }, |
| |
| /** |
| This method is invoked if the `filterFunction` property is |
| changed on a `DS.FilteredRecordArray`. |
| |
| It essentially re-runs the filter from scratch. This same |
| method is invoked when the filter is created in th first place. |
| |
| @method updateFilter |
| @param array |
| @param type |
| @param filter |
| */ |
| updateFilter: function(array, type, filter) { |
| var typeMap = this.store.typeMapFor(type), |
| records = typeMap.records, record; |
| |
| for (var i=0, l=records.length; i<l; i++) { |
| record = records[i]; |
| |
| if (!get(record, 'isDeleted') && !get(record, 'isEmpty')) { |
| this.updateRecordArray(array, filter, type, record); |
| } |
| } |
| }, |
| |
| /** |
| Create a `DS.ManyArray` for a type and list of record references, and index |
| the `ManyArray` under each reference. This allows us to efficiently remove |
| records from `ManyArray`s when they are deleted. |
| |
| @method createManyArray |
| @param {Class} type |
| @param {Array} references |
| @return {DS.ManyArray} |
| */ |
| createManyArray: function(type, records) { |
| var manyArray = ManyArray.create({ |
| type: type, |
| content: records, |
| store: this.store |
| }); |
| |
| forEach(records, function(record) { |
| var arrays = this.recordArraysForRecord(record); |
| arrays.add(manyArray); |
| }, this); |
| |
| return manyArray; |
| }, |
| |
| /** |
| Create a `DS.RecordArray` for a type and register it for updates. |
| |
| @method createRecordArray |
| @param {Class} type |
| @return {DS.RecordArray} |
| */ |
| createRecordArray: function(type) { |
| var array = RecordArray.create({ |
| type: type, |
| content: Ember.A(), |
| store: this.store, |
| isLoaded: true |
| }); |
| |
| this.registerFilteredRecordArray(array, type); |
| |
| return array; |
| }, |
| |
| /** |
| Create a `DS.FilteredRecordArray` for a type and register it for updates. |
| |
| @method createFilteredRecordArray |
| @param {Class} type |
| @param {Function} filter |
| @param {Object} query (optional |
| @return {DS.FilteredRecordArray} |
| */ |
| createFilteredRecordArray: function(type, filter, query) { |
| var array = FilteredRecordArray.create({ |
| query: query, |
| type: type, |
| content: Ember.A(), |
| store: this.store, |
| manager: this, |
| filterFunction: filter |
| }); |
| |
| this.registerFilteredRecordArray(array, type, filter); |
| |
| return array; |
| }, |
| |
| /** |
| Create a `DS.AdapterPopulatedRecordArray` for a type with given query. |
| |
| @method createAdapterPopulatedRecordArray |
| @param {Class} type |
| @param {Object} query |
| @return {DS.AdapterPopulatedRecordArray} |
| */ |
| createAdapterPopulatedRecordArray: function(type, query) { |
| var array = AdapterPopulatedRecordArray.create({ |
| type: type, |
| query: query, |
| content: Ember.A(), |
| store: this.store, |
| manager: this |
| }); |
| |
| this._adapterPopulatedRecordArrays.push(array); |
| |
| return array; |
| }, |
| |
| /** |
| Register a RecordArray for a given type to be backed by |
| a filter function. This will cause the array to update |
| automatically when records of that type change attribute |
| values or states. |
| |
| @method registerFilteredRecordArray |
| @param {DS.RecordArray} array |
| @param {Class} type |
| @param {Function} filter |
| */ |
| registerFilteredRecordArray: function(array, type, filter) { |
| var recordArrays = this.filteredRecordArrays.get(type); |
| recordArrays.push(array); |
| |
| this.updateFilter(array, type, filter); |
| }, |
| |
| // Internally, we maintain a map of all unloaded IDs requested by |
| // a ManyArray. As the adapter loads data into the store, the |
| // store notifies any interested ManyArrays. When the ManyArray's |
| // total number of loading records drops to zero, it becomes |
| // `isLoaded` and fires a `didLoad` event. |
| registerWaitingRecordArray: function(record, array) { |
| var loadingRecordArrays = record._loadingRecordArrays || []; |
| loadingRecordArrays.push(array); |
| record._loadingRecordArrays = loadingRecordArrays; |
| }, |
| |
| willDestroy: function(){ |
| this._super(); |
| |
| forEach(flatten(values(this.filteredRecordArrays.values)), destroy); |
| forEach(this._adapterPopulatedRecordArrays, destroy); |
| } |
| }); |
| |
| function values(obj) { |
| var result = []; |
| var keys = Ember.keys(obj); |
| |
| for (var i = 0; i < keys.length; i++) { |
| result.push(obj[keys[i]]); |
| } |
| |
| return result; |
| } |
| |
| function destroy(entry) { |
| entry.destroy(); |
| } |
| |
| function flatten(list) { |
| var length = list.length; |
| var result = Ember.A(); |
| |
| for (var i = 0; i < length; i++) { |
| result = result.concat(list[i]); |
| } |
| |
| return result; |
| } |
| |
| __exports__["default"] = RecordArrayManager; |
| }); |
| define("ember-data/lib/system/record_arrays", |
| ["./record_arrays/record_array","./record_arrays/filtered_record_array","./record_arrays/adapter_populated_record_array","./record_arrays/many_array","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var RecordArray = __dependency1__["default"]; |
| var FilteredRecordArray = __dependency2__["default"]; |
| var AdapterPopulatedRecordArray = __dependency3__["default"]; |
| var ManyArray = __dependency4__["default"]; |
| |
| __exports__.RecordArray = RecordArray; |
| __exports__.FilteredRecordArray = FilteredRecordArray; |
| __exports__.AdapterPopulatedRecordArray = AdapterPopulatedRecordArray; |
| __exports__.ManyArray = ManyArray; |
| }); |
| define("ember-data/lib/system/record_arrays/adapter_populated_record_array", |
| ["./record_array","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var RecordArray = __dependency1__["default"]; |
| /** |
| @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 |
| */ |
| var AdapterPopulatedRecordArray = 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: Ember.copy(meta) |
| }); |
| |
| records.forEach(function(record) { |
| this.manager.recordArraysForRecord(record).add(this); |
| }, this); |
| |
| // TODO: should triggering didLoad event be the last action of the runLoop? |
| Ember.run.once(this, 'trigger', 'didLoad'); |
| } |
| }); |
| |
| __exports__["default"] = AdapterPopulatedRecordArray; |
| }); |
| define("ember-data/lib/system/record_arrays/filtered_record_array", |
| ["./record_array","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var RecordArray = __dependency1__["default"]; |
| |
| /** |
| @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 |
| */ |
| var FilteredRecordArray = 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: function() { |
| var manager = get(this, 'manager'); |
| manager.updateFilter(this, get(this, 'type'), get(this, 'filterFunction')); |
| }, |
| |
| updateFilter: Ember.observer(function() { |
| Ember.run.once(this, this._updateFilter); |
| }, 'filterFunction') |
| }); |
| |
| __exports__["default"] = FilteredRecordArray; |
| }); |
| define("ember-data/lib/system/record_arrays/many_array", |
| ["./record_array","../changes","exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var RecordArray = __dependency1__["default"]; |
| var RelationshipChange = __dependency2__.RelationshipChange; |
| |
| /** |
| @module ember-data |
| */ |
| |
| var get = Ember.get, set = Ember.set; |
| var map = Ember.EnumerableUtils.map; |
| |
| function sync(change) { |
| change.sync(); |
| } |
| |
| /** |
| 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 |
| */ |
| var ManyArray = 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'); |
| |
| var unloadedRecords = records.filterProperty('isEmpty', true); |
| store.fetchMany(unloadedRecords, owner); |
| }, |
| |
| // 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<index+removed; i++) { |
| var record = get(this, 'content').objectAt(i); |
| |
| var change = RelationshipChange.createChange(owner, record, get(this, 'store'), { |
| parentType: owner.constructor, |
| changeType: "remove", |
| kind: "hasMany", |
| key: name |
| }); |
| |
| this._changesToSync.add(change); |
| } |
| } |
| |
| return this._super.apply(this, arguments); |
| }, |
| |
| arrayContentDidChange: function(index, removed, added) { |
| this._super.apply(this, arguments); |
| |
| var owner = get(this, 'owner'), |
| name = get(this, 'name'), |
| store = get(this, 'store'); |
| |
| if (!owner._suspendedRelationships) { |
| // This code is the second half of code that started in |
| // `arrayContentWillChange`. It gets or creates a change |
| // from the child object, and adds the current owner as |
| // the new parent. |
| for (var i=index; i<index+added; i++) { |
| var record = get(this, 'content').objectAt(i); |
| |
| var change = RelationshipChange.createChange(owner, record, store, { |
| parentType: owner.constructor, |
| changeType: "add", |
| kind:"hasMany", |
| key: name |
| }); |
| change.hasManyName = name; |
| |
| this._changesToSync.add(change); |
| } |
| |
| // We wait until the array has finished being |
| // mutated before syncing the OneToManyChanges created |
| // in arrayContentWillChange, so that the array |
| // membership test in the sync() logic operates |
| // on the final results. |
| this._changesToSync.forEach(sync); |
| |
| this._changesToSync.clear(); |
| } |
| }, |
| |
| /** |
| Create a child record within the owner |
| |
| @method createRecord |
| @private |
| @param {Object} hash |
| @return {DS.Model} record |
| */ |
| createRecord: function(hash) { |
| var owner = get(this, 'owner'), |
| store = get(owner, 'store'), |
| type = get(this, 'type'), |
| record; |
| |
| Ember.assert("You cannot add '" + type.typeKey + "' records to this polymorphic relationship.", !get(this, 'isPolymorphic')); |
| |
| record = store.createRecord.call(store, type, hash); |
| this.pushObject(record); |
| |
| return record; |
| } |
| }); |
| |
| __exports__["default"] = ManyArray; |
| }); |
| define("ember-data/lib/system/record_arrays/record_array", |
| ["../store","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var PromiseArray = __dependency1__.PromiseArray; |
| 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 |
| */ |
| |
| var 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('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('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('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('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 PromiseArray.create({ promise: promise }); |
| }, |
| |
| _dissociateFromOwnRecords: function() { |
| var array = this; |
| |
| this.forEach(function(record){ |
| var recordArrays = record._recordArrays; |
| |
| if (recordArrays) { |
| recordArrays.remove(array); |
| } |
| }); |
| }, |
| |
| willDestroy: function(){ |
| this._dissociateFromOwnRecords(); |
| this._super(); |
| } |
| }); |
| |
| __exports__["default"] = RecordArray; |
| }); |
| define("ember-data/lib/system/relationship-meta", |
| ["../../../ember-inflector/lib/system","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var singularize = __dependency1__.singularize; |
| |
| function typeForRelationshipMeta(store, meta) { |
| var typeKey, type; |
| |
| typeKey = meta.type || meta.key; |
| if (typeof typeKey === 'string') { |
| if (meta.kind === 'hasMany') { |
| typeKey = singularize(typeKey); |
| } |
| type = store.modelFor(typeKey); |
| } else { |
| type = meta.type; |
| } |
| |
| return type; |
| } |
| |
| __exports__.typeForRelationshipMeta = typeForRelationshipMeta;function relationshipFromMeta(store, meta) { |
| return { |
| key: meta.key, |
| kind: meta.kind, |
| type: typeForRelationshipMeta(store, meta), |
| options: meta.options, |
| parentType: meta.parentType, |
| isRelationship: true |
| }; |
| } |
| |
| __exports__.relationshipFromMeta = relationshipFromMeta; |
| }); |
| define("ember-data/lib/system/relationships", |
| ["./relationships/belongs_to","./relationships/has_many","../system/relationships/ext","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var belongsTo = __dependency1__["default"]; |
| var hasMany = __dependency2__["default"]; |
| |
| |
| __exports__.belongsTo = belongsTo; |
| __exports__.hasMany = hasMany; |
| }); |
| define("ember-data/lib/system/relationships/belongs_to", |
| ["../model","../store","../changes","../relationship-meta","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { |
| "use strict"; |
| var get = Ember.get, set = Ember.set, |
| isNone = Ember.isNone; |
| |
| var Promise = Ember.RSVP.Promise; |
| |
| var Model = __dependency1__.Model; |
| var PromiseObject = __dependency2__.PromiseObject; |
| var RelationshipChange = __dependency3__.RelationshipChange; |
| var relationshipFromMeta = __dependency4__.relationshipFromMeta; |
| var typeForRelationshipMeta = __dependency4__.typeForRelationshipMeta; |
| |
| /** |
| @module ember-data |
| */ |
| |
| function asyncBelongsTo(type, options, meta) { |
| return Ember.computed('data', function(key, value) { |
| var data = get(this, 'data'), |
| store = get(this, 'store'), |
| promiseLabel = "DS: Async belongsTo " + this + " : " + key, |
| promise; |
| |
| meta.key = key; |
| |
| if (arguments.length === 2) { |
| Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof typeForRelationshipMeta(store, meta)); |
| return value === undefined ? null : PromiseObject.create({ |
| promise: Promise.cast(value, promiseLabel) |
| }); |
| } |
| |
| var link = data.links && data.links[key], |
| belongsTo = data[key]; |
| |
| if(!isNone(belongsTo)) { |
| promise = store.fetchRecord(belongsTo) || Promise.cast(belongsTo, promiseLabel); |
| return PromiseObject.create({ |
| promise: promise |
| }); |
| } else if (link) { |
| promise = store.findBelongsTo(this, link, relationshipFromMeta(store, meta)); |
| return PromiseObject.create({ |
| promise: promise |
| }); |
| } else { |
| return null; |
| } |
| }).meta(meta); |
| } |
| |
| /** |
| `DS.belongsTo` is used to define One-To-One and One-To-Many |
| relationships on a [DS.Model](/api/data/classes/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 |
| */ |
| function belongsTo(type, options) { |
| if (typeof type === 'object') { |
| options = type; |
| type = undefined; |
| } else { |
| Ember.assert("The first argument to DS.belongsTo must be a string representing a model type key, e.g. use DS.belongsTo('person') to define a relation to the App.Person model", !!type && (typeof type === 'string' || Model.detect(type))); |
| } |
| |
| options = options || {}; |
| |
| var meta = { |
| type: type, |
| isRelationship: true, |
| options: options, |
| kind: 'belongsTo', |
| key: null |
| }; |
| |
| if (options.async) { |
| return asyncBelongsTo(type, options, meta); |
| } |
| |
| return Ember.computed('data', 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; |
| }).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 |
| */ |
| 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 = 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 = RelationshipChange.createChange(record, newParent, store, { key: key, kind: "belongsTo", changeType: "add" }); |
| |
| change.sync(); |
| } |
| } |
| |
| delete this._changesToSync[key]; |
| }) |
| }); |
| |
| __exports__["default"] = belongsTo; |
| }); |
| define("ember-data/lib/system/relationships/ext", |
| ["../../../../ember-inflector/lib/system","../relationship-meta","../model"], |
| function(__dependency1__, __dependency2__, __dependency3__) { |
| "use strict"; |
| var singularize = __dependency1__.singularize; |
| var typeForRelationshipMeta = __dependency2__.typeForRelationshipMeta; |
| var relationshipFromMeta = __dependency2__.relationshipFromMeta; |
| var Model = __dependency3__.Model; |
| |
| 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 |
| */ |
| 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. |
| */ |
| |
| 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) { |
| meta.key = name; |
| var relationshipsForType = map.get(typeForRelationshipMeta(this.store, meta)); |
| |
| relationshipsForType.push({ name: name, kind: meta.kind }); |
| } |
| }); |
| |
| return map; |
| }).cacheable(false), |
| |
| /** |
| 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) { |
| meta.key = name; |
| type = typeForRelationshipMeta(this.store, meta); |
| |
| 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; |
| }).cacheable(false), |
| |
| /** |
| 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(); |
| |
| this.eachComputedProperty(function(name, meta) { |
| if (meta.isRelationship) { |
| meta.key = name; |
| var relationship = relationshipFromMeta(this.store, meta); |
| relationship.type = typeForRelationshipMeta(this.store, meta); |
| map.set(name, relationship); |
| } |
| }); |
| |
| return map; |
| }).cacheable(false), |
| |
| /** |
| 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); |
| }); |
| } |
| }); |
| |
| 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); |
| } |
| }); |
| }); |
| define("ember-data/lib/system/relationships/has_many", |
| ["../store","../relationship-meta","exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| /** |
| @module ember-data |
| */ |
| |
| var PromiseArray = __dependency1__.PromiseArray; |
| var get = Ember.get, set = Ember.set, setProperties = Ember.setProperties; |
| var relationshipFromMeta = __dependency2__.relationshipFromMeta; |
| var typeForRelationshipMeta = __dependency2__.typeForRelationshipMeta; |
| |
| function asyncHasMany(type, options, meta) { |
| return Ember.computed('data', function(key) { |
| var relationship = this._relationships[key], |
| promiseLabel = "DS: Async hasMany " + this + " : " + key; |
| |
| meta.key = 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, relationshipFromMeta(store, meta), resolver); |
| } else { |
| rel = store.findMany(this, data[key], typeForRelationshipMeta(store, meta), 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 PromiseArray.create({ |
| promise: promise |
| }); |
| }).meta(meta).readOnly(); |
| } |
| |
| 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', |
| key: null |
| }; |
| |
| if (options.async) { |
| return asyncHasMany(type, options, meta); |
| } |
| |
| return Ember.computed('data', function(key) { |
| 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], typeForRelationshipMeta(store, meta)); |
| }); |
| }).meta(meta).readOnly(); |
| } |
| |
| /** |
| `DS.hasMany` is used to define One-To-Many and Many-To-Many |
| relationships on a [DS.Model](/api/data/classes/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 |
| */ |
| function hasMany(type, options) { |
| if (typeof type === 'object') { |
| options = type; |
| type = undefined; |
| } |
| return hasRelationship(type, options); |
| } |
| |
| __exports__["default"] = hasMany; |
| }); |
| define("ember-data/lib/system/store", |
| ["./adapter","ember-inflector/lib/system/string","exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| /*globals Ember*/ |
| /*jshint eqnull:true*/ |
| |
| /** |
| @module ember-data |
| */ |
| |
| var InvalidError = __dependency1__.InvalidError; |
| var Adapter = __dependency1__.Adapter; |
| var singularize = __dependency2__.singularize; |
| var get = Ember.get, set = Ember.set; |
| var once = Ember.run.once; |
| var isNone = Ember.isNone; |
| var forEach = Ember.EnumerableUtils.forEach; |
| var indexOf = Ember.EnumerableUtils.indexOf; |
| var map = Ember.EnumerableUtils.map; |
| var Promise = Ember.RSVP.Promise; |
| var copy = Ember.copy; |
| var Store, PromiseObject, PromiseArray, RecordArrayManager, Model; |
| |
| var camelize = Ember.String.camelize; |
| |
| // Implementors Note: |
| // |
| // The variables in this file are consistently named according to the following |
| // scheme: |
| // |
| // * +id+ means an identifier managed by an external source, provided inside |
| // the data provided by that source. These are always coerced to be strings |
| // before being used internally. |
| // * +clientId+ means 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. |
| // * +reference+ means a record reference object, which holds metadata about a |
| // record, even if it has not yet been fully materialized. |
| // * +type+ means a subclass of DS.Model. |
| |
| // Used by the store to normalize IDs entering the store. Despite the fact |
| // that developers may provide IDs as numbers (e.g., `store.find(Person, 1)`), |
| // it is important that internally we use strings, since IDs may be serialized |
| // and lose type information. For example, Ember's router may put a record's |
| // ID into the URL, and if we later try to deserialize that URL and find the |
| // corresponding record, we will not know if it is a string or a number. |
| function coerceId(id) { |
| return id == null ? null : id+''; |
| } |
| |
| /** |
| The store contains all of the data for records loaded from the server. |
| It is also responsible for creating instances of `DS.Model` that wrap |
| the individual data for a record, so that they can be bound to in your |
| Handlebars templates. |
| |
| Define your application's store like this: |
| |
| ```javascript |
| MyApp.Store = DS.Store.extend(); |
| ``` |
| |
| Most Ember.js applications will only have a single `DS.Store` that is |
| automatically created by their `Ember.Application`. |
| |
| You can retrieve models from the store in several ways. To retrieve a record |
| for a specific id, use `DS.Store`'s `find()` method: |
| |
| ```javascript |
| var person = store.find('person', 123); |
| ``` |
| |
| If your application has multiple `DS.Store` instances (an unusual case), you can |
| specify which store should be used: |
| |
| ```javascript |
| var person = store.find('person', 123); |
| ``` |
| |
| By default, the store will talk to your backend using a standard |
| REST mechanism. You can customize how the store talks to your |
| backend by specifying a custom adapter: |
| |
| ```javascript |
| MyApp.store = DS.Store.create({ |
| adapter: 'MyApp.CustomAdapter' |
| }); |
| ``` |
| |
| You can learn more about writing a custom adapter by reading the `DS.Adapter` |
| documentation. |
| |
| ### Store createRecord() vs. push() vs. pushPayload() vs. update() |
| |
| The store provides multiple ways to create new records object. They have |
| some subtle differences in their use which are detailed below: |
| |
| [createRecord](#method_createRecord) is used for creating new |
| records on the client side. This will return a new record in the |
| `created.uncommitted` state. In order to persist this record to the |
| backend you will need to call `record.save()`. |
| |
| [push](#method_push) is used to notify Ember Data's store of new or |
| updated records that exist in the backend. This will return a record |
| in the `loaded.saved` state. The primary use-case for `store#push` is |
| to notify Ember Data about record updates that happen |
| outside of the normal adapter methods (for example |
| [SSE](http://dev.w3.org/html5/eventsource/) or [Web |
| Sockets](http://www.w3.org/TR/2009/WD-websockets-20091222/)). |
| |
| [pushPayload](#method_pushPayload) is a convenience wrapper for |
| `store#push` that will deserialize payloads if the |
| Serializer implements a `pushPayload` method. |
| |
| [update](#method_update) works like `push`, except it can handle |
| partial attributes without overwriting the existing record |
| properties. |
| |
| Note: When creating a new record using any of the above methods |
| Ember Data will update `DS.RecordArray`s such as those returned by |
| `store#all()`, `store#findAll()` or `store#filter()`. This means any |
| data bindings or computed properties that depend on the RecordArray |
| will automatically be synced to include the new or updated record |
| values. |
| |
| @class Store |
| @namespace DS |
| @extends Ember.Object |
| */ |
| Store = Ember.Object.extend({ |
| |
| /** |
| @method init |
| @private |
| */ |
| init: function() { |
| // internal bookkeeping; not observable |
| if (!RecordArrayManager) { RecordArrayManager = requireModule("ember-data/lib/system/record_array_manager")["default"]; } |
| this.typeMaps = {}; |
| this.recordArrayManager = RecordArrayManager.create({ |
| store: this |
| }); |
| this._relationshipChanges = {}; |
| this._pendingSave = []; |
| }, |
| |
| /** |
| The adapter to use to communicate to a backend server or other persistence layer. |
| |
| This can be specified as an instance, class, or string. |
| |
| If you want to specify `App.CustomAdapter` as a string, do: |
| |
| ```js |
| adapter: 'custom' |
| ``` |
| |
| @property adapter |
| @default DS.RESTAdapter |
| @type {DS.Adapter|String} |
| */ |
| adapter: '-rest', |
| |
| /** |
| Returns a JSON representation of the record using a custom |
| type-specific serializer, if one exists. |
| |
| The available options are: |
| |
| * `includeId`: `true` if the record's ID should be included in |
| the JSON representation |
| |
| @method serialize |
| @private |
| @param {DS.Model} record the record to serialize |
| @param {Object} options an options hash |
| */ |
| serialize: function(record, options) { |
| return this.serializerFor(record.constructor.typeKey).serialize(record, options); |
| }, |
| |
| /** |
| This property returns the adapter, after resolving a possible |
| string key. |
| |
| If the supplied `adapter` was a class, or a String property |
| path resolved to a class, this property will instantiate the |
| class. |
| |
| This property is cacheable, so the same instance of a specified |
| adapter class should be used for the lifetime of the store. |
| |
| @property defaultAdapter |
| @private |
| @return DS.Adapter |
| */ |
| defaultAdapter: Ember.computed('adapter', function() { |
| var adapter = get(this, 'adapter'); |
| |
| Ember.assert('You tried to set `adapter` property to an instance of `DS.Adapter`, where it should be a name or a factory', !(adapter instanceof Adapter)); |
| |
| if (typeof adapter === 'string') { |
| adapter = this.container.lookup('adapter:' + adapter) || this.container.lookup('adapter:application') || this.container.lookup('adapter:-rest'); |
| } |
| |
| if (DS.Adapter.detect(adapter)) { |
| adapter = adapter.create({ |
| container: this.container |
| }); |
| } |
| |
| return adapter; |
| }), |
| |
| // ..................... |
| // . CREATE NEW RECORD . |
| // ..................... |
| |
| /** |
| Create a new record in the current store. The properties passed |
| to this method are set on the newly created record. |
| |
| To create a new instance of `App.Post`: |
| |
| ```js |
| store.createRecord('post', { |
| title: "Rails is omakase" |
| }); |
| ``` |
| |
| @method createRecord |
| @param {String} type |
| @param {Object} properties a hash of properties to set on the |
| newly created record. |
| @return {DS.Model} record |
| */ |
| createRecord: function(type, properties) { |
| type = this.modelFor(type); |
| |
| properties = copy(properties) || {}; |
| |
| // If the passed properties do not include a primary key, |
| // give the adapter an opportunity to generate one. Typically, |
| // client-side ID generators will use something like uuid.js |
| // to avoid conflicts. |
| |
| if (isNone(properties.id)) { |
| properties.id = this._generateId(type); |
| } |
| |
| // Coerce ID to a string |
| properties.id = coerceId(properties.id); |
| |
| var record = this.buildRecord(type, properties.id); |
| |
| // Move the record out of its initial `empty` state into |
| // the `loaded` state. |
| record.loadedData(); |
| |
| // Set the properties specified on the record. |
| record.setProperties(properties); |
| |
| return record; |
| }, |
| |
| /** |
| If possible, this method asks the adapter to generate an ID for |
| a newly created record. |
| |
| @method _generateId |
| @private |
| @param {String} type |
| @return {String} if the adapter can generate one, an ID |
| */ |
| _generateId: function(type) { |
| var adapter = this.adapterFor(type); |
| |
| if (adapter && adapter.generateIdForRecord) { |
| return adapter.generateIdForRecord(this); |
| } |
| |
| return null; |
| }, |
| |
| // ................. |
| // . DELETE RECORD . |
| // ................. |
| |
| /** |
| For symmetry, a record can be deleted via the store. |
| |
| Example |
| |
| ```javascript |
| var post = store.createRecord('post', { |
| title: "Rails is omakase" |
| }); |
| |
| store.deleteRecord(post); |
| ``` |
| |
| @method deleteRecord |
| @param {DS.Model} record |
| */ |
| deleteRecord: function(record) { |
| record.deleteRecord(); |
| }, |
| |
| /** |
| For symmetry, a record can be unloaded via the store. Only |
| non-dirty records can be unloaded. |
| |
| Example |
| |
| ```javascript |
| store.find('post', 1).then(function(post) { |
| store.unloadRecord(post); |
| }); |
| ``` |
| |
| @method unloadRecord |
| @param {DS.Model} record |
| */ |
| unloadRecord: function(record) { |
| record.unloadRecord(); |
| }, |
| |
| // ................ |
| // . FIND RECORDS . |
| // ................ |
| |
| /** |
| This is the main entry point into finding records. The first parameter to |
| this method is the model's name as a string. |
| |
| --- |
| |
| To find a record by ID, pass the `id` as the second parameter: |
| |
| ```javascript |
| store.find('person', 1); |
| ``` |
| |
| The `find` method will always return a **promise** that will be resolved |
| with the record. If the record was already in the store, the promise will |
| be resolved immediately. Otherwise, the store will ask the adapter's `find` |
| method to find the necessary data. |
| |
| The `find` method will always resolve its promise with the same object for |
| a given type and `id`. |
| |
| --- |
| |
| To find all records for a type, call `find` with no additional parameters: |
| |
| ```javascript |
| store.find('person'); |
| ``` |
| |
| This will ask the adapter's `findAll` method to find the records for the |
| given type, and return a promise that will be resolved once the server |
| returns the values. |
| |
| --- |
| |
| To find a record by a query, call `find` with a hash as the second |
| parameter: |
| |
| ```javascript |
| store.find('person', { page: 1 }); |
| ``` |
| |
| This will ask the adapter's `findQuery` method to find the records for |
| the query, and return a promise that will be resolved once the server |
| responds. |
| |
| @method find |
| @param {String or subclass of DS.Model} type |
| @param {Object|String|Integer|null} id |
| @return {Promise} promise |
| */ |
| find: function(type, id) { |
| Ember.assert("You need to pass a type to the store's find method", arguments.length >= 1); |
| Ember.assert("You may not pass `" + id + "` as id to the store's find method", arguments.length === 1 || !Ember.isNone(id)); |
| |
| if (arguments.length === 1) { |
| return this.findAll(type); |
| } |
| |
| // We are passed a query instead of an id. |
| if (Ember.typeOf(id) === 'object') { |
| return this.findQuery(type, id); |
| } |
| |
| return this.findById(type, coerceId(id)); |
| }, |
| |
| /** |
| This method returns a record for a given type and id combination. |
| |
| @method findById |
| @private |
| @param {String or subclass of DS.Model} type |
| @param {String|Integer} id |
| @return {Promise} promise |
| */ |
| findById: function(type, id) { |
| type = this.modelFor(type); |
| |
| var record = this.recordForId(type, id); |
| var fetchedRecord = this.fetchRecord(record); |
| |
| return promiseObject(fetchedRecord || record, "DS: Store#findById " + type + " with id: " + id); |
| }, |
| |
| /** |
| This method makes a series of requests to the adapter's `find` method |
| and returns a promise that resolves once they are all loaded. |
| |
| @private |
| @method findByIds |
| @param {String} type |
| @param {Array} ids |
| @return {Promise} promise |
| */ |
| findByIds: function(type, ids) { |
| var store = this; |
| var promiseLabel = "DS: Store#findByIds " + type; |
| return promiseArray(Ember.RSVP.all(map(ids, function(id) { |
| return store.findById(type, id); |
| })).then(Ember.A, null, "DS: Store#findByIds of " + type + " complete")); |
| }, |
| |
| /** |
| This method is called by `findById` if it discovers that a particular |
| type/id pair hasn't been loaded yet to kick off a request to the |
| adapter. |
| |
| @method fetchRecord |
| @private |
| @param {DS.Model} record |
| @return {Promise} promise |
| */ |
| fetchRecord: function(record) { |
| if (isNone(record)) { return null; } |
| if (record._loadingPromise) { return record._loadingPromise; } |
| if (!get(record, 'isEmpty')) { return null; } |
| |
| var type = record.constructor, |
| id = get(record, 'id'); |
| |
| var adapter = this.adapterFor(type); |
| |
| Ember.assert("You tried to find a record but you have no adapter (for " + type + ")", adapter); |
| Ember.assert("You tried to find a record but your adapter (for " + type + ") does not implement 'find'", adapter.find); |
| |
| var promise = _find(adapter, this, type, id); |
| record.loadingData(promise); |
| return promise; |
| }, |
| |
| /** |
| Get a record by a given type and ID without triggering a fetch. |
| |
| This method will synchronously return the record if it's available. |
| Otherwise, it will return null. |
| |
| ```js |
| var post = store.getById('post', 1); |
| ``` |
| |
| @method getById |
| @param {String or subclass of DS.Model} type |
| @param {String|Integer} id |
| @param {DS.Model} record |
| */ |
| getById: function(type, id) { |
| if (this.hasRecordForId(type, id)) { |
| return this.recordForId(type, id); |
| } else { |
| return null; |
| } |
| }, |
| |
| /** |
| This method is called by the record's `reload` method. |
| |
| This method calls the adapter's `find` method, which returns a promise. When |
| **that** promise resolves, `reloadRecord` will resolve the promise returned |
| by the record's `reload`. |
| |
| @method reloadRecord |
| @private |
| @param {DS.Model} record |
| @return {Promise} promise |
| */ |
| reloadRecord: function(record) { |
| var type = record.constructor, |
| adapter = this.adapterFor(type), |
| id = get(record, 'id'); |
| |
| Ember.assert("You cannot reload a record without an ID", id); |
| Ember.assert("You tried to reload a record but you have no adapter (for " + type + ")", adapter); |
| Ember.assert("You tried to reload a record but your adapter does not implement `find`", adapter.find); |
| |
| return _find(adapter, this, type, id); |
| }, |
| |
| /** |
| This method takes a list of records, groups the records by type, |
| converts the records into IDs, and then invokes the adapter's `findMany` |
| method. |
| |
| The records are grouped by type to invoke `findMany` on adapters |
| for each unique type in records. |
| |
| It is used both by a brand new relationship (via the `findMany` |
| method) or when the data underlying an existing relationship |
| changes. |
| |
| @method fetchMany |
| @private |
| @param {Array} records |
| @param {DS.Model} owner |
| @return {Promise} promise |
| */ |
| fetchMany: function(records, owner) { |
| if (!records.length) { |
| return Ember.RSVP.resolve(records); |
| } |
| |
| // Group By Type |
| var recordsByTypeMap = Ember.MapWithDefault.create({ |
| defaultValue: function() { return Ember.A(); } |
| }); |
| |
| forEach(records, function(record) { |
| recordsByTypeMap.get(record.constructor).push(record); |
| }); |
| |
| var promises = []; |
| |
| forEach(recordsByTypeMap, function(type, records) { |
| var ids = records.mapProperty('id'), |
| adapter = this.adapterFor(type); |
| |
| Ember.assert("You tried to load many records but you have no adapter (for " + type + ")", adapter); |
| Ember.assert("You tried to load many records but your adapter does not implement `findMany`", adapter.findMany); |
| |
| promises.push(_findMany(adapter, this, type, ids, owner)); |
| }, this); |
| |
| return Ember.RSVP.all(promises); |
| }, |
| |
| /** |
| Returns true if a record for a given type and ID is already loaded. |
| |
| @method hasRecordForId |
| @param {String or subclass of DS.Model} type |
| @param {String|Integer} id |
| @return {Boolean} |
| */ |
| hasRecordForId: function(type, id) { |
| id = coerceId(id); |
| type = this.modelFor(type); |
| return !!this.typeMapFor(type).idToRecord[id]; |
| }, |
| |
| /** |
| Returns id record for a given type and ID. If one isn't already loaded, |
| it builds a new record and leaves it in the `empty` state. |
| |
| @method recordForId |
| @private |
| @param {String or subclass of DS.Model} type |
| @param {String|Integer} id |
| @return {DS.Model} record |
| */ |
| recordForId: function(type, id) { |
| type = this.modelFor(type); |
| |
| id = coerceId(id); |
| |
| var record = this.typeMapFor(type).idToRecord[id]; |
| |
| if (!record) { |
| record = this.buildRecord(type, id); |
| } |
| |
| return record; |
| }, |
| |
| /** |
| @method findMany |
| @private |
| @param {DS.Model} owner |
| @param {Array} records |
| @param {String or subclass of DS.Model} type |
| @param {Resolver} resolver |
| @return {DS.ManyArray} records |
| */ |
| findMany: function(owner, records, type, resolver) { |
| type = this.modelFor(type); |
| |
| records = Ember.A(records); |
| |
| var unloadedRecords = records.filterProperty('isEmpty', true), |
| manyArray = this.recordArrayManager.createManyArray(type, records); |
| |
| forEach(unloadedRecords, function(record) { |
| record.loadingData(); |
| }); |
| |
| manyArray.loadingRecordsCount = unloadedRecords.length; |
| |
| if (unloadedRecords.length) { |
| forEach(unloadedRecords, function(record) { |
| this.recordArrayManager.registerWaitingRecordArray(record, manyArray); |
| }, this); |
| |
| resolver.resolve(this.fetchMany(unloadedRecords, owner)); |
| } else { |
| if (resolver) { resolver.resolve(); } |
| manyArray.set('isLoaded', true); |
| once(manyArray, 'trigger', 'didLoad'); |
| } |
| |
| return manyArray; |
| }, |
| |
| /** |
| If a relationship was originally populated by the adapter as a link |
| (as opposed to a list of IDs), this method is called when the |
| relationship is fetched. |
| |
| The link (which is usually a URL) is passed through unchanged, so the |
| adapter can make whatever request it wants. |
| |
| The usual use-case is for the server to register a URL as a link, and |
| then use that URL in the future to make a request for the relationship. |
| |
| @method findHasMany |
| @private |
| @param {DS.Model} owner |
| @param {any} link |
| @param {String or subclass of DS.Model} type |
| @return {Promise} promise |
| */ |
| findHasMany: function(owner, link, relationship, resolver) { |
| var adapter = this.adapterFor(owner.constructor); |
| |
| Ember.assert("You tried to load a hasMany relationship but you have no adapter (for " + owner.constructor + ")", adapter); |
| Ember.assert("You tried to load a hasMany relationship from a specified `link` in the original payload but your adapter does not implement `findHasMany`", adapter.findHasMany); |
| |
| var records = this.recordArrayManager.createManyArray(relationship.type, Ember.A([])); |
| resolver.resolve(_findHasMany(adapter, this, owner, link, relationship)); |
| return records; |
| }, |
| |
| /** |
| @method findBelongsTo |
| @private |
| @param {DS.Model} owner |
| @param {any} link |
| @param {Relationship} relationship |
| @return {Promise} promise |
| */ |
| findBelongsTo: function(owner, link, relationship) { |
| var adapter = this.adapterFor(owner.constructor); |
| |
| Ember.assert("You tried to load a belongsTo relationship but you have no adapter (for " + owner.constructor + ")", adapter); |
| Ember.assert("You tried to load a belongsTo relationship from a specified `link` in the original payload but your adapter does not implement `findBelongsTo`", adapter.findBelongsTo); |
| |
| return _findBelongsTo(adapter, this, owner, link, relationship); |
| }, |
| |
| /** |
| This method delegates a query to the adapter. This is the one place where |
| adapter-level semantics are exposed to the application. |
| |
| Exposing queries this way seems preferable to creating an abstract query |
| language for all server-side queries, and then require all adapters to |
| implement them. |
| |
| This method returns a promise, which is resolved with a `RecordArray` |
| once the server returns. |
| |
| @method findQuery |
| @private |
| @param {String or subclass of DS.Model} type |
| @param {any} query an opaque query to be used by the adapter |
| @return {Promise} promise |
| */ |
| findQuery: function(type, query) { |
| type = this.modelFor(type); |
| |
| var array = this.recordArrayManager |
| .createAdapterPopulatedRecordArray(type, query); |
| |
| var adapter = this.adapterFor(type); |
| |
| Ember.assert("You tried to load a query but you have no adapter (for " + type + ")", adapter); |
| Ember.assert("You tried to load a query but your adapter does not implement `findQuery`", adapter.findQuery); |
| |
| return promiseArray(_findQuery(adapter, this, type, query, array)); |
| }, |
| |
| /** |
| This method returns an array of all records adapter can find. |
| It triggers the adapter's `findAll` method to give it an opportunity to populate |
| the array with records of that type. |
| |
| @method findAll |
| @private |
| @param {String or subclass of DS.Model} type |
| @return {DS.AdapterPopulatedRecordArray} |
| */ |
| findAll: function(type) { |
| type = this.modelFor(type); |
| |
| return this.fetchAll(type, this.all(type)); |
| }, |
| |
| /** |
| @method fetchAll |
| @private |
| @param {DS.Model} type |
| @param {DS.RecordArray} array |
| @return {Promise} promise |
| */ |
| fetchAll: function(type, array) { |
| var adapter = this.adapterFor(type), |
| sinceToken = this.typeMapFor(type).metadata.since; |
| |
| set(array, 'isUpdating', true); |
| |
| Ember.assert("You tried to load all records but you have no adapter (for " + type + ")", adapter); |
| Ember.assert("You tried to load all records but your adapter does not implement `findAll`", adapter.findAll); |
| |
| return promiseArray(_findAll(adapter, this, type, sinceToken)); |
| }, |
| |
| /** |
| @method didUpdateAll |
| @param {DS.Model} type |
| */ |
| didUpdateAll: function(type) { |
| var findAllCache = this.typeMapFor(type).findAllCache; |
| set(findAllCache, 'isUpdating', false); |
| }, |
| |
| /** |
| This method returns a filtered array that contains all of the known records |
| for a given type. |
| |
| Note that because it's just a filter, it will have any locally |
| created records of the type. |
| |
| Also note that multiple calls to `all` for a given type will always |
| return the same RecordArray. |
| |
| Example |
| |
| ```javascript |
| var localPosts = store.all('post'); |
| ``` |
| |
| @method all |
| @param {String or subclass of DS.Model} type |
| @return {DS.RecordArray} |
| */ |
| all: function(type) { |
| type = this.modelFor(type); |
| |
| var typeMap = this.typeMapFor(type), |
| findAllCache = typeMap.findAllCache; |
| |
| if (findAllCache) { return findAllCache; } |
| |
| var array = this.recordArrayManager.createRecordArray(type); |
| |
| typeMap.findAllCache = array; |
| return array; |
| }, |
| |
| |
| /** |
| This method unloads all of the known records for a given type. |
| |
| ```javascript |
| store.unloadAll('post'); |
| ``` |
| |
| @method unloadAll |
| @param {String or subclass of DS.Model} type |
| */ |
| unloadAll: function(type) { |
| var modelType = this.modelFor(type); |
| var typeMap = this.typeMapFor(modelType); |
| var records = typeMap.records.slice(); |
| var record; |
| |
| for (var i = 0; i < records.length; i++) { |
| record = records[i]; |
| record.unloadRecord(); |
| record.destroy(); // maybe within unloadRecord |
| } |
| |
| typeMap.findAllCache = null; |
| }, |
| |
| /** |
| Takes a type and filter function, and returns a live RecordArray that |
| remains up to date as new records are loaded into the store or created |
| locally. |
| |
| The callback function takes a materialized record, and returns true |
| if the record should be included in the filter and false if it should |
| not. |
| |
| The filter function is called once on all records for the type when |
| it is created, and then once on each newly loaded or created record. |
| |
| If any of a record's properties change, or if it changes state, the |
| filter function will be invoked again to determine whether it should |
| still be in the array. |
| |
| Optionally you can pass a query which will be triggered at first. The |
| results returned by the server could then appear in the filter if they |
| match the filter function. |
| |
| Example |
| |
| ```javascript |
| store.filter('post', {unread: true}, function(post) { |
| return post.get('unread'); |
| }).then(function(unreadPosts) { |
| unreadPosts.get('length'); // 5 |
| var unreadPost = unreadPosts.objectAt(0); |
| unreadPost.set('unread', false); |
| unreadPosts.get('length'); // 4 |
| }); |
| ``` |
| |
| @method filter |
| @param {String or subclass of DS.Model} type |
| @param {Object} query optional query |
| @param {Function} filter |
| @return {DS.PromiseArray} |
| */ |
| filter: function(type, query, filter) { |
| var promise; |
| var length = arguments.length; |
| var array; |
| var hasQuery = length === 3; |
| |
| // allow an optional server query |
| if (hasQuery) { |
| promise = this.findQuery(type, query); |
| } else if (arguments.length === 2) { |
| filter = query; |
| } |
| |
| type = this.modelFor(type); |
| |
| if (hasQuery) { |
| array = this.recordArrayManager.createFilteredRecordArray(type, filter, query); |
| } else { |
| array = this.recordArrayManager.createFilteredRecordArray(type, filter); |
| } |
| |
| promise = promise || Promise.cast(array); |
| |
| |
| return promiseArray(promise.then(function() { |
| return array; |
| }, null, "DS: Store#filter of " + type)); |
| }, |
| |
| /** |
| This method returns if a certain record is already loaded |
| in the store. Use this function to know beforehand if a find() |
| will result in a request or that it will be a cache hit. |
| |
| Example |
| |
| ```javascript |
| store.recordIsLoaded('post', 1); // false |
| store.find('post', 1).then(function() { |
| store.recordIsLoaded('post', 1); // true |
| }); |
| ``` |
| |
| @method recordIsLoaded |
| @param {String or subclass of DS.Model} type |
| @param {string} id |
| @return {boolean} |
| */ |
| recordIsLoaded: function(type, id) { |
| if (!this.hasRecordForId(type, id)) { return false; } |
| return !get(this.recordForId(type, id), 'isEmpty'); |
| }, |
| |
| /** |
| This method returns the metadata for a specific type. |
| |
| @method metadataFor |
| @param {String or subclass of DS.Model} type |
| @return {object} |
| */ |
| metadataFor: function(type) { |
| type = this.modelFor(type); |
| return this.typeMapFor(type).metadata; |
| }, |
| |
| // ............ |
| // . UPDATING . |
| // ............ |
| |
| /** |
| If the adapter updates attributes or acknowledges creation |
| or deletion, the record will notify the store to update its |
| membership in any filters. |
| To avoid thrashing, this method is invoked only once per |
| |
| run loop per record. |
| |
| @method dataWasUpdated |
| @private |
| @param {Class} type |
| @param {DS.Model} record |
| */ |
| dataWasUpdated: function(type, record) { |
| this.recordArrayManager.recordDidChange(record); |
| }, |
| |
| // .............. |
| // . PERSISTING . |
| // .............. |
| |
| /** |
| This method is called by `record.save`, and gets passed a |
| resolver for the promise that `record.save` returns. |
| |
| It schedules saving to happen at the end of the run loop. |
| |
| @method scheduleSave |
| @private |
| @param {DS.Model} record |
| @param {Resolver} resolver |
| */ |
| scheduleSave: function(record, resolver) { |
| record.adapterWillCommit(); |
| this._pendingSave.push([record, resolver]); |
| once(this, 'flushPendingSave'); |
| }, |
| |
| /** |
| This method is called at the end of the run loop, and |
| flushes any records passed into `scheduleSave` |
| |
| @method flushPendingSave |
| @private |
| */ |
| flushPendingSave: function() { |
| var pending = this._pendingSave.slice(); |
| this._pendingSave = []; |
| |
| forEach(pending, function(tuple) { |
| var record = tuple[0], resolver = tuple[1], |
| adapter = this.adapterFor(record.constructor), |
| operation; |
| |
| if (get(record, 'currentState.stateName') === 'root.deleted.saved') { |
| return resolver.resolve(record); |
| } else if (get(record, 'isNew')) { |
| operation = 'createRecord'; |
| } else if (get(record, 'isDeleted')) { |
| operation = 'deleteRecord'; |
| } else { |
| operation = 'updateRecord'; |
| } |
| |
| resolver.resolve(_commit(adapter, this, operation, record)); |
| }, this); |
| }, |
| |
| /** |
| This method is called once the promise returned by an |
| adapter's `createRecord`, `updateRecord` or `deleteRecord` |
| is resolved. |
| |
| If the data provides a server-generated ID, it will |
| update the record and the store's indexes. |
| |
| @method didSaveRecord |
| @private |
| @param {DS.Model} record the in-flight record |
| @param {Object} data optional data (see above) |
| */ |
| didSaveRecord: function(record, data) { |
| if (data) { |
| // normalize relationship IDs into records |
| data = normalizeRelationships(this, record.constructor, data, record); |
| |
| this.updateId(record, data); |
| } |
| |
| record.adapterDidCommit(data); |
| }, |
| |
| /** |
| This method is called once the promise returned by an |
| adapter's `createRecord`, `updateRecord` or `deleteRecord` |
| is rejected with a `DS.InvalidError`. |
| |
| @method recordWasInvalid |
| @private |
| @param {DS.Model} record |
| @param {Object} errors |
| */ |
| recordWasInvalid: function(record, errors) { |
| record.adapterDidInvalidate(errors); |
| }, |
| |
| /** |
| This method is called once the promise returned by an |
| adapter's `createRecord`, `updateRecord` or `deleteRecord` |
| is rejected (with anything other than a `DS.InvalidError`). |
| |
| @method recordWasError |
| @private |
| @param {DS.Model} record |
| */ |
| recordWasError: function(record) { |
| record.adapterDidError(); |
| }, |
| |
| /** |
| When an adapter's `createRecord`, `updateRecord` or `deleteRecord` |
| resolves with data, this method extracts the ID from the supplied |
| data. |
| |
| @method updateId |
| @private |
| @param {DS.Model} record |
| @param {Object} data |
| */ |
| updateId: function(record, data) { |
| var oldId = get(record, 'id'), |
| id = coerceId(data.id); |
| |
| Ember.assert("An adapter cannot assign a new id to a record that already has an id. " + record + " had id: " + oldId + " and you tried to update it with " + id + ". 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.", oldId === null || id === oldId); |
| |
| this.typeMapFor(record.constructor).idToRecord[id] = record; |
| |
| set(record, 'id', id); |
| }, |
| |
| /** |
| Returns a map of IDs to client IDs for a given type. |
| |
| @method typeMapFor |
| @private |
| @param type |
| @return {Object} typeMap |
| */ |
| typeMapFor: function(type) { |
| var typeMaps = get(this, 'typeMaps'), |
| guid = Ember.guidFor(type), |
| typeMap; |
| |
| typeMap = typeMaps[guid]; |
| |
| if (typeMap) { return typeMap; } |
| |
| typeMap = { |
| idToRecord: {}, |
| records: [], |
| metadata: {}, |
| type: type |
| }; |
| |
| typeMaps[guid] = typeMap; |
| |
| return typeMap; |
| }, |
| |
| // ................ |
| // . LOADING DATA . |
| // ................ |
| |
| /** |
| This internal method is used by `push`. |
| |
| @method _load |
| @private |
| @param {String or subclass of DS.Model} type |
| @param {Object} data |
| @param {Boolean} partial the data should be merged into |
| the existing data, not replace it. |
| */ |
| _load: function(type, data, partial) { |
| var id = coerceId(data.id), |
| record = this.recordForId(type, id); |
| |
| record.setupData(data, partial); |
| this.recordArrayManager.recordDidChange(record); |
| |
| return record; |
| }, |
| |
| /** |
| Returns a model class for a particular key. Used by |
| methods that take a type key (like `find`, `createRecord`, |
| etc.) |
| |
| @method modelFor |
| @param {String or subclass of DS.Model} key |
| @return {subclass of DS.Model} |
| */ |
| modelFor: function(key) { |
| var factory; |
| |
| |
| if (typeof key === 'string') { |
| var normalizedKey = this.container.normalize('model:' + key); |
| |
| factory = this.container.lookupFactory(normalizedKey); |
| if (!factory) { throw new Ember.Error("No model was found for '" + key + "'"); } |
| factory.typeKey = this._normalizeTypeKey(normalizedKey.split(':', 2)[1]); |
| } else { |
| // A factory already supplied. Ensure it has a normalized key. |
| factory = key; |
| if (factory.typeKey) { |
| factory.typeKey = this._normalizeTypeKey(factory.typeKey); |
| } |
| } |
| |
| factory.store = this; |
| return factory; |
| }, |
| |
| /** |
| Push some data for a given type into the store. |
| |
| This method expects normalized data: |
| |
| * The ID is a key named `id` (an ID is mandatory) |
| * The names of attributes are the ones you used in |
| your model's `DS.attr`s. |
| * Your relationships must be: |
| * represented as IDs or Arrays of IDs |
| * represented as model instances |
| * represented as URLs, under the `links` key |
| |
| For this model: |
| |
| ```js |
| App.Person = DS.Model.extend({ |
| firstName: DS.attr(), |
| lastName: DS.attr(), |
| |
| children: DS.hasMany('person') |
| }); |
| ``` |
| |
| To represent the children as IDs: |
| |
| ```js |
| { |
| id: 1, |
| firstName: "Tom", |
| lastName: "Dale", |
| children: [1, 2, 3] |
| } |
| ``` |
| |
| To represent the children relationship as a URL: |
| |
| ```js |
| { |
| id: 1, |
| firstName: "Tom", |
| lastName: "Dale", |
| links: { |
| children: "/people/1/children" |
| } |
| } |
| ``` |
| |
| If you're streaming data or implementing an adapter, |
| make sure that you have converted the incoming data |
| into this form. |
| |
| This method can be used both to push in brand new |
| records, as well as to update existing records. |
| |
| @method push |
| @param {String or subclass of DS.Model} type |
| @param {Object} data |
| @return {DS.Model} the record that was created or |
| updated. |
| */ |
| push: function(type, data, _partial) { |
| // _partial is an internal param used by `update`. |
| // If passed, it means that the data should be |
| // merged into the existing data, not replace it. |
| |
| Ember.assert("You must include an `id` for " + type + " in a hash passed to `push`", data.id != null); |
| |
| type = this.modelFor(type); |
| |
| // normalize relationship IDs into records |
| data = normalizeRelationships(this, type, data); |
| |
| this._load(type, data, _partial); |
| |
| return this.recordForId(type, data.id); |
| }, |
| |
| /** |
| Push some raw data into the store. |
| |
| This method can be used both to push in brand new |
| records, as well as to update existing records. You |
| can push in more than one type of object at once. |
| All objects should be in the format expected by the |
| serializer. |
| |
| ```js |
| App.ApplicationSerializer = DS.ActiveModelSerializer; |
| |
| var pushData = { |
| posts: [ |
| {id: 1, post_title: "Great post", comment_ids: [2]} |
| ], |
| comments: [ |
| {id: 2, comment_body: "Insightful comment"} |
| ] |
| } |
| |
| store.pushPayload(pushData); |
| ``` |
| |
| By default, the data will be deserialized using a default |
| serializer (the application serializer if it exists). |
| |
| Alternativly, `pushPayload` will accept a model type which |
| will determine which serializer will process the payload. |
| However, the serializer itself (processing this data via |
| `normalizePayload`) will not know which model it is |
| deserializing. |
| |
| ```js |
| App.ApplicationSerializer = DS.ActiveModelSerializer; |
| App.PostSerializer = DS.JSONSerializer; |
| store.pushPayload('comment', pushData); // Will use the ApplicationSerializer |
| store.pushPayload('post', pushData); // Will use the PostSerializer |
| ``` |
| |
| @method pushPayload |
| @param {String} type Optionally, a model used to determine which serializer will be used |
| @param {Object} payload |
| */ |
| pushPayload: function (type, payload) { |
| var serializer; |
| if (!payload) { |
| payload = type; |
| serializer = defaultSerializer(this.container); |
| Ember.assert("You cannot use `store#pushPayload` without a type unless your default serializer defines `pushPayload`", serializer.pushPayload); |
| } else { |
| serializer = this.serializerFor(type); |
| } |
| serializer.pushPayload(this, payload); |
| }, |
| |
| /** |
| Update existing records in the store. Unlike [push](#method_push), |
| update will merge the new data properties with the existing |
| properties. This makes it safe to use with a subset of record |
| attributes. This method expects normalized data. |
| |
| `update` is useful if you app broadcasts partial updates to |
| records. |
| |
| ```js |
| App.Person = DS.Model.extend({ |
| firstName: DS.attr('string'), |
| lastName: DS.attr('string') |
| }); |
| |
| store.get('person', 1).then(function(tom) { |
| tom.get('firstName'); // Tom |
| tom.get('lastName'); // Dale |
| |
| var updateEvent = {id: 1, firstName: "TomHuda"}; |
| store.update('person', updateEvent); |
| |
| tom.get('firstName'); // TomHuda |
| tom.get('lastName'); // Dale |
| }); |
| ``` |
| |
| @method update |
| @param {String} type |
| @param {Object} data |
| @return {DS.Model} the record that was updated. |
| */ |
| update: function(type, data) { |
| Ember.assert("You must include an `id` for " + type + " in a hash passed to `update`", data.id != null); |
| |
| return this.push(type, data, true); |
| }, |
| |
| /** |
| If you have an Array of normalized data to push, |
| you can call `pushMany` with the Array, and it will |
| call `push` repeatedly for you. |
| |
| @method pushMany |
| @param {String or subclass of DS.Model} type |
| @param {Array} datas |
| @return {Array} |
| */ |
| pushMany: function(type, datas) { |
| return map(datas, function(data) { |
| return this.push(type, data); |
| }, this); |
| }, |
| |
| /** |
| If you have some metadata to set for a type |
| you can call `metaForType`. |
| |
| @method metaForType |
| @param {String or subclass of DS.Model} type |
| @param {Object} metadata |
| */ |
| metaForType: function(type, metadata) { |
| type = this.modelFor(type); |
| |
| Ember.merge(this.typeMapFor(type).metadata, metadata); |
| }, |
| |
| /** |
| Build a brand new record for a given type, ID, and |
| initial data. |
| |
| @method buildRecord |
| @private |
| @param {subclass of DS.Model} type |
| @param {String} id |
| @param {Object} data |
| @return {DS.Model} record |
| */ |
| buildRecord: function(type, id, data) { |
| var typeMap = this.typeMapFor(type), |
| idToRecord = typeMap.idToRecord; |
| |
| Ember.assert('The id ' + id + ' has already been used with another record of type ' + type.toString() + '.', !id || !idToRecord[id]); |
| Ember.assert("`" + Ember.inspect(type)+ "` does not appear to be an ember-data model", (typeof type._create === 'function') ); |
| |
| // lookupFactory should really return an object that creates |
| // instances with the injections applied |
| var record = type._create({ |
| id: id, |
| store: this, |
| container: this.container |
| }); |
| |
| if (data) { |
| record.setupData(data); |
| } |
| |
| // if we're creating an item, this process will be done |
| // later, once the object has been persisted. |
| if (id) { |
| idToRecord[id] = record; |
| } |
| |
| typeMap.records.push(record); |
| |
| return record; |
| }, |
| |
| // ............... |
| // . DESTRUCTION . |
| // ............... |
| |
| /** |
| When a record is destroyed, this un-indexes it and |
| removes it from any record arrays so it can be GCed. |
| |
| @method dematerializeRecord |
| @private |
| @param {DS.Model} record |
| */ |
| dematerializeRecord: function(record) { |
| var type = record.constructor, |
| typeMap = this.typeMapFor(type), |
| id = get(record, 'id'); |
| |
| record.updateRecordArrays(); |
| |
| if (id) { |
| delete typeMap.idToRecord[id]; |
| } |
| |
| var loc = indexOf(typeMap.records, record); |
| typeMap.records.splice(loc, 1); |
| }, |
| |
| // ........................ |
| // . RELATIONSHIP CHANGES . |
| // ........................ |
| |
| addRelationshipChangeFor: function(childRecord, childKey, parentRecord, parentKey, change) { |
| var clientId = childRecord.clientId, |
| parentClientId = parentRecord ? parentRecord : parentRecord; |
| var key = childKey + parentKey; |
| var changes = this._relationshipChanges; |
| if (!(clientId in changes)) { |
| changes[clientId] = {}; |
| } |
| if (!(parentClientId in changes[clientId])) { |
| changes[clientId][parentClientId] = {}; |
| } |
| if (!(key in changes[clientId][parentClientId])) { |
| changes[clientId][parentClientId][key] = {}; |
| } |
| changes[clientId][parentClientId][key][change.changeType] = change; |
| }, |
| |
| removeRelationshipChangeFor: function(clientRecord, childKey, parentRecord, parentKey, type) { |
| var clientId = clientRecord.clientId, |
| parentClientId = parentRecord ? parentRecord.clientId : parentRecord; |
| var changes = this._relationshipChanges; |
| var key = childKey + parentKey; |
| if (!(clientId in changes) || !(parentClientId in changes[clientId]) || !(key in changes[clientId][parentClientId])){ |
| return; |
| } |
| delete changes[clientId][parentClientId][key][type]; |
| }, |
| |
| relationshipChangePairsFor: function(record){ |
| var toReturn = []; |
| |
| if( !record ) { return toReturn; } |
| |
| //TODO(Igor) What about the other side |
| var changesObject = this._relationshipChanges[record.clientId]; |
| for (var objKey in changesObject){ |
| if(changesObject.hasOwnProperty(objKey)){ |
| for (var changeKey in changesObject[objKey]){ |
| if(changesObject[objKey].hasOwnProperty(changeKey)){ |
| toReturn.push(changesObject[objKey][changeKey]); |
| } |
| } |
| } |
| } |
| return toReturn; |
| }, |
| |
| // ...................... |
| // . PER-TYPE ADAPTERS |
| // ...................... |
| |
| /** |
| Returns the adapter for a given type. |
| |
| @method adapterFor |
| @private |
| @param {subclass of DS.Model} type |
| @return DS.Adapter |
| */ |
| adapterFor: function(type) { |
| var container = this.container, adapter; |
| |
| if (container) { |
| adapter = container.lookup('adapter:' + type.typeKey) || container.lookup('adapter:application'); |
| } |
| |
| return adapter || get(this, 'defaultAdapter'); |
| }, |
| |
| // .............................. |
| // . RECORD CHANGE NOTIFICATION . |
| // .............................. |
| |
| /** |
| Returns an instance of the serializer for a given type. For |
| example, `serializerFor('person')` will return an instance of |
| `App.PersonSerializer`. |
| |
| If no `App.PersonSerializer` is found, this method will look |
| for an `App.ApplicationSerializer` (the default serializer for |
| your entire application). |
| |
| If no `App.ApplicationSerializer` is found, it will fall back |
| to an instance of `DS.JSONSerializer`. |
| |
| @method serializerFor |
| @private |
| @param {String} type the record to serialize |
| @return {DS.Serializer} |
| */ |
| serializerFor: function(type) { |
| type = this.modelFor(type); |
| var adapter = this.adapterFor(type); |
| |
| return serializerFor(this.container, type.typeKey, adapter && adapter.defaultSerializer); |
| }, |
| |
| willDestroy: function() { |
| var typeMaps = this.typeMaps; |
| var keys = Ember.keys(typeMaps); |
| var store = this; |
| |
| var types = map(keys, byType); |
| |
| this.recordArrayManager.destroy(); |
| |
| forEach(types, this.unloadAll, this); |
| |
| function byType(entry) { |
| return typeMaps[entry]['type']; |
| } |
| |
| }, |
| |
| /** |
| All typeKeys are camelCase internally. Changing this function may |
| require changes to other normalization hooks (such as typeForRoot). |
| |
| @method _normalizeTypeKey |
| @private |
| @param {String} type |
| @return {String} if the adapter can generate one, an ID |
| */ |
| _normalizeTypeKey: function(key) { |
| return camelize(singularize(key)); |
| } |
| }); |
| |
| function normalizeRelationships(store, type, data, record) { |
| type.eachRelationship(function(key, relationship) { |
| // A link (usually a URL) was already provided in |
| // normalized form |
| if (data.links && data.links[key]) { |
| if (record && relationship.options.async) { record._relationships[key] = null; } |
| return; |
| } |
| |
| var kind = relationship.kind, |
| value = data[key]; |
| |
| if (value == null) { return; } |
| |
| if (kind === 'belongsTo') { |
| deserializeRecordId(store, data, key, relationship, value); |
| } else if (kind === 'hasMany') { |
| deserializeRecordIds(store, data, key, relationship, value); |
| addUnsavedRecords(record, key, value); |
| } |
| }); |
| |
| return data; |
| } |
| |
| function deserializeRecordId(store, data, key, relationship, id) { |
| if (!Model) { Model = requireModule("ember-data/lib/system/model")["Model"]; } |
| if (isNone(id) || id instanceof Model) { |
| return; |
| } |
| |
| var type; |
| |
| if (typeof id === 'number' || typeof id === 'string') { |
| type = typeFor(relationship, key, data); |
| data[key] = store.recordForId(type, id); |
| } else if (typeof id === 'object') { |
| // polymorphic |
| data[key] = store.recordForId(id.type, id.id); |
| } |
| } |
| |
| function typeFor(relationship, key, data) { |
| if (relationship.options.polymorphic) { |
| return data[key + "Type"]; |
| } else { |
| return relationship.type; |
| } |
| } |
| |
| function deserializeRecordIds(store, data, key, relationship, ids) { |
| for (var i=0, l=ids.length; i<l; i++) { |
| deserializeRecordId(store, ids, i, relationship, ids[i]); |
| } |
| } |
| |
| // If there are any unsaved records that are in a hasMany they won't be |
| // in the payload, so add them back in manually. |
| function addUnsavedRecords(record, key, data) { |
| if(record) { |
| Ember.A(data).pushObjects(record.get(key).filterBy('isNew')); |
| } |
| } |
| |
| // Delegation to the adapter and promise management |
| /** |
| A `PromiseArray` is an object that acts like both an `Ember.Array` |
| and a promise. When the promise is resolved the resulting value |
| will be set to the `PromiseArray`'s `content` property. This makes |
| it easy to create data bindings with the `PromiseArray` that will be |
| updated when the promise resolves. |
| |
| For more information see the [Ember.PromiseProxyMixin |
| documentation](/api/classes/Ember.PromiseProxyMixin.html). |
| |
| Example |
| |
| ```javascript |
| var promiseArray = DS.PromiseArray.create({ |
| promise: $.getJSON('/some/remote/data.json') |
| }); |
| |
| promiseArray.get('length'); // 0 |
| |
| promiseArray.then(function() { |
| promiseArray.get('length'); // 100 |
| }); |
| ``` |
| |
| @class PromiseArray |
| @namespace DS |
| @extends Ember.ArrayProxy |
| @uses Ember.PromiseProxyMixin |
| */ |
| PromiseArray = Ember.ArrayProxy.extend(Ember.PromiseProxyMixin); |
| /** |
| A `PromiseObject` is an object that acts like both an `Ember.Object` |
| and a promise. When the promise is resolved the the resulting value |
| will be set to the `PromiseObject`'s `content` property. This makes |
| it easy to create data bindings with the `PromiseObject` that will |
| be updated when the promise resolves. |
| |
| For more information see the [Ember.PromiseProxyMixin |
| documentation](/api/classes/Ember.PromiseProxyMixin.html). |
| |
| Example |
| |
| ```javascript |
| var promiseObject = DS.PromiseObject.create({ |
| promise: $.getJSON('/some/remote/data.json') |
| }); |
| |
| promiseObject.get('name'); // null |
| |
| promiseObject.then(function() { |
| promiseObject.get('name'); // 'Tomster' |
| }); |
| ``` |
| |
| @class PromiseObject |
| @namespace DS |
| @extends Ember.ObjectProxy |
| @uses Ember.PromiseProxyMixin |
| */ |
| PromiseObject = Ember.ObjectProxy.extend(Ember.PromiseProxyMixin); |
| |
| function promiseObject(promise, label) { |
| return PromiseObject.create({ |
| promise: Promise.cast(promise, label) |
| }); |
| } |
| |
| function promiseArray(promise, label) { |
| return PromiseArray.create({ |
| promise: Promise.cast(promise, label) |
| }); |
| } |
| |
| function isThenable(object) { |
| return object && typeof object.then === 'function'; |
| } |
| |
| function serializerFor(container, type, defaultSerializer) { |
| return container.lookup('serializer:'+type) || |
| container.lookup('serializer:application') || |
| container.lookup('serializer:' + defaultSerializer) || |
| container.lookup('serializer:-default'); |
| } |
| |
| function defaultSerializer(container) { |
| return container.lookup('serializer:application') || |
| container.lookup('serializer:-default'); |
| } |
| |
| function serializerForAdapter(adapter, type) { |
| var serializer = adapter.serializer, |
| defaultSerializer = adapter.defaultSerializer, |
| container = adapter.container; |
| |
| if (container && serializer === undefined) { |
| serializer = serializerFor(container, type.typeKey, defaultSerializer); |
| } |
| |
| if (serializer === null || serializer === undefined) { |
| serializer = { |
| extract: function(store, type, payload) { return payload; } |
| }; |
| } |
| |
| return serializer; |
| } |
| |
| function _find(adapter, store, type, id) { |
| var promise = adapter.find(store, type, id), |
| serializer = serializerForAdapter(adapter, type), |
| label = "DS: Handle Adapter#find of " + type + " with id: " + id; |
| |
| return Promise.cast(promise, label).then(function(adapterPayload) { |
| Ember.assert("You made a request for a " + type.typeKey + " with id " + id + ", but the adapter's response did not have any data", adapterPayload); |
| var payload = serializer.extract(store, type, adapterPayload, id, 'find'); |
| |
| return store.push(type, payload); |
| }, function(error) { |
| var record = store.getById(type, id); |
| record.notFound(); |
| throw error; |
| }, "DS: Extract payload of '" + type + "'"); |
| } |
| |
| function _findMany(adapter, store, type, ids, owner) { |
| var promise = adapter.findMany(store, type, ids, owner), |
| serializer = serializerForAdapter(adapter, type), |
| label = "DS: Handle Adapter#findMany of " + type; |
| |
| return Promise.cast(promise, label).then(function(adapterPayload) { |
| var payload = serializer.extract(store, type, adapterPayload, null, 'findMany'); |
| |
| Ember.assert("The response from a findMany must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array'); |
| |
| store.pushMany(type, payload); |
| }, null, "DS: Extract payload of " + type); |
| } |
| |
| function _findHasMany(adapter, store, record, link, relationship) { |
| var promise = adapter.findHasMany(store, record, link, relationship), |
| serializer = serializerForAdapter(adapter, relationship.type), |
| label = "DS: Handle Adapter#findHasMany of " + record + " : " + relationship.type; |
| |
| return Promise.cast(promise, label).then(function(adapterPayload) { |
| var payload = serializer.extract(store, relationship.type, adapterPayload, null, 'findHasMany'); |
| |
| Ember.assert("The response from a findHasMany must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array'); |
| |
| var records = store.pushMany(relationship.type, payload); |
| record.updateHasMany(relationship.key, records); |
| }, null, "DS: Extract payload of " + record + " : hasMany " + relationship.type); |
| } |
| |
| function _findBelongsTo(adapter, store, record, link, relationship) { |
| var promise = adapter.findBelongsTo(store, record, link, relationship), |
| serializer = serializerForAdapter(adapter, relationship.type), |
| label = "DS: Handle Adapter#findBelongsTo of " + record + " : " + relationship.type; |
| |
| return Promise.cast(promise, label).then(function(adapterPayload) { |
| var payload = serializer.extract(store, relationship.type, adapterPayload, null, 'findBelongsTo'); |
| var record = store.push(relationship.type, payload); |
| |
| record.updateBelongsTo(relationship.key, record); |
| return record; |
| }, null, "DS: Extract payload of " + record + " : " + relationship.type); |
| } |
| |
| function _findAll(adapter, store, type, sinceToken) { |
| var promise = adapter.findAll(store, type, sinceToken), |
| serializer = serializerForAdapter(adapter, type), |
| label = "DS: Handle Adapter#findAll of " + type; |
| |
| return Promise.cast(promise, label).then(function(adapterPayload) { |
| var payload = serializer.extract(store, type, adapterPayload, null, 'findAll'); |
| |
| Ember.assert("The response from a findAll must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array'); |
| |
| store.pushMany(type, payload); |
| store.didUpdateAll(type); |
| return store.all(type); |
| }, null, "DS: Extract payload of findAll " + type); |
| } |
| |
| function _findQuery(adapter, store, type, query, recordArray) { |
| var promise = adapter.findQuery(store, type, query, recordArray), |
| serializer = serializerForAdapter(adapter, type), |
| label = "DS: Handle Adapter#findQuery of " + type; |
| |
| return Promise.cast(promise, label).then(function(adapterPayload) { |
| var payload = serializer.extract(store, type, adapterPayload, null, 'findQuery'); |
| |
| Ember.assert("The response from a findQuery must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array'); |
| |
| recordArray.load(payload); |
| return recordArray; |
| }, null, "DS: Extract payload of findQuery " + type); |
| } |
| |
| function _commit(adapter, store, operation, record) { |
| var type = record.constructor, |
| promise = adapter[operation](store, type, record), |
| serializer = serializerForAdapter(adapter, type), |
| label = "DS: Extract and notify about " + operation + " completion of " + record; |
| |
| Ember.assert("Your adapter's '" + operation + "' method must return a promise, but it returned " + promise, isThenable(promise)); |
| |
| return promise.then(function(adapterPayload) { |
| var payload; |
| |
| if (adapterPayload) { |
| payload = serializer.extract(store, type, adapterPayload, get(record, 'id'), operation); |
| } else { |
| payload = adapterPayload; |
| } |
| |
| store.didSaveRecord(record, payload); |
| return record; |
| }, function(reason) { |
| if (reason instanceof InvalidError) { |
| store.recordWasInvalid(record, reason.errors); |
| } else { |
| store.recordWasError(record, reason); |
| } |
| |
| throw reason; |
| }, label); |
| } |
| |
| __exports__.Store = Store; |
| __exports__.PromiseArray = PromiseArray; |
| __exports__.PromiseObject = PromiseObject; |
| __exports__["default"] = Store; |
| }); |
| define("ember-data/lib/transforms", |
| ["./transforms/base","./transforms/number","./transforms/date","./transforms/string","./transforms/boolean","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) { |
| "use strict"; |
| var Transform = __dependency1__["default"]; |
| var NumberTransform = __dependency2__["default"]; |
| var DateTransform = __dependency3__["default"]; |
| var StringTransform = __dependency4__["default"]; |
| var BooleanTransform = __dependency5__["default"]; |
| |
| __exports__.Transform = Transform; |
| __exports__.NumberTransform = NumberTransform; |
| __exports__.DateTransform = DateTransform; |
| __exports__.StringTransform = StringTransform; |
| __exports__.BooleanTransform = BooleanTransform; |
| }); |
| define("ember-data/lib/transforms/base", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| /** |
| 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 |
| // Converts centigrade in the JSON to fahrenheit in the app |
| App.TemperatureTransform = DS.Transform.extend({ |
| deserialize: function(serialized) { |
| return (serialized * 1.8) + 32; |
| }, |
| serialize: function(deserialized) { |
| return (deserialized - 32) / 1.8; |
| } |
| }); |
| ``` |
| |
| Usage |
| |
| ```javascript |
| var attr = DS.attr; |
| App.Requirement = DS.Model.extend({ |
| name: attr('string'), |
| optionsArray: attr('raw') |
| }); |
| ``` |
| |
| @class Transform |
| @namespace DS |
| */ |
| var 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() |
| |
| }); |
| |
| __exports__["default"] = Transform; |
| }); |
| define("ember-data/lib/transforms/boolean", |
| ["./base","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var Transform = __dependency1__["default"]; |
| |
| /** |
| 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 |
| */ |
| var BooleanTransform = 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); |
| } |
| }); |
| __exports__["default"] = BooleanTransform; |
| }); |
| define("ember-data/lib/transforms/date", |
| ["./base","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| /** |
| 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 |
| */ |
| var Transform = __dependency1__["default"]; |
| var DateTransform = 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) { |
| var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; |
| var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; |
| |
| var pad = function(num) { |
| return num < 10 ? "0"+num : ""+num; |
| }; |
| |
| var utcYear = date.getUTCFullYear(), |
| utcMonth = date.getUTCMonth(), |
| utcDayOfMonth = date.getUTCDate(), |
| utcDay = date.getUTCDay(), |
| utcHours = date.getUTCHours(), |
| utcMinutes = date.getUTCMinutes(), |
| utcSeconds = date.getUTCSeconds(); |
| |
| |
| var dayOfWeek = days[utcDay]; |
| var dayOfMonth = pad(utcDayOfMonth); |
| var month = months[utcMonth]; |
| |
| return dayOfWeek + ", " + dayOfMonth + " " + month + " " + utcYear + " " + |
| pad(utcHours) + ":" + pad(utcMinutes) + ":" + pad(utcSeconds) + " GMT"; |
| } else { |
| return null; |
| } |
| } |
| |
| }); |
| |
| __exports__["default"] = DateTransform; |
| }); |
| define("ember-data/lib/transforms/number", |
| ["./base","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var Transform = __dependency1__["default"]; |
| |
| 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 |
| */ |
| var NumberTransform = Transform.extend({ |
| |
| deserialize: function(serialized) { |
| return empty(serialized) ? null : Number(serialized); |
| }, |
| |
| serialize: function(deserialized) { |
| return empty(deserialized) ? null : Number(deserialized); |
| } |
| }); |
| |
| __exports__["default"] = NumberTransform; |
| }); |
| define("ember-data/lib/transforms/string", |
| ["./base","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var Transform = __dependency1__["default"]; |
| 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 |
| */ |
| var StringTransform = Transform.extend({ |
| |
| deserialize: function(serialized) { |
| return none(serialized) ? null : String(serialized); |
| }, |
| |
| serialize: function(deserialized) { |
| return none(deserialized) ? null : String(deserialized); |
| } |
| |
| }); |
| |
| __exports__["default"] = StringTransform; |
| }); |
| define("ember-inflector/lib/ext/string", |
| ["../system/string"], |
| function(__dependency1__) { |
| "use strict"; |
| var pluralize = __dependency1__.pluralize; |
| var singularize = __dependency1__.singularize; |
| |
| if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) { |
| /** |
| See {{#crossLink "Ember.String/pluralize"}}{{/crossLink}} |
| |
| @method pluralize |
| @for String |
| */ |
| String.prototype.pluralize = function() { |
| return pluralize(this); |
| }; |
| |
| /** |
| See {{#crossLink "Ember.String/singularize"}}{{/crossLink}} |
| |
| @method singularize |
| @for String |
| */ |
| String.prototype.singularize = function() { |
| return singularize(this); |
| }; |
| } |
| }); |
| define("ember-inflector/lib/main", |
| ["./system","./ext/string","exports"], |
| function(__dependency1__, __dependency2__, __exports__) { |
| "use strict"; |
| var Inflector = __dependency1__.Inflector; |
| var defaultRules = __dependency1__.defaultRules; |
| var pluralize = __dependency1__.pluralize; |
| var singularize = __dependency1__.singularize; |
| |
| Inflector.defaultRules = defaultRules; |
| Ember.Inflector = Inflector; |
| |
| Ember.String.pluralize = pluralize; |
| Ember.String.singularize = singularize; |
| |
| |
| __exports__["default"] = Inflector; |
| |
| __exports__.pluralize = pluralize; |
| __exports__.singularize = singularize; |
| }); |
| define("ember-inflector/lib/system", |
| ["./system/inflector","./system/string","./system/inflections","exports"], |
| function(__dependency1__, __dependency2__, __dependency3__, __exports__) { |
| "use strict"; |
| var Inflector = __dependency1__["default"]; |
| |
| var pluralize = __dependency2__.pluralize; |
| var singularize = __dependency2__.singularize; |
| |
| var defaultRules = __dependency3__["default"]; |
| |
| |
| Inflector.inflector = new Inflector(defaultRules); |
| |
| __exports__.Inflector = Inflector; |
| __exports__.singularize = singularize; |
| __exports__.pluralize = pluralize; |
| __exports__.defaultRules = defaultRules; |
| }); |
| define("ember-inflector/lib/system/inflections", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| var 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' |
| ] |
| }; |
| |
| __exports__["default"] = defaultRules; |
| }); |
| define("ember-inflector/lib/system/inflector", |
| ["exports"], |
| function(__exports__) { |
| "use strict"; |
| var BLANK_REGEX = /^\s*$/; |
| |
| function loadUncountable(rules, uncountable) { |
| for (var i = 0, length = uncountable.length; i < length; i++) { |
| rules.uncountable[uncountable[i].toLowerCase()] = true; |
| } |
| } |
| |
| function loadIrregular(rules, irregularPairs) { |
| var pair; |
| |
| for (var i = 0, length = irregularPairs.length; i < length; i++) { |
| pair = irregularPairs[i]; |
| |
| rules.irregular[pair[0].toLowerCase()] = pair[1]; |
| rules.irregularInverse[pair[1].toLowerCase()] = pair[0]; |
| } |
| } |
| |
| /** |
| Inflector.Ember provides a mechanism for supplying inflection rules for your |
| application. Ember includes a default set of inflection rules, and provides an |
| API for providing additional rules. |
| |
| Examples: |
| |
| Creating an inflector with no rules. |
| |
| ```js |
| var inflector = new Ember.Inflector(); |
| ``` |
| |
| Creating an inflector with the default ember ruleset. |
| |
| ```js |
| var inflector = new Ember.Inflector(Ember.Inflector.defaultRules); |
| |
| inflector.pluralize('cow'); //=> '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; |
| } |
| }; |
| |
| __exports__["default"] = Inflector; |
| }); |
| define("ember-inflector/lib/system/string", |
| ["./inflector","exports"], |
| function(__dependency1__, __exports__) { |
| "use strict"; |
| var Inflector = __dependency1__["default"]; |
| var pluralize = function(word) { |
| return Inflector.inflector.pluralize(word); |
| }; |
| |
| var singularize = function(word) { |
| return Inflector.inflector.singularize(word); |
| }; |
| |
| __exports__.pluralize = pluralize; |
| __exports__.singularize = singularize; |
| }); |
| global.DS = requireModule('ember-data/lib/main')['default']; |
| }(Ember.lookup)); |