incubator-ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From yus...@apache.org
Subject svn commit: r1380953 [3/8] - in /incubator/ambari/branches/AMBARI-666: ./ ambari-web/ ambari-web/app/ ambari-web/app/assets/ ambari-web/app/assets/img/ ambari-web/app/assets/licenses/ ambari-web/app/controllers/ ambari-web/app/models/ ambari-web/app/ro...
Date Wed, 05 Sep 2012 02:39:12 GMT
Added: incubator/ambari/branches/AMBARI-666/ambari-web/vendor/scripts/ember-data-latest.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/AMBARI-666/ambari-web/vendor/scripts/ember-data-latest.js?rev=1380953&view=auto
==============================================================================
--- incubator/ambari/branches/AMBARI-666/ambari-web/vendor/scripts/ember-data-latest.js (added)
+++ incubator/ambari/branches/AMBARI-666/ambari-web/vendor/scripts/ember-data-latest.js Wed Sep  5 02:39:09 2012
@@ -0,0 +1,4176 @@
+(function() {
+window.DS = Ember.Namespace.create({
+  CURRENT_API_REVISION: 4
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set;
+
+/**
+  A record array is an array that contains records of a certain type. The record
+  array materializes records as needed when they are retrieved for the first
+  time. You should not create record arrays yourself. Instead, an instance of
+  DS.RecordArray or its subclasses will be returned by your application's store
+  in response to queries.
+*/
+
+DS.RecordArray = Ember.ArrayProxy.extend({
+
+  /**
+    The model type contained by this record array.
+
+    @type DS.Model
+  */
+  type: null,
+
+  // The array of client ids backing the record array. When a
+  // record is requested from the record array, the record
+  // for the client id at the same index is materialized, if
+  // necessary, by the store.
+  content: null,
+
+  // The store that created this record array.
+  store: null,
+
+  objectAtContent: function(index) {
+    var content = get(this, 'content'),
+        clientId = content.objectAt(index),
+        store = get(this, 'store');
+
+    if (clientId !== undefined) {
+      return store.findByClientId(get(this, 'type'), clientId);
+    }
+  }
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get;
+
+DS.FilteredRecordArray = DS.RecordArray.extend({
+  filterFunction: null,
+
+  replace: function() {
+    var type = get(this, 'type').toString();
+    throw new Error("The result of a client-side filter (on " + type + ") is immutable.");
+  },
+
+  updateFilter: Ember.observer(function() {
+    var store = get(this, 'store');
+    store.updateRecordArrayFilter(this, get(this, 'type'), get(this, 'filterFunction'));
+  }, 'filterFunction')
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set;
+
+DS.AdapterPopulatedRecordArray = DS.RecordArray.extend({
+  query: null,
+  isLoaded: false,
+
+  replace: function() {
+    var type = get(this, 'type').toString();
+    throw new Error("The result of a server query (on " + type + ") is immutable.");
+  },
+
+  load: function(array) {
+    var store = get(this, 'store'), type = get(this, 'type');
+
+    var clientIds = store.loadMany(type, array).clientIds;
+
+    this.beginPropertyChanges();
+    set(this, 'content', Ember.A(clientIds));
+    set(this, 'isLoaded', true);
+    this.endPropertyChanges();
+  }
+});
+
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor;
+
+var Set = function() {
+  this.hash = {};
+  this.list = [];
+};
+
+Set.prototype = {
+  add: function(item) {
+    var hash = this.hash,
+        guid = guidFor(item);
+
+    if (hash.hasOwnProperty(guid)) { return; }
+
+    hash[guid] = true;
+    this.list.push(item);
+  },
+
+  remove: function(item) {
+    var hash = this.hash,
+        guid = guidFor(item);
+
+    if (!hash.hasOwnProperty(guid)) { return; }
+
+    delete hash[guid];
+    var list = this.list,
+        index = Ember.EnumerableUtils.indexOf(this, item);
+
+    list.splice(index, 1);
+  },
+
+  isEmpty: function() {
+    return this.list.length === 0;
+  }
+};
+
+var LoadedState = Ember.State.extend({
+  recordWasAdded: function(manager, record) {
+    var dirty = manager.dirty, observer;
+    dirty.add(record);
+
+    observer = function() {
+      if (!get(record, 'isDirty')) {
+        record.removeObserver('isDirty', observer);
+        manager.send('childWasSaved', record);
+      }
+    };
+
+    record.addObserver('isDirty', observer);
+  },
+
+  recordWasRemoved: function(manager, record) {
+    var dirty = manager.dirty, observer;
+    dirty.add(record);
+
+    observer = function() {
+      record.removeObserver('isDirty', observer);
+      if (!get(record, 'isDirty')) { manager.send('childWasSaved', record); }
+    };
+
+    record.addObserver('isDirty', observer);
+  }
+});
+
+var states = {
+  loading: Ember.State.create({
+    isLoaded: false,
+    isDirty: false,
+
+    loadedRecords: function(manager, count) {
+      manager.decrement(count);
+    },
+
+    becameLoaded: function(manager) {
+      manager.transitionTo('clean');
+    }
+  }),
+
+  clean: LoadedState.create({
+    isLoaded: true,
+    isDirty: false,
+
+    recordWasAdded: function(manager, record) {
+      this._super(manager, record);
+      manager.goToState('dirty');
+    },
+
+    update: function(manager, clientIds) {
+      var manyArray = manager.manyArray;
+      set(manyArray, 'content', clientIds);
+    }
+  }),
+
+  dirty: LoadedState.create({
+    isLoaded: true,
+    isDirty: true,
+
+    childWasSaved: function(manager, child) {
+      var dirty = manager.dirty;
+      dirty.remove(child);
+
+      if (dirty.isEmpty()) { manager.send('arrayBecameSaved'); }
+    },
+
+    arrayBecameSaved: function(manager) {
+      manager.goToState('clean');
+    }
+  })
+};
+
+DS.ManyArrayStateManager = Ember.StateManager.extend({
+  manyArray: null,
+  initialState: 'loading',
+  states: states,
+
+  /**
+   This number is used to keep track of the number of outstanding
+   records that must be loaded before the array is considered
+   loaded. As results stream in, this number is decremented until
+   it becomes zero, at which case the `isLoaded` flag will be set
+   to true
+  */
+  counter: 0,
+
+  init: function() {
+    this._super();
+    this.dirty = new Set();
+    this.counter = get(this, 'manyArray.length');
+  },
+
+  decrement: function(count) {
+    var counter = this.counter = this.counter - count;
+
+    Ember.assert("Somehow the ManyArray loaded counter went below 0. This is probably an ember-data bug. Please report it at https://github.com/emberjs/data/issues", counter >= 0);
+
+    if (counter === 0) {
+      this.send('becameLoaded');
+    }
+  }
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set;
+
+DS.ManyArray = DS.RecordArray.extend({
+  init: function() {
+    set(this, 'stateManager', DS.ManyArrayStateManager.create({ manyArray: this }));
+
+    return this._super();
+  },
+
+  parentRecord: null,
+
+  isDirty: Ember.computed(function() {
+    return get(this, 'stateManager.currentState.isDirty');
+  }).property('stateManager.currentState').cacheable(),
+
+  isLoaded: Ember.computed(function() {
+    return get(this, 'stateManager.currentState.isLoaded');
+  }).property('stateManager.currentState').cacheable(),
+
+  send: function(event, context) {
+    this.get('stateManager').send(event, context);
+  },
+
+  fetch: function() {
+    var clientIds = get(this, 'content'),
+        store = get(this, 'store'),
+        type = get(this, 'type');
+
+    store.fetchUnloadedClientIds(type, clientIds);
+  },
+
+  // Overrides Ember.Array's replace method to implement
+  replaceContent: function(index, removed, added) {
+    var parentRecord = get(this, 'parentRecord');
+    var pendingParent = parentRecord && !get(parentRecord, 'id');
+    var stateManager = get(this, 'stateManager');
+
+    // Map the array of record objects into an array of  client ids.
+    added = added.map(function(record) {
+      Ember.assert("You can only add records of " + (get(this, 'type') && get(this, 'type').toString()) + " to this association.", !get(this, 'type') || (get(this, 'type') === record.constructor));
+
+      // If the record to which this many array belongs does not yet
+      // have an id, notify the newly-added record that it must wait
+      // for the parent to receive an id before the child can be
+      // saved.
+      if (pendingParent) {
+        record.send('waitingOn', parentRecord);
+      }
+
+      var oldParent = this.assignInverse(record, parentRecord);
+
+      record.get('transaction')
+        .relationshipBecameDirty(record, oldParent, parentRecord);
+
+      stateManager.send('recordWasAdded', record);
+
+      return record.get('clientId');
+    }, this);
+
+    var store = this.store;
+
+    var len = index+removed, record;
+    for (var i = index; i < len; i++) {
+      // TODO: null out inverse FK
+      record = this.objectAt(i);
+      var oldParent = this.assignInverse(record, parentRecord, true);
+
+      record.get('transaction')
+        .relationshipBecameDirty(record, parentRecord, null);
+
+      // If we put the child record into a pending state because
+      // we were waiting on the parent record to get an id, we
+      // can tell the child it no longer needs to wait.
+      if (pendingParent) {
+        record.send('doneWaitingOn', parentRecord);
+      }
+
+      stateManager.send('recordWasAdded', record);
+    }
+
+    this._super(index, removed, added);
+  },
+
+  assignInverse: function(record, parentRecord, remove) {
+    var associationMap = get(record.constructor, 'associations'),
+        possibleAssociations = associationMap.get(parentRecord.constructor),
+        possible, actual, oldParent;
+
+    if (!possibleAssociations) { return; }
+
+    for (var i = 0, l = possibleAssociations.length; i < l; i++) {
+      possible = possibleAssociations[i];
+
+      if (possible.kind === 'belongsTo') {
+        actual = possible;
+        break;
+      }
+    }
+
+    if (actual) {
+      oldParent = get(record, actual.name);
+      set(record, actual.name, remove ? null : parentRecord);
+      return oldParent;
+    }
+  },
+
+  // Create a child record within the parentRecord
+  createRecord: function(hash, transaction) {
+    var parentRecord = get(this, 'parentRecord'),
+        store = get(parentRecord, 'store'),
+        type = get(this, 'type'),
+        record;
+
+    transaction = transaction || get(parentRecord, 'transaction');
+
+    record = store.createRecord.call(store, type, hash, transaction);
+    this.pushObject(record);
+
+    return record;
+  }
+});
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt,
+    removeObject = Ember.EnumerableUtils.removeObject;
+
+/**
+  A transaction allows you to collect multiple records into a unit of work
+  that can be committed or rolled back as a group.
+
+  For example, if a record has local modifications that have not yet
+  been saved, calling `commit()` on its transaction will cause those
+  modifications to be sent to the adapter to be saved. Calling
+  `rollback()` on its transaction would cause all of the modifications to
+  be discarded and the record to return to the last known state before
+  changes were made.
+
+  If a newly created record's transaction is rolled back, it will
+  immediately transition to the deleted state.
+
+  If you do not explicitly create a transaction, a record is assigned to
+  an implicit transaction called the default transaction. In these cases,
+  you can treat your application's instance of `DS.Store` as a transaction
+  and call the `commit()` and `rollback()` methods on the store itself.
+
+  Once a record has been successfully committed or rolled back, it will
+  be moved back to the implicit transaction. Because it will now be in
+  a clean state, it can be moved to a new transaction if you wish.
+
+  ### Creating a Transaction
+
+  To create a new transaction, call the `transaction()` method of your
+  application's `DS.Store` instance:
+
+      var transaction = App.store.transaction();
+
+  This will return a new instance of `DS.Transaction` with no records
+  yet assigned to it.
+
+  ### Adding Existing Records
+
+  Add records to a transaction using the `add()` method:
+
+      record = App.store.find(Person, 1);
+      transaction.add(record);
+
+  Note that only records whose `isDirty` flag is `false` may be added
+  to a transaction. Once modifications to a record have been made
+  (its `isDirty` flag is `true`), it is not longer able to be added to
+  a transaction.
+
+  ### Creating New Records
+
+  Because newly created records are dirty from the time they are created,
+  and because dirty records can not be added to a transaction, you must
+  use the `createRecord()` method to assign new records to a transaction.
+
+  For example, instead of this:
+
+    var transaction = store.transaction();
+    var person = Person.createRecord({ name: "Steve" });
+
+    // won't work because person is dirty
+    transaction.add(person);
+
+  Call `createRecord()` on the transaction directly:
+
+    var transaction = store.transaction();
+    transaction.createRecord(Person, { name: "Steve" });
+
+  ### Asynchronous Commits
+
+  Typically, all of the records in a transaction will be committed
+  together. However, new records that have a dependency on other new
+  records need to wait for their parent record to be saved and assigned an
+  ID. In that case, the child record will continue to live in the
+  transaction until its parent is saved, at which time the transaction will
+  attempt to commit again.
+
+  For this reason, you should not re-use transactions once you have committed
+  them. Always make a new transaction and move the desired records to it before
+  calling commit.
+*/
+
+DS.Transaction = Ember.Object.extend({
+  /**
+    @private
+
+    Creates the bucket data structure used to segregate records by
+    type.
+  */
+  init: function() {
+    set(this, 'buckets', {
+      clean:    Ember.Map.create(),
+      created:  Ember.Map.create(),
+      updated:  Ember.Map.create(),
+      deleted:  Ember.Map.create(),
+      inflight: Ember.Map.create()
+    });
+
+    this.dirtyRelationships = {
+      byChild: Ember.Map.create(),
+      byNewParent: Ember.Map.create(),
+      byOldParent: Ember.Map.create()
+    };
+  },
+
+  /**
+    Creates a new record of the given type and assigns it to the transaction
+    on which the method was called.
+
+    This is useful as only clean records can be added to a transaction and
+    new records created using other methods immediately become dirty.
+
+    @param {DS.Model} type the model type to create
+    @param {Object} hash the data hash to assign the new record
+  */
+  createRecord: function(type, hash) {
+    var store = get(this, 'store');
+
+    return store.createRecord(type, hash, this);
+  },
+
+  /**
+    Adds an existing record to this transaction. Only records without
+    modficiations (i.e., records whose `isDirty` property is `false`)
+    can be added to a transaction.
+
+    @param {DS.Model} record the record to add to the transaction
+  */
+  add: function(record) {
+    // we could probably make this work if someone has a valid use case. Do you?
+    Ember.assert("Once a record has changed, you cannot move it into a different transaction", !get(record, 'isDirty'));
+
+    var recordTransaction = get(record, 'transaction'),
+        defaultTransaction = get(this, 'store.defaultTransaction');
+
+    Ember.assert("Models cannot belong to more than one transaction at a time.", recordTransaction === defaultTransaction);
+
+    this.adoptRecord(record);
+  },
+
+  /**
+    Commits the transaction, which causes all of the modified records that
+    belong to the transaction to be sent to the adapter to be saved.
+
+    Once you call `commit()` on a transaction, you should not re-use it.
+
+    When a record is saved, it will be removed from this transaction and
+    moved back to the store's default transaction.
+  */
+  commit: function() {
+    var self = this,
+        iterate;
+
+    iterate = function(bucketType, fn, binding) {
+      var dirty = self.bucketForType(bucketType);
+
+      dirty.forEach(function(type, records) {
+        if (records.isEmpty()) { return; }
+
+        var array = [];
+
+        records.forEach(function(record) {
+          record.send('willCommit');
+
+          if (get(record, 'isPending') === false) {
+            array.push(record);
+          }
+        });
+
+        fn.call(binding, type, array);
+      });
+    };
+
+    var commitDetails = {
+      updated: {
+        eachType: function(fn, binding) { iterate('updated', fn, binding); }
+      },
+
+      created: {
+        eachType: function(fn, binding) { iterate('created', fn, binding); }
+      },
+
+      deleted: {
+        eachType: function(fn, binding) { iterate('deleted', fn, binding); }
+      }
+    };
+
+    var store = get(this, 'store');
+    var adapter = get(store, '_adapter');
+
+    this.removeCleanRecords();
+
+    if (adapter && adapter.commit) { adapter.commit(store, commitDetails); }
+    else { throw fmt("Adapter is either null or does not implement `commit` method", this); }
+  },
+
+  /**
+    Rolling back a transaction resets the records that belong to
+    that transaction.
+
+    Updated records have their properties reset to the last known
+    value from the persistence layer. Deleted records are reverted
+    to a clean, non-deleted state. Newly created records immediately
+    become deleted, and are not sent to the adapter to be persisted.
+
+    After the transaction is rolled back, any records that belong
+    to it will return to the store's default transaction, and the
+    current transaction should not be used again.
+  */
+  rollback: function() {
+    var store = get(this, 'store'),
+        dirty;
+
+    // Loop through all of the records in each of the dirty states
+    // and initiate a rollback on them. As a side effect of telling
+    // the record to roll back, it should also move itself out of
+    // the dirty bucket and into the clean bucket.
+    ['created', 'updated', 'deleted', 'inflight'].forEach(function(bucketType) {
+      dirty = this.bucketForType(bucketType);
+
+      dirty.forEach(function(type, records) {
+        records.forEach(function(record) {
+          record.send('rollback');
+        });
+      });
+    }, this);
+
+    // Now that all records in the transaction are guaranteed to be
+    // clean, migrate them all to the store's default transaction.
+    this.removeCleanRecords();
+  },
+
+  /**
+    @private
+
+    Removes a record from this transaction and back to the store's
+    default transaction.
+
+    Note: This method is private for now, but should probably be exposed
+    in the future once we have stricter error checking (for example, in the
+    case of the record being dirty).
+
+    @param {DS.Model} record
+  */
+  remove: function(record) {
+    var defaultTransaction = get(this, 'store.defaultTransaction');
+    defaultTransaction.adoptRecord(record);
+  },
+
+  /**
+    @private
+
+    Removes all of the records in the transaction's clean bucket.
+  */
+  removeCleanRecords: function() {
+    var clean = this.bucketForType('clean'),
+        self = this;
+
+    clean.forEach(function(type, records) {
+      records.forEach(function(record) {
+        self.remove(record);
+      });
+    });
+  },
+
+  /**
+    @private
+
+    Returns the bucket for the given bucket type. For example, you might call
+    `this.bucketForType('updated')` to get the `Ember.Map` that contains all
+    of the records that have changes pending.
+
+    @param {String} bucketType the type of bucket
+    @returns Ember.Map
+  */
+  bucketForType: function(bucketType) {
+    var buckets = get(this, 'buckets');
+
+    return get(buckets, bucketType);
+  },
+
+  /**
+    @private
+
+    This method moves a record into a different transaction without the normal
+    checks that ensure that the user is not doing something weird, like moving
+    a dirty record into a new transaction.
+
+    It is designed for internal use, such as when we are moving a clean record
+    into a new transaction when the transaction is committed.
+
+    This method must not be called unless the record is clean.
+
+    @param {DS.Model} record
+  */
+  adoptRecord: function(record) {
+    var oldTransaction = get(record, 'transaction');
+
+    if (oldTransaction) {
+      oldTransaction.removeFromBucket('clean', record);
+    }
+
+    this.addToBucket('clean', record);
+    set(record, 'transaction', this);
+  },
+
+  /**
+    @private
+
+    Adds a record to the named bucket.
+
+    @param {String} bucketType one of `clean`, `created`, `updated`, or `deleted`
+  */
+  addToBucket: function(bucketType, record) {
+    var bucket = this.bucketForType(bucketType),
+        type = record.constructor;
+
+    var records = bucket.get(type);
+
+    if (!records) {
+      records = Ember.OrderedSet.create();
+      bucket.set(type, records);
+    }
+
+    records.add(record);
+  },
+
+  /**
+    @private
+
+    Removes a record from the named bucket.
+
+    @param {String} bucketType one of `clean`, `created`, `updated`, or `deleted`
+  */
+  removeFromBucket: function(bucketType, record) {
+    var bucket = this.bucketForType(bucketType),
+        type = record.constructor;
+
+    var records = bucket.get(type);
+    records.remove(record);
+  },
+
+  /**
+    @private
+
+    Called by a ManyArray when a new record is added to it. This
+    method will index a relationship description by the child
+    record, its old parent, and its new parent.
+
+    The store will provide this description to the adapter's
+    shouldCommit method, so it can determine whether any of
+    the records is pending another record. The store will also
+    provide a list of these descriptions to the adapter's commit
+    method.
+
+    @param {DS.Model} record the new child record
+    @param {DS.Model} oldParent the parent that the child is
+      moving from, or null
+    @param {DS.Model} newParent the parent that the child is
+      moving to, or null
+  */
+  relationshipBecameDirty: function(child, oldParent, newParent) {
+    var relationships = this.dirtyRelationships, relationship;
+
+    var relationshipsForChild = relationships.byChild.get(child),
+        possibleRelationship,
+        needsNewEntries = true;
+
+    // If the child has any existing dirty relationships in this
+    // transaction, we need to collapse the old relationship
+    // into the new one. For example, if we change the parent of
+    // a child record before saving, there is no need to save the
+    // record that was its parent temporarily.
+    if (relationshipsForChild) {
+
+      // Loop through all of the relationships we know about that
+      // contain the same child as the new relationship.
+      for (var i=0, l=relationshipsForChild.length; i<l; i++) {
+        relationship = relationshipsForChild[i];
+
+        // If the parent of the child record has changed, there is
+        // no need to update the old parent that had not yet been saved.
+        //
+        // This case is two changes in a record's parent:
+        //
+        //   A -> B
+        //   B -> C
+        //
+        // In this case, there is no need to remember the A->B
+        // change. We can collapse both changes into:
+        //
+        //   A -> C
+        //
+        // Another possible case is:
+        //
+        //   A -> B
+        //   B -> A
+        //
+        // In this case, we don't need to do anything. We can
+        // simply remove the original A->B change and call it
+        // a day.
+        if (relationship.newParent === oldParent) {
+          oldParent = relationship.oldParent;
+          this.removeRelationship(relationship);
+
+          // This is the case of A->B followed by B->A.
+          if (relationship.oldParent === newParent) {
+            needsNewEntries = false;
+          }
+        }
+      }
+    }
+
+    relationship = {
+      child: child,
+      oldParent: oldParent,
+      newParent: newParent
+    };
+
+    // If we didn't go A->B and then B->A, add new dirty relationship
+    // entries.
+    if (needsNewEntries) {
+      this.addRelationshipTo('byChild', child, relationship);
+      this.addRelationshipTo('byOldParent', oldParent, relationship);
+      this.addRelationshipTo('byNewParent', newParent, relationship);
+    }
+  },
+
+  removeRelationship: function(relationship) {
+    var relationships = this.dirtyRelationships;
+
+    removeObject(relationships.byOldParent.get(relationship.oldParent), relationship);
+    removeObject(relationships.byNewParent.get(relationship.newParent), relationship);
+    removeObject(relationships.byChild.get(relationship.child), relationship);
+  },
+
+  addRelationshipTo: function(type, record, description) {
+    var map = this.dirtyRelationships[type];
+
+    var relationships = map.get(record);
+
+    if (!relationships) {
+      relationships = [ description ];
+      map.set(record, relationships);
+    } else {
+      relationships.push(description);
+    }
+  },
+
+  /**
+    @private
+
+    Called by a record's state manager to indicate that the record has entered
+    a dirty state. The record will be moved from the `clean` bucket and into
+    the appropriate dirty bucket.
+
+    @param {String} bucketType one of `created`, `updated`, or `deleted`
+  */
+  recordBecameDirty: function(bucketType, record) {
+    this.removeFromBucket('clean', record);
+    this.addToBucket(bucketType, record);
+  },
+
+  /**
+    @private
+
+    Called by a record's state manager to indicate that the record has entered
+    inflight state. The record will be moved from its current dirty bucket and into
+    the `inflight` bucket.
+
+    @param {String} bucketType one of `created`, `updated`, or `deleted`
+  */
+  recordBecameInFlight: function(kind, record) {
+    this.removeFromBucket(kind, record);
+    this.addToBucket('inflight', record);
+  },
+
+  /**
+    @private
+
+    Called by a record's state manager to indicate that the record has entered
+    a clean state. The record will be moved from its current dirty or inflight bucket and into
+    the `clean` bucket.
+
+    @param {String} bucketType one of `created`, `updated`, or `deleted`
+  */
+  recordBecameClean: function(kind, record) {
+    this.removeFromBucket(kind, record);
+
+    this.remove(record);
+  }
+});
+
+})();
+
+
+
+(function() {
+/*globals Ember*/
+var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
+
+var DATA_PROXY = {
+  get: function(name) {
+    return this.savedData[name];
+  }
+};
+
+// These values are used in the data cache when clientIds are
+// needed but the underlying data has not yet been loaded by
+// the server.
+var UNLOADED = 'unloaded';
+var LOADING = 'loading';
+
+// 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 hash provided by that source.
+//   * +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.
+//   * +type+ means a subclass of DS.Model.
+
+/**
+  The store contains all of the hashes for records loaded from the server.
+  It is also responsible for creating instances of DS.Model when you request one
+  of these data hashes, so that they can be bound to in your Handlebars templates.
+
+  Create a new store like this:
+
+       MyApp.store = DS.Store.create();
+
+  You can retrieve DS.Model instances from the store in several ways. To retrieve
+  a record for a specific id, use the `find()` method:
+
+       var record = MyApp.store.find(MyApp.Contact, 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:
+
+       MyApp.store = DS.Store.create({
+         adapter: 'MyApp.CustomAdapter'
+       });
+
+    You can learn more about writing a custom adapter by reading the `DS.Adapter`
+    documentation.
+*/
+DS.Store = Ember.Object.extend({
+
+  /**
+    Many methods can be invoked without specifying which store should be used.
+    In those cases, the first store created will be used as the default. If
+    an application has multiple stores, it should specify which store to use
+    when performing actions, such as finding records by id.
+
+    The init method registers this store as the default if none is specified.
+  */
+  init: function() {
+    // Enforce API revisioning. See BREAKING_CHANGES.md for more.
+    var revision = get(this, 'revision');
+
+    if (revision !== DS.CURRENT_API_REVISION && !Ember.ENV.TESTING) {
+      throw new Error("Error: The Ember Data library has had breaking API changes since the last time you updated the library. Please review the list of breaking changes at https://github.com/emberjs/data/blob/master/BREAKING_CHANGES.md, then update your store's `revision` property to " + DS.CURRENT_API_REVISION);
+    }
+
+    if (!get(DS, 'defaultStore') || get(this, 'isDefaultStore')) {
+      set(DS, 'defaultStore', this);
+    }
+
+    // internal bookkeeping; not observable
+    this.typeMaps = {};
+    this.recordCache = [];
+    this.clientIdToId = {};
+    this.recordArraysByClientId = {};
+
+    // Internally, we maintain a map of all unloaded IDs requested by
+    // a ManyArray. As the adapter loads hashes into the store, the
+    // store notifies any interested ManyArrays. When the ManyArray's
+    // total number of loading records drops to zero, it becomes
+    // `isLoaded` and fires a `didLoad` event.
+    this.loadingRecordArrays = {};
+
+    set(this, 'defaultTransaction', this.transaction());
+
+    return this._super();
+  },
+
+  /**
+    Returns a new transaction scoped to this store.
+
+    @see {DS.Transaction}
+    @returns DS.Transaction
+  */
+  transaction: function() {
+    return DS.Transaction.create({ store: this });
+  },
+
+  /**
+    @private
+
+    This is used only by the record's DataProxy. Do not use this directly.
+  */
+  dataForRecord: function(record) {
+    var type = record.constructor,
+        clientId = get(record, 'clientId'),
+        typeMap = this.typeMapFor(type);
+
+    return typeMap.cidToHash[clientId];
+  },
+
+  /**
+    The adapter to use to communicate to a backend server or other persistence layer.
+
+    This can be specified as an instance, a class, or a property path that specifies
+    where the adapter can be located.
+
+    @property {DS.Adapter|String}
+  */
+  adapter: null,
+
+  /**
+    @private
+
+    This property returns the adapter, after resolving a possible String.
+
+    @returns DS.Adapter
+  */
+  _adapter: Ember.computed(function() {
+    var adapter = get(this, 'adapter');
+    if (typeof adapter === 'string') {
+      return get(this, adapter, false) || get(window, adapter);
+    }
+    return adapter;
+  }).property('adapter').cacheable(),
+
+  // A monotonically increasing number to be used to uniquely identify
+  // data hashes and records.
+  clientIdCounter: 1,
+
+  // .....................
+  // . CREATE NEW RECORD .
+  // .....................
+
+  /**
+    Create a new record in the current store. The properties passed
+    to this method are set on the newly created record.
+
+    @param {subclass of DS.Model} type
+    @param {Object} properties a hash of properties to set on the
+      newly created record.
+    @returns DS.Model
+  */
+  createRecord: function(type, properties, transaction) {
+    properties = properties || {};
+
+    // Create a new instance of the model `type` and put it
+    // into the specified `transaction`. If no transaction is
+    // specified, the default transaction will be used.
+    //
+    // NOTE: A `transaction` is specified when the
+    // `transaction.createRecord` API is used.
+    var record = type._create({
+      store: this
+    });
+
+    transaction = transaction || get(this, 'defaultTransaction');
+    transaction.adoptRecord(record);
+
+    // Extract the primary key from the `properties` hash,
+    // based on the `primaryKey` for the model type.
+    var primaryKey = get(record, 'primaryKey'),
+        id = properties[primaryKey] || null;
+
+    // If the passed properties do not include a primary key,
+    // give the adapter an opportunity to generate one.
+    var adapter;
+    if (Ember.none(id)) {
+      adapter = get(this, 'adapter');
+      if (adapter && adapter.generateIdForRecord) {
+        id = adapter.generateIdForRecord(this, record);
+        properties.id = id;
+      }
+    }
+
+    var hash = {}, clientId;
+
+    // Push the hash into the store. If present, associate the
+    // extracted `id` with the hash.
+    clientId = this.pushHash(hash, id, type);
+
+    record.send('didChangeData');
+
+    var recordCache = get(this, 'recordCache');
+
+    // Now that we have a clientId, attach it to the record we
+    // just created.
+    set(record, 'clientId', clientId);
+
+    // Store the record we just created in the record cache for
+    // this clientId.
+    recordCache[clientId] = record;
+
+    // Set the properties specified on the record.
+    record.setProperties(properties);
+
+    this.updateRecordArrays(type, clientId, get(record, 'data'));
+
+    return record;
+  },
+
+  // .................
+  // . DELETE RECORD .
+  // .................
+
+  /**
+    For symmetry, a record can be deleted via the store.
+
+    @param {DS.Model} record
+  */
+  deleteRecord: function(record) {
+    record.send('deleteRecord');
+  },
+
+  // ................
+  // . FIND RECORDS .
+  // ................
+
+  /**
+    This is the main entry point into finding records. The first
+    parameter to this method is always a subclass of `DS.Model`.
+
+    You can use the `find` method on a subclass of `DS.Model`
+    directly if your application only has one store. For
+    example, instead of `store.find(App.Person, 1)`, you could
+    say `App.Person.find(1)`.
+
+    ---
+
+    To find a record by ID, pass the `id` as the second parameter:
+
+        store.find(App.Person, 1);
+        App.Person.find(1);
+
+    If the record with that `id` had not previously been loaded,
+    the store will return an empty record immediately and ask
+    the adapter to find the data by calling the adapter's `find`
+    method.
+
+    The `find` method will always return the same object for a
+    given type and `id`. To check whether the adapter has populated
+    a record, you can check its `isLoaded` property.
+
+    ---
+
+    To find all records for a type, call `find` with no additional
+    parameters:
+
+        store.find(App.Person);
+        App.Person.find();
+
+    This will return a `RecordArray` representing all known records
+    for the given type and kick off a request to the adapter's
+    `findAll` method to load any additional records for the type.
+
+    The `RecordArray` returned by `find()` is live. If any more
+    records for the type are added at a later time through any
+    mechanism, it will automatically update to reflect the change.
+
+    ---
+
+    To find a record by a query, call `find` with a hash as the
+    second parameter:
+
+        store.find(App.Person, { page: 1 });
+        App.Person.find({ page: 1 });
+
+    This will return a `RecordArray` immediately, but it will always
+    be an empty `RecordArray` at first. It will call the adapter's
+    `findQuery` method, which will populate the `RecordArray` once
+    the server has returned results.
+
+    You can check whether a query results `RecordArray` has loaded
+    by checking its `isLoaded` property.
+  */
+  find: function(type, id, query) {
+    if (id === undefined) {
+      return this.findAll(type);
+    }
+
+    if (query !== undefined) {
+      return this.findMany(type, id, query);
+    } else if (Ember.typeOf(id) === 'object') {
+      return this.findQuery(type, id);
+    }
+
+    if (Ember.isArray(id)) {
+      return this.findMany(type, id);
+    }
+
+    var clientId = this.typeMapFor(type).idToCid[id];
+
+    return this.findByClientId(type, clientId, id);
+  },
+
+  findByClientId: function(type, clientId, id) {
+    var recordCache = get(this, 'recordCache'),
+        dataCache, record;
+
+    // If there is already a clientId assigned for this
+    // type/id combination, try to find an existing
+    // record for that id and return. Otherwise,
+    // materialize a new record and set its data to the
+    // value we already have.
+    if (clientId !== undefined) {
+      record = recordCache[clientId];
+
+      if (!record) {
+        // create a new instance of the model type in the
+        // 'isLoading' state
+        record = this.materializeRecord(type, clientId);
+
+        dataCache = this.typeMapFor(type).cidToHash;
+
+        if (typeof dataCache[clientId] === 'object') {
+          record.send('didChangeData');
+        }
+      }
+    } else {
+      clientId = this.pushHash(LOADING, id, type);
+
+      // create a new instance of the model type in the
+      // 'isLoading' state
+      record = this.materializeRecord(type, clientId, id);
+
+      // let the adapter set the data, possibly async
+      var adapter = get(this, '_adapter');
+      if (adapter && adapter.find) { adapter.find(this, type, id); }
+      else { throw fmt("Adapter is either null or does not implement `find` method", this); }
+    }
+
+    return record;
+  },
+
+  /**
+    @private
+
+    Given a type and array of `clientId`s, determines which of those
+    `clientId`s has not yet been loaded.
+
+    In preparation for loading, this method also marks any unloaded
+    `clientId`s as loading.
+  */
+  neededClientIds: function(type, clientIds) {
+    var neededClientIds = [],
+        typeMap = this.typeMapFor(type),
+        dataCache = typeMap.cidToHash,
+        clientId;
+
+    for (var i=0, l=clientIds.length; i<l; i++) {
+      clientId = clientIds[i];
+      if (dataCache[clientId] === UNLOADED) {
+        neededClientIds.push(clientId);
+        dataCache[clientId] = LOADING;
+      }
+    }
+
+    return neededClientIds;
+  },
+
+  /**
+    @private
+
+    This method is the entry point that associations use to update
+    themselves when their underlying data changes.
+
+    First, it determines which of its `clientId`s are still unloaded,
+    then converts the needed `clientId`s to IDs and invokes `findMany`
+    on the adapter.
+  */
+  fetchUnloadedClientIds: function(type, clientIds) {
+    var neededClientIds = this.neededClientIds(type, clientIds);
+    this.fetchMany(type, neededClientIds);
+  },
+
+  /**
+    @private
+
+    This method takes a type and list of `clientId`s, converts the
+    `clientId`s into IDs, and then invokes the adapter's `findMany`
+    method.
+
+    It is used both by a brand new association (via the `findMany`
+    method) or when the data underlying an existing association
+    changes (via the `fetchUnloadedClientIds` method).
+  */
+  fetchMany: function(type, clientIds) {
+    var clientIdToId = this.clientIdToId;
+
+    var neededIds = Ember.EnumerableUtils.map(clientIds, function(clientId) {
+      return clientIdToId[clientId];
+    });
+
+    if (!neededIds.length) { return; }
+
+    var adapter = get(this, '_adapter');
+    if (adapter && adapter.findMany) { adapter.findMany(this, type, neededIds); }
+    else { throw fmt("Adapter is either null or does not implement `findMany` method", this); }
+  },
+
+  /**
+    @private
+
+    `findMany` is the entry point that associations use to generate a
+    new `ManyArray` for the list of IDs specified by the server for
+    the association.
+
+    Its responsibilities are:
+
+    * convert the IDs into clientIds
+    * determine which of the clientIds still need to be loaded
+    * create a new ManyArray whose content is *all* of the clientIds
+    * notify the ManyArray of the number of its elements that are
+      already loaded
+    * insert the unloaded clientIds into the `loadingRecordArrays`
+      bookkeeping structure, which will allow the `ManyArray` to know
+      when all of its loading elements are loaded from the server.
+    * ask the adapter to load the unloaded elements, by invoking
+      findMany with the still-unloaded IDs.
+  */
+  findMany: function(type, ids) {
+    // 1. Convert ids to client ids
+    // 2. Determine which of the client ids need to be loaded
+    // 3. Create a new ManyArray whose content is ALL of the clientIds
+    // 4. Decrement the ManyArray's counter by the number of loaded clientIds
+    // 5. Put the ManyArray into our bookkeeping data structure, keyed on
+    //    the needed clientIds
+    // 6. Ask the adapter to load the records for the unloaded clientIds (but
+    //    convert them back to ids)
+
+    var clientIds = this.clientIdsForIds(type, ids);
+
+    var neededClientIds = this.neededClientIds(type, clientIds),
+        manyArray = this.createManyArray(type, Ember.A(clientIds)),
+        loadedCount = clientIds.length - neededClientIds.length,
+        loadingRecordArrays = this.loadingRecordArrays,
+        clientId, i, l;
+
+    manyArray.send('loadedRecords', loadedCount);
+
+    if (neededClientIds.length) {
+      for (i=0, l=neededClientIds.length; i<l; i++) {
+        clientId = neededClientIds[i];
+        if (loadingRecordArrays[clientId]) {
+          loadingRecordArrays[clientId].push(manyArray);
+        } else {
+          this.loadingRecordArrays[clientId] = [ manyArray ];
+        }
+      }
+
+      this.fetchMany(type, neededClientIds);
+    }
+
+    return manyArray;
+  },
+
+  findQuery: function(type, query) {
+    var array = DS.AdapterPopulatedRecordArray.create({ type: type, content: Ember.A([]), store: this });
+    var adapter = get(this, '_adapter');
+    if (adapter && adapter.findQuery) { adapter.findQuery(this, type, query, array); }
+    else { throw fmt("Adapter is either null or does not implement `findQuery` method", this); }
+    return array;
+  },
+
+  findAll: function(type) {
+
+    var typeMap = this.typeMapFor(type),
+        findAllCache = typeMap.findAllCache;
+
+    if (findAllCache) { return findAllCache; }
+
+    var array = DS.RecordArray.create({ type: type, content: Ember.A([]), store: this });
+    this.registerRecordArray(array, type);
+
+    var adapter = get(this, '_adapter');
+    if (adapter && adapter.findAll) { adapter.findAll(this, type); }
+
+    typeMap.findAllCache = array;
+    return array;
+  },
+
+  filter: function(type, query, filter) {
+    // allow an optional server query
+    if (arguments.length === 3) {
+      this.findQuery(type, query);
+    } else if (arguments.length === 2) {
+      filter = query;
+    }
+
+    var array = DS.FilteredRecordArray.create({ type: type, content: Ember.A([]), store: this, filterFunction: filter });
+
+    this.registerRecordArray(array, type, filter);
+
+    return array;
+  },
+
+  recordIsLoaded: function(type, id) {
+    return !Ember.none(this.typeMapFor(type).idToCid[id]);
+  },
+
+  // ............
+  // . UPDATING .
+  // ............
+
+  hashWasUpdated: function(type, clientId, record) {
+    // Because hash updates are invoked at the end of the run loop,
+    // it is possible that a record might be deleted after its hash
+    // has been modified and this method was scheduled to be called.
+    //
+    // If that's the case, the record would have already been removed
+    // from all record arrays; calling updateRecordArrays would just
+    // add it back. If the record is deleted, just bail. It shouldn't
+    // give us any more trouble after this.
+
+    if (get(record, 'isDeleted')) { return; }
+    this.updateRecordArrays(type, clientId, get(record, 'data'));
+  },
+
+  // ..............
+  // . PERSISTING .
+  // ..............
+
+  commit: function() {
+    var defaultTransaction = get(this, 'defaultTransaction');
+    set(this, 'defaultTransaction', this.transaction());
+
+    defaultTransaction.commit();
+  },
+
+  didUpdateRecords: function(array, hashes) {
+    if (hashes) {
+      array.forEach(function(record, idx) {
+        this.didUpdateRecord(record, hashes[idx]);
+      }, this);
+    } else {
+      array.forEach(function(record) {
+        this.didUpdateRecord(record);
+      }, this);
+    }
+  },
+
+  didUpdateRecord: function(record, hash) {
+    if (hash) {
+      var clientId = get(record, 'clientId'),
+          dataCache = this.typeMapFor(record.constructor).cidToHash;
+
+      dataCache[clientId] = hash;
+      record.send('didChangeData');
+      record.hashWasUpdated();
+    } else {
+      record.send('didSaveData');
+    }
+
+    record.send('didCommit');
+  },
+
+  didDeleteRecords: function(array) {
+    array.forEach(function(record) {
+      record.send('didCommit');
+    });
+  },
+
+  didDeleteRecord: function(record) {
+    record.send('didCommit');
+  },
+
+  _didCreateRecord: function(record, hash, typeMap, clientId, primaryKey) {
+    var recordData = get(record, 'data'), id, changes;
+
+    if (hash) {
+      typeMap.cidToHash[clientId] = hash;
+
+      // If the server returns a hash, we assume that the server's version
+      // of the data supercedes the local changes.
+      record.beginPropertyChanges();
+      record.send('didChangeData');
+      recordData.adapterDidUpdate();
+      record.hashWasUpdated();
+      record.endPropertyChanges();
+
+      id = hash[primaryKey];
+
+      typeMap.idToCid[id] = clientId;
+      this.clientIdToId[clientId] = id;
+    } else {
+      recordData.commit();
+    }
+
+    record.send('didCommit');
+  },
+
+
+  didCreateRecords: function(type, array, hashes) {
+    var primaryKey = type.proto().primaryKey,
+        typeMap = this.typeMapFor(type),
+        clientId;
+
+    for (var i=0, l=get(array, 'length'); i<l; i++) {
+      var record = array[i], hash = hashes[i];
+      clientId = get(record, 'clientId');
+
+      this._didCreateRecord(record, hash, typeMap, clientId, primaryKey);
+    }
+  },
+
+  didCreateRecord: function(record, hash) {
+    var type = record.constructor,
+        typeMap = this.typeMapFor(type),
+        clientId, primaryKey;
+
+    // The hash is optional, but if it is not provided, the client must have
+    // provided a primary key.
+
+    primaryKey = type.proto().primaryKey;
+
+    // TODO: Make Ember.assert more flexible
+    if (hash) {
+      Ember.assert("The server must provide a primary key: " + primaryKey, get(hash, primaryKey));
+    } else {
+      Ember.assert("The server did not return data, and you did not create a primary key (" + primaryKey + ") on the client", get(get(record, 'data'), primaryKey));
+    }
+
+    clientId = get(record, 'clientId');
+
+    this._didCreateRecord(record, hash, typeMap, clientId, primaryKey);
+  },
+
+  recordWasInvalid: function(record, errors) {
+    record.send('becameInvalid', errors);
+  },
+
+  // .................
+  // . RECORD ARRAYS .
+  // .................
+
+  registerRecordArray: function(array, type, filter) {
+    var recordArrays = this.typeMapFor(type).recordArrays;
+
+    recordArrays.push(array);
+
+    this.updateRecordArrayFilter(array, type, filter);
+  },
+
+  createManyArray: function(type, clientIds) {
+    var array = DS.ManyArray.create({ type: type, content: clientIds, store: this });
+
+    clientIds.forEach(function(clientId) {
+      var recordArrays = this.recordArraysForClientId(clientId);
+      recordArrays.add(array);
+    }, this);
+
+    return array;
+  },
+
+  updateRecordArrayFilter: function(array, type, filter) {
+    var typeMap = this.typeMapFor(type),
+        dataCache = typeMap.cidToHash,
+        clientIds = typeMap.clientIds,
+        clientId, hash, proxy;
+
+    var recordCache = get(this, 'recordCache'),
+        foundRecord,
+        record;
+
+    for (var i=0, l=clientIds.length; i<l; i++) {
+      clientId = clientIds[i];
+      foundRecord = false;
+
+      hash = dataCache[clientId];
+      if (typeof hash === 'object') {
+        if (record = recordCache[clientId]) {
+          if (!get(record, 'isDeleted')) {
+            proxy = get(record, 'data');
+            foundRecord = true;
+          }
+        } else {
+          DATA_PROXY.savedData = hash;
+          proxy = DATA_PROXY;
+          foundRecord = true;
+        }
+
+        if (foundRecord) { this.updateRecordArray(array, filter, type, clientId, proxy); }
+      }
+    }
+  },
+
+  updateRecordArrays: function(type, clientId, dataProxy) {
+    var recordArrays = this.typeMapFor(type).recordArrays,
+        filter;
+
+    recordArrays.forEach(function(array) {
+      filter = get(array, 'filterFunction');
+      this.updateRecordArray(array, filter, type, clientId, dataProxy);
+    }, this);
+
+    // loop through all manyArrays containing an unloaded copy of this
+    // clientId and notify them that the record was loaded.
+    var manyArrays = this.loadingRecordArrays[clientId], manyArray;
+
+    if (manyArrays) {
+      for (var i=0, l=manyArrays.length; i<l; i++) {
+        manyArrays[i].send('loadedRecords', 1);
+      }
+
+      this.loadingRecordArrays[clientId] = null;
+    }
+  },
+
+  updateRecordArray: function(array, filter, type, clientId, dataProxy) {
+    var shouldBeInArray;
+
+    if (!filter) {
+      shouldBeInArray = true;
+    } else {
+      shouldBeInArray = filter(dataProxy);
+    }
+
+    var content = get(array, 'content');
+    var alreadyInArray = content.indexOf(clientId) !== -1;
+
+    var recordArrays = this.recordArraysForClientId(clientId);
+
+    if (shouldBeInArray && !alreadyInArray) {
+      recordArrays.add(array);
+      content.pushObject(clientId);
+    } else if (!shouldBeInArray && alreadyInArray) {
+      recordArrays.remove(array);
+      content.removeObject(clientId);
+    }
+  },
+
+  removeFromRecordArrays: function(record) {
+    var clientId = get(record, 'clientId');
+    var recordArrays = this.recordArraysForClientId(clientId);
+
+    recordArrays.forEach(function(array) {
+      var content = get(array, 'content');
+      content.removeObject(clientId);
+    });
+  },
+
+  // ............
+  // . INDEXING .
+  // ............
+
+  recordArraysForClientId: function(clientId) {
+    var recordArrays = get(this, 'recordArraysByClientId');
+    var ret = recordArrays[clientId];
+
+    if (!ret) {
+      ret = recordArrays[clientId] = Ember.OrderedSet.create();
+    }
+
+    return ret;
+  },
+
+  typeMapFor: function(type) {
+    var typeMaps = get(this, 'typeMaps');
+    var guidForType = Ember.guidFor(type);
+
+    var typeMap = typeMaps[guidForType];
+
+    if (typeMap) {
+      return typeMap;
+    } else {
+      return (typeMaps[guidForType] =
+        {
+          idToCid: {},
+          clientIds: [],
+          cidToHash: {},
+          recordArrays: []
+      });
+    }
+  },
+
+  /** @private
+
+    For a given type and id combination, returns the client id used by the store.
+    If no client id has been assigned yet, one will be created and returned.
+
+    @param {DS.Model} type
+    @param {String|Number} id
+  */
+  clientIdForId: function(type, id) {
+    var clientId = this.typeMapFor(type).idToCid[id];
+
+    if (clientId !== undefined) { return clientId; }
+
+    return this.pushHash(UNLOADED, id, type);
+  },
+
+  /**
+    @private
+
+    This method works exactly like `clientIdForId`, but does not
+    require looking up the `typeMap` for every `clientId` and
+    invoking a method per `clientId`.
+  */
+  clientIdsForIds: function(type, ids) {
+    var typeMap = this.typeMapFor(type),
+        idToClientIdMap = typeMap.idToCid;
+
+    return Ember.EnumerableUtils.map(ids, function(id) {
+      var clientId = idToClientIdMap[id];
+      if (clientId) { return clientId; }
+      return this.pushHash(UNLOADED, id, type);
+    }, this);
+  },
+
+  // ................
+  // . LOADING DATA .
+  // ................
+
+  /**
+    Load a new data hash into the store for a given id and type combination.
+    If data for that record had been loaded previously, the new information
+    overwrites the old.
+
+    If the record you are loading data for has outstanding changes that have not
+    yet been saved, an exception will be thrown.
+
+    @param {DS.Model} type
+    @param {String|Number} id
+    @param {Object} hash the data hash to load
+  */
+  load: function(type, id, hash) {
+    if (hash === undefined) {
+      hash = id;
+      var primaryKey = type.proto().primaryKey;
+      Ember.assert("A data hash was loaded for a record of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", primaryKey in hash);
+      id = hash[primaryKey];
+    }
+
+    var typeMap = this.typeMapFor(type),
+        dataCache = typeMap.cidToHash,
+        clientId = typeMap.idToCid[id],
+        recordCache = get(this, 'recordCache');
+
+    if (clientId !== undefined) {
+      dataCache[clientId] = hash;
+
+      var record = recordCache[clientId];
+      if (record) {
+        record.send('didChangeData');
+      }
+    } else {
+      clientId = this.pushHash(hash, id, type);
+    }
+
+    DATA_PROXY.savedData = hash;
+    this.updateRecordArrays(type, clientId, DATA_PROXY);
+
+    return { id: id, clientId: clientId };
+  },
+
+  loadMany: function(type, ids, hashes) {
+    var clientIds = Ember.A([]);
+
+    if (hashes === undefined) {
+      hashes = ids;
+      ids = [];
+      var primaryKey = type.proto().primaryKey;
+
+      ids = Ember.EnumerableUtils.map(hashes, function(hash) {
+        return hash[primaryKey];
+      });
+    }
+
+    for (var i=0, l=get(ids, 'length'); i<l; i++) {
+      var loaded = this.load(type, ids[i], hashes[i]);
+      clientIds.pushObject(loaded.clientId);
+    }
+
+    return { clientIds: clientIds, ids: ids };
+  },
+
+  /** @private
+
+    Stores a data hash for the specified type and id combination and returns
+    the client id.
+
+    @param {Object} hash
+    @param {String|Number} id
+    @param {DS.Model} type
+    @returns {Number}
+  */
+  pushHash: function(hash, id, type) {
+    var typeMap = this.typeMapFor(type);
+
+    var idToClientIdMap = typeMap.idToCid,
+        clientIdToIdMap = this.clientIdToId,
+        clientIds = typeMap.clientIds,
+        dataCache = typeMap.cidToHash;
+
+    var clientId = ++this.clientIdCounter;
+
+    dataCache[clientId] = hash;
+
+    // if we're creating an item, this process will be done
+    // later, once the object has been persisted.
+    if (id) {
+      idToClientIdMap[id] = clientId;
+      clientIdToIdMap[clientId] = id;
+    }
+
+    clientIds.push(clientId);
+
+    return clientId;
+  },
+
+  // ..........................
+  // . RECORD MATERIALIZATION .
+  // ..........................
+
+  materializeRecord: function(type, clientId, id) {
+    var record;
+
+    get(this, 'recordCache')[clientId] = record = type._create({
+      store: this,
+      clientId: clientId,
+      _id: id
+    });
+
+    get(this, 'defaultTransaction').adoptRecord(record);
+
+    record.send('loadingData');
+    return record;
+  },
+
+  destroy: function() {
+    if (get(DS, 'defaultStore') === this) {
+      set(DS, 'defaultStore', null);
+    }
+
+    return this._super();
+  }
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor;
+
+/**
+  This file encapsulates the various states that a record can transition
+  through during its lifecycle.
+
+  ### State Manager
+
+  A record's state manager explicitly tracks what state a record is in
+  at any given time. For instance, if a record is newly created and has
+  not yet been sent to the adapter to be saved, it would be in the
+  `created.uncommitted` state.  If a record has had local modifications
+  made to it that are in the process of being saved, the record would be
+  in the `updated.inFlight` state. (These state paths will be explained
+  in more detail below.)
+
+  Events are sent by the record or its store to the record's state manager.
+  How the state manager reacts to these events is dependent on which state
+  it is in. In some states, certain events will be invalid and will cause
+  an exception to be raised.
+
+  States are hierarchical. For example, a record can be in the
+  `deleted.start` state, then transition into the `deleted.inFlight` state.
+  If a child state does not implement an event handler, the state manager
+  will attempt to invoke the event on all parent states until the root state is
+  reached. The state hierarchy of a record is described in terms of a path
+  string. You can determine a record's current state by getting its manager's
+  current state path:
+
+        record.get('stateManager.currentState.path');
+        //=> "created.uncommitted"
+
+  The `DS.Model` states are themselves stateless. What we mean is that,
+  though each instance of a record also has a unique instance of a
+  `DS.StateManager`, 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 state
+  manager points to this global, immutable shared instance. How does a
+  state know which record it should be acting on?  We pass a reference to
+  the current state manager as the first parameter to every method invoked
+  on a state.
+
+  The state manager 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. If you need access to the record being acted on, you can
+  retrieve the state manager's `record` property. For example, if you had
+  an event handler `myEvent`:
+
+      myEvent: function(manager) {
+        var record = manager.get('record');
+        record.doSomething();
+      }
+
+  For more information about state managers in general, see the Ember.js
+  documentation on `Ember.StateManager`.
+
+  ### Events, Flags, and Transitions
+
+  A state may implement zero or more events, flags, or transitions.
+
+  #### Events
+
+  Events are named functions that are invoked when sent to a record. The
+  state manager 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:
+
+      aState: DS.State.create({
+        myEvent: function(manager, param) {
+          console.log("Received myEvent with "+param);
+        }
+      })
+
+  To trigger this event:
+
+      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 state manager's `goToState()` 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
+        * start <-- currentState
+        * inFlight
+      * updated
+        * inFlight
+
+  If we are currently in the `start` state, calling
+  `goToState('inFlight')` would transition to the `created.inFlight` state,
+  while calling `goToState('updated.inFlight')` would transition to
+  the `updated.inFlight` state.
+
+  Remember that *only events* should ever cause a state transition. You should
+  never call `goToState()` 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:
+
+      var statePath = record.get('stateManager.currentState.path');
+      if (statePath === 'created.inFlight') {
+        doSomething();
+      }
+
+  You can say:
+
+      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.
+
+  #### Transitions
+
+  Transitions are like event handlers but are called automatically upon
+  entering or exiting a state. To implement a transition, just call a method
+  either `enter` or `exit`:
+
+      myState: DS.State.create({
+        // Gets called automatically when entering
+        // this state.
+        enter: function(manager) {
+          console.log("Entered myState");
+        }
+      })
+
+   Note that enter and exit events are called once per transition. If the
+   current state changes, but changes to another child state of the parent,
+   the transition event on the parent will not be triggered.
+*/
+
+var stateProperty = Ember.computed(function(key) {
+  var parent = get(this, 'parentState');
+  if (parent) {
+    return get(parent, key);
+  }
+}).property();
+
+var isEmptyObject = function(object) {
+  for (var name in object) {
+    if (object.hasOwnProperty(name)) { return false; }
+  }
+
+  return true;
+};
+
+var hasDefinedProperties = function(object) {
+  for (var name in object) {
+    if (object.hasOwnProperty(name) && object[name]) { return true; }
+  }
+
+  return false;
+};
+
+DS.State = Ember.State.extend({
+  isLoaded: stateProperty,
+  isDirty: stateProperty,
+  isSaving: stateProperty,
+  isDeleted: stateProperty,
+  isError: stateProperty,
+  isNew: stateProperty,
+  isValid: stateProperty,
+  isPending: stateProperty,
+
+  // For states that are substates of a
+  // DirtyState (updated or created), it is
+  // useful to be able to determine which
+  // type of dirty state it is.
+  dirtyType: stateProperty
+});
+
+var setProperty = function(manager, context) {
+  var key = context.key, value = context.value;
+
+  var record = get(manager, 'record'),
+      data = get(record, 'data');
+
+  set(data, key, value);
+};
+
+var setAssociation = function(manager, context) {
+  var key = context.key, value = context.value;
+
+  var record = get(manager, 'record'),
+      data = get(record, 'data');
+
+  data.setAssociation(key, value);
+};
+
+var didChangeData = function(manager) {
+  var record = get(manager, 'record'),
+      data = get(record, 'data');
+
+  data._savedData = null;
+  record.notifyPropertyChange('data');
+};
+
+// The waitingOn event shares common functionality
+// between the different dirty states, but each is
+// treated slightly differently. This method is exposed
+// so that each implementation can invoke the common
+// behavior, and then implement the behavior specific
+// to the state.
+var waitingOn = function(manager, object) {
+  var record = get(manager, 'record'),
+      pendingQueue = get(record, 'pendingQueue'),
+      objectGuid = guidFor(object);
+
+  var observer = function() {
+    if (get(object, 'id')) {
+      manager.send('doneWaitingOn', object);
+      Ember.removeObserver(object, 'id', observer);
+    }
+  };
+
+  pendingQueue[objectGuid] = [object, observer];
+  Ember.addObserver(object, 'id', observer);
+};
+
+// 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's transaction 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.
+// * isPending: A record `isPending` when it belongs to an
+//   association on another record and that record has not been
+//   saved. A record in this state cannot be saved because it
+//   lacks a "foreign key" that will be supplied by its parent
+//   association when the parent record has been created. When
+//   the adapter reports that the parent has saved, the
+//   `isPending` property on all children will become `false`
+//   and the transaction will try to commit the records.
+
+// This mixin is mixed into various uncommitted states. Make
+// sure to mix it in *after* the class definition, so its
+// super points to the class definition.
+var Uncommitted = Ember.Mixin.create({
+  setProperty: setProperty,
+  setAssociation: setAssociation
+});
+
+// These mixins are mixed into substates of the concrete
+// subclasses of DirtyState.
+
+var CreatedUncommitted = Ember.Mixin.create({
+  deleteRecord: function(manager) {
+    var record = get(manager, 'record');
+    this._super(manager);
+
+    record.withTransaction(function(t) {
+      t.recordBecameClean('created', record);
+    });
+    manager.goToState('deleted.saved');
+  }
+});
+
+var UpdatedUncommitted = Ember.Mixin.create({
+  deleteRecord: function(manager) {
+    this._super(manager);
+
+    var record = get(manager, 'record');
+
+    record.withTransaction(function(t) {
+      t.recordBecameClean('updated', record);
+    });
+
+    manager.goToState('deleted');
+  }
+});
+
+// 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.
+var DirtyState = DS.State.extend({
+  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.
+  uncommitted: DS.State.extend({
+    // TRANSITIONS
+    enter: function(manager) {
+      var dirtyType = get(this, 'dirtyType'),
+          record = get(manager, 'record');
+
+      record.withTransaction(function (t) {
+        t.recordBecameDirty(dirtyType, record);
+      });
+    },
+
+    // EVENTS
+    deleteRecord: Ember.K,
+
+    waitingOn: function(manager, object) {
+      waitingOn(manager, object);
+      manager.goToState('pending');
+    },
+
+    willCommit: function(manager) {
+      manager.goToState('inFlight');
+    },
+
+    becameInvalid: function(manager) {
+      var dirtyType = get(this, 'dirtyType'),
+          record = get(manager, 'record');
+
+      record.withTransaction(function (t) {
+        t.recordBecameInFlight(dirtyType, record);
+      });
+
+      manager.goToState('invalid');
+    },
+
+    rollback: function(manager) {
+      var record = get(manager, 'record'),
+          dirtyType = get(this, 'dirtyType'),
+          data = get(record, 'data');
+
+      data.rollback();
+
+      record.withTransaction(function(t) {
+        t.recordBecameClean(dirtyType, record);
+      });
+
+      manager.goToState('saved');
+    }
+  }, Uncommitted),
+
+  // 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: DS.State.extend({
+    // FLAGS
+    isSaving: true,
+
+    // TRANSITIONS
+    enter: function(manager) {
+      var dirtyType = get(this, 'dirtyType'),
+          record = get(manager, 'record');
+
+      record.withTransaction(function (t) {
+        t.recordBecameInFlight(dirtyType, record);
+      });
+    },
+
+    // EVENTS
+    didCommit: function(manager) {
+      var dirtyType = get(this, 'dirtyType'),
+          record = get(manager, 'record');
+
+      record.withTransaction(function(t) {
+        t.recordBecameClean('inflight', record);
+      });
+
+      manager.goToState('saved');
+      manager.send('invokeLifecycleCallbacks', dirtyType);
+    },
+
+    becameInvalid: function(manager, errors) {
+      var record = get(manager, 'record');
+
+      set(record, 'errors', errors);
+
+      manager.goToState('invalid');
+      manager.send('invokeLifecycleCallbacks');
+    },
+
+    becameError: function(manager) {
+      manager.goToState('error');
+      manager.send('invokeLifecycleCallbacks');
+    },
+
+    didChangeData: didChangeData
+  }),
+
+  // If a record becomes associated with a newly created
+  // parent record, it will be `pending` until the parent
+  // record has successfully persisted. Once this happens,
+  // this record can use the parent's primary key as its
+  // foreign key.
+  //
+  // If the record's transaction had already started to
+  // commit, the record will transition to the `inFlight`
+  // state. If it had not, the record will transition to
+  // the `uncommitted` state.
+  pending: DS.State.extend({
+    initialState: 'uncommitted',
+
+    // FLAGS
+    isPending: true,
+
+    // SUBSTATES
+
+    // A pending record whose transaction has not yet
+    // started to commit is in this state.
+    uncommitted: DS.State.extend({
+      // EVENTS
+      deleteRecord: function(manager) {
+        var record = get(manager, 'record'),
+            pendingQueue = get(record, 'pendingQueue'),
+            tuple;
+
+        // since we are leaving the pending state, remove any
+        // observers we have registered on other records.
+        for (var prop in pendingQueue) {
+          if (!pendingQueue.hasOwnProperty(prop)) { continue; }
+
+          tuple = pendingQueue[prop];
+          Ember.removeObserver(tuple[0], 'id', tuple[1]);
+        }
+      },
+
+      willCommit: function(manager) {
+        manager.goToState('committing');
+      },
+
+      doneWaitingOn: function(manager, object) {
+        var record = get(manager, 'record'),
+            pendingQueue = get(record, 'pendingQueue'),
+            objectGuid = guidFor(object);
+
+        delete pendingQueue[objectGuid];
+
+        if (isEmptyObject(pendingQueue)) {
+          manager.send('doneWaiting');
+        }
+      },
+
+      doneWaiting: function(manager) {
+        var dirtyType = get(this, 'dirtyType');
+        manager.goToState(dirtyType + '.uncommitted');
+      }
+    }, Uncommitted),
+
+    // A pending record whose transaction has started
+    // to commit is in this state. Since it has not yet
+    // been sent to the adapter, it is not `inFlight`
+    // until all of its dependencies have been committed.
+    committing: DS.State.extend({
+      // FLAGS
+      isSaving: true,
+
+      // EVENTS
+      doneWaitingOn: function(manager, object) {
+        var record = get(manager, 'record'),
+            pendingQueue = get(record, 'pendingQueue'),
+            objectGuid = guidFor(object);
+
+        delete pendingQueue[objectGuid];
+
+        if (isEmptyObject(pendingQueue)) {
+          manager.send('doneWaiting');
+        }
+      },
+
+      doneWaiting: function(manager) {
+        var record = get(manager, 'record'),
+            transaction = get(record, 'transaction');
+
+        // Now that the record is no longer pending, schedule
+        // the transaction to commit.
+        Ember.run.once(transaction, transaction.commit);
+      },
+
+      willCommit: function(manager) {
+        var record = get(manager, 'record'),
+            pendingQueue = get(record, 'pendingQueue');
+
+        if (isEmptyObject(pendingQueue)) {
+          var dirtyType = get(this, 'dirtyType');
+          manager.goToState(dirtyType + '.inFlight');
+        }
+      }
+    })
+  }),
+
+  // 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: DS.State.extend({
+    // FLAGS
+    isValid: false,
+
+    exit: function(manager) {
+      var record = get(manager, 'record');
+
+      record.withTransaction(function (t) {
+        t.recordBecameClean('inflight', record);
+      });
+    },
+
+    // EVENTS
+    deleteRecord: function(manager) {
+      manager.goToState('deleted');
+    },
+
+    setAssociation: setAssociation,
+
+    setProperty: function(manager, context) {
+      setProperty(manager, context);
+
+      var record = get(manager, 'record'),
+          errors = get(record, 'errors'),
+          key = context.key;
+
+      set(errors, key, null);
+
+      if (!hasDefinedProperties(errors)) {
+        manager.send('becameValid');
+      }
+    },
+
+    rollback: function(manager) {
+      manager.send('becameValid');
+      manager.send('rollback');
+    },
+
+    becameValid: function(manager) {
+      manager.goToState('uncommitted');
+    },
+
+    invokeLifecycleCallbacks: function(manager) {
+      var record = get(manager, 'record');
+      record.trigger('becameInvalid', record);
+    }
+  })
+});
+
+// The created and updated states are created outside the state
+// chart so we can reopen their substates and add mixins as
+// necessary.
+
+var createdState = DirtyState.create({
+  dirtyType: 'created',
+
+  // FLAGS
+  isNew: true
+});
+
+var updatedState = DirtyState.create({
+  dirtyType: 'updated'
+});
+
+// The created.uncommitted state and created.pending.uncommitted share
+// some logic defined in CreatedUncommitted.
+createdState.states.uncommitted.reopen(CreatedUncommitted);
+createdState.states.pending.states.uncommitted.reopen(CreatedUncommitted);
+
+// The created.uncommitted state needs to immediately transition to the
+// deleted state if it is rolled back.
+createdState.states.uncommitted.reopen({
+  rollback: function(manager) {
+    this._super(manager);
+    manager.goToState('deleted.saved');
+  }
+});
+
+// The updated.uncommitted state and updated.pending.uncommitted share
+// some logic defined in UpdatedUncommitted.
+updatedState.states.uncommitted.reopen(UpdatedUncommitted);
+updatedState.states.pending.states.uncommitted.reopen(UpdatedUncommitted);
+updatedState.states.inFlight.reopen({
+  didSaveData: function(manager) {
+    var record = get(manager, 'record'),
+        data = get(record, 'data');
+
+    data.saveData();
+    data.adapterDidUpdate();
+  }
+});
+
+var states = {
+  rootState: Ember.State.create({
+    // FLAGS
+    isLoaded: false,
+    isDirty: false,
+    isSaving: false,
+    isDeleted: false,
+    isError: false,
+    isNew: false,
+    isValid: true,
+    isPending: false,
+
+    // 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: DS.State.create({
+      // EVENTS
+      loadingData: function(manager) {
+        manager.goToState('loading');
+      },
+
+      didChangeData: function(manager) {
+        didChangeData(manager);
+
+        manager.goToState('loaded.created');
+      }
+    }),
+
+    // A record enters this state when the store askes
+    // 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: DS.State.create({
+      // TRANSITIONS
+      exit: function(manager) {
+        var record = get(manager, 'record');
+        record.trigger('didLoad');
+      },
+
+      // EVENTS
+      didChangeData: function(manager, data) {
+        didChangeData(manager);
+        manager.send('loadedData');
+      },
+
+      loadedData: function(manager) {
+        manager.goToState('loaded');
+      }
+    }),
+
+    // 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: DS.State.create({
+      initialState: 'saved',
+
+      // FLAGS
+      isLoaded: true,
+
+      // SUBSTATES
+
+      // If there are no local changes to a record, it remains
+      // in the `saved` state.
+      saved: DS.State.create({
+
+        // EVENTS
+        setProperty: function(manager, context) {
+          setProperty(manager, context);
+          manager.goToState('updated');
+        },
+
+        setAssociation: function(manager, context) {
+          setAssociation(manager, context);
+          manager.goToState('updated');
+        },
+
+        didChangeData: didChangeData,
+
+        deleteRecord: function(manager) {
+          manager.goToState('deleted');
+        },
+
+        waitingOn: function(manager, object) {
+          waitingOn(manager, object);
+          manager.goToState('updated.pending');
+        },
+
+        invokeLifecycleCallbacks: function(manager, dirtyType) {
+          var record = get(manager, 'record');
+          if (dirtyType === 'created') {
+            record.trigger('didCreate', record);
+          } else {
+            record.trigger('didUpdate', record);
+          }
+        }
+      }),
+
+      // 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: DS.State.create({
+      // FLAGS
+      isDeleted: true,
+      isLoaded: true,
+      isDirty: true,
+
+      // TRANSITIONS
+      enter: function(manager) {
+        var record = get(manager, 'record'),
+            store = get(record, 'store');
+
+        store.removeFromRecordArrays(record);
+      },
+
+      // SUBSTATES
+
+      // When a record is deleted, it enters the `start`
+      // state. It will exit this state when the record's
+      // transaction starts to commit.
+      start: DS.State.create({
+        // TRANSITIONS
+        enter: function(manager) {
+          var record = get(manager, 'record');
+
+          record.withTransaction(function(t) {
+            t.recordBecameDirty('deleted', record);
+          });
+        },
+
+        // EVENTS
+        willCommit: function(manager) {
+          manager.goToState('inFlight');
+        },
+
+        rollback: function(manager) {
+          var record = get(manager, 'record'),
+              data = get(record, 'data');
+
+          data.rollback();
+          record.withTransaction(function(t) {
+            t.recordBecameClean('deleted', record);
+          });
+          manager.goToState('loaded');
+        }
+      }),
+
+      // After a record's transaction is committing, but
+      // before the adapter indicates that the deletion
+      // has saved to the server, a record is in the
+      // `inFlight` substate of `deleted`.
+      inFlight: DS.State.create({
+        // FLAGS
+        isSaving: true,
+
+        // TRANSITIONS
+        enter: function(manager) {
+          var record = get(manager, 'record');
+
+          record.withTransaction(function (t) {
+            t.recordBecameInFlight('deleted', record);
+          });
+        },
+
+        // EVENTS
+        didCommit: function(manager) {
+          var record = get(manager, 'record');
+
+          record.withTransaction(function(t) {
+            t.recordBecameClean('inflight', record);
+          });
+
+          manager.goToState('saved');
+
+          manager.send('invokeLifecycleCallbacks');
+        }
+      }),
+
+      // Once the adapter indicates that the deletion has
+      // been saved, the record enters the `saved` substate
+      // of `deleted`.
+      saved: DS.State.create({
+        // FLAGS
+        isDirty: false,
+
+        invokeLifecycleCallbacks: function(manager) {
+          var record = get(manager, 'record');
+          record.trigger('didDelete', record);
+        }
+      })
+    }),
+
+    // If the adapter indicates that there was an unknown
+    // error saving a record, the record enters the `error`
+    // state.
+    error: DS.State.create({
+      isError: true,
+
+      // EVENTS
+
+      invokeLifecycleCallbacks: function(manager) {
+        var record = get(manager, 'record');
+        record.trigger('becameError', record);
+      }
+    })
+  })
+};
+
+DS.StateManager = Ember.StateManager.extend({
+  record: null,
+  initialState: 'rootState',
+  states: states
+});
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set;
+
+//  When a record is changed on the client, it is considered "dirty"--there are
+//  pending changes that need to be saved to a persistence layer, such as a
+//  server.
+//
+//  If the record is rolled back, it re-enters a clean state, any changes are
+//  discarded, and its attributes are reset back to the last known good copy
+//  of the data that came from the server.
+//
+//  If the record is committed, the changes are sent to the server to be saved,
+//  and once the server confirms that they are valid, the record's "canonical"
+//  data becomes the original canonical data plus the changes merged in.
+//
+//  A DataProxy is an object that encapsulates this change tracking. It
+//  contains three buckets:
+//
+//  * `savedData` - the last-known copy of the data from the server
+//  * `unsavedData` - a hash that contains any changes that have not yet
+//     been committed
+//  * `associations` - this is similar to `savedData`, but holds the client
+//    ids of associated records
+//
+//  When setting a property on the object, the value is placed into the
+//  `unsavedData` bucket:
+//
+//      proxy.set('key', 'value');
+//
+//      // unsavedData:
+//      {
+//        key: "value"
+//      }
+//
+//  When retrieving a property from the object, it first looks to see
+//  if that value exists in the `unsavedData` bucket, and returns it if so.
+//  Otherwise, it returns the value from the `savedData` bucket.
+//
+//  When the adapter notifies a record that it has been saved, it merges the
+//  `unsavedData` bucket into the `savedData` bucket. If the record's
+//  transaction is rolled back, the `unsavedData` hash is simply discarded.
+//
+//  This object is a regular JS object for performance. It is only
+//  used internally for bookkeeping purposes.
+
+var DataProxy = DS._DataProxy = function(record) {
+  this.record = record;
+
+  this.unsavedData = {};
+
+  this.associations = {};
+};
+
+DataProxy.prototype = {
+  get: function(key) { return Ember.get(this, key); },
+  set: function(key, value) { return Ember.set(this, key, value); },
+
+  setAssociation: function(key, value) {
+    this.associations[key] = value;
+  },
+
+  savedData: function() {
+    var savedData = this._savedData;
+    if (savedData) { return savedData; }
+
+    var record = this.record,
+        clientId = get(record, 'clientId'),
+        store = get(record, 'store');
+
+    if (store) {
+      savedData = store.dataForRecord(record);
+      this._savedData = savedData;
+      return savedData;
+    }
+  },
+
+  unknownProperty: function(key) {
+    var unsavedData = this.unsavedData,
+        associations = this.associations,
+        savedData = this.savedData(),
+        store;
+
+    var value = unsavedData[key], association;
+
+    // if this is a belongsTo association, this will
+    // be a clientId.
+    association = associations[key];
+
+    if (association !== undefined) {
+      store = get(this.record, 'store');
+      return store.clientIdToId[association];
+    }
+
+    if (savedData && value === undefined) {
+      value = savedData[key];
+    }
+
+    return value;
+  },
+
+  setUnknownProperty: function(key, value) {
+    var record = this.record,
+        unsavedData = this.unsavedData;
+
+    unsavedData[key] = value;
+
+    record.hashWasUpdated();
+
+    return value;
+  },
+
+  commit: function() {
+    this.saveData();
+
+    this.record.notifyPropertyChange('data');
+  },
+
+  rollback: function() {
+    this.unsavedData = {};
+
+    this.record.notifyPropertyChange('data');
+  },
+
+  saveData: function() {
+    var record = this.record;
+
+    var unsavedData = this.unsavedData;
+    var savedData = this.savedData();
+
+    for (var prop in unsavedData) {
+      if (unsavedData.hasOwnProperty(prop)) {
+        savedData[prop] = unsavedData[prop];
+        delete unsavedData[prop];
+      }
+    }
+  },
+
+  adapterDidUpdate: function() {
+    this.unsavedData = {};
+  }
+};
+
+})();
+
+
+
+(function() {
+var get = Ember.get, set = Ember.set, none = Ember.none;
+
+var retrieveFromCurrentState = Ember.computed(function(key) {
+  return get(get(this, 'stateManager.currentState'), key);
+}).property('stateManager.currentState').cacheable();
+
+DS.Model = Ember.Object.extend(Ember.Evented, {
+  isLoaded: retrieveFromCurrentState,
+  isDirty: retrieveFromCurrentState,
+  isSaving: retrieveFromCurrentState,
+  isDeleted: retrieveFromCurrentState,
+  isError: retrieveFromCurrentState,
+  isNew: retrieveFromCurrentState,
+  isPending: retrieveFromCurrentState,
+  isValid: retrieveFromCurrentState,
+
+  clientId: null,
+  transaction: null,
+  stateManager: null,
+  pendingQueue: null,
+  errors: null,
+
+  // because unknownProperty is used, any internal property
+  // must be initialized here.
+  primaryKey: 'id',
+  id: Ember.computed(function(key, value) {
+    var primaryKey = get(this, 'primaryKey'),
+        data = get(this, 'data');
+
+    if (arguments.length === 2) {
+      set(data, primaryKey, value);
+      return value;
+    }
+
+    var id = get(data, primaryKey);
+    return id ? id : this._id;
+  }).property('primaryKey', 'data'),
+
+  // The following methods are callbacks invoked by `toJSON`. You
+  // can override one of the callbacks to override specific behavior,
+  // or toJSON itself.
+  //
+  // If you override toJSON, you can invoke these callbacks manually
+  // to get the default behavior.
+
+  /**
+    Add the record's primary key to the JSON hash.
+
+    The default implementation uses the record's specified `primaryKey`
+    and the `id` computed property, which are passed in as parameters.
+
+    @param {Object} json the JSON hash being built
+    @param {Number|String} id the record's id
+    @param {String} key the primaryKey for the record
+  */
+  addIdToJSON: function(json, id, key) {
+    if (id) { json[key] = id; }
+  },
+
+  /**
+    Add the attributes' current values to the JSON hash.
+
+    The default implementation gets the current value of each
+    attribute from the `data`, and uses a `defaultValue` if
+    specified in the `DS.attr` definition.
+
+    @param {Object} json the JSON hash being build
+    @param {Ember.Map} attributes a Map of attributes
+    @param {DataProxy} data the record's data, accessed with `get` and `set`.
+  */
+  addAttributesToJSON: function(json, attributes, data) {
+    attributes.forEach(function(name, meta) {
+      var key = meta.key(this.constructor),
+          value = get(data, key);
+
+      if (value === undefined) {
+        value = meta.options.defaultValue;
+      }
+
+      json[key] = value;
+    }, this);
+  },
+
+  /**
+    Add the value of a `hasMany` association to the JSON hash.
+
+    The default implementation honors the `embedded` option
+    passed to `DS.hasMany`. If embedded, `toJSON` is recursively
+    called on the child records. If not, the `id` of each
+    record is added.
+
+    Note that if a record is not embedded and does not
+    yet have an `id` (usually provided by the server), it
+    will not be included in the output.
+
+    @param {Object} json the JSON hash being built
+    @param {DataProxy} data the record's data, accessed with `get` and `set`.
+    @param {Object} meta information about the association
+    @param {Object} options options passed to `toJSON`
+  */
+  addHasManyToJSON: function(json, data, meta, options) {
+    var key = meta.key,
+        manyArray = get(this, key),
+        records = [], i, l,
+        clientId, id;
+
+    if (meta.options.embedded) {
+      // TODO: Avoid materializing embedded hashes if possible
+      manyArray.forEach(function(record) {
+        records.push(record.toJSON(options));
+      });
+    } else {
+      var clientIds = get(manyArray, 'content');
+
+      for (i=0, l=clientIds.length; i<l; i++) {
+        clientId = clientIds[i];
+        id = get(this, 'store').clientIdToId[clientId];
+
+        if (id !== undefined) {

[... 1220 lines stripped ...]


Mime
View raw message