diff --git a/README.md b/README.md
index c8d0bf5..c1c2dd1 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,7 @@ Configuration happens in `config/application.js` as part of the call to `create(
window.App = require('app').default.create({
deviseEmberAuth: {
signInPath: "/sign-in", // the URL users will see in the browser for the sign in page
+ userModelType: "user", // **ember-data only** name of the model that represents your user; same thing you'd pass to `store.find("...")` in a route
deviseSignInPath: "/users/sign_in", // the URL to POST to for creating a session
deviseSignOutPath: "/users/sign_out", // the URL to DELETE to for signing out
currentSessionPath: "/sessions/current" // the URL for getting the current signed-in state; this is currently added by the gem
@@ -155,4 +156,15 @@ export default Ember.Route.extend({
});
```
+#### Display information about currently signed-in user
+
+You can access a `currentUser` property in any template to get details about the current user. If you are using ember-data, this will deserialize the `/sessions/current` response (provided by the support gem) using a configurable model name (defaults to "user").
+
+For example, assuming you have a fullName & email property on your user model, you can say:
+
+```handlebars
+Signed in as: {{currentUser.fullName}} ({{currentUser.email}})
+```
+
+
[©2014 D-I](http://www.d-i.co)
diff --git a/app/models/authenticator.js b/app/models/authenticator.js
index 78b1bef..6004569 100644
--- a/app/models/authenticator.js
+++ b/app/models/authenticator.js
@@ -11,7 +11,14 @@ var Authenticator = Ember.Object.extend({
passwordWillChange: function() {
this.set("passwordInvalid", false);
}.observesBefore("password"),
- setupSession: function(session) {
+ setupSession: function(store, session) {
+
+ if(store && typeof store.pushPayload === 'function') {
+ var type = this.get("userModelType");
+ store.pushPayload(type, session);
+ session = store.find(type, session[type].id);
+ }
+
this.set("isSignedIn", true)
.set("currentUser", session);
return session;
@@ -20,19 +27,20 @@ var Authenticator = Ember.Object.extend({
this.set("isSignedIn", false)
.set("currentUser", null);
},
+ // store: ember-data store instance; how do we handle non-ember-data?
// Options: skip: true|false // Doesn't make ajax request for session
- loadSession: function(storeOrFinder, options) {
+ loadSession: function(store, options) {
if(this.get("isSignedIn") && this.get("currentUser")) {
return Ember.RSVP.resolve(this.get("currentUser"));
} else if(options.skip) {
return Ember.RSVP.resolve(null);
} else {
- return this._loadSession(options);
+ return this._loadSession(store, options);
}
},
- _loadSession: function () {
+ _loadSession: function (store) {
var result,
- setup = this.setupSession.bind(this),
+ setup = this.setupSession.bind(this, store),
teardown = this.teardownSession.bind(this);
return this.ajax("get", this.get("currentSessionPath"))
diff --git a/config/configuration.js b/config/configuration.js
index 93f56f5..762a633 100644
--- a/config/configuration.js
+++ b/config/configuration.js
@@ -2,6 +2,7 @@ var defaults = {
signInPath: "/sign-in",
deviseSignInPath: "/users/sign_in",
deviseSignOutPath: "/users/sign_out",
+ userModelType: "user",
currentSessionPath: "/sessions/current"
};
diff --git a/config/initializers/authenticator.js b/config/initializers/authenticator.js
index fe15a06..494c0d3 100644
--- a/config/initializers/authenticator.js
+++ b/config/initializers/authenticator.js
@@ -6,12 +6,14 @@ var initializer = {
initialize: function(container, app) {
var signInPath = getSetting(app, "deviseSignInPath"),
signOutPath = getSetting(app, "deviseSignOutPath"),
+ userModelType = getSetting(app, "userModelType"),
currentSessionPath = getSetting(app, "currentSessionPath");
var auth = Authenticator.create();
auth.set("signInPath", signInPath)
.set("signOutPath", signOutPath)
+ .set("userModelType", userModelType)
.set("currentSessionPath", currentSessionPath);
container.register("devise-simple-auth:authenticator", auth, {instantiate: false});
@@ -21,4 +23,3 @@ var initializer = {
};
export default initializer;
-
diff --git a/examples/ember-rails/Gemfile.lock b/examples/ember-rails/Gemfile.lock
index 004f8cc..03de92e 100644
--- a/examples/ember-rails/Gemfile.lock
+++ b/examples/ember-rails/Gemfile.lock
@@ -1,7 +1,7 @@
PATH
remote: ../../
specs:
- ember_devise_simple_auth (0.4.2)
+ ember_devise_simple_auth (0.4.4)
devise (>= 3.0.0)
GEM
diff --git a/examples/ember-rails/app/assets/javascripts/models/user.js b/examples/ember-rails/app/assets/javascripts/models/user.js
new file mode 100644
index 0000000..ed8a7d5
--- /dev/null
+++ b/examples/ember-rails/app/assets/javascripts/models/user.js
@@ -0,0 +1,9 @@
+DeviseSimpleAuthExample.User = DS.Model.extend({
+ firstName: DS.attr('string'),
+ lastName: DS.attr('string'),
+ email: DS.attr('string'),
+ fullName: function() {
+ return [this.get('firstName'),
+ this.get('lastName')].join(' ');
+ }.property('firstName', 'lastName')
+});
diff --git a/examples/ember-rails/app/assets/javascripts/store.js b/examples/ember-rails/app/assets/javascripts/store.js
index 7db86ad..3e54447 100644
--- a/examples/ember-rails/app/assets/javascripts/store.js
+++ b/examples/ember-rails/app/assets/javascripts/store.js
@@ -3,5 +3,5 @@
DeviseSimpleAuthExample.Store = DS.Store.extend({
// Override the default adapter with the `DS.ActiveModelAdapter` which
// is built to work nicely with the ActiveModel::Serializers gem.
- adapter: '_ams'
+ adapter: '-active-model'
});
diff --git a/examples/ember-rails/app/assets/javascripts/templates/dashboard.hbs b/examples/ember-rails/app/assets/javascripts/templates/dashboard.hbs
index bcd73d9..33aafd3 100644
--- a/examples/ember-rails/app/assets/javascripts/templates/dashboard.hbs
+++ b/examples/ember-rails/app/assets/javascripts/templates/dashboard.hbs
@@ -1,6 +1,7 @@
Dashboard
-You are signed in as: {{currentUser.email}}
+You are signed in as: {{currentUser.fullName}} ({{currentUser.email}})
+
This is your dashboard, where you might some some really interesting stuff.
Actually, not really. We all know there is nothing actually interesting in this world.
diff --git a/examples/ember-rails/app/serializers/user_serializer.rb b/examples/ember-rails/app/serializers/user_serializer.rb
new file mode 100644
index 0000000..f11f20a
--- /dev/null
+++ b/examples/ember-rails/app/serializers/user_serializer.rb
@@ -0,0 +1,3 @@
+class UserSerializer < ActiveModel::Serializer
+ attributes :id, :first_name, :last_name, :email
+end
diff --git a/examples/ember-rails/db/migrate/20140319152327_add_name_to_users.rb b/examples/ember-rails/db/migrate/20140319152327_add_name_to_users.rb
new file mode 100644
index 0000000..2f30a88
--- /dev/null
+++ b/examples/ember-rails/db/migrate/20140319152327_add_name_to_users.rb
@@ -0,0 +1,6 @@
+class AddNameToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :first_name, :string
+ add_column :users, :last_name, :string
+ end
+end
diff --git a/examples/ember-rails/db/schema.rb b/examples/ember-rails/db/schema.rb
index 4296b86..9e3f149 100644
--- a/examples/ember-rails/db/schema.rb
+++ b/examples/ember-rails/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20140228151317) do
+ActiveRecord::Schema.define(version: 20140319152327) do
create_table "users", force: true do |t|
t.datetime "created_at"
@@ -26,6 +26,8 @@
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
+ t.string "first_name"
+ t.string "last_name"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
diff --git a/examples/ember-rails/db/seeds.rb b/examples/ember-rails/db/seeds.rb
index c6f4571..d1d7d00 100644
--- a/examples/ember-rails/db/seeds.rb
+++ b/examples/ember-rails/db/seeds.rb
@@ -6,7 +6,7 @@
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
# Mayor.create(name: 'Emanuel', city: cities.first)
password = 'abcd1234'
-user = User.create_with(password: 'abcd1234').find_or_create_by(email: 'ember@example.com')
+user = User.create_with(password: 'abcd1234').find_or_create_by(email: 'ember@example.com', first_name: 'Tomster', last_name: 'Katz')
puts
puts "Great! You've created a user!"
diff --git a/examples/ember-rails/vendor/assets/ember/development/ember-data.js b/examples/ember-rails/vendor/assets/ember/development/ember-data.js
new file mode 100644
index 0000000..e4133a4
--- /dev/null
+++ b/examples/ember-rails/vendor/assets/ember/development/ember-data.js
@@ -0,0 +1,11134 @@
+// Fetched from channel: beta, with url http://builds.emberjs.com/beta/ember-data.js
+// Fetched on: 2014-03-19T15:47:46Z
+/*!
+ * @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.7.f87cba88
+ */
+(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 "famous_people"
+ ```
+
+ @method pathForType
+ @param {String} type
+ @returns String
+ */
+ pathForType: function(type) {
+ var decamelized = decamelize(type);
+ var underscored = underscore(decamelized);
+ return pluralize(underscored);
+ },
+
+ /**
+ The ActiveModelAdapter overrides the `ajaxError` method
+ to return a DS.InvalidError for all 422 Unprocessable Entity
+ responses.
+
+ A 422 HTTP response from the server generally implies that the request
+ was well formed but the API was unable to process it because the
+ content was not semantically correct or meaningful per the API.
+
+ For more information on 422 HTTP Error code see 11.2 WebDAV RFC 4918
+ https://tools.ietf.org/html/rfc4918#section-11.2
+
+ @method ajaxError
+ @param jqXHR
+ @returns error
+ */
+ ajaxError: function(jqXHR) {
+ var error = this._super(jqXHR);
+
+ if (jqXHR && jqXHR.status === 422) {
+ var response = Ember.$.parseJSON(jqXHR.responseText),
+ errors = {};
+
+ if (response.errors !== undefined) {
+ var jsonErrors = response.errors;
+
+ forEach(Ember.keys(jsonErrors), function(key) {
+ errors[Ember.String.camelize(key)] = jsonErrors[key];
+ });
+ }
+
+ return new 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;
+
+ var ActiveModelSerializer = RESTSerializer.extend({
+ // SERIALIZE
+
+ /**
+ Converts camelcased attributes to underscored when serializing.
+
+ @method keyForAttribute
+ @param {String} attribute
+ @returns String
+ */
+ keyForAttribute: function(attr) {
+ return decamelize(attr);
+ },
+
+ /**
+ Underscores relationship names and appends "_id" or "_ids" when serializing
+ relationship keys.
+
+ @method keyForRelationship
+ @param {String} key
+ @param {String} kind
+ @returns String
+ */
+ keyForRelationship: function(key, kind) {
+ key = decamelize(key);
+ if (kind === "belongsTo") {
+ return key + "_id";
+ } else if (kind === "hasMany") {
+ return singularize(key) + "_ids";
+ } else {
+ return key;
+ }
+ },
+
+ /**
+ Does not serialize hasMany relationships by default.
+ */
+ serializeHasMany: Ember.K,
+
+ /**
+ Underscores the JSON root keys when serializing.
+
+ @method serializeIntoHash
+ @param {Object} hash
+ @param {subclass of DS.Model} type
+ @param {DS.Model} record
+ @param {Object} options
+ */
+ serializeIntoHash: function(data, type, record, options) {
+ var root = underscore(decamelize(type.typeKey));
+ data[root] = this.serialize(record, options);
+ },
+
+ /**
+ Serializes a polymorphic type as a fully capitalized model name.
+
+ @method serializePolymorphicType
+ @param {DS.Model} record
+ @param {Object} json
+ @param relationship
+ */
+ serializePolymorphicType: function(record, json, relationship) {
+ var key = relationship.key,
+ belongsTo = get(record, key);
+ key = this.keyForAttribute(key);
+ json[key + "_type"] = capitalize(camelize(belongsTo.constructor.typeKey));
+ },
+
+ // EXTRACT
+
+ /**
+ Extracts the model typeKey from underscored root objects.
+
+ @method typeForRoot
+ @param {String} root
+ @returns String the model's typeKey
+ */
+ typeForRoot: function(root) {
+ var camelized = camelize(root);
+ return singularize(camelized);
+ },
+
+ /**
+ Add extra step to `DS.RESTSerializer.normalize` so links are
+ normalized.
+
+ If your payload looks like this
+
+ ```js
+ {
+ "post": {
+ "id": 1,
+ "title": "Rails is omakase",
+ "links": { "flagged_comments": "api/comments/flagged" }
+ }
+ }
+ ```
+ The normalized version would look like this
+
+ ```js
+ {
+ "post": {
+ "id": 1,
+ "title": "Rails is omakase",
+ "links": { "flaggedComments": "api/comments/flagged" }
+ }
+ }
+ ```
+
+ @method normalize
+ @param {subclass of DS.Model} type
+ @param {Object} hash
+ @param {String} prop
+ @returns Object
+ */
+
+ normalize: function(type, hash, prop) {
+ this.normalizeLinks(hash);
+
+ return this._super(type, hash, prop);
+ },
+
+ /**
+ Convert `snake_cased` links to `camelCase`
+
+ @method normalizeLinks
+ @param {Object} hash
+ */
+
+ normalizeLinks: function(data){
+ if (data.links) {
+ var links = data.links;
+
+ for (var link in links) {
+ var camelizedLink = camelize(link);
+
+ if (camelizedLink !== link) {
+ links[camelizedLink] = links[link];
+ delete links[link];
+ }
+ }
+ }
+ },
+
+ /**
+ Normalize the polymorphic type from the JSON.
+
+ Normalize:
+ ```js
+ {
+ id: "1"
+ minion: { type: "evil_minion", id: "12"}
+ }
+ ```
+
+ To:
+ ```js
+ {
+ id: "1"
+ minion: { type: "evilMinion", id: "12"}
+ }
+ ```
+
+ @method normalizeRelationships
+ @private
+ */
+ normalizeRelationships: function(type, hash) {
+ var payloadKey, payload;
+
+ if (this.keyForRelationship) {
+ type.eachRelationship(function(key, relationship) {
+ if (relationship.options.polymorphic) {
+ payloadKey = this.keyForAttribute(key);
+ payload = hash[payloadKey];
+ if (payload && payload.type) {
+ payload.type = this.typeForRoot(payload.type);
+ } else if (payload && relationship.kind === "hasMany") {
+ var self = this;
+ forEach(payload, function(single) {
+ single.type = self.typeForRoot(single.type);
+ });
+ }
+ } else {
+ payloadKey = this.keyForRelationship(key, relationship.kind);
+ payload = hash[payloadKey];
+ }
+
+ hash[key] = payload;
+
+ if (key !== payloadKey) {
+ delete hash[payloadKey];
+ }
+ }, this);
+ }
+ }
+ });
+
+ __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 pluralize = __dependency1__.pluralize;
+
+ /**
+ The EmbeddedRecordsMixin allows you to add embedded record support to your
+ serializers.
+ To set up embedded records, you include the mixin into the serializer and then
+ define your embedded relations.
+
+ ```js
+ App.PostSerializer = DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, {
+ attrs: {
+ comments: {embedded: 'always'}
+ }
+ })
+ ```
+
+ Currently only `{embedded: 'always'}` records are supported.
+
+ @class EmbeddedRecordsMixin
+ @namespace DS
+ */
+ var EmbeddedRecordsMixin = Ember.Mixin.create({
+
+ /**
+ Serialize has-may relationship when it is configured as embedded objects.
+
+ @method serializeHasMany
+ */
+ serializeHasMany: function(record, json, relationship) {
+ var key = relationship.key,
+ attrs = get(this, 'attrs'),
+ embed = attrs && attrs[key] && attrs[key].embedded === 'always';
+
+ if (embed) {
+ json[this.keyForAttribute(key)] = get(record, key).map(function(relation) {
+ var data = relation.serialize(),
+ primaryKey = get(this, 'primaryKey');
+
+ data[primaryKey] = get(relation, primaryKey);
+
+ return data;
+ }, this);
+ }
+ },
+
+ /**
+ Extract embedded objects out of the payload for a single object
+ and add them as sideloaded objects instead.
+
+ @method extractSingle
+ */
+ extractSingle: function(store, primaryType, payload, recordId, requestType) {
+ var root = this.keyForAttribute(primaryType.typeKey),
+ partial = payload[root];
+
+ updatePayloadWithEmbedded(store, this, primaryType, partial, payload);
+
+ return this._super(store, primaryType, payload, recordId, requestType);
+ },
+
+ /**
+ Extract embedded objects out of a standard payload
+ and add them as sideloaded objects instead.
+
+ @method extractArray
+ */
+ extractArray: function(store, type, payload) {
+ var root = this.keyForAttribute(type.typeKey),
+ partials = payload[pluralize(root)];
+
+ forEach(partials, function(partial) {
+ updatePayloadWithEmbedded(store, this, type, partial, payload);
+ }, this);
+
+ return this._super(store, type, payload);
+ }
+ });
+
+ function updatePayloadWithEmbedded(store, serializer, type, partial, payload) {
+ var attrs = get(serializer, 'attrs');
+
+ if (!attrs) {
+ return;
+ }
+
+ type.eachRelationship(function(key, relationship) {
+ var expandedKey, embeddedTypeKey, attribute, ids,
+ config = attrs[key],
+ serializer = store.serializerFor(relationship.type.typeKey),
+ primaryKey = get(serializer, "primaryKey");
+
+ if (relationship.kind !== "hasMany") {
+ return;
+ }
+
+ if (config && (config.embedded === 'always' || config.embedded === 'load')) {
+ // underscore forces the embedded records to be side loaded.
+ // it is needed when main type === relationship.type
+ embeddedTypeKey = '_' + Ember.String.pluralize(relationship.type.typeKey);
+ expandedKey = this.keyForRelationship(key, relationship.kind);
+ attribute = this.keyForAttribute(key);
+ ids = [];
+
+ if (!partial[attribute]) {
+ return;
+ }
+
+ payload[embeddedTypeKey] = payload[embeddedTypeKey] || [];
+
+ forEach(partial[attribute], function(data) {
+ var embeddedType = store.modelFor(relationship.type.typeKey);
+ updatePayloadWithEmbedded(store, serializer, embeddedType, data, payload);
+ ids.push(data[primaryKey]);
+ payload[embeddedTypeKey].push(data);
+ });
+
+ partial[expandedKey] = ids;
+ delete partial[attribute];
+ }
+ }, serializer);
+ }
+
+ __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.
+ Its primarily used for development and testing. You can also use
+ `DS.FixtureAdapter` while working on the API but are not ready to
+ integrate yet. It is a fully functioning adapter. All CRUD methods
+ are implemented. You can also implement query logic that a remote
+ system would do. Its possible to do develop your entire application
+ with `DS.FixtureAdapter`.
+
+ 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. An array of
+ headers can be added to the adapter which are passed with every request:
+
+ ```js
+ DS.RESTAdapter.reopen({
+ headers: {
+ "API_KEY": "secret key",
+ "ANOTHER_HEADER": "Some header value"
+ }
+ });
+ ```
+
+ @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. An array of
+ headers can be added to the adapter which are passed with every request:
+
+ ```javascript
+ DS.RESTAdapter.reopen({
+ 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
+ @returns {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
+ @returns {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
+ @returns {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
+ @returns {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
+ @returns {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
+ @returns {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
+ @returns {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
+ @returns {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
+ @returns {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
+ @returns {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
+ DS.RESTAdapter.reopen({
+ pathForType: function(type) {
+ var decamelized = Ember.String.decamelize(type);
+ return Ember.String.pluralize(decamelized);
+ };
+ });
+ ```
+
+ @method pathForType
+ @param {String} type
+ @returns {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) {
+ 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);
+ }
+
+ if (this.headers !== undefined) {
+ var headers = this.headers;
+ 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.7.f87cba88'
+ @static
+ */
+ DS = Ember.Namespace.create({
+ VERSION: '1.0.0-beta.7.f87cba88'
+ });
+
+ if ('undefined' !== typeof window) {
+ window.DS = DS;
+ }
+
+ if (Ember.libraries) {
+ Ember.libraries.registerCoreLibrary('Ember Data', DS.VERSION);
+ }
+ }
+
+ __exports__["default"] = DS;
+ });
+define("ember-data/lib/ext/date",
+ [],
+ function() {
+ "use strict";
+ /**
+ @module ember-data
+ */
+
+ /**
+ Date.parse with progressive enhancement for ISO 8601
+
+ © 2011 Colin Snover
+
+ Released under MIT license.
+
+ @class Date
+ @namespace Ember
+ @static
+ */
+ Ember.Date = Ember.Date || {};
+
+ var origParse = Date.parse, numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
+
+ /**
+ @method parse
+ @param date
+ */
+ Ember.Date.parse = function (date) {
+ var timestamp, struct, minutesOffset = 0;
+
+ // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
+ // before falling back to any implementation-specific date parsing, so that’s what we do, even if native
+ // implementations could be faster
+ // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm
+ if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) {
+ // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC
+ for (var i = 0, k; (k = numericKeys[i]); ++i) {
+ struct[k] = +struct[k] || 0;
+ }
+
+ // allow undefined days and months
+ struct[2] = (+struct[2] || 1) - 1;
+ struct[3] = +struct[3] || 1;
+
+ if (struct[8] !== 'Z' && struct[9] !== undefined) {
+ minutesOffset = struct[10] * 60 + struct[11];
+
+ if (struct[9] === '+') {
+ minutesOffset = 0 - minutesOffset;
+ }
+ }
+
+ timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]);
+ }
+ else {
+ timestamp = origParse ? origParse(date) : NaN;
+ }
+
+ return timestamp;
+ };
+
+ if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Date) {
+ Date.parse = Ember.Date.parse;
+ }
+ });
+define("ember-data/lib/initializers",
+ ["./system/store","./serializers","./adapters","./system/debug/debug_adapter","./system/container_proxy","./transforms"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__) {
+ "use strict";
+ var Store = __dependency1__["default"];
+ var JSONSerializer = __dependency2__.JSONSerializer;
+ var RESTSerializer = __dependency2__.RESTSerializer;
+ var RESTAdapter = __dependency3__.RESTAdapter;
+ var DebugAdapter = __dependency4__["default"];
+ var ContainerProxy = __dependency5__["default"];
+ var BooleanTransform = __dependency6__.BooleanTransform;
+ var DateTransform = __dependency6__.DateTransform;
+ var StringTransform = __dependency6__.StringTransform;
+ var NumberTransform = __dependency6__.NumberTransform;
+
+ /**
+ @module ember-data
+ */
+
+ var set = Ember.set;
+
+ /*
+ This code registers an injection for Ember.Application.
+
+ If an Ember.js developer defines a subclass of DS.Store on their application,
+ this code will automatically instantiate it and make it available on the
+ router.
+
+ Additionally, after an application's controllers have been injected, they will
+ each have the store made available to them.
+
+ For example, imagine an Ember.js application with the following classes:
+
+ App.Store = DS.Store.extend({
+ adapter: 'custom'
+ });
+
+ App.PostsController = Ember.ArrayController.extend({
+ // ...
+ });
+
+ When the application is initialized, `App.Store` will automatically be
+ instantiated, and the instance of `App.PostsController` will have its `store`
+ property set to that instance.
+
+ Note that this code will only be run if the `ember-application` package is
+ loaded. If Ember Data is being used in an environment other than a
+ typical application (e.g., node.js where only `ember-runtime` is available),
+ this code will be ignored.
+ */
+
+ Ember.onLoad('Ember.Application', function(Application) {
+ Application.initializer({
+ name: "store",
+
+ initialize: function(container, application) {
+ application.register('store:main', application.Store || 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
+ application.register('serializer:-default', JSONSerializer);
+ application.register('serializer:-rest', RESTSerializer);
+ application.register('adapter:-rest', RESTAdapter);
+
+ // Eagerly generate the store so defaultStore is populated.
+ // TODO: Do this in a finisher hook
+ container.lookup('store:main');
+ }
+ });
+
+ Application.initializer({
+ name: "transforms",
+ before: "store",
+
+ initialize: function(container, application) {
+ application.register('transform:boolean', BooleanTransform);
+ application.register('transform:date', DateTransform);
+ application.register('transform:number', NumberTransform);
+ application.register('transform:string', StringTransform);
+ }
+ });
+
+ Application.initializer({
+ name: "data-adapter",
+ before: "store",
+
+ initialize: function(container, application) {
+ application.register('data-adapter:main', DebugAdapter);
+ }
+ });
+
+ Application.initializer({
+ name: "injectStore",
+ before: "store",
+
+ initialize: function(container, application) {
+ application.inject('controller', 'store', 'store:main');
+ application.inject('route', 'store', 'store:main');
+ application.inject('serializer', 'store', 'store:main');
+ application.inject('data-adapter', 'store', 'store:main');
+ }
+ });
+
+ });
+ });
+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","./initializers","./system/container_proxy","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __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 ContainerProxy = __dependency18__["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;
+
+ __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",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ var get = Ember.get, set = Ember.set, isNone = Ember.isNone;
+
+ /**
+ 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',
+
+ /**
+ Given a subclass of `DS.Model` and a JSON object this method will
+ iterate through each attribute of the `DS.Model` and invoke the
+ `DS.Transform#deserialize` method on the matching property of the
+ JSON object. This method is typically called after the
+ serializer's `normalize` method.
+
+ @method applyTransforms
+ @private
+ @param {subclass of DS.Model} type
+ @param {Object} data The data to transform
+ @return {Object} data The transformed data object
+ */
+ applyTransforms: function(type, data) {
+ type.eachTransformedAttribute(function(key, type) {
+ var transform = this.transformFor(type);
+ data[key] = transform.deserialize(data[key]);
+ }, this);
+
+ return data;
+ },
+
+ /**
+ Normalizes a part of the JSON payload returned by
+ the server. You should override this method, munge the hash
+ and call super if you have generic normalization to do.
+
+ It takes the type of the record that is being normalized
+ (as a DS.Model class), the property where the hash was
+ originally found, and the hash to normalize.
+
+ You can use this method, for example, to normalize underscored keys to camelized
+ or other general-purpose normalizations.
+
+ Example
+
+ ```javascript
+ App.ApplicationSerializer = DS.JSONSerializer.extend({
+ normalize: function(type, hash) {
+ var fields = Ember.get(type, 'fields');
+ fields.forEach(function(field) {
+ var payloadField = Ember.String.underscore(field);
+ if (field === payloadField) { return; }
+
+ hash[field] = hash[payloadField];
+ delete hash[payloadField];
+ });
+ return this._super.apply(this, arguments);
+ }
+ });
+ ```
+
+ @method normalize
+ @param {subclass of DS.Model} type
+ @param {Object} hash
+ @return {Object}
+ */
+ normalize: function(type, hash) {
+ if (!hash) { return hash; }
+
+ this.applyTransforms(type, hash);
+ return hash;
+ },
+
+ // SERIALIZE
+ /**
+ Called when a record is saved in order to convert the
+ record into JSON.
+
+ By default, it creates a JSON object with a key for
+ each attribute and belongsTo relationship.
+
+ For example, consider this model:
+
+ ```javascript
+ App.Comment = DS.Model.extend({
+ title: DS.attr(),
+ body: DS.attr(),
+
+ author: DS.belongsTo('user')
+ });
+ ```
+
+ The default serialization would create a JSON object like:
+
+ ```javascript
+ {
+ "title": "Rails is unagi",
+ "body": "Rails? Omakase? O_O",
+ "author": 12
+ }
+ ```
+
+ By default, attributes are passed through as-is, unless
+ you specified an attribute type (`DS.attr('date')`). If
+ you specify a transform, the JavaScript value will be
+ serialized when inserted into the JSON hash.
+
+ By default, belongs-to relationships are converted into
+ IDs when inserted into the JSON hash.
+
+ ## IDs
+
+ `serialize` takes an options hash with a single option:
+ `includeId`. If this option is `true`, `serialize` will,
+ by default include the ID in the JSON object it builds.
+
+ The adapter passes in `includeId: true` when serializing
+ a record for `createRecord`, but not for `updateRecord`.
+
+ ## Customization
+
+ Your server may expect a different JSON format than the
+ built-in serialization format.
+
+ In that case, you can implement `serialize` yourself and
+ return a JSON hash of your choosing.
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ serialize: function(post, options) {
+ var json = {
+ POST_TTL: post.get('title'),
+ POST_BDY: post.get('body'),
+ POST_CMS: post.get('comments').mapProperty('id')
+ }
+
+ if (options.includeId) {
+ json.POST_ID_ = post.get('id');
+ }
+
+ return json;
+ }
+ });
+ ```
+
+ ## Customizing an App-Wide Serializer
+
+ If you want to define a serializer for your entire
+ application, you'll probably want to use `eachAttribute`
+ and `eachRelationship` on the record.
+
+ ```javascript
+ App.ApplicationSerializer = DS.JSONSerializer.extend({
+ serialize: function(record, options) {
+ var json = {};
+
+ record.eachAttribute(function(name) {
+ json[serverAttributeName(name)] = record.get(name);
+ })
+
+ record.eachRelationship(function(name, relationship) {
+ if (relationship.kind === 'hasMany') {
+ json[serverHasManyName(name)] = record.get(name).mapBy('id');
+ }
+ });
+
+ if (options.includeId) {
+ json.ID_ = record.get('id');
+ }
+
+ return json;
+ }
+ });
+
+ function serverAttributeName(attribute) {
+ return attribute.underscore().toUpperCase();
+ }
+
+ function serverHasManyName(name) {
+ return serverAttributeName(name.singularize()) + "_IDS";
+ }
+ ```
+
+ This serializer will generate JSON that looks like this:
+
+ ```javascript
+ {
+ "TITLE": "Rails is omakase",
+ "BODY": "Yep. Omakase.",
+ "COMMENT_IDS": [ 1, 2, 3 ]
+ }
+ ```
+
+ ## Tweaking the Default JSON
+
+ If you just want to do some small tweaks on the default JSON,
+ you can call super first and make the tweaks on the returned
+ JSON.
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ serialize: function(record, options) {
+ var json = this._super.apply(this, arguments);
+
+ json.subject = json.title;
+ delete json.title;
+
+ return json;
+ }
+ });
+ ```
+
+ @method serialize
+ @param {subclass of DS.Model} record
+ @param {Object} options
+ @return {Object} json
+ */
+ serialize: function(record, options) {
+ var json = {};
+
+ if (options && options.includeId) {
+ var id = get(record, 'id');
+
+ if (id) {
+ json[get(this, 'primaryKey')] = id;
+ }
+ }
+
+ record.eachAttribute(function(key, attribute) {
+ this.serializeAttribute(record, json, key, attribute);
+ }, this);
+
+ record.eachRelationship(function(key, relationship) {
+ if (relationship.kind === 'belongsTo') {
+ this.serializeBelongsTo(record, json, relationship);
+ } else if (relationship.kind === 'hasMany') {
+ this.serializeHasMany(record, json, relationship);
+ }
+ }, this);
+
+ return json;
+ },
+
+ /**
+ `serializeAttribute` can be used to customize how `DS.attr`
+ properties are serialized
+
+ For example if you wanted to ensure all you attributes were always
+ serialized as properties on an `attributes` object you could
+ write:
+
+ ```javascript
+ App.ApplicationSerializer = DS.JSONSerializer.extend({
+ serializeAttribute: function(record, json, key, attributes) {
+ json.attributes = json.attributes || {};
+ this._super(record, json.attributes, key, attributes);
+ }
+ });
+ ```
+
+ @method serializeAttribute
+ @param {DS.Model} record
+ @param {Object} json
+ @param {String} key
+ @param {Object} attribute
+ */
+ serializeAttribute: function(record, json, key, attribute) {
+ var attrs = get(this, 'attrs');
+ var value = get(record, key), type = attribute.type;
+
+ if (type) {
+ var transform = this.transformFor(type);
+ value = transform.serialize(value);
+ }
+
+ // if provided, use the mapping provided by `attrs` in
+ // the serializer
+ key = attrs && attrs[key] || (this.keyForAttribute ? this.keyForAttribute(key) : key);
+
+ json[key] = value;
+ },
+
+ /**
+ `serializeBelongsTo` can be used to customize how `DS.belongsTo`
+ properties are serialized.
+
+ Example
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ serializeBelongsTo: function(record, json, relationship) {
+ var key = relationship.key;
+
+ var belongsTo = get(record, key);
+
+ key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key;
+
+ json[key] = Ember.isNone(belongsTo) ? belongsTo : belongsTo.toJSON();
+ }
+ });
+ ```
+
+ @method serializeBelongsTo
+ @param {DS.Model} record
+ @param {Object} json
+ @param {Object} relationship
+ */
+ serializeBelongsTo: function(record, json, relationship) {
+ var key = relationship.key;
+
+ var belongsTo = get(record, key);
+
+ key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key;
+
+ if (isNone(belongsTo)) {
+ json[key] = belongsTo;
+ } else {
+ json[key] = get(belongsTo, 'id');
+ }
+
+ if (relationship.options.polymorphic) {
+ this.serializePolymorphicType(record, json, relationship);
+ }
+ },
+
+ /**
+ `serializeHasMany` can be used to customize how `DS.hasMany`
+ properties are serialized.
+
+ Example
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ serializeHasMany: function(record, json, relationship) {
+ var key = relationship.key;
+ if (key === 'comments') {
+ return;
+ } else {
+ this._super.apply(this, arguments);
+ }
+ }
+ });
+ ```
+
+ @method serializeHasMany
+ @param {DS.Model} record
+ @param {Object} json
+ @param {Object} relationship
+ */
+ serializeHasMany: function(record, json, relationship) {
+ var key = relationship.key;
+
+ var relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship);
+
+ if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') {
+ json[key] = get(record, key).mapBy('id');
+ // TODO support for polymorphic manyToNone and manyToMany relationships
+ }
+ },
+
+ /**
+ You can use this method to customize how polymorphic objects are
+ serialized. Objects are considered to be polymorphic if
+ `{polymorphic: true}` is pass as the second argument to the
+ `DS.belongsTo` function.
+
+ Example
+
+ ```javascript
+ App.CommentSerializer = DS.JSONSerializer.extend({
+ serializePolymorphicType: function(record, json, relationship) {
+ var key = relationship.key,
+ belongsTo = get(record, key);
+ key = this.keyForAttribute ? this.keyForAttribute(key) : key;
+ json[key + "_type"] = belongsTo.constructor.typeKey;
+ }
+ });
+ ```
+
+ @method serializePolymorphicType
+ @param {DS.Model} record
+ @param {Object} json
+ @param {Object} relationship
+ */
+ serializePolymorphicType: Ember.K,
+
+ // EXTRACT
+
+ /**
+ The `extract` method is used to deserialize payload data from the
+ server. By default the `JSONSerializer` does not push the records
+ into the store. However records that subclass `JSONSerializer`
+ such as the `RESTSerializer` may push records into the store as
+ part of the extract call.
+
+ This method delegates to a more specific extract method based on
+ the `requestType`.
+
+ Example
+
+ ```javascript
+ var get = Ember.get;
+ socket.on('message', function(message) {
+ var modelName = message.model;
+ var data = message.data;
+ var type = store.modelFor(modelName);
+ var serializer = store.serializerFor(type.typeKey);
+ var record = serializer.extract(store, type, data, get(data, 'id'), 'single');
+ store.push(modelName, record);
+ });
+ ```
+
+ @method extract
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @param {String or Number} id
+ @param {String} requestType
+ @return {Object} json The deserialized payload
+ */
+ extract: function(store, type, payload, id, requestType) {
+ this.extractMeta(store, type, payload);
+
+ var specificExtract = "extract" + requestType.charAt(0).toUpperCase() + requestType.substr(1);
+ return this[specificExtract](store, type, payload, id, requestType);
+ },
+
+ /**
+ `extractFindAll` is a hook into the extract method used when a
+ call is made to `DS.Store#findAll`. By default this method is an
+ alias for [extractArray](#method_extractArray).
+
+ @method extractFindAll
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ @return {Array} array An array of deserialized objects
+ */
+ extractFindAll: 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, payload) {
+ return this.normalize(type, payload);
+ },
+
+ /**
+ `extractMeta` is used to deserialize any meta information in the
+ adapter payload. By default Ember Data expects meta information to
+ be located on the `meta` property of the payload object.
+
+ Example
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ extractMeta: function(store, type, payload) {
+ if (payload && payload._pagination) {
+ store.metaForType(type, payload._pagination);
+ delete payload._pagination;
+ }
+ }
+ });
+ ```
+
+ @method extractMeta
+ @param {DS.Store} store
+ @param {subclass of DS.Model} type
+ @param {Object} payload
+ */
+ extractMeta: function(store, type, payload) {
+ if (payload && payload.meta) {
+ store.metaForType(type, payload.meta);
+ delete payload.meta;
+ }
+ },
+
+ /**
+ `keyForAttribute` can be used to define rules for how to convert an
+ attribute name in your model to a key in your JSON.
+
+ Example
+
+ ```javascript
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
+ keyForAttribute: function(attr) {
+ return Ember.String.underscore(attr).toUpperCase();
+ }
+ });
+ ```
+
+ @method keyForAttribute
+ @param {String} key
+ @return {String} normalized key
+ */
+
+
+ /**
+ `keyForRelationship` can be used to define a custom key when
+ serializing relationship properties. By default `JSONSerializer`
+ does not provide an implementation of this method.
+
+ Example
+
+ ```javascript
+ App.PostSerializer = DS.JSONSerializer.extend({
+ keyForRelationship: function(key, relationship) {
+ return 'rel_' + Ember.String.underscore(key);
+ }
+ });
+ ```
+
+ @method keyForRelationship
+ @param {String} key
+ @param {String} relationship type
+ @return {String} normalized key
+ */
+
+ // HELPERS
+
+ /**
+ @method transformFor
+ @private
+ @param {String} attributeType
+ @param {Boolean} skipAssertion
+ @return {DS.Transform} transform
+ */
+ transformFor: function(attributeType, skipAssertion) {
+ var transform = this.container.lookup('transform:' + attributeType);
+ Ember.assert("Unable to find transform for '" + attributeType + "'", skipAssertion || !!transform);
+ return transform;
+ }
+ });
+
+ __exports__["default"] = JSONSerializer;
+ });
+define("ember-data/lib/serializers/rest_serializer",
+ ["./json_serializer","exports"],
+ function(__dependency1__, __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;
+
+ 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
+ @returns {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);
+ }
+
+ return this._super(type, hash, prop);
+ },
+
+ /**
+ 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(type, payload) {
+ delete payload.version;
+ delete payload.status;
+ return payload;
+ }
+ });
+ ```
+
+ @method normalizePayload
+ @param {subclass of DS.Model} type
+ @param {Object} hash
+ @returns {Object} the normalized payload
+ */
+ normalizePayload: function(type, payload) {
+ return payload;
+ },
+
+ /**
+ @method normalizeId
+ @private
+ */
+ normalizeId: function(hash) {
+ var primaryKey = get(this, 'primaryKey');
+
+ if (primaryKey === 'id') { return; }
+
+ hash.id = hash[primaryKey];
+ delete hash[primaryKey];
+ },
+
+ /**
+ @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 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, requestType) {
+ var comments = payload._embedded.comment;
+ delete payload._embedded;
+
+ payload = { comments: comments, post: payload };
+ return this._super(store, type, payload, id, requestType);
+ },
+
+ 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} type
+ @param {Object} payload
+ @param {String} id
+ @param {'find'|'createRecord'|'updateRecord'|'deleteRecord'} requestType
+ @returns {Object} the primary response to the original request
+ */
+ extractSingle: function(store, primaryType, payload, recordId, requestType) {
+ payload = this.normalizePayload(primaryType, 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, id, requestType) {
+ 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, id, requestType);
+ },
+
+ 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} type
+ @param {Object} payload
+ @param {'findAll'|'findMany'|'findHasMany'|'findQuery'} requestType
+ @returns {Array} The primary array that was returned in response
+ to the original query.
+ */
+ extractArray: function(store, primaryType, payload) {
+ payload = this.normalizePayload(primaryType, 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(null, payload);
+
+ for (var prop in payload) {
+ var typeName = this.typeForRoot(prop),
+ type = store.modelFor(typeName);
+
+ /*jshint loopfunc:true*/
+ var normalizedArray = map.call(Ember.makeArray(payload[prop]), function(hash) {
+ return this.normalize(type, hash, prop);
+ }, this);
+
+ store.pushMany(typeName, normalizedArray);
+ }
+ },
+
+ /**
+ You can use this method to normalize the JSON root keys returned
+ into the model type expected by your store.
+
+ For example, your server may return underscored root keys rather than
+ the expected camelcased versions.
+
+ ```js
+ App.ApplicationSerializer = DS.RESTSerializer.extend({
+ typeForRoot: function(root) {
+ var camelized = Ember.String.camelize(root);
+ return Ember.String.singularize(camelized);
+ }
+ });
+ ```
+
+ @method typeForRoot
+ @param {String} root
+ @returns {String} the model's typeKey
+ */
+ typeForRoot: function(root) {
+ return Ember.String.singularize(root);
+ },
+
+ // 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 camelized root keys.
+ 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) {
+ var root = Ember.String.camelize(type.typeKey);
+ hash[root] = 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"] = Ember.String.camelize(belongsTo.constructor.typeKey);
+ }
+ });
+
+ __exports__["default"] = RESTSerializer;
+ });
+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 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('')` 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).
+
+ `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('')` 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;
+
+ /**
+ @module ember-data
+ */
+
+ /**
+ Holds validation errors for a given record organized by attribute names.
+
+ @class Errors
+ @namespace DS
+ @extends Ember.Object
+ @uses Ember.Enumerable
+ @uses Ember.Evented
+ */
+ 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
+
+ @method errorsFor
+ @param {String} attribute
+ @returns {Array}
+ */
+ errorsFor: function(attribute) {
+ return get(this, 'errorsByAttributeName').get(attribute);
+ },
+
+ /**
+ */
+ messages: Ember.computed.mapBy('content', 'message'),
+
+ /**
+ @property content
+ @type {Array}
+ @private
+ */
+ content: Ember.computed(function() {
+ return Ember.A();
+ }),
+
+ /**
+ @method unknownProperty
+ @private
+ */
+ unknownProperty: function(attribute) {
+ var errors = this.errorsFor(attribute);
+ if (isEmpty(errors)) { return null; }
+ return errors;
+ },
+
+ /**
+ @method nextObject
+ @private
+ */
+ nextObject: function(index, previousObject, context) {
+ return get(this, 'content').objectAt(index);
+ },
+
+ /**
+ Total number of errors.
+
+ @property length
+ @type {Number}
+ @readOnly
+ */
+ length: Ember.computed.oneWay('content.length').readOnly(),
+
+ /**
+ @property isEmpty
+ @type {Boolean}
+ @readOnly
+ */
+ isEmpty: Ember.computed.not('length').readOnly(),
+
+ /**
+ Adds error messages to a given attribute and sends
+ `becameInvalid` event to the record.
+
+ @method add
+ @param {String} attribute
+ @param {Array|String} messages
+ */
+ add: function(attribute, messages) {
+ var wasEmpty = get(this, 'isEmpty');
+
+ messages = this._findOrCreateMessages(attribute, messages);
+ get(this, 'content').addObjects(messages);
+
+ this.notifyPropertyChange(attribute);
+ this.enumerableContentDidChange();
+
+ if (wasEmpty && !get(this, 'isEmpty')) {
+ this.trigger('becameInvalid');
+ }
+ },
+
+ /**
+ @method _findOrCreateMessages
+ @private
+ */
+ _findOrCreateMessages: function(attribute, messages) {
+ var errors = this.errorsFor(attribute);
+
+ return Ember.makeArray(messages).map(function(message) {
+ return errors.findBy('message', message) || {
+ attribute: attribute,
+ message: message
+ };
+ });
+ },
+
+ /**
+ Removes all error messages from the given attribute and sends
+ `becameValid` event to the record if there no more errors left.
+
+ @method remove
+ @param {String} attribute
+ */
+ remove: function(attribute) {
+ if (get(this, 'isEmpty')) { return; }
+
+ var content = get(this, 'content').rejectBy('attribute', attribute);
+ get(this, 'content').setObjects(content);
+
+ this.notifyPropertyChange(attribute);
+ this.enumerableContentDidChange();
+
+ if (get(this, 'isEmpty')) {
+ this.trigger('becameValid');
+ }
+ },
+
+ /**
+ Removes all error messages and sends `becameValid` event
+ to the record.
+
+ @method clear
+ */
+ clear: function() {
+ if (get(this, 'isEmpty')) { return; }
+
+ get(this, 'content').clear();
+ this.enumerableContentDidChange();
+
+ this.trigger('becameValid');
+ },
+
+ /**
+ Checks if there is error messages for the given attribute.
+
+ @method has
+ @param {String} attribute
+ @returns {Boolean} true if there some errors on given attribute
+ */
+ has: function(attribute) {
+ return !isEmpty(this.errorsFor(attribute));
+ }
+ });
+
+ __exports__["default"] = Errors;
+ });
+define("ember-data/lib/system/model/model",
+ ["./states","./errors","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var RootState = __dependency1__["default"];
+ var Errors = __dependency2__["default"];
+ /**
+ @module ember-data
+ */
+
+ var get = Ember.get, set = Ember.set,
+ merge = Ember.merge,
+ Promise = Ember.RSVP.Promise;
+
+ 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(App.Model);
+ record.get('isLoaded'); // true
+
+ store.find('model', 1).then(function(model) {
+ model.get('isLoaded'); // true
+ });
+ ```
+
+ @property isLoaded
+ @type {Boolean}
+ @readOnly
+ */
+ isLoaded: retrieveFromCurrentState,
+ /**
+ If this property is `true` the record is in the `dirty` state. The
+ record has local changes that have not yet been saved by the
+ adapter. This includes records that have been created (but not yet
+ saved) or deleted.
+
+ Example
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('isDirty'); // true
+
+ store.find('model', 1).then(function(model) {
+ model.get('isDirty'); // false
+ model.set('foo', 'some value');
+ model.set('isDirty'); // true
+ });
+ ```
+
+ @property isDirty
+ @type {Boolean}
+ @readOnly
+ */
+ isDirty: retrieveFromCurrentState,
+ /**
+ If this property is `true` the record is in the `saving` state. A
+ record enters the saving state when `save` is called, but the
+ adapter has not yet acknowledged that the changes have been
+ persisted to the backend.
+
+ Example
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('isSaving'); // false
+ var promise = record.save();
+ record.get('isSaving'); // true
+ promise.then(function() {
+ record.get('isSaving'); // false
+ });
+ ```
+
+ @property isSaving
+ @type {Boolean}
+ @readOnly
+ */
+ isSaving: retrieveFromCurrentState,
+ /**
+ If this property is `true` the record is in the `deleted` state
+ and has been marked for deletion. When `isDeleted` is true and
+ `isDirty` is true, the record is deleted locally but the deletion
+ was not yet persisted. When `isSaving` is true, the change is
+ in-flight. When both `isDirty` and `isSaving` are false, the
+ change has persisted.
+
+ Example
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('isDeleted'); // false
+ record.deleteRecord();
+ record.get('isDeleted'); // true
+ ```
+
+ @property isDeleted
+ @type {Boolean}
+ @readOnly
+ */
+ isDeleted: retrieveFromCurrentState,
+ /**
+ If this property is `true` the record is in the `new` state. A
+ record will be in the `new` state when it has been created on the
+ client and the adapter has not yet report that it was successfully
+ saved.
+
+ Example
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('isNew'); // true
+
+ record.save().then(function(model) {
+ model.get('isNew'); // false
+ });
+ ```
+
+ @property isNew
+ @type {Boolean}
+ @readOnly
+ */
+ isNew: retrieveFromCurrentState,
+ /**
+ If this property is `true` the record is in the `valid` state. A
+ record will be in the `valid` state when no client-side
+ validations have failed and the adapter did not report any
+ server-side validation failures.
+
+ @property isValid
+ @type {Boolean}
+ @readOnly
+ */
+ isValid: retrieveFromCurrentState,
+ /**
+ If the record is in the dirty state this property will report what
+ kind of change has caused it to move into the dirty
+ state. Possible values are:
+
+ - `created` The record has been created by the client and not yet saved to the adapter.
+ - `updated` The record has been updated by the client and not yet saved to the adapter.
+ - `deleted` The record has been deleted by the client and not yet saved to the adapter.
+
+ Example
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('dirtyType'); // 'created'
+ ```
+
+ @property dirtyType
+ @type {String}
+ @readOnly
+ */
+ dirtyType: retrieveFromCurrentState,
+
+ /**
+ If `true` the adapter reported that it was unable to save local
+ changes to the backend. This may also result in the record having
+ its `isValid` property become false if the adapter reported that
+ server-side validations failed.
+
+ Example
+
+ ```javascript
+ record.get('isError'); // false
+ record.set('foo', 'invalid value');
+ record.save().then(null, function() {
+ record.get('isError'); // true
+ });
+ ```
+
+ @property isError
+ @type {Boolean}
+ @readOnly
+ */
+ isError: false,
+ /**
+ If `true` the store is attempting to reload the record form the adapter.
+
+ Example
+
+ ```javascript
+ record.get('isReloading'); // false
+ record.reload();
+ record.get('isReloading'); // true
+ ```
+
+ @property isReloading
+ @type {Boolean}
+ @readOnly
+ */
+ isReloading: false,
+
+ /**
+ The `clientId` property is a transient numerical identifier
+ generated at runtime by the data store. It is important
+ primarily because newly created objects may not yet have an
+ externally generated id.
+
+ @property clientId
+ @private
+ @type {Number|String}
+ */
+ clientId: null,
+ /**
+ All ember models have an id property. This is an identifier
+ managed by an external source. These are always coerced to be
+ strings before being used internally. Note when declaring the
+ attributes for a model it is an error to declare an id
+ attribute.
+
+ ```javascript
+ var record = store.createRecord(App.Model);
+ record.get('id'); // null
+
+ store.find('model', 1).then(function(model) {
+ model.get('id'); // '1'
+ });
+ ```
+
+ @property id
+ @type {String}
+ */
+ id: null,
+
+ /**
+ @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 {Object}
+ */
+ 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
+ @returns {Object} an object whose values are primitive JSON values only
+ */
+ serialize: function(options) {
+ var store = get(this, 'store');
+ return store.serialize(this, options);
+ },
+
+ /**
+ Use [DS.JSONSerializer](DS.JSONSerializer.html) to
+ get the JSON representation of a record.
+
+ `toJSON` takes an optional hash as a parameter, currently
+ supported options are:
+
+ - `includeId`: `true` if the record's ID should be included in the
+ JSON representation.
+
+ @method toJSON
+ @param {Object} options
+ @returns {Object} A JSON representation of the object.
+ */
+ toJSON: function(options) {
+ // container is for lazy transform lookups
+ var serializer = DS.JSONSerializer.create({ container: this.container });
+ return serializer.serialize(this, options);
+ },
+
+ /**
+ Fired when the record is loaded from the server.
+
+ @event didLoad
+ */
+ didLoad: Ember.K,
+
+ /**
+ Fired when the record is updated.
+
+ @event didUpdate
+ */
+ didUpdate: Ember.K,
+
+ /**
+ Fired when the record is created.
+
+ @event didCreate
+ */
+ didCreate: Ember.K,
+
+ /**
+ Fired when the record is deleted.
+
+ @event didDelete
+ */
+ didDelete: Ember.K,
+
+ /**
+ Fired when the record becomes invalid.
+
+ @event becameInvalid
+ */
+ becameInvalid: Ember.K,
+
+ /**
+ Fired when the record enters the error state.
+
+ @event becameError
+ */
+ becameError: Ember.K,
+
+ /**
+ @property data
+ @private
+ @type {Object}
+ */
+ data: Ember.computed(function() {
+ this._data = this._data || {};
+ return this._data;
+ }).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 "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: No client-side validations have failed and the
+ // adapter did not report any server-side validation failures.
+
+ // The dirty state is a abstract state whose functionality is
+ // shared between the `created` and `updated` states.
+ //
+ // The deleted state shares the `isDirty` flag with the
+ // subclasses of `DirtyState`, but with a very different
+ // implementation.
+ //
+ // Dirty states have three child states:
+ //
+ // `uncommitted`: the store has not yet handed off the record
+ // to be saved.
+ // `inFlight`: the store has handed off the record to be saved,
+ // but the adapter has not yet acknowledged success.
+ // `invalid`: the record has invalid information and cannot be
+ // send to the adapter yet.
+ var DirtyState = {
+ initialState: 'uncommitted',
+
+ // FLAGS
+ isDirty: true,
+
+ // SUBSTATES
+
+ // When a record first becomes dirty, it is `uncommitted`.
+ // This means that there are local pending changes, but they
+ // have not yet begun to be saved, and are not invalid.
+ uncommitted: {
+ // EVENTS
+ didSetProperty: didSetProperty,
+
+ propertyWasReset: function(record, name) {
+ var stillDirty = false;
+
+ for (var prop in record._attributes) {
+ stillDirty = true;
+ break;
+ }
+
+ if (!stillDirty) { record.send('rolledBack'); }
+ },
+
+ pushedData: Ember.K,
+
+ becomeDirty: Ember.K,
+
+ willCommit: function(record) {
+ record.transitionTo('inFlight');
+ },
+
+ reloadRecord: function(record, resolve) {
+ resolve(get(record, 'store').reloadRecord(record));
+ },
+
+ rolledBack: function(record) {
+ record.transitionTo('loaded.saved');
+ },
+
+ becameInvalid: function(record) {
+ record.transitionTo('invalid');
+ },
+
+ rollback: function(record) {
+ record.rollback();
+ }
+ },
+
+ // Once a record has been handed off to the adapter to be
+ // saved, it is in the 'in flight' state. Changes to the
+ // record cannot be made during this window.
+ inFlight: {
+ // FLAGS
+ isSaving: true,
+
+ // EVENTS
+ didSetProperty: didSetProperty,
+ becomeDirty: Ember.K,
+ pushedData: Ember.K,
+
+ 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` state when its client-side
+ // invalidations have failed, or if the adapter has indicated
+ // the the record failed server-side invalidations.
+ invalid: {
+ // FLAGS
+ isValid: false,
+
+ // EVENTS
+ deleteRecord: function(record) {
+ record.transitionTo('deleted.uncommitted');
+ record.clearRelationships();
+ },
+
+ didSetProperty: function(record, context) {
+ get(record, 'errors').remove(context.name);
+
+ didSetProperty(record, context);
+ },
+
+ becomeDirty: Ember.K,
+
+ rolledBack: function(record) {
+ get(record, 'errors').clear();
+ },
+
+ becameValid: function(record) {
+ record.transitionTo('uncommitted');
+ },
+
+ invokeLifecycleCallbacks: function(record) {
+ record.triggerLater('becameInvalid', record);
+ }
+ }
+ };
+
+ // The created and updated states are created outside the state
+ // chart so we can reopen their substates and add mixins as
+ // necessary.
+
+ function deepClone(object) {
+ var clone = {}, value;
+
+ for (var prop in object) {
+ value = object[prop];
+ if (value && typeof value === 'object') {
+ clone[prop] = deepClone(value);
+ } else {
+ clone[prop] = value;
+ }
+ }
+
+ return clone;
+ }
+
+ function mixin(original, hash) {
+ for (var prop in hash) {
+ original[prop] = hash[prop];
+ }
+
+ return original;
+ }
+
+ function dirtyState(options) {
+ var newState = deepClone(DirtyState);
+ return mixin(newState, options);
+ }
+
+ var createdState = dirtyState({
+ dirtyType: 'created',
+ // FLAGS
+ isNew: true
+ });
+
+ createdState.uncommitted.rolledBack = function(record) {
+ record.transitionTo('deleted.saved');
+ };
+
+ var updatedState = dirtyState({
+ dirtyType: 'updated'
+ });
+
+ createdState.uncommitted.deleteRecord = function(record) {
+ record.clearRelationships();
+ record.transitionTo('deleted.saved');
+ };
+
+ createdState.uncommitted.rollback = function(record) {
+ DirtyState.uncommitted.rollback.apply(this, arguments);
+ record.transitionTo('deleted.saved');
+ };
+
+ createdState.uncommitted.propertyWasReset = Ember.K;
+
+ 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);
+ }
+ }
+ },
+
+ 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 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 [ { name: 'users', kind: 'hasMany' },
+ // { name: 'owner', kind: 'belongsTo' } ]
+ relationships.get(App.Post);
+ //=> [ { name: 'posts', kind: 'hasMany' } ]
+ ```
+
+ @property relationships
+ @static
+ @type Ember.Map
+ @readOnly
+ */
+ relationships: Ember.computed(function() {
+ var map = new Ember.MapWithDefault({
+ defaultValue: function() { return []; }
+ });
+
+ // Loop through each computed property on the class
+ this.eachComputedProperty(function(name, meta) {
+
+ // If the computed property is a relationship, add
+ // it to the map.
+ if (meta.isRelationship) {
+ if (typeof meta.type === 'string') {
+ meta.type = this.store.modelFor(meta.type);
+ }
+
+ var relationshipsForType = map.get(meta.type);
+
+ relationshipsForType.push({ name: name, kind: meta.kind });
+ }
+ });
+
+ return map;
+ }),
+
+ /**
+ A hash containing lists of the model's relationships, grouped
+ by the relationship kind. For example, given a model with this
+ definition:
+
+ ```javascript
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany('user'),
+ owner: DS.belongsTo('user'),
+
+ posts: DS.hasMany('post')
+ });
+ ```
+
+ This property would contain the following:
+
+ ```javascript
+ var relationshipNames = Ember.get(App.Blog, 'relationshipNames');
+ relationshipNames.hasMany;
+ //=> ['users', 'posts']
+ relationshipNames.belongsTo;
+ //=> ['owner']
+ ```
+
+ @property relationshipNames
+ @static
+ @type Object
+ @readOnly
+ */
+ relationshipNames: Ember.computed(function() {
+ var names = { hasMany: [], belongsTo: [] };
+
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ names[meta.kind].push(name);
+ }
+ });
+
+ return names;
+ }),
+
+ /**
+ An array of types directly related to a model. Each type will be
+ included once, regardless of the number of relationships it has with
+ the model.
+
+ For example, given a model with this definition:
+
+ ```javascript
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany('user'),
+ owner: DS.belongsTo('user'),
+
+ posts: DS.hasMany('post')
+ });
+ ```
+
+ This property would contain the following:
+
+ ```javascript
+ var relatedTypes = Ember.get(App.Blog, 'relatedTypes');
+ //=> [ App.User, App.Post ]
+ ```
+
+ @property relatedTypes
+ @static
+ @type Ember.Array
+ @readOnly
+ */
+ relatedTypes: Ember.computed(function() {
+ var type,
+ types = Ember.A();
+
+ // Loop through each computed property on the class,
+ // and create an array of the unique types involved
+ // in relationships
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ type = meta.type;
+
+ if (typeof type === 'string') {
+ type = get(this, type, false) || this.store.modelFor(type);
+ }
+
+ Ember.assert("You specified a hasMany (" + meta.type + ") on " + meta.parentType + " but " + meta.type + " was not found.", type);
+
+ if (!types.contains(type)) {
+ Ember.assert("Trying to sideload " + name + " on " + this.toString() + " but the type doesn't exist.", !!type);
+ types.push(type);
+ }
+ }
+ });
+
+ return types;
+ }),
+
+ /**
+ A map whose keys are the relationships of a model and whose values are
+ relationship descriptors.
+
+ For example, given a model with this
+ definition:
+
+ ```javascript
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany('user'),
+ owner: DS.belongsTo('user'),
+
+ posts: DS.hasMany('post')
+ });
+ ```
+
+ This property would contain the following:
+
+ ```javascript
+ var relationshipsByName = Ember.get(App.Blog, 'relationshipsByName');
+ relationshipsByName.get('users');
+ //=> { key: 'users', kind: 'hasMany', type: App.User }
+ relationshipsByName.get('owner');
+ //=> { key: 'owner', kind: 'belongsTo', type: App.User }
+ ```
+
+ @property relationshipsByName
+ @static
+ @type Ember.Map
+ @readOnly
+ */
+ relationshipsByName: Ember.computed(function() {
+ var map = Ember.Map.create(), type;
+
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ meta.key = name;
+ type = meta.type;
+
+ if (!type && meta.kind === 'hasMany') {
+ type = singularize(name);
+ } else if (!type) {
+ type = name;
+ }
+
+ if (typeof type === 'string') {
+ meta.type = this.store.modelFor(type);
+ }
+
+ map.set(name, meta);
+ }
+ });
+
+ return map;
+ }),
+
+ /**
+ A map whose keys are the fields of the model and whose values are strings
+ describing the kind of the field. A model's fields are the union of all of its
+ attributes and relationships.
+
+ For example:
+
+ ```javascript
+
+ App.Blog = DS.Model.extend({
+ users: DS.hasMany('user'),
+ owner: DS.belongsTo('user'),
+
+ posts: DS.hasMany('post'),
+
+ title: DS.attr('string')
+ });
+
+ var fields = Ember.get(App.Blog, 'fields');
+ fields.forEach(function(field, kind) {
+ console.log(field, kind);
+ });
+
+ // prints:
+ // users, hasMany
+ // owner, belongsTo
+ // posts, hasMany
+ // title, attribute
+ ```
+
+ @property fields
+ @static
+ @type Ember.Map
+ @readOnly
+ */
+ fields: Ember.computed(function() {
+ var map = Ember.Map.create();
+
+ this.eachComputedProperty(function(name, meta) {
+ if (meta.isRelationship) {
+ map.set(name, meta.kind);
+ } else if (meta.isAttribute) {
+ map.set(name, 'attribute');
+ }
+ });
+
+ return map;
+ }),
+
+ /**
+ Given a callback, iterates over each of the relationships in the model,
+ invoking the callback with the name of each relationship and its relationship
+ descriptor.
+
+ @method eachRelationship
+ @static
+ @param {Function} callback the callback to invoke
+ @param {any} binding the value to which the callback's `this` should be bound
+ */
+ eachRelationship: function(callback, binding) {
+ get(this, 'relationshipsByName').forEach(function(name, relationship) {
+ callback.call(binding, name, relationship);
+ });
+ },
+
+ /**
+ Given a callback, iterates over each of the types related to a model,
+ invoking the callback with the related type's class. Each type will be
+ returned just once, regardless of how many different relationships it has
+ with a model.
+
+ @method eachRelatedType
+ @static
+ @param {Function} callback the callback to invoke
+ @param {any} binding the value to which the callback's `this` should be bound
+ */
+ eachRelatedType: function(callback, binding) {
+ get(this, 'relatedTypes').forEach(function(type) {
+ callback.call(binding, type);
+ });
+ }
+ });
+
+ 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",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ @module ember-data
+ */
+
+ var get = Ember.get, set = Ember.set, setProperties = Ember.setProperties;
+
+ function asyncHasMany(type, options, meta) {
+ return Ember.computed('data', function(key) {
+ var relationship = this._relationships[key],
+ promiseLabel = "DS: Async hasMany " + this + " : " + key;
+
+ if (!relationship) {
+ var resolver = Ember.RSVP.defer(promiseLabel);
+ relationship = buildRelationship(this, key, options, function(store, data) {
+ var link = data.links && data.links[key];
+ var rel;
+ if (link) {
+ rel = store.findHasMany(this, link, meta, resolver);
+ } else {
+ rel = store.findMany(this, data[key], meta.type, resolver);
+ }
+ // cache the promise so we can use it
+ // when we come back and don't need to rebuild
+ // the relationship.
+ set(rel, 'promise', resolver.promise);
+ return rel;
+ });
+ }
+
+ var promise = relationship.get('promise').then(function() {
+ return relationship;
+ }, null, "DS: Async hasMany records received");
+
+ return DS.PromiseArray.create({
+ promise: promise
+ });
+ }).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'
+ };
+
+ 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], meta.type);
+ });
+ }).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",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /*globals Ember*/
+ /*jshint eqnull:true*/
+
+ /**
+ @module ember-data
+ */
+
+ 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;
+
+ // 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(App.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.
+
+ @class Store
+ @namespace DS
+ @extends Ember.Object
+ */
+ Store = Ember.Object.extend({
+
+ /**
+ @method init
+ @private
+ */
+ init: function() {
+ // internal bookkeeping; not observable
+ this.typeMaps = {};
+ this.recordArrayManager = DS.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
+ @returns 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 DS.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.
+ @returns {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
+ @returns {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(App.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
+ @returns {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
+ @returns {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; }
+
+ // 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
+ @returns {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
+ @returns {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
+ @returns {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 local_posts = store.all(App.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(App.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(App.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;
+
+ // allow an optional server query
+ if (arguments.length === 3) {
+ promise = this.findQuery(type, query);
+ } else if (arguments.length === 2) {
+ filter = query;
+ }
+
+ type = this.modelFor(type);
+
+ var 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(App.Post, 1); // false
+ store.find(App.Post, 1).then(function() {
+ store.recordIsLoaded(App.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, '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
+ @returns {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 = normalizedKey.split(':', 2)[1];
+ } else {
+ // A factory already supplied.
+ factory = key;
+ }
+
+ 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
+ @returns {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` 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.
+
+ The data will be automatically deserialized using the
+ serializer for the `type` param.
+
+ 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('post', pushData);
+ ```
+
+ @method pushPayload
+ @param {String} type
+ @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: function(type, data) {
+ Ember.assert("You must include an `id` 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
+ @returns {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
+ @returns 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 map = this.typeMaps;
+ var keys = Ember.keys(map);
+ var store = this;
+ var types = keys.map(byType);
+
+ this.recordArrayManager.destroy();
+
+ types.forEach(this.unloadAll, this);
+
+ function byType(entry) {
+ return map[entry].type;
+ }
+ }
+ });
+
+ 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 (isNone(id) || id instanceof DS.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 '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'];
+}(window));
diff --git a/examples/ember-rails/vendor/assets/ember/production/ember-data.js b/examples/ember-rails/vendor/assets/ember/production/ember-data.js
new file mode 100644
index 0000000..ef0b713
--- /dev/null
+++ b/examples/ember-rails/vendor/assets/ember/production/ember-data.js
@@ -0,0 +1,12 @@
+// Fetched from channel: beta, with url http://builds.emberjs.com/beta/ember-data.min.js
+// Fetched on: 2014-03-19T15:47:47Z
+/*!
+ * @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.7.f87cba88
+ */
+!function(a){var b,c,d,e;!function(){var a={},f={};b=function(b,c,d){a[b]={deps:c,callback:d}},e=d=c=function(b){function d(a){if("."!==a.charAt(0))return a;for(var c=a.split("/"),d=b.split("/").slice(0,-1),e=0,f=c.length;f>e;e++){var g=c[e];if(".."===g)d.pop();else{if("."===g)continue;d.push(g)}}return d.join("/")}if(e._eak_seen=a,f[b])return f[b];if(f[b]={},!a[b])throw new Error("Could not find module "+b);for(var g,h=a[b],i=h.deps,j=h.callback,k=[],l=0,m=i.length;m>l;l++)k.push("exports"===i[l]?g={}:c(d(i[l])));var n=j.apply(this,k);return f[b]=g||n}}(),b("activemodel-adapter/lib/initializers",["../../ember-data/lib/system/container_proxy","./system/active_model_serializer","./system/active_model_adapter"],function(a,b,c){"use strict";var d=a["default"],e=b["default"],f=c["default"];Ember.onLoad("Ember.Application",function(a){a.initializer({name:"activeModelAdapter",initialize:function(a,b){var c=new d(a);c.registerDeprecations([{deprecated:"serializer:_ams",valid:"serializer:-active-model"},{deprecated:"adapter:_ams",valid:"adapter:-active-model"}]),b.register("serializer:-active-model",e),b.register("adapter:-active-model",f)}})})}),b("activemodel-adapter/lib/main",["./system","./initializers","exports"],function(a,b,c){"use strict";var d=a.ActiveModelAdapter,e=a.ActiveModelSerializer,f=a.EmbeddedRecordsMixin;c.ActiveModelAdapter=d,c.ActiveModelSerializer=e,c.EmbeddedRecordsMixin=f}),b("activemodel-adapter/lib/system",["./system/embedded_records_mixin","./system/active_model_adapter","./system/active_model_serializer","exports"],function(a,b,c,d){"use strict";var e=a["default"],f=b["default"],g=c["default"];d.EmbeddedRecordsMixin=e,d.ActiveModelAdapter=f,d.ActiveModelSerializer=g}),b("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(a,b,c,d,e,f){"use strict";var g=a.RESTAdapter,h=b.InvalidError,i=c.pluralize,j=(d["default"],e["default"],Ember.EnumerableUtils.forEach),k=Ember.String.decamelize,l=Ember.String.underscore,m=g.extend({defaultSerializer:"-active-model",pathForType:function(a){var b=k(a),c=l(b);return i(c)},ajaxError:function(a){var b=this._super(a);if(a&&422===a.status){var c=Ember.$.parseJSON(a.responseText),d={};if(void 0!==c.errors){var e=c.errors;j(Ember.keys(e),function(a){d[Ember.String.camelize(a)]=e[a]})}return new h(d)}return b}});f["default"]=m}),b("activemodel-adapter/lib/system/active_model_serializer",["../../../ember-inflector/lib/main","../../../ember-data/lib/serializers/rest_serializer","exports"],function(a,b,c){"use strict";var d=a.singularize,e=b["default"],f=Ember.get,g=Ember.EnumerableUtils.forEach,h=Ember.String.camelize,i=Ember.String.capitalize,j=Ember.String.decamelize,k=Ember.String.underscore,l=e.extend({keyForAttribute:function(a){return j(a)},keyForRelationship:function(a,b){return a=j(a),"belongsTo"===b?a+"_id":"hasMany"===b?d(a)+"_ids":a},serializeHasMany:Ember.K,serializeIntoHash:function(a,b,c,d){var e=k(j(b.typeKey));a[e]=this.serialize(c,d)},serializePolymorphicType:function(a,b,c){var d=c.key,e=f(a,d);d=this.keyForAttribute(d),b[d+"_type"]=i(h(e.constructor.typeKey))},typeForRoot:function(a){var b=h(a);return d(b)},normalize:function(a,b,c){return this.normalizeLinks(b),this._super(a,b,c)},normalizeLinks:function(a){if(a.links){var b=a.links;for(var c in b){var d=h(c);d!==c&&(b[d]=b[c],delete b[c])}}},normalizeRelationships:function(a,b){var c,d;this.keyForRelationship&&a.eachRelationship(function(a,e){if(e.options.polymorphic){if(c=this.keyForAttribute(a),d=b[c],d&&d.type)d.type=this.typeForRoot(d.type);else if(d&&"hasMany"===e.kind){var f=this;g(d,function(a){a.type=f.typeForRoot(a.type)})}}else c=this.keyForRelationship(a,e.kind),d=b[c];b[a]=d,a!==c&&delete b[c]},this)}});c["default"]=l}),b("activemodel-adapter/lib/system/embedded_records_mixin",["../../../ember-inflector/lib/main","exports"],function(a,b){"use strict";function c(a,b,f,g,h){var i=d(b,"attrs");i&&f.eachRelationship(function(b,f){var j,k,l,m,n=i[b],o=a.serializerFor(f.type.typeKey),p=d(o,"primaryKey");if("hasMany"===f.kind&&n&&("always"===n.embedded||"load"===n.embedded)){if(k="_"+Ember.String.pluralize(f.type.typeKey),j=this.keyForRelationship(b,f.kind),l=this.keyForAttribute(b),m=[],!g[l])return;h[k]=h[k]||[],e(g[l],function(b){var d=a.modelFor(f.type.typeKey);c(a,o,d,b,h),m.push(b[p]),h[k].push(b)}),g[j]=m,delete g[l]}},b)}var d=Ember.get,e=Ember.EnumerableUtils.forEach,f=a.pluralize,g=Ember.Mixin.create({serializeHasMany:function(a,b,c){var e=c.key,f=d(this,"attrs"),g=f&&f[e]&&"always"===f[e].embedded;g&&(b[this.keyForAttribute(e)]=d(a,e).map(function(a){var b=a.serialize(),c=d(this,"primaryKey");return b[c]=d(a,c),b},this))},extractSingle:function(a,b,d,e,f){var g=this.keyForAttribute(b.typeKey),h=d[g];return c(a,this,b,h,d),this._super(a,b,d,e,f)},extractArray:function(a,b,d){var g=this.keyForAttribute(b.typeKey),h=d[f(g)];return e(h,function(e){c(a,this,b,e,d)},this),this._super(a,b,d)}});b["default"]=g}),b("ember-data/lib/adapters",["./adapters/fixture_adapter","./adapters/rest_adapter","exports"],function(a,b,c){"use strict";var d=a["default"],e=b["default"];c.RESTAdapter=e,c.FixtureAdapter=d}),b("ember-data/lib/adapters/fixture_adapter",["../system/adapter","exports"],function(a,b){"use strict";var c=Ember.get,d=Ember.String.fmt,e=Ember.EnumerableUtils.indexOf,f=0,g=a["default"],h=g.extend({serializer:null,simulateRemoteResponse:!0,latency:50,fixturesForType:function(a){if(a.FIXTURES){var b=Ember.A(a.FIXTURES);return b.map(function(a){var b=typeof a.id;if("number"!==b&&"string"!==b)throw new Error(d("the id property must be defined as a number or string for fixture %@",[a]));return a.id=a.id+"",a})}return null},queryFixtures:function(){},updateFixtures:function(a,b){a.FIXTURES||(a.FIXTURES=[]);var c=a.FIXTURES;this.deleteLoadedFixture(a,b),c.push(b)},mockJSON:function(a,b,c){return a.serializerFor(b).serialize(c,{includeId:!0})},generateIdForRecord:function(){return"fixture-"+f++},find:function(a,b,c){var d,e=this.fixturesForType(b);return e&&(d=Ember.A(e).findProperty("id",c)),d?this.simulateRemoteCall(function(){return d},this):void 0},findMany:function(a,b,c){var d=this.fixturesForType(b);return d&&(d=d.filter(function(a){return-1!==e(c,a.id)})),d?this.simulateRemoteCall(function(){return d},this):void 0},findAll:function(a,b){var c=this.fixturesForType(b);return this.simulateRemoteCall(function(){return c},this)},findQuery:function(a,b,c){var d=this.fixturesForType(b);return d=this.queryFixtures(d,c,b),d?this.simulateRemoteCall(function(){return d},this):void 0},createRecord:function(a,b,c){var d=this.mockJSON(a,b,c);return this.updateFixtures(b,d),this.simulateRemoteCall(function(){return d},this)},updateRecord:function(a,b,c){var d=this.mockJSON(a,b,c);return this.updateFixtures(b,d),this.simulateRemoteCall(function(){return d},this)},deleteRecord:function(a,b,c){var d=this.mockJSON(a,b,c);return this.deleteLoadedFixture(b,d),this.simulateRemoteCall(function(){return null})},deleteLoadedFixture:function(a,b){var c=this.findExistingFixture(a,b);if(c){var d=e(a.FIXTURES,c);return a.FIXTURES.splice(d,1),!0}},findExistingFixture:function(a,b){var d=this.fixturesForType(a),e=c(b,"id");return this.findFixtureById(d,e)},findFixtureById:function(a,b){return Ember.A(a).find(function(a){return""+c(a,"id")==""+b?!0:!1})},simulateRemoteCall:function(a,b){var d=this;return new Ember.RSVP.Promise(function(e){c(d,"simulateRemoteResponse")?Ember.run.later(function(){e(a.call(b))},c(d,"latency")):Ember.run.schedule("actions",null,function(){e(a.call(b))})},"DS: FixtureAdapter#simulateRemoteCall")}});b["default"]=h}),b("ember-data/lib/adapters/rest_adapter",["../system/adapter","exports"],function(a,b){"use strict";var c=a["default"],d=Ember.get,e=(Ember.set,Ember.ArrayPolyfills.forEach),f=c.extend({defaultSerializer:"-rest",find:function(a,b,c){return this.ajax(this.buildURL(b.typeKey,c),"GET")},findAll:function(a,b,c){var d;return c&&(d={since:c}),this.ajax(this.buildURL(b.typeKey),"GET",{data:d})},findQuery:function(a,b,c){return this.ajax(this.buildURL(b.typeKey),"GET",{data:c})},findMany:function(a,b,c){return this.ajax(this.buildURL(b.typeKey),"GET",{data:{ids:c}})},findHasMany:function(a,b,c){var e=d(this,"host"),f=d(b,"id"),g=b.constructor.typeKey;return e&&"/"===c.charAt(0)&&"/"!==c.charAt(1)&&(c=e+c),this.ajax(this.urlPrefix(c,this.buildURL(g,f)),"GET")},findBelongsTo:function(a,b,c){var e=d(b,"id"),f=b.constructor.typeKey;return this.ajax(this.urlPrefix(c,this.buildURL(f,e)),"GET")},createRecord:function(a,b,c){var d={},e=a.serializerFor(b.typeKey);return e.serializeIntoHash(d,b,c,{includeId:!0}),this.ajax(this.buildURL(b.typeKey),"POST",{data:d})},updateRecord:function(a,b,c){var e={},f=a.serializerFor(b.typeKey);f.serializeIntoHash(e,b,c);var g=d(c,"id");return this.ajax(this.buildURL(b.typeKey,g),"PUT",{data:e})},deleteRecord:function(a,b,c){var e=d(c,"id");return this.ajax(this.buildURL(b.typeKey,e),"DELETE")},buildURL:function(a,b){var c=[],e=d(this,"host"),f=this.urlPrefix();return a&&c.push(this.pathForType(a)),b&&c.push(b),f&&c.unshift(f),c=c.join("/"),!e&&c&&(c="/"+c),c},urlPrefix:function(a,b){var c=d(this,"host"),e=d(this,"namespace"),f=[];return a?"/"===a.charAt(0)?c&&(a=a.slice(1),f.push(c)):/^http(s)?:\/\//.test(a)||f.push(b):(c&&f.push(c),e&&f.push(e)),a&&f.push(a),f.join("/")},pathForType:function(a){var b=Ember.String.camelize(a);return Ember.String.pluralize(b)},ajaxError:function(a){return a&&(a.then=null),a},ajax:function(a,b,c){var d=this;return new Ember.RSVP.Promise(function(e,f){c=d.ajaxOptions(a,b,c),c.success=function(a){Ember.run(null,e,a)},c.error=function(a){Ember.run(null,f,d.ajaxError(a))},Ember.$.ajax(c)},"DS: RestAdapter#ajax "+b+" to "+a)},ajaxOptions:function(a,b,c){if(c=c||{},c.url=a,c.type=b,c.dataType="json",c.context=this,c.data&&"GET"!==b&&(c.contentType="application/json; charset=utf-8",c.data=JSON.stringify(c.data)),void 0!==this.headers){var d=this.headers;c.beforeSend=function(a){e.call(Ember.keys(d),function(b){a.setRequestHeader(b,d[b])})}}return c}});b["default"]=f}),b("ember-data/lib/core",["exports"],function(a){"use strict";var b;"undefined"==typeof b&&(b=Ember.Namespace.create({VERSION:"1.0.0-beta.7.f87cba88"}),"undefined"!=typeof window&&(window.DS=b),Ember.libraries&&Ember.libraries.registerCoreLibrary("Ember Data",b.VERSION)),a["default"]=b}),b("ember-data/lib/ext/date",[],function(){"use strict";Ember.Date=Ember.Date||{};var a=Date.parse,b=[1,4,5,6,7,10,11];Ember.Date.parse=function(c){var d,e,f=0;if(e=/^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(c)){for(var g,h=0;g=b[h];++h)e[g]=+e[g]||0;e[2]=(+e[2]||1)-1,e[3]=+e[3]||1,"Z"!==e[8]&&void 0!==e[9]&&(f=60*e[10]+e[11],"+"===e[9]&&(f=0-f)),d=Date.UTC(e[1],e[2],e[3],e[4],e[5]+f,e[6],e[7])}else d=a?a(c):0/0;return d},(Ember.EXTEND_PROTOTYPES===!0||Ember.EXTEND_PROTOTYPES.Date)&&(Date.parse=Ember.Date.parse)}),b("ember-data/lib/initializers",["./system/store","./serializers","./adapters","./system/debug/debug_adapter","./system/container_proxy","./transforms"],function(a,b,c,d,e,f){"use strict";{var g=a["default"],h=b.JSONSerializer,i=b.RESTSerializer,j=c.RESTAdapter,k=d["default"],l=e["default"],m=f.BooleanTransform,n=f.DateTransform,o=f.StringTransform,p=f.NumberTransform;Ember.set}Ember.onLoad("Ember.Application",function(a){a.initializer({name:"store",initialize:function(a,b){b.register("store:main",b.Store||g);var c=new l(a);c.registerDeprecations([{deprecated:"serializer:_default",valid:"serializer:-default"},{deprecated:"serializer:_rest",valid:"serializer:-rest"},{deprecated:"adapter:_rest",valid:"adapter:-rest"}]),b.register("serializer:-default",h),b.register("serializer:-rest",i),b.register("adapter:-rest",j),a.lookup("store:main")}}),a.initializer({name:"transforms",before:"store",initialize:function(a,b){b.register("transform:boolean",m),b.register("transform:date",n),b.register("transform:number",p),b.register("transform:string",o)}}),a.initializer({name:"data-adapter",before:"store",initialize:function(a,b){b.register("data-adapter:main",k)}}),a.initializer({name:"injectStore",before:"store",initialize:function(a,b){b.inject("controller","store","store:main"),b.inject("route","store","store:main"),b.inject("serializer","store","store:main"),b.inject("data-adapter","store","store:main")}})})}),b("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","./initializers","./system/container_proxy","exports"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s){"use strict";Ember.RSVP.Promise.cast=Ember.RSVP.Promise.cast||Ember.RSVP.resolve;var t=a["default"],u=c.Store,v=c.PromiseArray,w=c.PromiseObject,x=d.Model,y=d.Errors,z=d.RootState,A=d.attr,B=e.AttributeChange,C=e.RelationshipChange,D=e.RelationshipChangeAdd,E=(e.RelationshipChangeRemove,e.OneToManyChange),F=(e.ManyToNoneChange,e.OneToOneChange),G=e.ManyToManyChange,H=f.InvalidError,I=f.Adapter,J=g["default"],K=h.RecordArray,L=h.FilteredRecordArray,M=h.AdapterPopulatedRecordArray,N=h.ManyArray,O=i["default"],P=j.RESTAdapter,Q=j.FixtureAdapter,R=k["default"],S=l["default"],T=n.ActiveModelAdapter,U=n.ActiveModelSerializer,V=n.EmbeddedRecordsMixin,W=o.Transform,X=o.DateTransform,Y=o.NumberTransform,Z=o.StringTransform,$=o.BooleanTransform,_=p.hasMany,ab=p.belongsTo,bb=r["default"];t.Store=u,t.PromiseArray=v,t.PromiseObject=w,t.Model=x,t.RootState=z,t.attr=A,t.Errors=y,t.AttributeChange=B,t.RelationshipChange=C,t.RelationshipChangeAdd=D,t.OneToManyChange=E,t.ManyToNoneChange=E,t.OneToOneChange=F,t.ManyToManyChange=G,t.Adapter=I,t.InvalidError=H,t.DebugAdapter=J,t.RecordArray=K,t.FilteredRecordArray=L,t.AdapterPopulatedRecordArray=M,t.ManyArray=N,t.RecordArrayManager=O,t.RESTAdapter=P,t.FixtureAdapter=Q,t.RESTSerializer=S,t.JSONSerializer=R,t.Transform=W,t.DateTransform=X,t.StringTransform=Z,t.NumberTransform=Y,t.BooleanTransform=$,t.ActiveModelAdapter=T,t.ActiveModelSerializer=U,t.EmbeddedRecordsMixin=V,t.belongsTo=ab,t.hasMany=_,t.ContainerProxy=bb,s["default"]=t}),b("ember-data/lib/serializers",["./serializers/json_serializer","./serializers/rest_serializer","exports"],function(a,b,c){"use strict";var d=a["default"],e=b["default"];c.JSONSerializer=d,c.RESTSerializer=e}),b("ember-data/lib/serializers/json_serializer",["exports"],function(a){"use strict";var b=Ember.get,c=(Ember.set,Ember.isNone),d=Ember.Object.extend({primaryKey:"id",applyTransforms:function(a,b){return a.eachTransformedAttribute(function(a,c){var d=this.transformFor(c);b[a]=d.deserialize(b[a])},this),b},normalize:function(a,b){return b?(this.applyTransforms(a,b),b):b},serialize:function(a,c){var d={};if(c&&c.includeId){var e=b(a,"id");e&&(d[b(this,"primaryKey")]=e)}return a.eachAttribute(function(b,c){this.serializeAttribute(a,d,b,c)},this),a.eachRelationship(function(b,c){"belongsTo"===c.kind?this.serializeBelongsTo(a,d,c):"hasMany"===c.kind&&this.serializeHasMany(a,d,c)},this),d},serializeAttribute:function(a,c,d,e){var f=b(this,"attrs"),g=b(a,d),h=e.type;if(h){var i=this.transformFor(h);g=i.serialize(g)}d=f&&f[d]||(this.keyForAttribute?this.keyForAttribute(d):d),c[d]=g},serializeBelongsTo:function(a,d,e){var f=e.key,g=b(a,f);f=this.keyForRelationship?this.keyForRelationship(f,"belongsTo"):f,d[f]=c(g)?g:b(g,"id"),e.options.polymorphic&&this.serializePolymorphicType(a,d,e)},serializeHasMany:function(a,c,d){var e=d.key,f=DS.RelationshipChange.determineRelationshipType(a.constructor,d);("manyToNone"===f||"manyToMany"===f)&&(c[e]=b(a,e).mapBy("id"))},serializePolymorphicType:Ember.K,extract:function(a,b,c,d,e){this.extractMeta(a,b,c);var f="extract"+e.charAt(0).toUpperCase()+e.substr(1);return this[f](a,b,c,d,e)},extractFindAll:function(a,b,c){return this.extractArray(a,b,c)},extractFindQuery:function(a,b,c){return this.extractArray(a,b,c)},extractFindMany:function(a,b,c){return this.extractArray(a,b,c)},extractFindHasMany:function(a,b,c){return this.extractArray(a,b,c)},extractCreateRecord:function(a,b,c){return this.extractSave(a,b,c)},extractUpdateRecord:function(a,b,c){return this.extractSave(a,b,c)},extractDeleteRecord:function(a,b,c){return this.extractSave(a,b,c)},extractFind:function(a,b,c){return this.extractSingle(a,b,c)},extractFindBelongsTo:function(a,b,c){return this.extractSingle(a,b,c)},extractSave:function(a,b,c){return this.extractSingle(a,b,c)},extractSingle:function(a,b,c){return this.normalize(b,c)},extractArray:function(a,b,c){return this.normalize(b,c)},extractMeta:function(a,b,c){c&&c.meta&&(a.metaForType(b,c.meta),delete c.meta)},transformFor:function(a){var b=this.container.lookup("transform:"+a);return b}});a["default"]=d}),b("ember-data/lib/serializers/rest_serializer",["./json_serializer","exports"],function(a,b){"use strict";function c(a){return null==a?null:a+""}var d=a["default"],e=Ember.get,f=(Ember.set,Ember.ArrayPolyfills.forEach),g=Ember.ArrayPolyfills.map,h=d.extend({normalize:function(a,b,c){return this.normalizeId(b),this.normalizeAttributes(a,b),this.normalizeRelationships(a,b),this.normalizeUsingDeclaredMapping(a,b),this.normalizeHash&&this.normalizeHash[c]&&this.normalizeHash[c](b),this._super(a,b,c)},normalizePayload:function(a,b){return b},normalizeId:function(a){var b=e(this,"primaryKey");"id"!==b&&(a.id=a[b],delete a[b])},normalizeUsingDeclaredMapping:function(a,b){var c,d,f=e(this,"attrs");if(f)for(d in f)c=f[d],c&&c.key&&(c=c.key),"string"==typeof c&&(b[d]=b[c],delete b[c])},normalizeAttributes:function(a,b){var c;this.keyForAttribute&&a.eachAttribute(function(a){c=this.keyForAttribute(a),a!==c&&(b[a]=b[c],delete b[c])},this)},normalizeRelationships:function(a,b){var c;this.keyForRelationship&&a.eachRelationship(function(a,d){c=this.keyForRelationship(a,d.kind),a!==c&&(b[a]=b[c],delete b[c])},this)},extractSingle:function(a,b,d,e){d=this.normalizePayload(b,d);var g,h=b.typeKey;for(var i in d){var j=this.typeForRoot(i),k=a.modelFor(j),l=k.typeKey===h;l&&"array"!==Ember.typeOf(d[i])?g=this.normalize(b,d[i],i):f.call(d[i],function(b){var d=this.typeForRoot(i),f=a.modelFor(d),h=a.serializerFor(f);b=h.normalize(f,b,i);var j=l&&!e&&!g,k=l&&c(b.id)===e;j||k?g=b:a.push(d,b)},this)}return g},extractArray:function(a,b,c){c=this.normalizePayload(b,c);var d,e=b.typeKey;for(var f in c){var h=f,i=!1;"_"===f.charAt(0)&&(i=!0,h=f.substr(1));var j=this.typeForRoot(h),k=a.modelFor(j),l=a.serializerFor(k),m=!i&&k.typeKey===e,n=g.call(c[f],function(a){return l.normalize(k,a,f)},this);m?d=n:a.pushMany(j,n)}return d},pushPayload:function(a,b){b=this.normalizePayload(null,b);for(var c in b){var d=this.typeForRoot(c),e=a.modelFor(d),f=g.call(Ember.makeArray(b[c]),function(a){return this.normalize(e,a,c)},this);a.pushMany(d,f)}},typeForRoot:function(a){return Ember.String.singularize(a)},serialize:function(){return this._super.apply(this,arguments)},serializeIntoHash:function(a,b,c,d){var e=Ember.String.camelize(b.typeKey);a[e]=this.serialize(c,d)},serializePolymorphicType:function(a,b,c){var d=c.key,f=e(a,d);d=this.keyForAttribute?this.keyForAttribute(d):d,b[d+"Type"]=Ember.String.camelize(f.constructor.typeKey)}});b["default"]=h}),b("ember-data/lib/system/adapter",["exports"],function(a){"use strict";var b=Ember.get,c=(Ember.set,Ember.ArrayPolyfills.map),d=["description","fileName","lineNumber","message","name","number","stack"],e=function(a){var b=Error.prototype.constructor.call(this,"The backend rejected the commit because it was invalid: "+Ember.inspect(a));this.errors=a;for(var c=0,e=d.length;e>c;c++)this[d[c]]=b[d[c]]};e.prototype=Ember.create(Error.prototype);var f=Ember.Object.extend({find:Ember.required(Function),findAll:null,findQuery:null,generateIdForRecord:null,serialize:function(a,c){return b(a,"store").serializerFor(a.constructor.typeKey).serialize(a,c)},createRecord:Ember.required(Function),updateRecord:Ember.required(Function),deleteRecord:Ember.required(Function),findMany:function(a,b,d){var e=c.call(d,function(c){return this.find(a,b,c)},this);return Ember.RSVP.all(e)}});a.InvalidError=e,a.Adapter=f,a["default"]=f}),b("ember-data/lib/system/changes",["./changes/attribute_change","./changes/relationship_change","exports"],function(a,b,c){"use strict";var d=a["default"],e=b.RelationshipChange,f=b.RelationshipChangeAdd,g=b.RelationshipChangeRemove,h=b.OneToManyChange,i=b.ManyToNoneChange,j=b.OneToOneChange,k=b.ManyToManyChange;c.AttributeChange=d,c.RelationshipChange=e,c.RelationshipChangeAdd=f,c.RelationshipChangeRemove=g,c.OneToManyChange=h,c.ManyToNoneChange=i,c.OneToOneChange=j,c.ManyToManyChange=k}),b("ember-data/lib/system/changes/attribute_change",["exports"],function(a){"use strict";function b(a){this.record=a.record,this.store=a.store,this.name=a.name,this.value=a.value,this.oldValue=a.oldValue}b.createChange=function(a){return new b(a)},b.prototype={sync:function(){this.value!==this.oldValue&&(this.record.send("becomeDirty"),this.record.updateRecordArraysLater()),this.destroy()},destroy:function(){delete this.record._changesToSync[this.name]}},a["default"]=b}),b("ember-data/lib/system/changes/relationship_change",["../model","exports"],function(a,b){"use strict";function c(a){return"object"==typeof a&&(!a.then||"function"!=typeof a.then)}var d=a.Model,e=Ember.get,f=Ember.set,g=Ember.EnumerableUtils.forEach,h=function(a){this.parentRecord=a.parentRecord,this.childRecord=a.childRecord,this.firstRecord=a.firstRecord,this.firstRecordKind=a.firstRecordKind,this.firstRecordName=a.firstRecordName,this.secondRecord=a.secondRecord,this.secondRecordKind=a.secondRecordKind,this.secondRecordName=a.secondRecordName,this.changeType=a.changeType,this.store=a.store,this.committed={}},i=function(a){h.call(this,a)},j=function(a){h.call(this,a)};h.create=function(a){return new h(a)},i.create=function(a){return new i(a)},j.create=function(a){return new j(a)};var k={},l={},m={},n={},o={};h._createChange=function(a){return"add"===a.changeType?i.create(a):"remove"===a.changeType?j.create(a):void 0},h.determineRelationshipType=function(a,b){var c,d,e=b.key,f=b.kind,g=a.inverseFor(e);return g&&(c=g.name,d=g.kind),g?"belongsTo"===d?"belongsTo"===f?"oneToOne":"manyToOne":"belongsTo"===f?"oneToMany":"manyToMany":"belongsTo"===f?"oneToNone":"manyToNone"},h.createChange=function(a,b,c,d){var e,f=a.constructor;return e=h.determineRelationshipType(f,d),"oneToMany"===e?k.createChange(a,b,c,d):"manyToOne"===e?k.createChange(b,a,c,d):"oneToNone"===e?l.createChange(a,b,c,d):"manyToNone"===e?m.createChange(a,b,c,d):"oneToOne"===e?n.createChange(a,b,c,d):"manyToMany"===e?o.createChange(a,b,c,d):void 0},l.createChange=function(a,b,c,d){var e=d.key,f=h._createChange({parentRecord:b,childRecord:a,firstRecord:a,store:c,changeType:d.changeType,firstRecordName:e,firstRecordKind:"belongsTo"});return c.addRelationshipChangeFor(a,e,b,null,f),f},m.createChange=function(a,b,c,d){var e=d.key,f=h._createChange({parentRecord:a,childRecord:b,secondRecord:a,store:c,changeType:d.changeType,secondRecordName:d.key,secondRecordKind:"hasMany"});return c.addRelationshipChangeFor(a,e,b,null,f),f},o.createChange=function(a,b,c,d){var e=d.key,f=h._createChange({parentRecord:b,childRecord:a,firstRecord:a,secondRecord:b,firstRecordKind:"hasMany",secondRecordKind:"hasMany",store:c,changeType:d.changeType,firstRecordName:e});return c.addRelationshipChangeFor(a,e,b,null,f),f},n.createChange=function(a,b,c,d){var e;d.parentType?e=d.parentType.inverseFor(d.key).name:d.key&&(e=d.key);var f=h._createChange({parentRecord:b,childRecord:a,firstRecord:a,secondRecord:b,firstRecordKind:"belongsTo",secondRecordKind:"belongsTo",store:c,changeType:d.changeType,firstRecordName:e});return c.addRelationshipChangeFor(a,e,b,null,f),f},n.maintainInvariant=function(a,b,c,d){if("add"===a.changeType&&b.recordIsMaterialized(c)){var f=e(c,d);if(f){var g=n.createChange(c,f,b,{parentType:a.parentType,hasManyName:a.hasManyName,changeType:"remove",key:a.key});b.addRelationshipChangeFor(c,d,a.parentRecord,null,g),g.sync()}}},k.createChange=function(a,b,c,d){var e;d.parentType?(e=d.parentType.inverseFor(d.key).name,k.maintainInvariant(d,c,a,e)):d.key&&(e=d.key);var f=h._createChange({parentRecord:b,childRecord:a,firstRecord:a,secondRecord:b,firstRecordKind:"belongsTo",secondRecordKind:"hasMany",store:c,changeType:d.changeType,firstRecordName:e});return c.addRelationshipChangeFor(a,e,b,f.getSecondRecordName(),f),f},k.maintainInvariant=function(a,b,c,d){if("add"===a.changeType&&c){var f=e(c,d);if(f){var g=k.createChange(c,f,b,{parentType:a.parentType,hasManyName:a.hasManyName,changeType:"remove",key:a.key});b.addRelationshipChangeFor(c,d,a.parentRecord,g.getSecondRecordName(),g),g.sync()}}},h.prototype={getSecondRecordName:function(){var a,b=this.secondRecordName;if(!b){if(a=this.secondRecord,!a)return;var c=this.firstRecord.constructor,d=c.inverseFor(this.firstRecordName);this.secondRecordName=d.name}return this.secondRecordName},getFirstRecordName:function(){var a=this.firstRecordName;return a},destroy:function(){var a=this.childRecord,b=this.getFirstRecordName(),c=this.getSecondRecordName(),d=this.store;d.removeRelationshipChangeFor(a,b,this.parentRecord,c,this.changeType)},getSecondRecord:function(){return this.secondRecord},getFirstRecord:function(){return this.firstRecord},coalesce:function(){var a=this.store.relationshipChangePairsFor(this.firstRecord);g(a,function(a){var b=a.add,c=a.remove;b&&c&&(b.destroy(),c.destroy())})}},i.prototype=Ember.create(h.create({})),j.prototype=Ember.create(h.create({})),i.prototype.changeType="add",i.prototype.sync=function(){var a=this.getSecondRecordName(),b=this.getFirstRecordName(),g=this.getFirstRecord(),h=this.getSecondRecord();h instanceof d&&g instanceof d&&("belongsTo"===this.secondRecordKind?h.suspendRelationshipObservers(function(){f(h,a,g)}):"hasMany"===this.secondRecordKind&&h.suspendRelationshipObservers(function(){var b=e(h,a);c(b)&&b.addObject(g)})),g instanceof d&&h instanceof d&&e(g,b)!==h&&("belongsTo"===this.firstRecordKind?g.suspendRelationshipObservers(function(){f(g,b,h)}):"hasMany"===this.firstRecordKind&&g.suspendRelationshipObservers(function(){var a=e(g,b);c(a)&&a.addObject(h)})),this.coalesce()},j.prototype.changeType="remove",j.prototype.sync=function(){var a=this.getSecondRecordName(),b=this.getFirstRecordName(),g=this.getFirstRecord(),h=this.getSecondRecord();h instanceof d&&g instanceof d&&("belongsTo"===this.secondRecordKind?h.suspendRelationshipObservers(function(){f(h,a,null)}):"hasMany"===this.secondRecordKind&&h.suspendRelationshipObservers(function(){var b=e(h,a);c(b)&&b.removeObject(g)})),g instanceof d&&e(g,b)&&("belongsTo"===this.firstRecordKind?g.suspendRelationshipObservers(function(){f(g,b,null)}):"hasMany"===this.firstRecordKind&&g.suspendRelationshipObservers(function(){var a=e(g,b);c(a)&&a.removeObject(h)})),this.coalesce()},b.RelationshipChange=h,b.RelationshipChangeAdd=i,b.RelationshipChangeRemove=j,b.OneToManyChange=k,b.ManyToNoneChange=m,b.OneToOneChange=n,b.ManyToManyChange=o}),b("ember-data/lib/system/container_proxy",["exports"],function(a){"use strict";var b=function(a){this.container=a};b.prototype.aliasedFactory=function(a,b){var c=this;return{create:function(){return b&&b(),c.container.lookup(a)}}},b.prototype.registerAlias=function(a,b,c){var d=this.aliasedFactory(b,c);return this.container.register(a,d)},b.prototype.registerDeprecation=function(a,b){var c=function(){};return this.registerAlias(a,b,c)},b.prototype.registerDeprecations=function(a){for(var b=a.length;b>0;b--){var c=a[b-1],d=c.deprecated,e=c.valid;this.registerDeprecation(d,e)}},a["default"]=b}),b("ember-data/lib/system/debug",["./debug/debug_info","./debug/debug_adapter","exports"],function(a,b,c){"use strict";var d=b["default"];c["default"]=d}),b("ember-data/lib/system/debug/debug_adapter",["../model","exports"],function(a,b){"use strict";var c=a.Model,d=Ember.get,e=Ember.String.capitalize,f=Ember.String.underscore,g=Ember.DataAdapter.extend({getFilters:function(){return[{name:"isNew",desc:"New"},{name:"isModified",desc:"Modified"},{name:"isClean",desc:"Clean"}]},detect:function(a){return a!==c&&c.detect(a)},columnsForType:function(a){var b=[{name:"id",desc:"Id"}],c=0,g=this;return d(a,"attributes").forEach(function(a){if(c++>g.attributeLimit)return!1;var d=e(f(a).replace("_"," "));b.push({name:a,desc:d})}),b},getRecords:function(a){return this.get("store").all(a)},getRecordColumnValues:function(a){var b=this,c=0,e={id:d(a,"id")};return a.eachAttribute(function(f){if(c++>b.attributeLimit)return!1;var g=d(a,f);e[f]=g}),e},getRecordKeywords:function(a){var b=[],c=Ember.A(["id"]);return a.eachAttribute(function(a){c.push(a)}),c.forEach(function(c){b.push(d(a,c))}),b},getRecordFilterValues:function(a){return{isNew:a.get("isNew"),isModified:a.get("isDirty")&&!a.get("isNew"),isClean:!a.get("isDirty")}},getRecordColor:function(a){var b="black";return a.get("isNew")?b="green":a.get("isDirty")&&(b="blue"),b},observeRecord:function(a,b){var c=Ember.A(),d=this,e=Ember.A(["id","isNew","isDirty"]);a.eachAttribute(function(a){e.push(a)}),e.forEach(function(e){var f=function(){b(d.wrapRecord(a))};Ember.addObserver(a,e,f),c.push(function(){Ember.removeObserver(a,e,f)})});var f=function(){c.forEach(function(a){a()})};return f}});b["default"]=g}),b("ember-data/lib/system/debug/debug_info",["../model","exports"],function(a,b){"use strict";var c=a.Model;c.reopen({_debugInfo:function(){var a=["id"],b={belongsTo:[],hasMany:[]},c=[];this.eachAttribute(function(b){a.push(b)},this),this.eachRelationship(function(a,d){b[d.kind].push(a),c.push(a)});var d=[{name:"Attributes",properties:a,expand:!0},{name:"Belongs To",properties:b.belongsTo,expand:!0},{name:"Has Many",properties:b.hasMany,expand:!0},{name:"Flags",properties:["isLoaded","isDirty","isSaving","isDeleted","isError","isNew","isValid"]}];return{propertyInfo:{includeOtherProperties:!0,groups:d,expensiveProperties:c}}}}),b["default"]=c}),b("ember-data/lib/system/model",["./model/model","./model/attributes","./model/states","./model/errors","exports"],function(a,b,c,d,e){"use strict";var f=a["default"],g=b["default"],h=c["default"],i=d["default"];e.Model=f,e.RootState=h,e.attr=g,e.Errors=i}),b("ember-data/lib/system/model/attributes",["./model","exports"],function(a,b){"use strict";function c(a,b){return"function"==typeof b.defaultValue?b.defaultValue.apply(null,arguments):b.defaultValue}function d(a,b){return a._attributes.hasOwnProperty(b)||a._inFlightAttributes.hasOwnProperty(b)||a._data.hasOwnProperty(b)}function e(a,b){return a._attributes.hasOwnProperty(b)?a._attributes[b]:a._inFlightAttributes.hasOwnProperty(b)?a._inFlightAttributes[b]:a._data[b]}function f(a,b){b=b||{};var f={type:a,isAttribute:!0,options:b};return Ember.computed("data",function(a,f){if(arguments.length>1){var g=e(this,a);return f!==g&&(this._attributes[a]=f,this.send("didSetProperty",{name:a,oldValue:g,originalValue:this._data[a],value:f})),f}return d(this,a)?e(this,a):c(this,b,a)}).meta(f)}var g=a["default"],h=Ember.get;g.reopenClass({attributes:Ember.computed(function(){var a=Ember.Map.create();return this.eachComputedProperty(function(b,c){c.isAttribute&&(c.name=b,a.set(b,c))}),a}),transformedAttributes:Ember.computed(function(){var a=Ember.Map.create();return this.eachAttribute(function(b,c){c.type&&a.set(b,c.type)}),a}),eachAttribute:function(a,b){h(this,"attributes").forEach(function(c,d){a.call(b,c,d)},b)},eachTransformedAttribute:function(a,b){h(this,"transformedAttributes").forEach(function(c,d){a.call(b,c,d)})}}),g.reopen({eachAttribute:function(a,b){this.constructor.eachAttribute(a,b)}}),b["default"]=f}),b("ember-data/lib/system/model/errors",["exports"],function(a){"use strict";
+var b=Ember.get,c=Ember.isEmpty,d=Ember.Object.extend(Ember.Enumerable,Ember.Evented,{registerHandlers:function(a,b,c){this.on("becameInvalid",a,b),this.on("becameValid",a,c)},errorsByAttributeName:Ember.reduceComputed("content",{initialValue:function(){return Ember.MapWithDefault.create({defaultValue:function(){return Ember.A()}})},addedItem:function(a,b){return a.get(b.attribute).pushObject(b),a},removedItem:function(a,b){return a.get(b.attribute).removeObject(b),a}}),errorsFor:function(a){return b(this,"errorsByAttributeName").get(a)},messages:Ember.computed.mapBy("content","message"),content:Ember.computed(function(){return Ember.A()}),unknownProperty:function(a){var b=this.errorsFor(a);return c(b)?null:b},nextObject:function(a){return b(this,"content").objectAt(a)},length:Ember.computed.oneWay("content.length").readOnly(),isEmpty:Ember.computed.not("length").readOnly(),add:function(a,c){var d=b(this,"isEmpty");c=this._findOrCreateMessages(a,c),b(this,"content").addObjects(c),this.notifyPropertyChange(a),this.enumerableContentDidChange(),d&&!b(this,"isEmpty")&&this.trigger("becameInvalid")},_findOrCreateMessages:function(a,b){var c=this.errorsFor(a);return Ember.makeArray(b).map(function(b){return c.findBy("message",b)||{attribute:a,message:b}})},remove:function(a){if(!b(this,"isEmpty")){var c=b(this,"content").rejectBy("attribute",a);b(this,"content").setObjects(c),this.notifyPropertyChange(a),this.enumerableContentDidChange(),b(this,"isEmpty")&&this.trigger("becameValid")}},clear:function(){b(this,"isEmpty")||(b(this,"content").clear(),this.enumerableContentDidChange(),this.trigger("becameValid"))},has:function(a){return!c(this.errorsFor(a))}});a["default"]=d}),b("ember-data/lib/system/model/model",["./states","./errors","exports"],function(a,b,c){"use strict";var d=a["default"],e=b["default"],f=Ember.get,g=Ember.set,h=Ember.merge,i=Ember.RSVP.Promise,j=Ember.computed("currentState",function(a){return f(f(this,"currentState"),a)}).readOnly(),k=Ember.Object.extend(Ember.Evented,{_recordArrays:void 0,_relationships:void 0,_loadingRecordArrays:void 0,isEmpty:j,isLoading:j,isLoaded:j,isDirty:j,isSaving:j,isDeleted:j,isNew:j,isValid:j,dirtyType:j,isError:!1,isReloading:!1,clientId:null,id:null,currentState:d.empty,errors:Ember.computed(function(){var a=e.create();return a.registerHandlers(this,function(){this.send("becameInvalid")},function(){this.send("becameValid")}),a}).readOnly(),serialize:function(a){var b=f(this,"store");return b.serialize(this,a)},toJSON:function(a){var b=DS.JSONSerializer.create({container:this.container});return b.serialize(this,a)},didLoad:Ember.K,didUpdate:Ember.K,didCreate:Ember.K,didDelete:Ember.K,becameInvalid:Ember.K,becameError:Ember.K,data:Ember.computed(function(){return this._data=this._data||{},this._data}).readOnly(),_data:null,init:function(){this._super(),this._setup()},_setup:function(){this._changesToSync={},this._deferredTriggers=[],this._data={},this._attributes={},this._inFlightAttributes={},this._relationships={}},send:function(a,b){var c=f(this,"currentState");return c[a]||this._unhandledEvent(c,a,b),c[a](this,b)},transitionTo:function(a){var b=a.split(".",1),c=f(this,"currentState"),d=c;do d.exit&&d.exit(this),d=d.parentState;while(!d.hasOwnProperty(b));var e,h,i=a.split("."),j=[],k=[];for(e=0,h=i.length;h>e;e++)d=d[i[e]],d.enter&&k.push(d),d.setup&&j.push(d);for(e=0,h=k.length;h>e;e++)k[e].enter(this);for(g(this,"currentState",d),e=0,h=j.length;h>e;e++)j[e].setup(this);this.updateRecordArraysLater()},_unhandledEvent:function(a,b,c){var d="Attempted to handle event `"+b+"` ";throw d+="on "+String(this)+" while in state ",d+=a.stateName+". ",void 0!==c&&(d+="Called with "+Ember.inspect(c)+"."),new Ember.Error(d)},withTransaction:function(a){var b=f(this,"transaction");b&&a(b)},loadingData:function(a){this.send("loadingData",a)},loadedData:function(){this.send("loadedData")},notFound:function(){this.send("notFound")},pushedData:function(){this.send("pushedData")},deleteRecord:function(){this.send("deleteRecord")},destroyRecord:function(){return this.deleteRecord(),this.save()},unloadRecord:function(){this.isDestroyed||this.send("unloadRecord")},clearRelationships:function(){this.eachRelationship(function(a,b){if("belongsTo"===b.kind)g(this,a,null);else if("hasMany"===b.kind){var c=this._relationships[a];c&&c.destroy()}},this)},updateRecordArrays:function(){this._updatingRecordArraysLater=!1,f(this,"store").dataWasUpdated(this.constructor,this)},changedAttributes:function(){var a,b=f(this,"_data"),c=f(this,"_attributes"),d={};for(a in c)d[a]=[b[a],c[a]];return d},adapterWillCommit:function(){this.send("willCommit")},adapterDidCommit:function(a){g(this,"isError",!1),a?this._data=a:Ember.mixin(this._data,this._inFlightAttributes),this._inFlightAttributes={},this.send("didCommit"),this.updateRecordArraysLater(),a&&this.suspendRelationshipObservers(function(){this.notifyPropertyChange("data")})},adapterDidDirty:function(){this.send("becomeDirty"),this.updateRecordArraysLater()},dataDidChange:Ember.observer(function(){this.reloadHasManys()},"data"),reloadHasManys:function(){var a=f(this.constructor,"relationshipsByName");this.updateRecordArraysLater(),a.forEach(function(a,b){this._data.links&&this._data.links[a]||"hasMany"===b.kind&&this.hasManyDidChange(b.key)},this)},hasManyDidChange:function(a){var b=this._relationships[a];if(b){var c=this._data[a]||[];g(b,"content",Ember.A(c)),g(b,"isLoaded",!0),b.trigger("didLoad")}},updateRecordArraysLater:function(){this._updatingRecordArraysLater||(this._updatingRecordArraysLater=!0,Ember.run.schedule("actions",this,this.updateRecordArrays))},setupData:function(a,b){b?Ember.merge(this._data,a):this._data=a;var c=this._relationships;this.eachRelationship(function(b,d){a.links&&a.links[b]||d.options.async&&(c[b]=null)}),a&&this.pushedData(),this.suspendRelationshipObservers(function(){this.notifyPropertyChange("data")})},materializeId:function(a){g(this,"id",a)},materializeAttributes:function(a){h(this._data,a)},materializeAttribute:function(a,b){this._data[a]=b},updateHasMany:function(a,b){this._data[a]=b,this.hasManyDidChange(a)},updateBelongsTo:function(a,b){this._data[a]=b},rollback:function(){this._attributes={},f(this,"isError")&&(this._inFlightAttributes={},g(this,"isError",!1)),f(this,"isValid")||(this._inFlightAttributes={}),this.send("rolledBack"),this.suspendRelationshipObservers(function(){this.notifyPropertyChange("data")})},toStringExtension:function(){return f(this,"id")},suspendRelationshipObservers:function(a,b){var c=f(this.constructor,"relationshipNames").belongsTo,d=this;try{this._suspendedRelationships=!0,Ember._suspendObservers(d,c,null,"belongsToDidChange",function(){Ember._suspendBeforeObservers(d,c,null,"belongsToWillChange",function(){a.call(b||d)})})}finally{this._suspendedRelationships=!1}},save:function(){var a="DS: Model#save "+this,b=Ember.RSVP.defer(a);return this.get("store").scheduleSave(this,b),this._inFlightAttributes=this._attributes,this._attributes={},DS.PromiseObject.create({promise:b.promise})},reload:function(){g(this,"isReloading",!0);var a=this,b="DS: Model#reload of "+this,c=new i(function(b){a.send("reloadRecord",b)},b).then(function(){return a.set("isReloading",!1),a.set("isError",!1),a},function(b){throw a.set("isError",!0),b},"DS: Model#reload complete, update flags");return DS.PromiseObject.create({promise:c})},adapterDidUpdateAttribute:function(a,b){void 0!==b?(this._data[a]=b,this.notifyPropertyChange(a)):this._data[a]=this._inFlightAttributes[a],this.updateRecordArraysLater()},adapterDidInvalidate:function(a){function b(b){a[b]&&c.add(b,a[b])}var c=f(this,"errors");this.eachAttribute(b),this.eachRelationship(b)},adapterDidError:function(){this.send("becameError"),g(this,"isError",!0)},trigger:function(a){Ember.tryInvoke(this,a,[].slice.call(arguments,1)),this._super.apply(this,arguments)},triggerLater:function(){1===this._deferredTriggers.push(arguments)&&Ember.run.schedule("actions",this,"_triggerDeferredTriggers")},_triggerDeferredTriggers:function(){for(var a=0,b=this._deferredTriggers.length;b>a;a++)this.trigger.apply(this,this._deferredTriggers[a]);this._deferredTriggers.length=0},willDestroy:function(){this._super(),this.clearRelationships()}});k.reopenClass({_create:k.create,create:function(){throw new Ember.Error("You should not call `create` on a model. Instead, call `store.createRecord` with the attributes you would like to set.")}}),c["default"]=k}),b("ember-data/lib/system/model/states",["exports"],function(a){"use strict";function b(a,b){b.value===b.originalValue?(delete a._attributes[b.name],a.send("propertyWasReset",b.name)):b.value!==b.oldValue&&a.send("becomeDirty"),a.updateRecordArraysLater()}function c(a){var b,d={};for(var e in a)b=a[e],d[e]=b&&"object"==typeof b?c(b):b;return d}function d(a,b){for(var c in b)a[c]=b[c];return a}function e(a){var b=c(j);return d(b,a)}function f(){}function g(a,b,c){a=d(b?Ember.create(b):{},a),a.parentState=b,a.stateName=c;for(var e in a)a.hasOwnProperty(e)&&"parentState"!==e&&"stateName"!==e&&"object"==typeof a[e]&&(a[e]=g(a[e],a,c+"."+e));return a}var h=Ember.get,i=Ember.set,j={initialState:"uncommitted",isDirty:!0,uncommitted:{didSetProperty:b,propertyWasReset:function(a){var b=!1;for(var c in a._attributes){b=!0;break}b||a.send("rolledBack")},pushedData:Ember.K,becomeDirty:Ember.K,willCommit:function(a){a.transitionTo("inFlight")},reloadRecord:function(a,b){b(h(a,"store").reloadRecord(a))},rolledBack:function(a){a.transitionTo("loaded.saved")},becameInvalid:function(a){a.transitionTo("invalid")},rollback:function(a){a.rollback()}},inFlight:{isSaving:!0,didSetProperty:b,becomeDirty:Ember.K,pushedData:Ember.K,unloadRecord:function(){},willCommit:Ember.K,didCommit:function(a){var b=h(this,"dirtyType");a.transitionTo("saved"),a.send("invokeLifecycleCallbacks",b)},becameInvalid:function(a){a.transitionTo("invalid"),a.send("invokeLifecycleCallbacks")},becameError:function(a){a.transitionTo("uncommitted"),a.triggerLater("becameError",a)}},invalid:{isValid:!1,deleteRecord:function(a){a.transitionTo("deleted.uncommitted"),a.clearRelationships()},didSetProperty:function(a,c){h(a,"errors").remove(c.name),b(a,c)},becomeDirty:Ember.K,rolledBack:function(a){h(a,"errors").clear()},becameValid:function(a){a.transitionTo("uncommitted")},invokeLifecycleCallbacks:function(a){a.triggerLater("becameInvalid",a)}}},k=e({dirtyType:"created",isNew:!0});k.uncommitted.rolledBack=function(a){a.transitionTo("deleted.saved")};var l=e({dirtyType:"updated"});k.uncommitted.deleteRecord=function(a){a.clearRelationships(),a.transitionTo("deleted.saved")},k.uncommitted.rollback=function(a){j.uncommitted.rollback.apply(this,arguments),a.transitionTo("deleted.saved")},k.uncommitted.propertyWasReset=Ember.K,l.inFlight.unloadRecord=f,l.uncommitted.deleteRecord=function(a){a.transitionTo("deleted.uncommitted"),a.clearRelationships()};var m={isEmpty:!1,isLoading:!1,isLoaded:!1,isDirty:!1,isSaving:!1,isDeleted:!1,isNew:!1,isValid:!0,rolledBack:Ember.K,unloadRecord:function(a){a.clearRelationships(),a.transitionTo("deleted.saved")},propertyWasReset:Ember.K,empty:{isEmpty:!0,loadingData:function(a,b){a._loadingPromise=b,a.transitionTo("loading")},loadedData:function(a){a.transitionTo("loaded.created.uncommitted"),a.suspendRelationshipObservers(function(){a.notifyPropertyChange("data")})},pushedData:function(a){a.transitionTo("loaded.saved"),a.triggerLater("didLoad")}},loading:{isLoading:!0,exit:function(a){a._loadingPromise=null},pushedData:function(a){a.transitionTo("loaded.saved"),a.triggerLater("didLoad"),i(a,"isError",!1)},becameError:function(a){a.triggerLater("becameError",a)},notFound:function(a){a.transitionTo("empty")}},loaded:{initialState:"saved",isLoaded:!0,saved:{setup:function(a){var b=a._attributes,c=!1;for(var d in b)if(b.hasOwnProperty(d)){c=!0;break}c&&a.adapterDidDirty()},didSetProperty:b,pushedData:Ember.K,becomeDirty:function(a){a.transitionTo("updated.uncommitted")},willCommit:function(a){a.transitionTo("updated.inFlight")},reloadRecord:function(a,b){b(h(a,"store").reloadRecord(a))},deleteRecord:function(a){a.transitionTo("deleted.uncommitted"),a.clearRelationships()},unloadRecord:function(a){a.clearRelationships(),a.transitionTo("deleted.saved")},didCommit:function(a){a.send("invokeLifecycleCallbacks",h(a,"lastDirtyType"))},notFound:Ember.K},created:k,updated:l},deleted:{initialState:"uncommitted",dirtyType:"deleted",isDeleted:!0,isLoaded:!0,isDirty:!0,setup:function(a){a.updateRecordArrays()},uncommitted:{willCommit:function(a){a.transitionTo("inFlight")},rollback:function(a){a.rollback()},becomeDirty:Ember.K,deleteRecord:Ember.K,rolledBack:function(a){a.transitionTo("loaded.saved")}},inFlight:{isSaving:!0,unloadRecord:f,willCommit:Ember.K,didCommit:function(a){a.transitionTo("saved"),a.send("invokeLifecycleCallbacks")},becameError:function(a){a.transitionTo("uncommitted"),a.triggerLater("becameError",a)}},saved:{isDirty:!1,setup:function(a){var b=h(a,"store");b.dematerializeRecord(a)},invokeLifecycleCallbacks:function(a){a.triggerLater("didDelete",a),a.triggerLater("didCommit",a)}}},invokeLifecycleCallbacks:function(a,b){"created"===b?a.triggerLater("didCreate",a):a.triggerLater("didUpdate",a),a.triggerLater("didCommit",a)}};m=g(m,null,"root"),a["default"]=m}),b("ember-data/lib/system/record_array_manager",["./record_arrays","exports"],function(a,b){"use strict";function c(a){for(var b=[],c=Ember.keys(a),d=0;dd;d++)c=c.concat(a[d]);return c}var f=a.ManyArray,g=Ember.get,h=(Ember.set,Ember.EnumerableUtils.forEach),i=Ember.Object.extend({init:function(){this.filteredRecordArrays=Ember.MapWithDefault.create({defaultValue:function(){return[]}}),this.changedRecords=[],this._adapterPopulatedRecordArrays=[]},recordDidChange:function(a){1===this.changedRecords.push(a)&&Ember.run.schedule("actions",this,this.updateRecordArrays)},recordArraysForRecord:function(a){return a._recordArrays=a._recordArrays||Ember.OrderedSet.create(),a._recordArrays},updateRecordArrays:function(){h(this.changedRecords,function(a){g(a,"isDeleted")?this._recordWasDeleted(a):this._recordWasChanged(a)},this),this.changedRecords.length=0},_recordWasDeleted:function(a){var b=a._recordArrays;b&&h(b,function(b){b.removeRecord(a)})},_recordWasChanged:function(a){var b,c=a.constructor,d=this.filteredRecordArrays.get(c);h(d,function(d){b=g(d,"filterFunction"),this.updateRecordArray(d,b,c,a)},this);var e=a._loadingRecordArrays;if(e){for(var f=0,i=e.length;i>f;f++)e[f].loadedRecord();a._loadingRecordArrays=[]}},updateRecordArray:function(a,b,c,d){var e;e=b?b(d):!0;var f=this.recordArraysForRecord(d);e?(f.add(a),a.addRecord(d)):e||(f.remove(a),a.removeRecord(d))},updateFilter:function(a,b,c){for(var d,e=this.store.typeMapFor(b),f=e.records,h=0,i=f.length;i>h;h++)d=f[h],g(d,"isDeleted")||g(d,"isEmpty")||this.updateRecordArray(a,c,b,d)},createManyArray:function(a,b){var c=f.create({type:a,content:b,store:this.store});return h(b,function(a){var b=this.recordArraysForRecord(a);b.add(c)},this),c},createRecordArray:function(a){var b=DS.RecordArray.create({type:a,content:Ember.A(),store:this.store,isLoaded:!0});return this.registerFilteredRecordArray(b,a),b},createFilteredRecordArray:function(a,b){var c=DS.FilteredRecordArray.create({type:a,content:Ember.A(),store:this.store,manager:this,filterFunction:b});return this.registerFilteredRecordArray(c,a,b),c},createAdapterPopulatedRecordArray:function(a,b){var c=DS.AdapterPopulatedRecordArray.create({type:a,query:b,content:Ember.A(),store:this.store});return this._adapterPopulatedRecordArrays.push(c),c},registerFilteredRecordArray:function(a,b,c){var d=this.filteredRecordArrays.get(b);d.push(a),this.updateFilter(a,b,c)},registerWaitingRecordArray:function(a,b){var c=a._loadingRecordArrays||[];c.push(b),a._loadingRecordArrays=c},willDestroy:function(){this._super(),e(c(this.filteredRecordArrays.values)).forEach(d),this._adapterPopulatedRecordArrays.forEach(d)}});b["default"]=i}),b("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(a,b,c,d,e){"use strict";var f=a["default"],g=b["default"],h=c["default"],i=d["default"];e.RecordArray=f,e.FilteredRecordArray=g,e.AdapterPopulatedRecordArray=h,e.ManyArray=i}),b("ember-data/lib/system/record_arrays/adapter_populated_record_array",["./record_array","exports"],function(a,b){"use strict";var c=a["default"],d=Ember.get,e=(Ember.set,c.extend({query:null,replace:function(){var a=d(this,"type").toString();throw new Error("The result of a server query (on "+a+") is immutable.")},load:function(a){var b=d(this,"store"),c=d(this,"type"),e=b.pushMany(c,a),f=b.metadataFor(c);this.setProperties({content:Ember.A(e),isLoaded:!0,meta:f}),Ember.run.once(this,"trigger","didLoad")}}));b["default"]=e}),b("ember-data/lib/system/record_arrays/filtered_record_array",["./record_array","exports"],function(a,b){"use strict";var c=a["default"],d=Ember.get,e=c.extend({filterFunction:null,isLoaded:!0,replace:function(){var a=d(this,"type").toString();throw new Error("The result of a client-side filter (on "+a+") is immutable.")},updateFilter:Ember.observer(function(){var a=d(this,"manager");a.updateFilter(this,d(this,"type"),d(this,"filterFunction"))},"filterFunction")});b["default"]=e}),b("ember-data/lib/system/record_arrays/many_array",["./record_array","../changes","exports"],function(a,b,c){"use strict";function d(a){a.sync()}var e=a["default"],f=b.RelationshipChange,g=Ember.get,h=Ember.set,i=Ember.EnumerableUtils.map,j=e.extend({init:function(){this._super.apply(this,arguments),this._changesToSync=Ember.OrderedSet.create()},name:null,owner:null,isPolymorphic:!1,isLoaded:!1,promise:null,loadingRecordsCount:function(a){this.loadingRecordsCount=a},loadedRecord:function(){this.loadingRecordsCount--,0===this.loadingRecordsCount&&(h(this,"isLoaded",!0),this.trigger("didLoad"))},fetch:function(){var a=g(this,"content"),b=g(this,"store"),c=g(this,"owner"),d=Ember.RSVP.defer("DS: ManyArray#fetch "+g(this,"type")),e=a.filterProperty("isEmpty",!0);b.fetchMany(e,c,d)},replaceContent:function(a,b,c){c=i(c,function(a){return a},this),this._super(a,b,c)},arrangedContentDidChange:function(){Ember.run.once(this,"fetch")},arrayContentWillChange:function(a,b){var c=g(this,"owner"),d=g(this,"name");if(!c._suspendedRelationships)for(var e=a;a+b>e;e++){var h=g(this,"content").objectAt(e),i=f.createChange(c,h,g(this,"store"),{parentType:c.constructor,changeType:"remove",kind:"hasMany",key:d});this._changesToSync.add(i)}return this._super.apply(this,arguments)},arrayContentDidChange:function(a,b,c){this._super.apply(this,arguments);var e=g(this,"owner"),h=g(this,"name"),i=g(this,"store");if(!e._suspendedRelationships){for(var j=a;a+c>j;j++){var k=g(this,"content").objectAt(j),l=f.createChange(e,k,i,{parentType:e.constructor,changeType:"add",kind:"hasMany",key:h});l.hasManyName=h,this._changesToSync.add(l)}this._changesToSync.forEach(d),this._changesToSync.clear()}},createRecord:function(a){var b,c=g(this,"owner"),d=g(c,"store"),e=g(this,"type");return b=d.createRecord.call(d,e,a),this.pushObject(b),b}});c["default"]=j}),b("ember-data/lib/system/record_arrays/record_array",["../store","exports"],function(a,b){"use strict";var c=a.PromiseArray,d=Ember.get,e=(Ember.set,Ember.ArrayProxy.extend(Ember.Evented,{type:null,content:null,isLoaded:!1,isUpdating:!1,store:null,objectAtContent:function(a){var b=d(this,"content");return b.objectAt(a)},update:function(){if(!d(this,"isUpdating")){var a=d(this,"store"),b=d(this,"type");return a.fetchAll(b,this)}},addRecord:function(a){d(this,"content").addObject(a)},removeRecord:function(a){d(this,"content").removeObject(a)},save:function(){var a="DS: RecordArray#save "+d(this,"type"),b=Ember.RSVP.all(this.invoke("save"),a).then(function(a){return Ember.A(a)},null,"DS: RecordArray#save apply Ember.NativeArray");return c.create({promise:b})},_dissociateFromOwnRecords:function(){var a=this;this.forEach(function(b){var c=b._recordArrays;c&&c.remove(a)})},willDestroy:function(){this._dissociateFromOwnRecords(),this._super()}}));b["default"]=e}),b("ember-data/lib/system/relationships",["./relationships/belongs_to","./relationships/has_many","../system/relationships/ext","exports"],function(a,b,c,d){"use strict";var e=a["default"],f=b["default"];d.belongsTo=e,d.hasMany=f}),b("ember-data/lib/system/relationships/belongs_to",["../model","exports"],function(a,b){"use strict";function c(a,b,c){return Ember.computed("data",function(a,b){var d,h=e(this,"data"),i=e(this,"store"),j="DS: Async belongsTo "+this+" : "+a;if(2===arguments.length)return void 0===b?null:DS.PromiseObject.create({promise:g.cast(b,j)});var k=h.links&&h.links[a],l=h[a];return f(l)?k?(d=i.findBelongsTo(this,k,c),DS.PromiseObject.create({promise:d})):null:(d=i.fetchRecord(l)||g.cast(l,j),DS.PromiseObject.create({promise:d}))}).meta(c)}function d(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var d={type:a,isRelationship:!0,options:b,kind:"belongsTo"};return b.async?c(a,b,d):Ember.computed("data",function(b,c){var d,g,h=e(this,"data"),i=e(this,"store");return g="string"==typeof a?i.modelFor(a):a,2===arguments.length?void 0===c?null:c:(d=h[b],f(d)?null:(i.fetchRecord(d),d))}).meta(d)}var e=Ember.get,f=(Ember.set,Ember.isNone),g=Ember.RSVP.Promise,h=a.Model;h.reopen({belongsToWillChange:Ember.beforeObserver(function(a,b){if(e(a,"isLoaded")){var c=e(a,b);if(c){var d=e(a,"store"),f=DS.RelationshipChange.createChange(a,c,d,{key:b,kind:"belongsTo",changeType:"remove"});f.sync(),this._changesToSync[b]=f}}}),belongsToDidChange:Ember.immediateObserver(function(a,b){if(e(a,"isLoaded")){var c=e(a,b);if(c){var d=e(a,"store"),f=DS.RelationshipChange.createChange(a,c,d,{key:b,kind:"belongsTo",changeType:"add"});f.sync()}}delete this._changesToSync[b]})}),b["default"]=d}),b("ember-data/lib/system/relationships/ext",["../../../../ember-inflector/lib/system","../model"],function(a,b){"use strict";{var c=a.singularize,d=b.Model,e=Ember.get;Ember.set}d.reopen({didDefineProperty:function(a,b,c){if(c instanceof Ember.Descriptor){var d=c.meta();d.isRelationship&&"belongsTo"===d.kind&&(Ember.addObserver(a,b,null,"belongsToDidChange"),Ember.addBeforeObserver(a,b,null,"belongsToWillChange")),d.parentType=a.constructor}}}),d.reopenClass({typeForRelationship:function(a){var b=e(this,"relationshipsByName").get(a);return b&&b.type},inverseFor:function(a){function b(a,c,d){d=d||[];var f=e(c,"relationships");if(f){var g=f.get(a);return g&&d.push.apply(d,f.get(a)),a.superclass&&b(a.superclass,c,d),d}}var c=this.typeForRelationship(a);if(!c)return null;var d=this.metaForProperty(a).options;if(null===d.inverse)return null;var f,g;if(d.inverse)f=d.inverse,g=Ember.get(c,"relationshipsByName").get(f).kind;else{var h=b(this,c);if(0===h.length)return null;f=h[0].name,g=h[0].kind}return{type:c,name:f,kind:g}},relationships:Ember.computed(function(){var a=new Ember.MapWithDefault({defaultValue:function(){return[]}});return this.eachComputedProperty(function(b,c){if(c.isRelationship){"string"==typeof c.type&&(c.type=this.store.modelFor(c.type));var d=a.get(c.type);d.push({name:b,kind:c.kind})}}),a}),relationshipNames:Ember.computed(function(){var a={hasMany:[],belongsTo:[]};return this.eachComputedProperty(function(b,c){c.isRelationship&&a[c.kind].push(b)}),a}),relatedTypes:Ember.computed(function(){var a,b=Ember.A();return this.eachComputedProperty(function(c,d){d.isRelationship&&(a=d.type,"string"==typeof a&&(a=e(this,a,!1)||this.store.modelFor(a)),b.contains(a)||b.push(a))}),b}),relationshipsByName:Ember.computed(function(){var a,b=Ember.Map.create();return this.eachComputedProperty(function(d,e){e.isRelationship&&(e.key=d,a=e.type,a||"hasMany"!==e.kind?a||(a=d):a=c(d),"string"==typeof a&&(e.type=this.store.modelFor(a)),b.set(d,e))}),b}),fields:Ember.computed(function(){var a=Ember.Map.create();return this.eachComputedProperty(function(b,c){c.isRelationship?a.set(b,c.kind):c.isAttribute&&a.set(b,"attribute")}),a}),eachRelationship:function(a,b){e(this,"relationshipsByName").forEach(function(c,d){a.call(b,c,d)})},eachRelatedType:function(a,b){e(this,"relatedTypes").forEach(function(c){a.call(b,c)})}}),d.reopen({eachRelationship:function(a,b){this.constructor.eachRelationship(a,b)}})}),b("ember-data/lib/system/relationships/has_many",["exports"],function(a){"use strict";function b(a,b,d){return Ember.computed("data",function(a){var e=this._relationships[a],f="DS: Async hasMany "+this+" : "+a;if(!e){var h=Ember.RSVP.defer(f);e=c(this,a,b,function(b,c){var e,f=c.links&&c.links[a];return e=f?b.findHasMany(this,f,d,h):b.findMany(this,c[a],d.type,h),g(e,"promise",h.promise),e})}var i=e.get("promise").then(function(){return e},null,"DS: Async hasMany records received");return DS.PromiseArray.create({promise:i})}).meta(d).readOnly()}function c(a,b,c,d){var e=a._relationships;if(e[b])return e[b];var g=f(a,"data"),i=f(a,"store"),j=e[b]=d.call(a,i,g);return h(j,{owner:a,name:b,isPolymorphic:c.polymorphic})}function d(a,d){d=d||{};var e={type:a,isRelationship:!0,options:d,kind:"hasMany"};return d.async?b(a,d,e):Ember.computed("data",function(a){return c(this,a,d,function(b,c){c[a];return b.findMany(this,c[a],e.type)})}).meta(e).readOnly()}function e(a,b){return"object"==typeof a&&(b=a,a=void 0),d(a,b)}var f=Ember.get,g=Ember.set,h=Ember.setProperties;a["default"]=e}),b("ember-data/lib/system/store",["exports"],function(a){"use strict";function b(a){return null==a?null:a+""}function c(a,b,c,e){return b.eachRelationship(function(b,h){if(c.links&&c.links[b])return void(e&&h.options.async&&(e._relationships[b]=null));var i=h.kind,j=c[b];null!=j&&("belongsTo"===i?d(a,c,b,h,j):"hasMany"===i&&(f(a,c,b,h,j),g(e,b,j)))}),c}function d(a,b,c,d,f){if(!(z(f)||f instanceof DS.Model)){var g;"number"==typeof f||"string"==typeof f?(g=e(d,c,b),b[c]=a.recordForId(g,f)):"object"==typeof f&&(b[c]=a.recordForId(f.type,f.id))}}function e(a,b,c){return a.options.polymorphic?c[b+"Type"]:a.type}function f(a,b,c,e,f){for(var g=0,h=f.length;h>g;g++)d(a,f,g,e,f[g])}function g(a,b,c){a&&c.pushObjects(a.get(b).filterBy("isNew"))}function h(a,b){return u.create({promise:D.cast(a,b)})}function i(a,b){return v.create({promise:D.cast(a,b)})}function j(a,b,c){return a.lookup("serializer:"+b)||a.lookup("serializer:application")||a.lookup("serializer:"+c)||a.lookup("serializer:-default")}function k(a){return a.lookup("serializer:application")||a.lookup("serializer:-default")}function l(a,b){var c=a.serializer,d=a.defaultSerializer,e=a.container;return e&&void 0===c&&(c=j(e,b.typeKey,d)),(null===c||void 0===c)&&(c={extract:function(a,b,c){return c}}),c}function m(a,b,c,d){var e=a.find(b,c,d),f=l(a,c),g="DS: Handle Adapter#find of "+c+" with id: "+d;return D.cast(e,g).then(function(a){var e=f.extract(b,c,a,d,"find");return b.push(c,e)},function(a){var e=b.getById(c,d);throw e.notFound(),a},"DS: Extract payload of '"+c+"'")}function n(a,b,c,d,e){var f=a.findMany(b,c,d,e),g=l(a,c),h="DS: Handle Adapter#findMany of "+c;return D.cast(f,h).then(function(a){var d=g.extract(b,c,a,null,"findMany");b.pushMany(c,d)},null,"DS: Extract payload of "+c)}function o(a,b,c,d,e){var f=a.findHasMany(b,c,d,e),g=l(a,e.type),h="DS: Handle Adapter#findHasMany of "+c+" : "+e.type;return D.cast(f,h).then(function(a){var d=g.extract(b,e.type,a,null,"findHasMany"),f=b.pushMany(e.type,d);c.updateHasMany(e.key,f)},null,"DS: Extract payload of "+c+" : hasMany "+e.type)}function p(a,b,c,d,e){var f=a.findBelongsTo(b,c,d,e),g=l(a,e.type),h="DS: Handle Adapter#findBelongsTo of "+c+" : "+e.type;return D.cast(f,h).then(function(a){var c=g.extract(b,e.type,a,null,"findBelongsTo"),d=b.push(e.type,c);return d.updateBelongsTo(e.key,d),d},null,"DS: Extract payload of "+c+" : "+e.type)}function q(a,b,c,d){var e=a.findAll(b,c,d),f=l(a,c),g="DS: Handle Adapter#findAll of "+c;return D.cast(e,g).then(function(a){var d=f.extract(b,c,a,null,"findAll");return b.pushMany(c,d),b.didUpdateAll(c),b.all(c)},null,"DS: Extract payload of findAll "+c)}function r(a,b,c,d,e){var f=a.findQuery(b,c,d,e),g=l(a,c),h="DS: Handle Adapter#findQuery of "+c;return D.cast(f,h).then(function(a){var d=g.extract(b,c,a,null,"findQuery");return e.load(d),e},null,"DS: Extract payload of findQuery "+c)}function s(a,b,c,d){var e=d.constructor,f=a[c](b,e,d),g=l(a,e),h="DS: Extract and notify about "+c+" completion of "+d;return f.then(function(a){var f;return f=a?g.extract(b,e,a,w(d,"id"),c):a,b.didSaveRecord(d,f),d},function(a){throw a instanceof DS.InvalidError?b.recordWasInvalid(d,a.errors):b.recordWasError(d,a),a},h)}var t,u,v,w=Ember.get,x=Ember.set,y=Ember.run.once,z=Ember.isNone,A=Ember.EnumerableUtils.forEach,B=Ember.EnumerableUtils.indexOf,C=Ember.EnumerableUtils.map,D=Ember.RSVP.Promise,E=Ember.copy;t=Ember.Object.extend({init:function(){this.typeMaps={},this.recordArrayManager=DS.RecordArrayManager.create({store:this}),this._relationshipChanges={},this._pendingSave=[]},adapter:"-rest",serialize:function(a,b){return this.serializerFor(a.constructor.typeKey).serialize(a,b)},defaultAdapter:Ember.computed("adapter",function(){var a=w(this,"adapter");return"string"==typeof a&&(a=this.container.lookup("adapter:"+a)||this.container.lookup("adapter:application")||this.container.lookup("adapter:-rest")),DS.Adapter.detect(a)&&(a=a.create({container:this.container})),a}),createRecord:function(a,c){a=this.modelFor(a),c=E(c)||{},z(c.id)&&(c.id=this._generateId(a)),c.id=b(c.id);var d=this.buildRecord(a,c.id);return d.loadedData(),d.setProperties(c),d},_generateId:function(a){var b=this.adapterFor(a);return b&&b.generateIdForRecord?b.generateIdForRecord(this):null},deleteRecord:function(a){a.deleteRecord()},unloadRecord:function(a){a.unloadRecord()},find:function(a,c){return 1===arguments.length?this.findAll(a):"object"===Ember.typeOf(c)?this.findQuery(a,c):this.findById(a,b(c))},findById:function(a,b){a=this.modelFor(a);var c=this.recordForId(a,b),d=this.fetchRecord(c);return h(d||c,"DS: Store#findById "+a+" with id: "+b)},findByIds:function(a,b){var c=this;return i(Ember.RSVP.all(C(b,function(b){return c.findById(a,b)})).then(Ember.A,null,"DS: Store#findByIds of "+a+" complete"))},fetchRecord:function(a){if(z(a))return null;if(a._loadingPromise)return a._loadingPromise;if(!w(a,"isEmpty"))return null;var b=a.constructor,c=w(a,"id"),d=this.adapterFor(b),e=m(d,this,b,c);return a.loadingData(e),e},getById:function(a,b){return this.hasRecordForId(a,b)?this.recordForId(a,b):null},reloadRecord:function(a){var b=a.constructor,c=this.adapterFor(b),d=w(a,"id");return m(c,this,b,d)},fetchMany:function(a,b){if(a.length){var c=Ember.MapWithDefault.create({defaultValue:function(){return Ember.A()}});A(a,function(a){c.get(a.constructor).push(a)});var d=[];return A(c,function(a,c){var e=c.mapProperty("id"),f=this.adapterFor(a);d.push(n(f,this,a,e,b))},this),Ember.RSVP.all(d)}},hasRecordForId:function(a,c){return c=b(c),a=this.modelFor(a),!!this.typeMapFor(a).idToRecord[c]},recordForId:function(a,c){a=this.modelFor(a),c=b(c);var d=this.typeMapFor(a).idToRecord[c];return d||(d=this.buildRecord(a,c)),d},findMany:function(a,b,c,d){c=this.modelFor(c),b=Ember.A(b);var e=b.filterProperty("isEmpty",!0),f=this.recordArrayManager.createManyArray(c,b);return A(e,function(a){a.loadingData()}),f.loadingRecordsCount=e.length,e.length?(A(e,function(a){this.recordArrayManager.registerWaitingRecordArray(a,f)},this),d.resolve(this.fetchMany(e,a))):(d&&d.resolve(),f.set("isLoaded",!0),y(f,"trigger","didLoad")),f},findHasMany:function(a,b,c,d){var e=this.adapterFor(a.constructor),f=this.recordArrayManager.createManyArray(c.type,Ember.A([]));return d.resolve(o(e,this,a,b,c)),f},findBelongsTo:function(a,b,c){var d=this.adapterFor(a.constructor);return p(d,this,a,b,c)},findQuery:function(a,b){a=this.modelFor(a);var c=this.recordArrayManager.createAdapterPopulatedRecordArray(a,b),d=this.adapterFor(a);
+return i(r(d,this,a,b,c))},findAll:function(a){return a=this.modelFor(a),this.fetchAll(a,this.all(a))},fetchAll:function(a,b){var c=this.adapterFor(a),d=this.typeMapFor(a).metadata.since;return x(b,"isUpdating",!0),i(q(c,this,a,d))},didUpdateAll:function(a){var b=this.typeMapFor(a).findAllCache;x(b,"isUpdating",!1)},all:function(a){a=this.modelFor(a);var b=this.typeMapFor(a),c=b.findAllCache;if(c)return c;var d=this.recordArrayManager.createRecordArray(a);return b.findAllCache=d,d},unloadAll:function(a){for(var b,c=this.modelFor(a),d=this.typeMapFor(c),e=d.records.slice(),f=0;fa?"0"+a:""+a},e=a.getUTCFullYear(),f=a.getUTCMonth(),g=a.getUTCDate(),h=a.getUTCDay(),i=a.getUTCHours(),j=a.getUTCMinutes(),k=a.getUTCSeconds(),l=b[h],m=d(g),n=c[f];return l+", "+m+" "+n+" "+e+" "+d(i)+":"+d(j)+":"+d(k)+" GMT"}return null}});b["default"]=d}),b("ember-data/lib/transforms/number",["./base","exports"],function(a,b){"use strict";var c=a["default"],d=Ember.isEmpty,e=c.extend({deserialize:function(a){return d(a)?null:Number(a)},serialize:function(a){return d(a)?null:Number(a)}});b["default"]=e}),b("ember-data/lib/transforms/string",["./base","exports"],function(a,b){"use strict";var c=a["default"],d=Ember.isNone,e=c.extend({deserialize:function(a){return d(a)?null:String(a)},serialize:function(a){return d(a)?null:String(a)}});b["default"]=e}),b("ember-inflector/lib/ext/string",["../system/string"],function(a){"use strict";var b=a.pluralize,c=a.singularize;(Ember.EXTEND_PROTOTYPES===!0||Ember.EXTEND_PROTOTYPES.String)&&(String.prototype.pluralize=function(){return b(this)},String.prototype.singularize=function(){return c(this)})}),b("ember-inflector/lib/main",["./system","./ext/string","exports"],function(a,b,c){"use strict";var d=a.Inflector,e=a.inflections,f=a.pluralize,g=a.singularize;d.defaultRules=e,Ember.Inflector=d,Ember.String.pluralize=f,Ember.String.singularize=g,c["default"]=d,c.pluralize=f,c.singularize=g}),b("ember-inflector/lib/system",["./system/inflector","./system/string","./system/inflections","exports"],function(a,b,c,d){"use strict";var e=a["default"],f=b.pluralize,g=b.singularize,h=c["default"];e.inflector=new e(h),d.Inflector=e,d.singularize=g,d.pluralize=f,d.defaultRules=h}),b("ember-inflector/lib/system/inflections",["exports"],function(a){"use strict";var b={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"]};a["default"]=b}),b("ember-inflector/lib/system/inflector",["exports"],function(a){"use strict";function b(a,b){for(var c=0,d=b.length;d>c;c++)a.uncountable[b[c].toLowerCase()]=!0}function c(a,b){for(var c,d=0,e=b.length;e>d;d++)c=b[d],a.irregular[c[0].toLowerCase()]=c[1],a.irregularInverse[c[1].toLowerCase()]=c[0]}function d(a){a=a||{},a.uncountable=a.uncountable||{},a.irregularPairs=a.irregularPairs||{};var d=this.rules={plurals:a.plurals||[],singular:a.singular||[],irregular:{},irregularInverse:{},uncountable:{}};b(d,a.uncountable),c(d,a.irregularPairs)}var e=/^\s*$/;d.prototype={plural:function(a,b){this.rules.plurals.push([a,b.toLowerCase()])},singular:function(a,b){this.rules.singular.push([a,b.toLowerCase()])},uncountable:function(a){b(this.rules,[a.toLowerCase()])},irregular:function(a,b){c(this.rules,[[a,b]])},pluralize:function(a){return this.inflect(a,this.rules.plurals,this.rules.irregular)},singularize:function(a){return this.inflect(a,this.rules.singular,this.rules.irregularInverse)},inflect:function(a,b,c){var d,f,g,h,i,j,k,l;if(i=e.test(a))return a;if(h=a.toLowerCase(),j=this.rules.uncountable[h])return a;if(k=c&&c[h])return k;for(var m=b.length,n=0;m>n&&(d=b[m-1],l=d[0],!l.test(a));m--);return d=d||[],l=d[0],f=d[1],g=a.replace(l,f)}},a["default"]=d}),b("ember-inflector/lib/system/string",["./inflector","exports"],function(a,b){"use strict";var c=a["default"],d=function(a){return c.inflector.pluralize(a)},e=function(a){return c.inflector.singularize(a)};b.pluralize=d,b.singularize=e}),a.DS=c("ember-data/lib/main")["default"]}(window);