mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
12053 lines
No EOL
366 KiB
JavaScript
12053 lines
No EOL
366 KiB
JavaScript
/*!
|
||
* @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)); |