Comparing version 1.3.3 to 1.4.0
@@ -1,489 +0,349 @@ | ||
/*! | ||
* EmberFire is the officially supported adapter for using Firebase with | ||
* Ember Data. The DS.FirebaseAdapter provides all of the standard DS.Adapter | ||
* methods and will automatically synchronize the store with Firebase. | ||
* | ||
* EmberFire 1.3.3 | ||
* https://github.com/firebase/emberfire/ | ||
* License: MIT | ||
*/ | ||
(function() { | ||
"use strict"; | ||
"use strict"; | ||
var ember$$default = window.Ember; | ||
var ember$data$$default = window.DS; | ||
/* Only enable if Ember Data is included */ | ||
if (window.DS === undefined) { | ||
return; | ||
} | ||
var $$$utils$to$promise$$default = function(fn, context, _args, errorMsg) { | ||
var args = _args || []; | ||
return new ember$$default.RSVP.Promise(function(resolve, reject) { | ||
var callback = function(error) { | ||
if (error) { | ||
if (errorMsg && typeof error === 'object') { | ||
error.location = errorMsg; | ||
} | ||
reject(error); | ||
} else { | ||
resolve(); | ||
} | ||
}; | ||
args.push(callback); | ||
fn.apply(context, args); | ||
}); | ||
}; | ||
var EmberFire = Ember.Namespace.create({ | ||
VERSION: '1.3.3' | ||
}); | ||
var $$$$$$addon$adapters$firebase$$fmt = ember$$default.String.fmt; | ||
var $$$$$$addon$adapters$firebase$$Promise = ember$$default.RSVP.Promise; | ||
var $$$$$$addon$adapters$firebase$$forEach = ember$$default.EnumerableUtils.forEach; | ||
var $$$$$$addon$adapters$firebase$$filter = ember$$default.EnumerableUtils.filter; | ||
var $$$$$$addon$adapters$firebase$$map = ember$$default.EnumerableUtils.map; | ||
var $$$$$$addon$adapters$firebase$$indexOf = ember$$default.EnumerableUtils.indexOf; | ||
if (Ember.libraries) { | ||
Ember.libraries.registerCoreLibrary('EmberFire', EmberFire.VERSION); | ||
} | ||
var $$$$$$addon$adapters$firebase$$uniq = function (arr) { | ||
var ret = ember$$default.A(); | ||
//Monkeypatch the store until ED gives us a good way to listen to push events | ||
DS.Store.reopen({ | ||
push: function(typeName, data, _partial) { | ||
var record = this._super(typeName, data, _partial); | ||
var adapter = this.adapterFor(record.constructor); | ||
if (adapter.recordWasPushed) { | ||
adapter.recordWasPushed(this, typeName, record); | ||
} | ||
return record; | ||
}, | ||
arr.forEach(function(k) { | ||
if ($$$$$$addon$adapters$firebase$$indexOf(ret, k) < 0) { | ||
ret.push(k); | ||
} | ||
}); | ||
recordWillUnload: function(record) { | ||
var adapter = this.adapterFor(record.constructor); | ||
if (adapter.recordWillUnload) { | ||
adapter.recordWillUnload(this, record); | ||
} | ||
}, | ||
return ret; | ||
}; | ||
recordWillDelete: function (record) { | ||
var adapter = this.adapterFor(record.constructor); | ||
if (adapter.recordWillDelete) { | ||
adapter.recordWillDelete(this, record); | ||
} | ||
} | ||
}); | ||
DS.Model.reopen({ | ||
unloadRecord: function() { | ||
this.store.recordWillUnload(this); | ||
return this._super(); | ||
}, | ||
deleteRecord: function () { | ||
this.store.recordWillDelete(this); | ||
this._super(); | ||
} | ||
}); | ||
// Shortcuts | ||
var Promise = Ember.RSVP.Promise; | ||
var map = Ember.EnumerableUtils.map; | ||
var forEach = Ember.EnumerableUtils.forEach; | ||
var fmt = Ember.String.fmt; | ||
var $$$$$$addon$adapters$firebase$$default = ember$data$$default.Adapter.extend(ember$$default.Evented, { | ||
defaultSerializer: '-firebase', | ||
var toPromise = function(fn, context, _args, errorMsg) { | ||
var args = _args || []; | ||
return new Promise(function(resolve, reject) { | ||
var callback = function(error) { | ||
if (error) { | ||
if (errorMsg && typeof error === 'object') { | ||
error.location = errorMsg; | ||
} | ||
reject(error); | ||
} else { | ||
resolve(); | ||
} | ||
}; | ||
args.push(callback); | ||
fn.apply(context, args); | ||
}); | ||
}; | ||
/** | ||
Endpoint paths can be customized by setting the Firebase property on the | ||
adapter: | ||
/** | ||
The Firebase serializer helps normalize relationships and can be extended on | ||
a per model basis. | ||
*/ | ||
DS.FirebaseSerializer = DS.JSONSerializer.extend(Ember.Evented, { | ||
```js | ||
DS.FirebaseAdapter.extend({ | ||
firebase: new Firebase('https://<my-firebase>.firebaseio.com/') | ||
}); | ||
``` | ||
//We need to account for Firebase turning key/value pairs with ids '1' and '0' into arrays | ||
//See https://github.com/firebase/emberfire/issues/124 | ||
_normalizeNumberIDs: function(hash, key) { | ||
var newHash = []; | ||
if (hash[key][0] === true) { | ||
newHash.push('0'); | ||
} | ||
if (hash[key][1] === true) { | ||
newHash.push('1'); | ||
} | ||
hash[key] = newHash; | ||
}, | ||
Requests for `App.Post` now target `https://<my-firebase>.firebaseio.com/posts`. | ||
normalizeHasMany: function(type, hash, relationship) { | ||
var key = relationship.key; | ||
if (typeof hash[key] === 'object' && !Ember.isArray(hash[key])) { | ||
hash[key] = Ember.keys(hash[key]); | ||
} | ||
//We need to account for Firebase turning key/value pairs with ids '1' and '0' into arrays | ||
//See https://github.com/firebase/emberfire/issues/124 | ||
else if (Ember.isArray(hash[key]) && hash[key].length < 3 && (hash[key][0] === true || hash[key][1] === true)) { | ||
this._normalizeNumberIDs(hash, key); | ||
} | ||
else if (Ember.isArray(hash[key])) { | ||
throw new Error(fmt('%@ relationship %@(\'%@\') must be a key/value map in Firebase. Example: { "%@": { "%@_id": true } }', [type.toString(), relationship.kind, relationship.type.typeKey, key, relationship.type.typeKey])); | ||
} | ||
}, | ||
@property firebase | ||
@type {Firebase} | ||
*/ | ||
normalizeEmbeddedHasMany: function(type, hash, relationship) { | ||
var key = relationship.key; | ||
var embeddedRecordPayload = hash[key]; | ||
var embeddedKey; | ||
if (!hash[key]) { | ||
return; | ||
} | ||
for (embeddedKey in embeddedRecordPayload) { | ||
var record = embeddedRecordPayload[embeddedKey]; | ||
if (record !== null && typeof record === 'object') { | ||
record.id = embeddedKey; | ||
init: function() { | ||
var firebase = this.get('firebase'); | ||
if (!firebase || typeof firebase !== 'object') { | ||
throw new Error('Please set the `firebase` property on the adapter.'); | ||
} | ||
this.store.push(relationship.type, this.normalize(relationship.type, record)); | ||
} | ||
hash[key] = Ember.keys(hash[key]); | ||
}, | ||
// If provided Firebase reference was a query (eg: limits), make it a ref. | ||
this._ref = firebase.ref(); | ||
// Keep track of what types `.findAll()` has been called for | ||
this._findAllMapForType = {}; | ||
// Keep a cache to check modified relationships against | ||
this._recordCacheForType = {}; | ||
// Used to batch records into the store | ||
this._queue = []; | ||
}, | ||
normalizeEmbeddedBelongsTo: function(type, hash, relationship) { | ||
var key = relationship.key; | ||
if (!hash[key]) { | ||
return; | ||
} | ||
var embeddedRecordPayload = hash[key]; | ||
if (typeof embeddedRecordPayload.id !== 'string') { | ||
throw new Error(fmt('Embedded relationship "%@" of "%@" must contain an "id" property in the payload', [relationship.type.typeKey, type])); | ||
} | ||
this.store.push(relationship.type, this.normalize(relationship.type, embeddedRecordPayload)); | ||
hash[key] = embeddedRecordPayload.id; | ||
}, | ||
/** | ||
Uses push() to generate chronologically ordered unique IDs. | ||
*/ | ||
generateIdForRecord: function() { | ||
return this._getKey(this._ref.push()); | ||
}, | ||
normalizeBelongsTo: Ember.K, | ||
/** | ||
Called after `extractSingle()`. This method checks the model | ||
for `hasMany` relationships and makes sure the value is an object. | ||
The object is then converted to an Array using `Ember.keys` | ||
*/ | ||
normalize: function(type, hash) { | ||
var serializer = this; | ||
// Check if the model contains any 'hasMany' relationships | ||
type.eachRelationship(function(key, relationship) { | ||
if (relationship.kind === 'hasMany') { | ||
if (relationship.options.embedded) { | ||
serializer.normalizeEmbeddedHasMany(type, hash, relationship); | ||
} else { | ||
serializer.normalizeHasMany(type, hash, relationship); | ||
} | ||
} else { | ||
if (relationship.options.embedded) { | ||
serializer.normalizeEmbeddedBelongsTo(type, hash, relationship); | ||
} else { | ||
serializer.normalizeBelongsTo(type, hash, relationship); | ||
} | ||
/** | ||
Use the Firebase DataSnapshot's key as the record id | ||
@param {Object} snapshot - A Firebase snapshot | ||
@param {Object} payload - The payload that will be pushed into the store | ||
@return {Object} payload | ||
*/ | ||
_assignIdToPayload: function(snapshot) { | ||
var payload = snapshot.val(); | ||
if (payload !== null && typeof payload === 'object' && typeof payload.id === 'undefined') { | ||
payload.id = this._getKey(snapshot); | ||
} | ||
}); | ||
return this._super.apply(this, arguments); | ||
}, | ||
return payload; | ||
}, | ||
/** | ||
Called on a records returned from `find()` and all records | ||
returned from `findAll()` | ||
/** | ||
Called by the store to retrieve the JSON for a given type and ID. The | ||
method will return a promise which will resolve when the value is | ||
successfully fetched from Firebase. | ||
This method also checkes for `embedded: true`, extracts the | ||
embedded records, pushes them into the store, and then replaces | ||
the records with an array of ids | ||
*/ | ||
extractSingle: function(store, type, payload) { | ||
return this.normalize(type, payload); | ||
}, | ||
Additionally, from this point on, the object's value in the store will | ||
also be automatically updated whenever the remote value changes. | ||
*/ | ||
find: function(store, type, id) { | ||
var adapter = this; | ||
var ref = this._getRef(type, id); | ||
/** | ||
Called after the adpter runs `findAll()` or `findMany()`. This method runs | ||
`extractSingle()` on each item in the payload and as a result each item | ||
will have `normalize()` called on it | ||
*/ | ||
extractArray: function(store, type, payload) { | ||
return map(payload, function(item) { | ||
return this.extractSingle(store, type, item); | ||
}, this); | ||
}, | ||
return new $$$$$$addon$adapters$firebase$$Promise(function(resolve, reject) { | ||
ref.once('value', function(snapshot) { | ||
var payload = adapter._assignIdToPayload(snapshot); | ||
adapter._updateRecordCacheForType(type, payload); | ||
if (payload === null) { | ||
var error = new Error($$$$$$addon$adapters$firebase$$fmt('no record was found at %@', [ref.toString()])); | ||
error.recordId = id; | ||
reject(error); | ||
} | ||
else { | ||
resolve(payload); | ||
} | ||
}, | ||
function(err) { | ||
reject(err); | ||
}); | ||
}, $$$$$$addon$adapters$firebase$$fmt('DS: FirebaseAdapter#find %@ to %@', [type, ref.toString()])); | ||
}, | ||
/** | ||
Overrides ember-data's `serializeHasMany` to serialize oneToMany | ||
relationships. | ||
*/ | ||
serializeHasMany: function(record, json, relationship) { | ||
var key = relationship.key; | ||
var payloadKey = this.keyForRelationship ? this.keyForRelationship(key, "hasMany") : key; | ||
json[payloadKey] = Ember.A(record.get(key)).mapBy('id'); | ||
}, | ||
recordWasPushed: function(store, type, record) { | ||
if (!record.__listening) { | ||
this.listenForChanges(store, type, record); | ||
} | ||
}, | ||
serializeBelongsTo: function(record, json, relationship) { | ||
this._super(record, json, relationship); | ||
var key = relationship.key; | ||
var payloadKey = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : relationship.key; | ||
if (typeof json[key] === "undefined" || json[key] === '') { | ||
delete json[key]; | ||
} | ||
} | ||
recordWillUnload: function(store, record) { | ||
var ref = this._getRef(record.typeKey, record.get('id')); | ||
ref.off('value'); | ||
}, | ||
}); | ||
recordWillDelete: function(store, record) { | ||
var adapter = this; | ||
/** | ||
The Firebase adapter allows your store to communicate with the Firebase | ||
realtime service. To use the adapter in your app, extend DS.FirebaseAdapter | ||
and customize the endpoint to point to the Firebase URL where you want this | ||
data to be stored. | ||
record.eachRelationship(function (key, relationship) { | ||
if (relationship.kind === 'belongsTo') { | ||
var parentRecord = record.get(relationship.key); | ||
var inverseKey = record.inverseFor(relationship.key); | ||
if (inverseKey && parentRecord.get('id')) { | ||
var parentRef = adapter._getRef(inverseKey.type, parentRecord.get('id')); | ||
adapter._removeHasManyRecord(store, parentRef, inverseKey.name, record.id); | ||
} | ||
} | ||
}); | ||
}, | ||
The adapter will automatically communicate with Firebase to persist your | ||
records as neccessary. Importantly, the adapter will also update the store | ||
in realtime when changes are made to the Firebase by other clients or | ||
otherwise. | ||
*/ | ||
DS.FirebaseAdapter = DS.Adapter.extend(Ember.Evented, { | ||
listenForChanges: function(store, type, record) { | ||
record.__listening = true; | ||
var serializer = store.serializerFor(type); | ||
var adapter = this; | ||
var ref = this._getRef(type, record.get('id')); | ||
var called = false; | ||
ref.on('value', function(snapshot) { | ||
if (called) { | ||
adapter._handleChildValue(store, type, serializer, snapshot); | ||
} | ||
called = true; | ||
}); | ||
}, | ||
defaultSerializer: '-firebase', | ||
/** | ||
findMany | ||
*/ | ||
findMany: undefined, | ||
/** | ||
Endpoint paths can be customized by setting the Firebase property on the | ||
adapter: | ||
/** | ||
Called by the store to retrieve the JSON for all of the records for a | ||
given type. The method will return a promise which will resolve when the | ||
value is successfully fetched from Firebase. | ||
```js | ||
DS.FirebaseAdapter.extend({ | ||
firebase: new Firebase('https://<my-firebase>.firebaseio.com/') | ||
}); | ||
``` | ||
Additionally, from this point on, any records of this type that are added, | ||
removed or modified from Firebase will automatically be reflected in the | ||
store. | ||
*/ | ||
findAll: function(store, type) { | ||
var adapter = this; | ||
var ref = this._getRef(type); | ||
Requests for `App.Post` now target `https://<my-firebase>.firebaseio.com/posts`. | ||
return new $$$$$$addon$adapters$firebase$$Promise(function(resolve, reject) { | ||
// Listen for child events on the type | ||
ref.once('value', function(snapshot) { | ||
if (!adapter._findAllHasEventsForType(type)) { | ||
adapter._findAllAddEventListeners(store, type, ref); | ||
} | ||
var results = []; | ||
snapshot.forEach(function(childSnapshot) { | ||
var payload = adapter._assignIdToPayload(childSnapshot); | ||
adapter._updateRecordCacheForType(type, payload); | ||
results.push(payload); | ||
}); | ||
resolve(results); | ||
}, function(error) { | ||
reject(error); | ||
}); | ||
}, $$$$$$addon$adapters$firebase$$fmt('DS: FirebaseAdapter#findAll %@ to %@', [type, ref.toString()])); | ||
}, | ||
@property firebase | ||
@type {Firebase} | ||
*/ | ||
findQuery: function(store, type, query) { | ||
var adapter = this; | ||
var ref = this._getRef(type); | ||
init: function() { | ||
var firebase = this.get('firebase'); | ||
if (!firebase || typeof firebase !== 'object') { | ||
throw new Error('Please set the `firebase` property on the adapter.'); | ||
} | ||
// If provided Firebase reference was a query (eg: limits), make it a ref. | ||
this._ref = firebase.ref(); | ||
// Keep track of what types `.findAll()` has been called for | ||
this._findAllMapForType = {}; | ||
// Keep a cache to check modified relationships against | ||
this._recordCacheForType = {}; | ||
// Used to batch records into the store | ||
this._queue = []; | ||
}, | ||
ref = this.applyQueryToRef(ref, query); | ||
/** | ||
Uses push() to generate chronologically ordered unique IDs. | ||
*/ | ||
generateIdForRecord: function() { | ||
return this._getKey(this._ref.push()); | ||
}, | ||
return new $$$$$$addon$adapters$firebase$$Promise(function(resolve, reject) { | ||
// Listen for child events on the type | ||
ref.once('value', function(snapshot) { | ||
if (!adapter._findAllHasEventsForType(type)) { | ||
adapter._findAllAddEventListeners(store, type, ref); | ||
} | ||
var results = []; | ||
snapshot.forEach(function(childSnapshot) { | ||
var payload = adapter._assignIdToPayload(childSnapshot); | ||
adapter._updateRecordCacheForType(type, payload); | ||
results.push(payload); | ||
}); | ||
resolve(results); | ||
}, function(error) { | ||
reject(error); | ||
}); | ||
}, $$$$$$addon$adapters$firebase$$fmt('DS: FirebaseAdapter#findQuery %@ with %@', [type, query])); | ||
}, | ||
/** | ||
Use the Firebase DataSnapshot's key as the record id | ||
applyQueryToRef: function (ref, query) { | ||
@param {Object} snapshot - A Firebase snapshot | ||
@param {Object} payload - The payload that will be pushed into the store | ||
@return {Object} payload | ||
*/ | ||
_assignIdToPayload: function(snapshot) { | ||
var payload = snapshot.val(); | ||
if (payload !== null && typeof payload === 'object' && typeof payload.id === 'undefined') { | ||
payload.id = this._getKey(snapshot); | ||
} | ||
return payload; | ||
}, | ||
if (!query.orderBy) { | ||
query.orderBy = '_key'; | ||
} | ||
/** | ||
Called by the store to retrieve the JSON for a given type and ID. The | ||
method will return a promise which will resolve when the value is | ||
successfully fetched from Firebase. | ||
if (query.orderBy === '_key'){ | ||
ref = ref.orderByKey(); | ||
} else if (query.orderBy === '_value') { | ||
ref = ref.orderByValue(); | ||
} else if (query.orderBy === '_priority') { | ||
ref = ref.orderByPriority(); | ||
} else { | ||
ref.orderByChild(query.orderBy); | ||
} | ||
Additionally, from this point on, the object's value in the store will | ||
also be automatically updated whenever the remote value changes. | ||
*/ | ||
find: function(store, type, id) { | ||
var adapter = this; | ||
var ref = this._getRef(type, id); | ||
var serializer = store.serializerFor(type); | ||
return new Promise(function(resolve, reject) { | ||
ref.once('value', function(snapshot) { | ||
var payload = adapter._assignIdToPayload(snapshot); | ||
adapter._updateRecordCacheForType(type, payload); | ||
if (payload === null) { | ||
var error = new Error(fmt('no record was found at %@', [ref.toString()])); | ||
error.recordId = id; | ||
reject(error); | ||
['limitToFirst', 'limitToLast', 'startAt', 'endAt', 'equalTo'].forEach(function (key) { | ||
if (query[key] || query[key] === '') { | ||
ref = ref[key](query[key]); | ||
} | ||
else { | ||
resolve(payload); | ||
} | ||
}, | ||
function(err) { | ||
reject(err); | ||
}); | ||
}, fmt('DS: FirebaseAdapter#find %@ to %@', [type, ref.toString()])); | ||
}, | ||
recordWasPushed: function(store, type, record) { | ||
if (!record.__listening) { | ||
this.listenForChanges(store, type, record); | ||
} | ||
}, | ||
return ref; | ||
}, | ||
recordWillUnload: function(store, record) { | ||
var ref = this._getRef(record.typeKey, record.get('id')); | ||
ref.off('value'); | ||
}, | ||
/** | ||
Keep track of what types `.findAll()` has been called for | ||
so duplicate listeners aren't added | ||
*/ | ||
_findAllMapForType: undefined, | ||
recordWillDelete: function(store, record) { | ||
var adapter = this; | ||
/** | ||
Determine if the current type is already listening for children events | ||
*/ | ||
_findAllHasEventsForType: function(type) { | ||
return !ember$$default.isNone(this._findAllMapForType[type]); | ||
}, | ||
record.eachRelationship(function (key, relationship) { | ||
if (relationship.kind === 'belongsTo') { | ||
var parentRecord = record.get(relationship.key); | ||
var inverseKey = record.inverseFor(relationship.key); | ||
if (inverseKey && parentRecord.get('id')) { | ||
var parentRef = adapter._getRef(inverseKey.type, parentRecord.get('id')); | ||
adapter._removeHasManyRecord(store, parentRef, inverseKey.name, record.id); | ||
} | ||
} | ||
}); | ||
}, | ||
/** | ||
After `.findAll()` is called on a type, continue to listen for | ||
`child_added`, `child_removed`, and `child_changed` | ||
*/ | ||
_findAllAddEventListeners: function(store, type, ref) { | ||
this._findAllMapForType[type] = true; | ||
listenForChanges: function(store, type, record) { | ||
record.__listening = true; | ||
var serializer = store.serializerFor(type); | ||
var adapter = this; | ||
var ref = this._getRef(type, record.get('id')); | ||
var called = false; | ||
ref.on('value', function(snapshot) { | ||
if (called) { | ||
adapter._handleChildValue(store, type, serializer, snapshot); | ||
} | ||
called = true; | ||
}); | ||
}, | ||
var adapter = this; | ||
var serializer = store.serializerFor(type); | ||
/** | ||
findMany | ||
*/ | ||
findMany: undefined, | ||
/** | ||
Called by the store to retrieve the JSON for all of the records for a | ||
given type. The method will return a promise which will resolve when the | ||
value is successfully fetched from Firebase. | ||
Additionally, from this point on, any records of this type that are added, | ||
removed or modified from Firebase will automatically be reflected in the | ||
store. | ||
*/ | ||
findAll: function(store, type) { | ||
var adapter = this; | ||
var ref = this._getRef(type); | ||
return new Promise(function(resolve, reject) { | ||
// Listen for child events on the type | ||
ref.once('value', function(snapshot) { | ||
if (!adapter._findAllHasEventsForType(type)) { | ||
adapter._findAllAddEventListeners(store, type, ref); | ||
ref.on('child_added', function(snapshot) { | ||
if (!store.hasRecordForId(type, adapter._getKey(snapshot))) { | ||
adapter._handleChildValue(store, type, serializer, snapshot); | ||
} | ||
var results = []; | ||
snapshot.forEach(function(childSnapshot) { | ||
var payload = adapter._assignIdToPayload(childSnapshot); | ||
adapter._updateRecordCacheForType(type, payload); | ||
results.push(payload); | ||
}); | ||
resolve(results); | ||
}, function(error) { | ||
reject(error); | ||
}); | ||
}, fmt('DS: FirebaseAdapter#findAll %@ to %@', [type, ref.toString()])); | ||
}, | ||
}, | ||
/** | ||
Keep track of what types `.findAll()` has been called for | ||
so duplicate listeners aren't added | ||
*/ | ||
_findAllMapForType: undefined, | ||
/** | ||
Determine if the current type is already listening for children events | ||
*/ | ||
_findAllHasEventsForType: function(type) { | ||
return !Ember.isNone(this._findAllMapForType[type]); | ||
}, | ||
/** | ||
After `.findAll()` is called on a type, continue to listen for | ||
`child_added`, `child_removed`, and `child_changed` | ||
*/ | ||
_findAllAddEventListeners: function(store, type, ref) { | ||
this._findAllMapForType[type] = true; | ||
var adapter = this; | ||
var serializer = store.serializerFor(type); | ||
ref.on('child_added', function(snapshot) { | ||
if (!store.hasRecordForId(type, adapter._getKey(snapshot))) { | ||
adapter._handleChildValue(store, type, serializer, snapshot); | ||
/** | ||
Push a new child record into the store | ||
*/ | ||
_handleChildValue: function(store, type, serializer, snapshot) { | ||
//No idea why we need this, we are alredy turning off the callback by | ||
//calling ref.off in recordWillUnload. Something is fishy here | ||
if (store.isDestroying) { | ||
return; | ||
} | ||
}); | ||
}, | ||
var value = snapshot.val(); | ||
if (value === null) { | ||
var id = this._getKey(snapshot); | ||
var record = store.getById(type, id); | ||
//TODO refactor using ED | ||
if (!record.get('isDeleted')) { | ||
record.deleteRecord(); | ||
} | ||
} else { | ||
var payload = this._assignIdToPayload(snapshot); | ||
this._enqueue(function() { | ||
store.push(type, serializer.extractSingle(store, type, payload)); | ||
}); | ||
} | ||
}, | ||
/** | ||
Push a new child record into the store | ||
*/ | ||
_handleChildValue: function(store, type, serializer, snapshot) { | ||
//No idea why we need this, we are alredy turning off the callback by | ||
//calling ref.off in recordWillUnload. Something is fishy here | ||
if (store.isDestroying) { | ||
return; | ||
} | ||
var value = snapshot.val(); | ||
if (value === null) { | ||
var id = this._getKey(snapshot); | ||
var record = store.getById(type, id); | ||
//TODO refactor using ED | ||
if (!record.get('isDeleted')) { | ||
record.deleteRecord(); | ||
} | ||
} else { | ||
var payload = this._assignIdToPayload(snapshot); | ||
this._enqueue(function() { | ||
store.push(type, serializer.extractSingle(store, type, payload)); | ||
/** | ||
`createRecord` is an alias for `updateRecord` because calling \ | ||
`ref.set()` would wipe out any existing relationships | ||
*/ | ||
createRecord: function(store, type, record) { | ||
var adapter = this; | ||
return this.updateRecord(store, type, record).then(function() { | ||
adapter.listenForChanges(store, type, record); | ||
}); | ||
} | ||
}, | ||
}, | ||
/** | ||
`createRecord` is an alias for `updateRecord` because calling \ | ||
`ref.set()` would wipe out any existing relationships | ||
*/ | ||
createRecord: function(store, type, record) { | ||
var adapter = this; | ||
return this.updateRecord(store, type, record).then(function() { | ||
adapter.listenForChanges(store, type, record); | ||
}); | ||
}, | ||
/** | ||
Called by the store when a record is created/updated via the `save` | ||
method on a model record instance. | ||
/** | ||
Called by the store when a record is created/updated via the `save` | ||
method on a model record instance. | ||
The `updateRecord` method serializes the record and performs an `update()` | ||
at the the Firebase location and a `.set()` at any relationship locations | ||
The method will return a promise which will be resolved when the data and | ||
any relationships have been successfully saved to Firebase. | ||
The `updateRecord` method serializes the record and performs an `update()` | ||
at the the Firebase location and a `.set()` at any relationship locations | ||
The method will return a promise which will be resolved when the data and | ||
any relationships have been successfully saved to Firebase. | ||
We take an optional record reference, in order for this method to be usable | ||
for saving nested records as well. | ||
We take an optional record reference, in order for this method to be usable | ||
for saving nested records as well. | ||
*/ | ||
updateRecord: function(store, type, record, _recordRef) { | ||
var adapter = this; | ||
var recordRef = _recordRef || this._getRef(type, record.id); | ||
var recordCache = adapter._getRecordCache(type.typeKey, record.get('id')); | ||
*/ | ||
updateRecord: function(store, type, record, _recordRef) { | ||
var adapter = this; | ||
var recordRef = _recordRef || this._getRef(type, record.id); | ||
var recordCache = Ember.get(adapter._recordCacheForType, fmt('%@.%@', [type.typeKey, record.get('id')])) || {}; | ||
var serializedRecord = record.serialize({includeId:false}); | ||
var serializedRecord = record.serialize({includeId:false}); | ||
return new Promise(function(resolve, reject) { | ||
var savedRelationships = Ember.A(); | ||
record.eachRelationship(function(key, relationship) { | ||
var save; | ||
if (relationship.kind === 'hasMany') { | ||
return new $$$$$$addon$adapters$firebase$$Promise(function(resolve, reject) { | ||
var savedRelationships = ember$$default.A(); | ||
record.eachRelationship(function(key, relationship) { | ||
var save; | ||
if (relationship.kind === 'hasMany') { | ||
if (serializedRecord[key]) { | ||
@@ -495,3 +355,3 @@ save = adapter._saveHasManyRelationship(store, type, relationship, serializedRecord[key], recordRef, recordCache); | ||
} | ||
} else { | ||
} else { | ||
if (relationship.options.embedded === true && serializedRecord[key]) { | ||
@@ -502,245 +362,445 @@ save = adapter._saveBelongsToRecord(store, type, relationship, serializedRecord[key], recordRef); | ||
} | ||
} | ||
} | ||
}); | ||
var relationshipsPromise = ember$$default.RSVP.allSettled(savedRelationships); | ||
var recordPromise = adapter._updateRecord(recordRef, serializedRecord); | ||
ember$$default.RSVP.hashSettled({relationships: relationshipsPromise, record: recordPromise}).then(function(promises) { | ||
var rejected = ember$$default.A(promises.relationships.value).filterBy('state', 'rejected'); | ||
if (promises.record.state === 'rejected') { | ||
rejected.push(promises.record); | ||
} | ||
// Throw an error if any of the relationships failed to save | ||
if (rejected.length !== 0) { | ||
var error = new Error($$$$$$addon$adapters$firebase$$fmt('Some errors were encountered while saving %@ %@', [type, record.id])); | ||
error.errors = rejected.mapBy('reason'); | ||
reject(error); | ||
} else { | ||
resolve(); | ||
} | ||
}); | ||
}, $$$$$$addon$adapters$firebase$$fmt('DS: FirebaseAdapter#updateRecord %@ to %@', [type, recordRef.toString()])); | ||
}, | ||
//Just update the record itself without caring for the relationships | ||
_updateRecord: function(recordRef, serializedRecord) { | ||
return $$$utils$to$promise$$default(recordRef.update, recordRef, [serializedRecord]); | ||
}, | ||
/** | ||
Call _saveHasManyRelationshipRecord on each record in the relationship | ||
and then resolve once they have all settled | ||
*/ | ||
_saveHasManyRelationship: function(store, type, relationship, ids, recordRef, recordCache) { | ||
if (!ember$$default.isArray(ids)) { | ||
throw new Error('hasMany relationships must must be an array'); | ||
} | ||
var adapter = this; | ||
var idsCache = ember$$default.A(recordCache[relationship.key]); | ||
var dirtyRecords = []; | ||
// Added | ||
var addedRecords = $$$$$$addon$adapters$firebase$$filter(ids, function(id) { | ||
return !idsCache.contains(id); | ||
}); | ||
var relationshipsPromise = Ember.RSVP.allSettled(savedRelationships); | ||
var recordPromise = adapter._updateRecord(recordRef, serializedRecord); | ||
// Dirty | ||
dirtyRecords = $$$$$$addon$adapters$firebase$$filter(ids, function(id) { | ||
var type = relationship.type; | ||
return store.hasRecordForId(type, id) && store.getById(type, id).get('isDirty') === true; | ||
}); | ||
Ember.RSVP.hashSettled({relationships: relationshipsPromise, record: recordPromise}).then(function(promises) { | ||
var rejected = Ember.A(promises.relationships.value).filterBy('state', 'rejected'); | ||
if (promises.record.state === 'rejected') { | ||
rejected.push(promises.record); | ||
dirtyRecords = $$$$$$addon$adapters$firebase$$map($$$$$$addon$adapters$firebase$$uniq(dirtyRecords.concat(addedRecords)), function(id) { | ||
return adapter._saveHasManyRecord(store, relationship, recordRef, id); | ||
}); | ||
// Removed | ||
var removedRecords = $$$$$$addon$adapters$firebase$$filter(idsCache, function(id) { | ||
return !ids.contains(id); | ||
}); | ||
removedRecords = $$$$$$addon$adapters$firebase$$map(removedRecords, function(id) { | ||
return adapter._removeHasManyRecord(store, recordRef, relationship.key, id); | ||
}); | ||
// Combine all the saved records | ||
var savedRecords = dirtyRecords.concat(removedRecords); | ||
// Wait for all the updates to finish | ||
return ember$$default.RSVP.allSettled(savedRecords).then(function(savedRecords) { | ||
var rejected = ember$$default.A(ember$$default.A(savedRecords).filterBy('state', 'rejected')); | ||
if (rejected.get('length') === 0) { | ||
// Update the cache | ||
recordCache[relationship.key] = ids; | ||
return savedRecords; | ||
} | ||
// Throw an error if any of the relationships failed to save | ||
if (rejected.length !== 0) { | ||
var error = new Error(fmt('Some errors were encountered while saving %@ %@', [type, record.id])); | ||
error.errors = rejected.mapBy('reason'); | ||
reject(error); | ||
} else { | ||
resolve(); | ||
else { | ||
var error = new Error($$$$$$addon$adapters$firebase$$fmt('Some errors were encountered while saving a hasMany relationship %@ -> %@', [relationship.parentType, relationship.type])); | ||
error.errors = ember$$default.A(rejected).mapBy('reason'); | ||
throw error; | ||
} | ||
}); | ||
}, fmt('DS: FirebaseAdapter#updateRecord %@ to %@', [type, recordRef.toString()])); | ||
}, | ||
}, | ||
//Just update the record itself without caring for the relationships | ||
_updateRecord: function(recordRef, serializedRecord) { | ||
return toPromise(recordRef.update, recordRef, [serializedRecord]); | ||
}, | ||
/** | ||
If the relationship is `async: true`, create a child ref | ||
named with the record id and set the value to true | ||
/** | ||
Call _saveHasManyRelationshipRecord on each record in the relationship | ||
and then resolve once they have all settled | ||
*/ | ||
_saveHasManyRelationship: function(store, type, relationship, ids, recordRef, recordCache) { | ||
if (!Ember.isArray(ids)) { | ||
throw new Error('hasMany relationships must must be an array'); | ||
} | ||
var adapter = this; | ||
var idsCache = Ember.A(recordCache[relationship.key]); | ||
ids = Ember.A(ids); | ||
var dirtyRecords = []; | ||
If the relationship is `embedded: true`, create a child ref | ||
named with the record id and update the value to the serialized | ||
version of the record | ||
*/ | ||
_saveHasManyRecord: function(store, relationship, parentRef, id) { | ||
var ref = this._getRelationshipRef(parentRef, relationship.key, id); | ||
var record = store.getById(relationship.type, id); | ||
var isEmbedded = relationship.options.embedded === true; | ||
if (isEmbedded) { | ||
return this.updateRecord(store, relationship.type, record, ref); | ||
} | ||
// Added | ||
var addedRecords = ids.filter(function(id) { | ||
return !idsCache.contains(id); | ||
}); | ||
return $$$utils$to$promise$$default(ref.set, ref, [true]); | ||
}, | ||
// Dirty | ||
dirtyRecords = ids.filter(function(id) { | ||
var type = relationship.type; | ||
return store.hasRecordForId(type, id) && store.getById(type, id).get('isDirty') === true; | ||
}); | ||
dirtyRecords = Ember.A(dirtyRecords.concat(addedRecords)).uniq().map(function(id) { | ||
return adapter._saveHasManyRecord(store, relationship, recordRef, id); | ||
}); | ||
// Removed | ||
var removedRecords = idsCache.filter(function(id) { | ||
return !ids.contains(id); | ||
}); | ||
removedRecords = Ember.A(removedRecords).map(function(id) { | ||
return adapter._removeHasManyRecord(store, recordRef, relationship.key, id); | ||
}); | ||
// Combine all the saved records | ||
var savedRecords = dirtyRecords.concat(removedRecords); | ||
// Wait for all the updates to finish | ||
return Ember.RSVP.allSettled(savedRecords).then(function(savedRecords) { | ||
var rejected = Ember.A(Ember.A(savedRecords).filterBy('state', 'rejected')); | ||
if (rejected.get('length') === 0) { | ||
// Update the cache | ||
recordCache[relationship.key] = ids; | ||
return savedRecords; | ||
/** | ||
Remove a relationship | ||
*/ | ||
_removeHasManyRecord: function(store, parentRef, key, id) { | ||
var ref = this._getRelationshipRef(parentRef, key, id); | ||
return $$$utils$to$promise$$default(ref.remove, ref, [], ref.toString()); | ||
}, | ||
/** | ||
Save an embedded record | ||
*/ | ||
_saveBelongsToRecord: function(store, type, relationship, id, parentRef) { | ||
var ref = parentRef.child(relationship.key); | ||
var record = store.getById(relationship.type, id); | ||
return this.updateRecord(store, relationship.type, record, ref); | ||
}, | ||
/** | ||
Called by the store when a record is deleted. | ||
*/ | ||
deleteRecord: function(store, type, record) { | ||
var ref = this._getRef(type, record.get('id')); | ||
return $$$utils$to$promise$$default(ref.remove, ref); | ||
}, | ||
/** | ||
Determines a path fo a given type | ||
*/ | ||
pathForType: function(type) { | ||
var camelized = ember$$default.String.camelize(type); | ||
return ember$$default.String.pluralize(camelized); | ||
}, | ||
/** | ||
Return a Firebase reference for a given type and optional ID. | ||
*/ | ||
_getRef: function(type, id) { | ||
var ref = this._ref; | ||
if (type) { | ||
ref = ref.child(this.pathForType(type.typeKey)); | ||
} | ||
else { | ||
var error = new Error(fmt('Some errors were encountered while saving a hasMany relationship %@ -> %@', [relationship.parentType, relationship.type])); | ||
error.errors = Ember.A(rejected).mapBy('reason'); | ||
throw error; | ||
if (id) { | ||
ref = ref.child(id); | ||
} | ||
}); | ||
}, | ||
return ref; | ||
}, | ||
/** | ||
If the relationship is `async: true`, create a child ref | ||
named with the record id and set the value to true | ||
/** | ||
Return a Firebase reference based on a relationship key and record id | ||
*/ | ||
_getRelationshipRef: function(ref, key, id) { | ||
return ref.child(key).child(id); | ||
}, | ||
If the relationship is `embedded: true`, create a child ref | ||
named with the record id and update the value to the serialized | ||
version of the record | ||
*/ | ||
_saveHasManyRecord: function(store, relationship, parentRef, id) { | ||
var ref = this._getRelationshipRef(parentRef, relationship.key, id); | ||
var record = store.getById(relationship.type, id); | ||
var isEmbedded = relationship.options.embedded === true; | ||
if (isEmbedded) { | ||
return this.updateRecord(store, relationship.type, record, ref); | ||
} | ||
/** | ||
The amount of time (ms) before the _queue is flushed | ||
*/ | ||
_queueFlushDelay: (1000/60), // 60fps | ||
return toPromise(ref.set, ref, [true]); | ||
}, | ||
/** | ||
Called after the first item is pushed into the _queue | ||
*/ | ||
_queueScheduleFlush: function() { | ||
ember$$default.run.later(this, this._queueFlush, this._queueFlushDelay); | ||
}, | ||
/** | ||
Remove a relationship | ||
*/ | ||
_removeHasManyRecord: function(store, parentRef, key, id) { | ||
var ref = this._getRelationshipRef(parentRef, key, id); | ||
return toPromise(ref.remove, ref, [], ref.toString()); | ||
}, | ||
/** | ||
Call each function in the _queue and the reset the _queue | ||
*/ | ||
_queueFlush: function() { | ||
$$$$$$addon$adapters$firebase$$forEach(this._queue, function(queueItem) { | ||
var fn = queueItem[0]; | ||
var args = queueItem[1]; | ||
fn.apply(null, args); | ||
}); | ||
this._queue.length = 0; | ||
}, | ||
/** | ||
Save an embedded record | ||
*/ | ||
_saveBelongsToRecord: function(store, type, relationship, id, parentRef) { | ||
var ref = parentRef.child(relationship.key); | ||
var record = store.getById(relationship.type, id); | ||
return this.updateRecord(store, relationship.type, record, ref); | ||
}, | ||
/** | ||
Push a new function into the _queue and then schedule a | ||
flush if the item is the first to be pushed | ||
*/ | ||
_enqueue: function(callback, args) { | ||
//Only do the queueing if we scheduled a delay | ||
if (this._queueFlushDelay) { | ||
var length = this._queue.push([callback, args]); | ||
if (length === 1) { | ||
this._queueScheduleFlush(); | ||
} | ||
} else { | ||
callback.apply(null, args); | ||
} | ||
}, | ||
/** | ||
Called by the store when a record is deleted. | ||
*/ | ||
deleteRecord: function(store, type, record) { | ||
var ref = this._getRef(type, record.get('id')); | ||
return toPromise(ref.remove, ref); | ||
}, | ||
/** | ||
A cache of hasMany relationships that can be used to | ||
diff against new relationships when a model is saved | ||
*/ | ||
_recordCacheForType: undefined, | ||
/** | ||
Determines a path fo a given type | ||
*/ | ||
pathForType: function(type) { | ||
var camelized = Ember.String.camelize(type); | ||
return Ember.String.pluralize(camelized); | ||
}, | ||
/** | ||
_updateHasManyCacheForType | ||
*/ | ||
_updateRecordCacheForType: function(type, payload) { | ||
if (!payload) { return; } | ||
var id = payload.id; | ||
var typeKey = type.typeKey; | ||
var cache = this._getRecordCache(typeKey, id); | ||
// Only cache relationships for now | ||
type.eachRelationship(function(key, relationship) { | ||
if (relationship.kind === 'hasMany') { | ||
var ids = payload[key]; | ||
cache[key] = !ember$$default.isNone(ids) ? ember$$default.A(ember$$default.keys(ids)) : ember$$default.A(); | ||
} | ||
}); | ||
}, | ||
/** | ||
Return a Firebase reference for a given type and optional ID. | ||
*/ | ||
_getRef: function(type, id) { | ||
var ref = this._ref; | ||
if (type) { | ||
ref = ref.child(this.pathForType(type.typeKey)); | ||
/** | ||
Get or create the cache for a record | ||
*/ | ||
_getRecordCache: function (typeKey, id) { | ||
var cache = this._recordCacheForType; | ||
cache[typeKey] = cache[typeKey] || {}; | ||
cache[typeKey][id] = cache[typeKey][id] || {}; | ||
return cache[typeKey][id]; | ||
}, | ||
/** | ||
* A utility for retrieving the key name of a Firebase ref or | ||
* DataSnapshot. This is backwards-compatible with `name()` | ||
* from Firebase 1.x.x and `key()` from Firebase 2.0.0+. Once | ||
* support for Firebase 1.x.x is dropped in EmberFire, this | ||
* helper can be removed. | ||
*/ | ||
_getKey: function(refOrSnapshot) { | ||
return (typeof refOrSnapshot.key === 'function') ? refOrSnapshot.key() : refOrSnapshot.name(); | ||
} | ||
if (id) { | ||
ref = ref.child(id); | ||
} | ||
return ref; | ||
}, | ||
}); | ||
/** | ||
Return a Firebase reference based on a relationship key and record id | ||
*/ | ||
_getRelationshipRef: function(ref, key, id) { | ||
return ref.child(key).child(id); | ||
}, | ||
var $$$$$$addon$serializers$firebase$$map = ember$$default.EnumerableUtils.map; | ||
var $$$$$$addon$serializers$firebase$$fmt = ember$$default.String.fmt; | ||
/** | ||
The amount of time (ms) before the _queue is flushed | ||
*/ | ||
_queueFlushDelay: (1000/60), // 60fps | ||
var $$$$$$addon$serializers$firebase$$default = ember$data$$default.JSONSerializer.extend(ember$$default.Evented, { | ||
/** | ||
Called after the first item is pushed into the _queue | ||
*/ | ||
_queueScheduleFlush: function() { | ||
Ember.run.later(this, this._queueFlush, this._queueFlushDelay); | ||
}, | ||
//We need to account for Firebase turning key/value pairs with ids '1' and '0' into arrays | ||
//See https://github.com/firebase/emberfire/issues/124 | ||
_normalizeNumberIDs: function(hash, key) { | ||
var newHash = []; | ||
if (hash[key][0] === true) { | ||
newHash.push('0'); | ||
} | ||
if (hash[key][1] === true) { | ||
newHash.push('1'); | ||
} | ||
hash[key] = newHash; | ||
}, | ||
/** | ||
Call each function in the _queue and the reset the _queue | ||
*/ | ||
_queueFlush: function() { | ||
forEach(this._queue, function(queueItem) { | ||
var fn = queueItem[0]; | ||
var args = queueItem[1]; | ||
fn.apply(null, args); | ||
}); | ||
this._queue.length = 0; | ||
}, | ||
normalizeHasMany: function(type, hash, relationship) { | ||
var key = relationship.key; | ||
if (typeof hash[key] === 'object' && !ember$$default.isArray(hash[key])) { | ||
hash[key] = ember$$default.keys(hash[key]); | ||
} | ||
//We need to account for Firebase turning key/value pairs with ids '1' and '0' into arrays | ||
//See https://github.com/firebase/emberfire/issues/124 | ||
else if (ember$$default.isArray(hash[key]) && hash[key].length < 3 && (hash[key][0] === true || hash[key][1] === true)) { | ||
this._normalizeNumberIDs(hash, key); | ||
} | ||
else if (ember$$default.isArray(hash[key])) { | ||
throw new Error($$$$$$addon$serializers$firebase$$fmt('%@ relationship %@(\'%@\') must be a key/value map in Firebase. Example: { "%@": { "%@_id": true } }', [type.toString(), relationship.kind, relationship.type.typeKey, key, relationship.type.typeKey])); | ||
} | ||
}, | ||
/** | ||
Push a new function into the _queue and then schedule a | ||
flush if the item is the first to be pushed | ||
*/ | ||
_enqueue: function(callback, args) { | ||
//Only do the queueing if we scheduled a delay | ||
if (this._queueFlushDelay) { | ||
var length = this._queue.push([callback, args]); | ||
if (length === 1) { | ||
this._queueScheduleFlush(); | ||
normalizeEmbeddedHasMany: function(type, hash, relationship) { | ||
var key = relationship.key; | ||
var embeddedRecordPayload = hash[key]; | ||
var embeddedKey; | ||
if (!hash[key]) { | ||
return; | ||
} | ||
} else { | ||
callback.apply(null, args); | ||
} | ||
}, | ||
for (embeddedKey in embeddedRecordPayload) { | ||
var record = embeddedRecordPayload[embeddedKey]; | ||
if (record !== null && typeof record === 'object') { | ||
record.id = embeddedKey; | ||
} | ||
this.store.push(relationship.type, this.normalize(relationship.type, record)); | ||
} | ||
hash[key] = ember$$default.keys(hash[key]); | ||
}, | ||
/** | ||
A cache of hasMany relationships that can be used to | ||
diff against new relationships when a model is saved | ||
*/ | ||
_recordCacheForType: undefined, | ||
normalizeEmbeddedBelongsTo: function(type, hash, relationship) { | ||
var key = relationship.key; | ||
if (!hash[key]) { | ||
return; | ||
} | ||
var embeddedRecordPayload = hash[key]; | ||
if (typeof embeddedRecordPayload.id !== 'string') { | ||
throw new Error($$$$$$addon$serializers$firebase$$fmt('Embedded relationship "%@" of "%@" must contain an "id" property in the payload', [relationship.type.typeKey, type])); | ||
} | ||
this.store.push(relationship.type, this.normalize(relationship.type, embeddedRecordPayload)); | ||
hash[key] = embeddedRecordPayload.id; | ||
}, | ||
/** | ||
_updateHasManyCacheForType | ||
*/ | ||
_updateRecordCacheForType: function(type, payload) { | ||
if (!payload) { return; } | ||
var adapter = this; | ||
var id = payload.id; | ||
var cache = adapter._recordCacheForType; | ||
var typeKey = type.typeKey; | ||
// Only cache relationships for now | ||
type.eachRelationship(function(key, relationship) { | ||
if (relationship.kind === 'hasMany') { | ||
var ids = payload[key]; | ||
cache[typeKey] = cache[typeKey] || {}; | ||
cache[typeKey][id] = cache[typeKey][id] || {}; | ||
cache[typeKey][id][key] = !Ember.isNone(ids) ? Ember.A(Ember.keys(ids)) : Ember.A(); | ||
normalizeBelongsTo: ember$$default.K, | ||
/** | ||
Called after `extractSingle()`. This method checks the model | ||
for `hasMany` relationships and makes sure the value is an object. | ||
The object is then converted to an Array using `Ember.keys` | ||
*/ | ||
normalize: function(type, hash) { | ||
var serializer = this; | ||
// Check if the model contains any 'hasMany' relationships | ||
type.eachRelationship(function(key, relationship) { | ||
if (relationship.kind === 'hasMany') { | ||
if (relationship.options.embedded) { | ||
serializer.normalizeEmbeddedHasMany(type, hash, relationship); | ||
} else { | ||
serializer.normalizeHasMany(type, hash, relationship); | ||
} | ||
} else { | ||
if (relationship.options.embedded) { | ||
serializer.normalizeEmbeddedBelongsTo(type, hash, relationship); | ||
} else { | ||
serializer.normalizeBelongsTo(type, hash, relationship); | ||
} | ||
} | ||
}); | ||
return this._super.apply(this, arguments); | ||
}, | ||
/** | ||
Called on a records returned from `find()` and all records | ||
returned from `findAll()` | ||
This method also checkes for `embedded: true`, extracts the | ||
embedded records, pushes them into the store, and then replaces | ||
the records with an array of ids | ||
*/ | ||
extractSingle: function(store, type, payload) { | ||
return this.normalize(type, payload); | ||
}, | ||
/** | ||
Called after the adpter runs `findAll()` or `findMany()`. This method runs | ||
`extractSingle()` on each item in the payload and as a result each item | ||
will have `normalize()` called on it | ||
*/ | ||
extractArray: function(store, type, payload) { | ||
return $$$$$$addon$serializers$firebase$$map(payload, function(item) { | ||
return this.extractSingle(store, type, item); | ||
}, this); | ||
}, | ||
/** | ||
Overrides ember-data's `serializeHasMany` to serialize oneToMany | ||
relationships. | ||
*/ | ||
serializeHasMany: function(record, json, relationship) { | ||
var key = relationship.key; | ||
var payloadKey = this.keyForRelationship ? this.keyForRelationship(key, "hasMany") : key; | ||
json[payloadKey] = ember$$default.A(record.get(key)).mapBy('id'); | ||
}, | ||
serializeBelongsTo: function(record, json, relationship) { | ||
this._super(record, json, relationship); | ||
var key = relationship.key; | ||
// var payloadKey = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : relationship.key; | ||
if (typeof json[key] === "undefined" || json[key] === '') { | ||
delete json[key]; | ||
} | ||
}); | ||
}, | ||
} | ||
/** | ||
* A utility for retrieving the key name of a Firebase ref or | ||
* DataSnapshot. This is backwards-compatible with `name()` | ||
* from Firebase 1.x.x and `key()` from Firebase 2.0.0+. Once | ||
* support for Firebase 1.x.x is dropped in EmberFire, this | ||
* helper can be removed. | ||
*/ | ||
_getKey: function(refOrSnapshot) { | ||
return (typeof refOrSnapshot.key === 'function') ? refOrSnapshot.key() : refOrSnapshot.name(); | ||
}); | ||
var $$$$$$addon$initializers$emberfire$$VERSION = '1.4.0'; | ||
if (ember$$default.libraries) { | ||
ember$$default.libraries.registerCoreLibrary('EmberFire', $$$$$$addon$initializers$emberfire$$VERSION); | ||
} | ||
}); | ||
/** | ||
Register the serializer and adapter | ||
*/ | ||
Ember.onLoad('Ember.Application', function(Application) { | ||
Application.initializer({ | ||
name: 'firebase', | ||
initialize: function(container, application) { | ||
application.register('adapter:-firebase', DS.FirebaseAdapter); | ||
application.register('serializer:-firebase', DS.FirebaseSerializer); | ||
var $$$$$$addon$initializers$emberfire$$default = { | ||
name: 'emberfire', | ||
initialize: function (container, app) { | ||
app.register('adapter:-firebase', $$$$$$addon$adapters$firebase$$default); | ||
app.register('serializer:-firebase', $$$$$$addon$serializers$firebase$$default); | ||
// Monkeypatch the store until ED gives us a good way to listen to push events | ||
ember$data$$default.Store.reopen({ | ||
push: function(typeName, data, _partial) { | ||
var record = this._super(typeName, data, _partial); | ||
var adapter = this.adapterFor(record.constructor); | ||
if (adapter.recordWasPushed) { | ||
adapter.recordWasPushed(this, typeName, record); | ||
} | ||
return record; | ||
}, | ||
recordWillUnload: function(record) { | ||
var adapter = this.adapterFor(record.constructor); | ||
if (adapter.recordWillUnload) { | ||
adapter.recordWillUnload(this, record); | ||
} | ||
}, | ||
recordWillDelete: function (record) { | ||
var adapter = this.adapterFor(record.constructor); | ||
if (adapter.recordWillDelete) { | ||
adapter.recordWillDelete(this, record); | ||
} | ||
} | ||
}); | ||
ember$data$$default.Model.reopen({ | ||
unloadRecord: function() { | ||
this.store.recordWillUnload(this); | ||
return this._super(); | ||
}, | ||
deleteRecord: function () { | ||
this.store.recordWillDelete(this); | ||
this._super(); | ||
} | ||
}); | ||
ember$data$$default.FirebaseAdapter = $$$$$$addon$adapters$firebase$$default; | ||
ember$data$$default.FirebaseSerializer = $$$$$$addon$serializers$firebase$$default; | ||
} | ||
}; | ||
/*! | ||
* EmberFire is the officially supported adapter for using Firebase with | ||
* Ember Data. The DS.FirebaseAdapter provides all of the standard DS.Adapter | ||
* methods and will automatically synchronize the store with Firebase. | ||
* | ||
* EmberFire 1.4.0 | ||
* https://github.com/firebase/emberfire/ | ||
* License: MIT | ||
*/ | ||
window.DS.FirebaseAdapter = $$$$$$addon$adapters$firebase$$default; | ||
window.DS.FirebaseSerializer = $$$$$$addon$serializers$firebase$$default; | ||
ember$$default.onLoad('Ember.Application', function(Application) { | ||
Application.initializer($$$$$$addon$initializers$emberfire$$default); | ||
}); | ||
}); | ||
})(); | ||
}).call(this); | ||
//# sourceMappingURL=emberfire.js.map |
@@ -1,10 +0,10 @@ | ||
/*! | ||
* EmberFire is the officially supported adapter for using Firebase with | ||
* Ember Data. The DS.FirebaseAdapter provides all of the standard DS.Adapter | ||
* methods and will automatically synchronize the store with Firebase. | ||
* | ||
* EmberFire 1.3.3 | ||
* https://github.com/firebase/emberfire/ | ||
* License: MIT | ||
*/ | ||
!function(){"use strict";if(void 0!==window.DS){var a=Ember.Namespace.create({VERSION:"1.3.3"});Ember.libraries&&Ember.libraries.registerCoreLibrary("EmberFire",a.VERSION),DS.Store.reopen({push:function(a,b,c){var d=this._super(a,b,c),e=this.adapterFor(d.constructor);return e.recordWasPushed&&e.recordWasPushed(this,a,d),d},recordWillUnload:function(a){var b=this.adapterFor(a.constructor);b.recordWillUnload&&b.recordWillUnload(this,a)},recordWillDelete:function(a){var b=this.adapterFor(a.constructor);b.recordWillDelete&&b.recordWillDelete(this,a)}}),DS.Model.reopen({unloadRecord:function(){return this.store.recordWillUnload(this),this._super()},deleteRecord:function(){this.store.recordWillDelete(this),this._super()}});var b=Ember.RSVP.Promise,c=Ember.EnumerableUtils.map,d=Ember.EnumerableUtils.forEach,e=Ember.String.fmt,f=function(a,c,d,e){var f=d||[];return new b(function(b,d){var g=function(a){a?(e&&"object"==typeof a&&(a.location=e),d(a)):b()};f.push(g),a.apply(c,f)})};DS.FirebaseSerializer=DS.JSONSerializer.extend(Ember.Evented,{_normalizeNumberIDs:function(a,b){var c=[];a[b][0]===!0&&c.push("0"),a[b][1]===!0&&c.push("1"),a[b]=c},normalizeHasMany:function(a,b,c){var d=c.key;if("object"!=typeof b[d]||Ember.isArray(b[d])){if(Ember.isArray(b[d])&&b[d].length<3&&(b[d][0]===!0||b[d][1]===!0))this._normalizeNumberIDs(b,d);else if(Ember.isArray(b[d]))throw new Error(e('%@ relationship %@(\'%@\') must be a key/value map in Firebase. Example: { "%@": { "%@_id": true } }',[a.toString(),c.kind,c.type.typeKey,d,c.type.typeKey]))}else b[d]=Ember.keys(b[d])},normalizeEmbeddedHasMany:function(a,b,c){var d,e=c.key,f=b[e];if(b[e]){for(d in f){var g=f[d];null!==g&&"object"==typeof g&&(g.id=d),this.store.push(c.type,this.normalize(c.type,g))}b[e]=Ember.keys(b[e])}},normalizeEmbeddedBelongsTo:function(a,b,c){var d=c.key;if(b[d]){var f=b[d];if("string"!=typeof f.id)throw new Error(e('Embedded relationship "%@" of "%@" must contain an "id" property in the payload',[c.type.typeKey,a]));this.store.push(c.type,this.normalize(c.type,f)),b[d]=f.id}},normalizeBelongsTo:Ember.K,normalize:function(a,b){var c=this;return a.eachRelationship(function(d,e){"hasMany"===e.kind?e.options.embedded?c.normalizeEmbeddedHasMany(a,b,e):c.normalizeHasMany(a,b,e):e.options.embedded?c.normalizeEmbeddedBelongsTo(a,b,e):c.normalizeBelongsTo(a,b,e)}),this._super.apply(this,arguments)},extractSingle:function(a,b,c){return this.normalize(b,c)},extractArray:function(a,b,d){return c(d,function(c){return this.extractSingle(a,b,c)},this)},serializeHasMany:function(a,b,c){var d=c.key,e=this.keyForRelationship?this.keyForRelationship(d,"hasMany"):d;b[e]=Ember.A(a.get(d)).mapBy("id")},serializeBelongsTo:function(a,b,c){this._super(a,b,c);{var d=c.key;this.keyForRelationship?this.keyForRelationship(d,"belongsTo"):c.key}("undefined"==typeof b[d]||""===b[d])&&delete b[d]}}),DS.FirebaseAdapter=DS.Adapter.extend(Ember.Evented,{defaultSerializer:"-firebase",init:function(){var a=this.get("firebase");if(!a||"object"!=typeof a)throw new Error("Please set the `firebase` property on the adapter.");this._ref=a.ref(),this._findAllMapForType={},this._recordCacheForType={},this._queue=[]},generateIdForRecord:function(){return this._getKey(this._ref.push())},_assignIdToPayload:function(a){var b=a.val();return null!==b&&"object"==typeof b&&"undefined"==typeof b.id&&(b.id=this._getKey(a)),b},find:function(a,c,d){{var f=this,g=this._getRef(c,d);a.serializerFor(c)}return new b(function(a,b){g.once("value",function(h){var i=f._assignIdToPayload(h);if(f._updateRecordCacheForType(c,i),null===i){var j=new Error(e("no record was found at %@",[g.toString()]));j.recordId=d,b(j)}else a(i)},function(a){b(a)})},e("DS: FirebaseAdapter#find %@ to %@",[c,g.toString()]))},recordWasPushed:function(a,b,c){c.__listening||this.listenForChanges(a,b,c)},recordWillUnload:function(a,b){var c=this._getRef(b.typeKey,b.get("id"));c.off("value")},recordWillDelete:function(a,b){var c=this;b.eachRelationship(function(d,e){if("belongsTo"===e.kind){var f=b.get(e.key),g=b.inverseFor(e.key);if(g&&f.get("id")){var h=c._getRef(g.type,f.get("id"));c._removeHasManyRecord(a,h,g.name,b.id)}}})},listenForChanges:function(a,b,c){c.__listening=!0;var d=a.serializerFor(b),e=this,f=this._getRef(b,c.get("id")),g=!1;f.on("value",function(c){g&&e._handleChildValue(a,b,d,c),g=!0})},findMany:void 0,findAll:function(a,c){var d=this,f=this._getRef(c);return new b(function(b,e){f.once("value",function(e){d._findAllHasEventsForType(c)||d._findAllAddEventListeners(a,c,f);var g=[];e.forEach(function(a){var b=d._assignIdToPayload(a);d._updateRecordCacheForType(c,b),g.push(b)}),b(g)},function(a){e(a)})},e("DS: FirebaseAdapter#findAll %@ to %@",[c,f.toString()]))},_findAllMapForType:void 0,_findAllHasEventsForType:function(a){return!Ember.isNone(this._findAllMapForType[a])},_findAllAddEventListeners:function(a,b,c){this._findAllMapForType[b]=!0;var d=this,e=a.serializerFor(b);c.on("child_added",function(c){a.hasRecordForId(b,d._getKey(c))||d._handleChildValue(a,b,e,c)})},_handleChildValue:function(a,b,c,d){if(!a.isDestroying){var e=d.val();if(null===e){var f=this._getKey(d),g=a.getById(b,f);g.get("isDeleted")||g.deleteRecord()}else{var h=this._assignIdToPayload(d);this._enqueue(function(){a.push(b,c.extractSingle(a,b,h))})}}},createRecord:function(a,b,c){var d=this;return this.updateRecord(a,b,c).then(function(){d.listenForChanges(a,b,c)})},updateRecord:function(a,c,d,f){var g=this,h=f||this._getRef(c,d.id),i=Ember.get(g._recordCacheForType,e("%@.%@",[c.typeKey,d.get("id")]))||{},j=d.serialize({includeId:!1});return new b(function(b,f){var k=Ember.A();d.eachRelationship(function(b,d){var e;"hasMany"===d.kind?j[b]&&(e=g._saveHasManyRelationship(a,c,d,j[b],h,i),k.push(e),delete j[b]):d.options.embedded===!0&&j[b]&&(e=g._saveBelongsToRecord(a,c,d,j[b],h),k.push(e),delete j[b])});var l=Ember.RSVP.allSettled(k),m=g._updateRecord(h,j);Ember.RSVP.hashSettled({relationships:l,record:m}).then(function(a){var g=Ember.A(a.relationships.value).filterBy("state","rejected");if("rejected"===a.record.state&&g.push(a.record),0!==g.length){var h=new Error(e("Some errors were encountered while saving %@ %@",[c,d.id]));h.errors=g.mapBy("reason"),f(h)}else b()})},e("DS: FirebaseAdapter#updateRecord %@ to %@",[c,h.toString()]))},_updateRecord:function(a,b){return f(a.update,a,[b])},_saveHasManyRelationship:function(a,b,c,d,f,g){if(!Ember.isArray(d))throw new Error("hasMany relationships must must be an array");var h=this,i=Ember.A(g[c.key]);d=Ember.A(d);var j=[],k=d.filter(function(a){return!i.contains(a)});j=d.filter(function(b){var d=c.type;return a.hasRecordForId(d,b)&&a.getById(d,b).get("isDirty")===!0}),j=Ember.A(j.concat(k)).uniq().map(function(b){return h._saveHasManyRecord(a,c,f,b)});var l=i.filter(function(a){return!d.contains(a)});l=Ember.A(l).map(function(b){return h._removeHasManyRecord(a,f,c.key,b)});var m=j.concat(l);return Ember.RSVP.allSettled(m).then(function(a){var b=Ember.A(Ember.A(a).filterBy("state","rejected"));if(0===b.get("length"))return g[c.key]=d,a;var f=new Error(e("Some errors were encountered while saving a hasMany relationship %@ -> %@",[c.parentType,c.type]));throw f.errors=Ember.A(b).mapBy("reason"),f})},_saveHasManyRecord:function(a,b,c,d){var e=this._getRelationshipRef(c,b.key,d),g=a.getById(b.type,d),h=b.options.embedded===!0;return h?this.updateRecord(a,b.type,g,e):f(e.set,e,[!0])},_removeHasManyRecord:function(a,b,c,d){var e=this._getRelationshipRef(b,c,d);return f(e.remove,e,[],e.toString())},_saveBelongsToRecord:function(a,b,c,d,e){var f=e.child(c.key),g=a.getById(c.type,d);return this.updateRecord(a,c.type,g,f)},deleteRecord:function(a,b,c){var d=this._getRef(b,c.get("id"));return f(d.remove,d)},pathForType:function(a){var b=Ember.String.camelize(a);return Ember.String.pluralize(b)},_getRef:function(a,b){var c=this._ref;return a&&(c=c.child(this.pathForType(a.typeKey))),b&&(c=c.child(b)),c},_getRelationshipRef:function(a,b,c){return a.child(b).child(c)},_queueFlushDelay:1e3/60,_queueScheduleFlush:function(){Ember.run.later(this,this._queueFlush,this._queueFlushDelay)},_queueFlush:function(){d(this._queue,function(a){var b=a[0],c=a[1];b.apply(null,c)}),this._queue.length=0},_enqueue:function(a,b){if(this._queueFlushDelay){var c=this._queue.push([a,b]);1===c&&this._queueScheduleFlush()}else a.apply(null,b)},_recordCacheForType:void 0,_updateRecordCacheForType:function(a,b){if(b){var c=this,d=b.id,e=c._recordCacheForType,f=a.typeKey;a.eachRelationship(function(a,c){if("hasMany"===c.kind){var g=b[a];e[f]=e[f]||{},e[f][d]=e[f][d]||{},e[f][d][a]=Ember.isNone(g)?Ember.A():Ember.A(Ember.keys(g))}})}},_getKey:function(a){return"function"==typeof a.key?a.key():a.name()}}),Ember.onLoad("Ember.Application",function(a){a.initializer({name:"firebase",initialize:function(a,b){b.register("adapter:-firebase",DS.FirebaseAdapter),b.register("serializer:-firebase",DS.FirebaseSerializer)}})})}}(); | ||
(function(){"use strict";var e=window.Ember,r=window.DS,t=function(r,t,i,n){var o=i||[];return new e.RSVP.Promise(function(e,i){var a=function(r){r?(n&&"object"==typeof r&&(r.location=n),i(r)):e()};o.push(a),r.apply(t,o)})},i=e.String.fmt,n=e.RSVP.Promise,o=e.EnumerableUtils.forEach,a=e.EnumerableUtils.filter,s=e.EnumerableUtils.map,d=e.EnumerableUtils.indexOf,u=function(r){var t=e.A();return r.forEach(function(e){d(t,e)<0&&t.push(e)}),t},l=r.Adapter.extend(e.Evented,{defaultSerializer:"-firebase",init:function(){var e=this.get("firebase");if(!e||"object"!=typeof e)throw new Error("Please set the `firebase` property on the adapter.");this._ref=e.ref(),this._findAllMapForType={},this._recordCacheForType={},this._queue=[]},generateIdForRecord:function(){return this._getKey(this._ref.push())},_assignIdToPayload:function(e){var r=e.val();return null!==r&&"object"==typeof r&&"undefined"==typeof r.id&&(r.id=this._getKey(e)),r},find:function(e,r,t){var o=this,a=this._getRef(r,t);return new n(function(e,n){a.once("value",function(s){var d=o._assignIdToPayload(s);if(o._updateRecordCacheForType(r,d),null===d){var u=new Error(i("no record was found at %@",[a.toString()]));u.recordId=t,n(u)}else e(d)},function(e){n(e)})},i("DS: FirebaseAdapter#find %@ to %@",[r,a.toString()]))},recordWasPushed:function(e,r,t){t.__listening||this.listenForChanges(e,r,t)},recordWillUnload:function(e,r){var t=this._getRef(r.typeKey,r.get("id"));t.off("value")},recordWillDelete:function(e,r){var t=this;r.eachRelationship(function(i,n){if("belongsTo"===n.kind){var o=r.get(n.key),a=r.inverseFor(n.key);if(a&&o.get("id")){var s=t._getRef(a.type,o.get("id"));t._removeHasManyRecord(e,s,a.name,r.id)}}})},listenForChanges:function(e,r,t){t.__listening=!0;var i=e.serializerFor(r),n=this,o=this._getRef(r,t.get("id")),a=!1;o.on("value",function(t){a&&n._handleChildValue(e,r,i,t),a=!0})},findMany:void 0,findAll:function(e,r){var t=this,o=this._getRef(r);return new n(function(i,n){o.once("value",function(n){t._findAllHasEventsForType(r)||t._findAllAddEventListeners(e,r,o);var a=[];n.forEach(function(e){var i=t._assignIdToPayload(e);t._updateRecordCacheForType(r,i),a.push(i)}),i(a)},function(e){n(e)})},i("DS: FirebaseAdapter#findAll %@ to %@",[r,o.toString()]))},findQuery:function(e,r,t){var o=this,a=this._getRef(r);return a=this.applyQueryToRef(a,t),new n(function(t,i){a.once("value",function(i){o._findAllHasEventsForType(r)||o._findAllAddEventListeners(e,r,a);var n=[];i.forEach(function(e){var t=o._assignIdToPayload(e);o._updateRecordCacheForType(r,t),n.push(t)}),t(n)},function(e){i(e)})},i("DS: FirebaseAdapter#findQuery %@ with %@",[r,t]))},applyQueryToRef:function(e,r){return r.orderBy||(r.orderBy="_key"),"_key"===r.orderBy?e=e.orderByKey():"_value"===r.orderBy?e=e.orderByValue():"_priority"===r.orderBy?e=e.orderByPriority():e.orderByChild(r.orderBy),["limitToFirst","limitToLast","startAt","endAt","equalTo"].forEach(function(t){(r[t]||""===r[t])&&(e=e[t](r[t]))}),e},_findAllMapForType:void 0,_findAllHasEventsForType:function(r){return!e.isNone(this._findAllMapForType[r])},_findAllAddEventListeners:function(e,r,t){this._findAllMapForType[r]=!0;var i=this,n=e.serializerFor(r);t.on("child_added",function(t){e.hasRecordForId(r,i._getKey(t))||i._handleChildValue(e,r,n,t)})},_handleChildValue:function(e,r,t,i){if(!e.isDestroying){var n=i.val();if(null===n){var o=this._getKey(i),a=e.getById(r,o);a.get("isDeleted")||a.deleteRecord()}else{var s=this._assignIdToPayload(i);this._enqueue(function(){e.push(r,t.extractSingle(e,r,s))})}}},createRecord:function(e,r,t){var i=this;return this.updateRecord(e,r,t).then(function(){i.listenForChanges(e,r,t)})},updateRecord:function(r,t,o,a){var s=this,d=a||this._getRef(t,o.id),u=s._getRecordCache(t.typeKey,o.get("id")),l=o.serialize({includeId:!1});return new n(function(n,a){var c=e.A();o.eachRelationship(function(e,i){var n;"hasMany"===i.kind?l[e]&&(n=s._saveHasManyRelationship(r,t,i,l[e],d,u),c.push(n),delete l[e]):i.options.embedded===!0&&l[e]&&(n=s._saveBelongsToRecord(r,t,i,l[e],d),c.push(n),delete l[e])});var h=e.RSVP.allSettled(c),f=s._updateRecord(d,l);e.RSVP.hashSettled({relationships:h,record:f}).then(function(r){var s=e.A(r.relationships.value).filterBy("state","rejected");if("rejected"===r.record.state&&s.push(r.record),0!==s.length){var d=new Error(i("Some errors were encountered while saving %@ %@",[t,o.id]));d.errors=s.mapBy("reason"),a(d)}else n()})},i("DS: FirebaseAdapter#updateRecord %@ to %@",[t,d.toString()]))},_updateRecord:function(e,r){return t(e.update,e,[r])},_saveHasManyRelationship:function(r,t,n,o,d,l){if(!e.isArray(o))throw new Error("hasMany relationships must must be an array");var c=this,h=e.A(l[n.key]),f=[],y=a(o,function(e){return!h.contains(e)});f=a(o,function(e){var t=n.type;return r.hasRecordForId(t,e)&&r.getById(t,e).get("isDirty")===!0}),f=s(u(f.concat(y)),function(e){return c._saveHasManyRecord(r,n,d,e)});var p=a(h,function(e){return!o.contains(e)});p=s(p,function(e){return c._removeHasManyRecord(r,d,n.key,e)});var v=f.concat(p);return e.RSVP.allSettled(v).then(function(r){var t=e.A(e.A(r).filterBy("state","rejected"));if(0===t.get("length"))return l[n.key]=o,r;var a=new Error(i("Some errors were encountered while saving a hasMany relationship %@ -> %@",[n.parentType,n.type]));throw a.errors=e.A(t).mapBy("reason"),a})},_saveHasManyRecord:function(e,r,i,n){var o=this._getRelationshipRef(i,r.key,n),a=e.getById(r.type,n),s=r.options.embedded===!0;return s?this.updateRecord(e,r.type,a,o):t(o.set,o,[!0])},_removeHasManyRecord:function(e,r,i,n){var o=this._getRelationshipRef(r,i,n);return t(o.remove,o,[],o.toString())},_saveBelongsToRecord:function(e,r,t,i,n){var o=n.child(t.key),a=e.getById(t.type,i);return this.updateRecord(e,t.type,a,o)},deleteRecord:function(e,r,i){var n=this._getRef(r,i.get("id"));return t(n.remove,n)},pathForType:function(r){var t=e.String.camelize(r);return e.String.pluralize(t)},_getRef:function(e,r){var t=this._ref;return e&&(t=t.child(this.pathForType(e.typeKey))),r&&(t=t.child(r)),t},_getRelationshipRef:function(e,r,t){return e.child(r).child(t)},_queueFlushDelay:1e3/60,_queueScheduleFlush:function(){e.run.later(this,this._queueFlush,this._queueFlushDelay)},_queueFlush:function(){o(this._queue,function(e){var r=e[0],t=e[1];r.apply(null,t)}),this._queue.length=0},_enqueue:function(e,r){if(this._queueFlushDelay){var t=this._queue.push([e,r]);1===t&&this._queueScheduleFlush()}else e.apply(null,r)},_recordCacheForType:void 0,_updateRecordCacheForType:function(r,t){if(t){var i=t.id,n=r.typeKey,o=this._getRecordCache(n,i);r.eachRelationship(function(r,i){if("hasMany"===i.kind){var n=t[r];o[r]=e.isNone(n)?e.A():e.A(e.keys(n))}})}},_getRecordCache:function(e,r){var t=this._recordCacheForType;return t[e]=t[e]||{},t[e][r]=t[e][r]||{},t[e][r]},_getKey:function(e){return"function"==typeof e.key?e.key():e.name()}}),c=e.EnumerableUtils.map,h=e.String.fmt,f=r.JSONSerializer.extend(e.Evented,{_normalizeNumberIDs:function(e,r){var t=[];e[r][0]===!0&&t.push("0"),e[r][1]===!0&&t.push("1"),e[r]=t},normalizeHasMany:function(r,t,i){var n=i.key;if("object"!=typeof t[n]||e.isArray(t[n])){if(e.isArray(t[n])&&t[n].length<3&&(t[n][0]===!0||t[n][1]===!0))this._normalizeNumberIDs(t,n);else if(e.isArray(t[n]))throw new Error(h('%@ relationship %@(\'%@\') must be a key/value map in Firebase. Example: { "%@": { "%@_id": true } }',[r.toString(),i.kind,i.type.typeKey,n,i.type.typeKey]))}else t[n]=e.keys(t[n])},normalizeEmbeddedHasMany:function(r,t,i){var n,o=i.key,a=t[o];if(t[o]){for(n in a){var s=a[n];null!==s&&"object"==typeof s&&(s.id=n),this.store.push(i.type,this.normalize(i.type,s))}t[o]=e.keys(t[o])}},normalizeEmbeddedBelongsTo:function(e,r,t){var i=t.key;if(r[i]){var n=r[i];if("string"!=typeof n.id)throw new Error(h('Embedded relationship "%@" of "%@" must contain an "id" property in the payload',[t.type.typeKey,e]));this.store.push(t.type,this.normalize(t.type,n)),r[i]=n.id}},normalizeBelongsTo:e.K,normalize:function(e,r){var t=this;return e.eachRelationship(function(i,n){"hasMany"===n.kind?n.options.embedded?t.normalizeEmbeddedHasMany(e,r,n):t.normalizeHasMany(e,r,n):n.options.embedded?t.normalizeEmbeddedBelongsTo(e,r,n):t.normalizeBelongsTo(e,r,n)}),this._super.apply(this,arguments)},extractSingle:function(e,r,t){return this.normalize(r,t)},extractArray:function(e,r,t){return c(t,function(t){return this.extractSingle(e,r,t)},this)},serializeHasMany:function(r,t,i){var n=i.key,o=this.keyForRelationship?this.keyForRelationship(n,"hasMany"):n;t[o]=e.A(r.get(n)).mapBy("id")},serializeBelongsTo:function(e,r,t){this._super(e,r,t);var i=t.key;("undefined"==typeof r[i]||""===r[i])&&delete r[i]}}),y="1.4.0";e.libraries&&e.libraries.registerCoreLibrary("EmberFire",y);var p={name:"emberfire",initialize:function(e,t){t.register("adapter:-firebase",l),t.register("serializer:-firebase",f),r.Store.reopen({push:function(e,r,t){var i=this._super(e,r,t),n=this.adapterFor(i.constructor);return n.recordWasPushed&&n.recordWasPushed(this,e,i),i},recordWillUnload:function(e){var r=this.adapterFor(e.constructor);r.recordWillUnload&&r.recordWillUnload(this,e)},recordWillDelete:function(e){var r=this.adapterFor(e.constructor);r.recordWillDelete&&r.recordWillDelete(this,e)}}),r.Model.reopen({unloadRecord:function(){return this.store.recordWillUnload(this),this._super()},deleteRecord:function(){this.store.recordWillDelete(this),this._super()}}),r.FirebaseAdapter=l,r.FirebaseSerializer=f}};/*! | ||
* EmberFire is the officially supported adapter for using Firebase with | ||
* Ember Data. The DS.FirebaseAdapter provides all of the standard DS.Adapter | ||
* methods and will automatically synchronize the store with Firebase. | ||
* | ||
* EmberFire 1.4.0 | ||
* https://github.com/firebase/emberfire/ | ||
* License: MIT | ||
*/ | ||
window.DS.FirebaseAdapter=l,window.DS.FirebaseSerializer=f,e.onLoad("Ember.Application",function(e){e.initializer(p)})}).call(this); |
{ | ||
"name": "emberfire", | ||
"description": "The officially supported Ember binding for Firebase", | ||
"version": "1.3.3", | ||
"version": "1.4.0", | ||
"author": "Firebase <support@firebase.com> (https://www.firebase.com/)", | ||
"homepage": "https://github.com/firebase/emberfire/", | ||
"directories": { | ||
"doc": "doc", | ||
"test": "tests" | ||
}, | ||
"scripts": { | ||
"start": "ember server", | ||
"build": "ember build", | ||
"test": "ember test", | ||
"legacy": "gulp legacy" | ||
}, | ||
"repository": { | ||
@@ -26,37 +36,33 @@ "type": "git", | ||
], | ||
"main": "dist/emberfire.js", | ||
"files": [ | ||
"lib/**/*", | ||
"dist/**/*", | ||
"vendor/**/*", | ||
"README.md", | ||
"package.json" | ||
], | ||
"ember-addon": { | ||
"main": "lib/ember-addon/index.js" | ||
"configPath": "tests/dummy/config" | ||
}, | ||
"dependencies": { | ||
"dependencies":{ | ||
"rimraf": "2.3.1" | ||
}, | ||
"devDependencies": { | ||
"grunt": "~0.4.5", | ||
"grunt-cli": "0.1.13", | ||
"grunt-contrib-clean": "~0.5.0", | ||
"grunt-contrib-concat": "~0.3.0", | ||
"grunt-contrib-connect": "~0.7.1", | ||
"grunt-contrib-jshint": "~0.6.2", | ||
"grunt-contrib-uglify": "~0.4.0", | ||
"grunt-contrib-watch": "~0.5.1", | ||
"grunt-contrib-qunit": "~0.4.0", | ||
"grunt-exec": "~0.4.2", | ||
"grunt-istanbul": "~0.2.5", | ||
"grunt-notify": "~0.2.7", | ||
"grunt-mocha-phantomjs": "~0.3.1", | ||
"grunt-mocha-test": "~0.5.0", | ||
"load-grunt-config": "~0.5.0", | ||
"grunt-contrib-copy": "~0.5.0" | ||
}, | ||
"scripts": { | ||
"test": "grunt test", | ||
"travis": "grunt" | ||
"broccoli-asset-rev": "2.0.1", | ||
"chalk": "1.0.0", | ||
"del": "1.1.1", | ||
"ember-cli": "0.1.15", | ||
"ember-cli-6to5": "3.0.0", | ||
"ember-cli-dependency-checker": "0.0.7", | ||
"ember-cli-htmlbars": "0.6.0", | ||
"ember-cli-inject-live-reload": "1.3.1", | ||
"ember-cli-mocha": "0.5.0", | ||
"ember-cli-uglify": "1.0.1", | ||
"ember-data": "1.0.0-beta.14.1", | ||
"ember-export-application-global": "1.0.2", | ||
"express": "4.12.2", | ||
"fs-extra": "0.16.4", | ||
"glob": "4.5.0", | ||
"gulp": "3.8.11", | ||
"gulp-concat": "2.5.2", | ||
"gulp-es6-module-transpiler": "0.2.1", | ||
"gulp-jshint": "1.9.2", | ||
"gulp-sourcemaps": "1.5.0", | ||
"gulp-uglify": "1.1.0", | ||
"inflection": "1.6.0", | ||
"rsvp": "3.0.17" | ||
} | ||
} |
@@ -33,3 +33,3 @@ # EmberFire (Firebase + Ember Data) | ||
<!-- EmberFire --> | ||
<script src="https://cdn.firebase.com/libs/emberfire/1.3.3/emberfire.min.js"></script> | ||
<script src="https://cdn.firebase.com/libs/emberfire/1.4.0/emberfire.min.js"></script> | ||
``` | ||
@@ -255,17 +255,20 @@ | ||
```bash | ||
$ git clone https://github.com/firebase/emberfire.git | ||
$ cd emberfire # go to the emberfire directory | ||
$ npm install -g grunt-cli # globally install grunt task runner | ||
$ npm install -g bower # globally install Bower package manager | ||
$ npm install # install local npm build / test dependencies | ||
$ bower install # install local JavaScript dependencies | ||
$ grunt watch # watch for source file changes | ||
``` | ||
### Installation | ||
`grunt watch` will watch for changes in the `/src/` directory and lint, concatenate, and minify the | ||
source files and run the test suite when a change occurs. The output files - `emberfire.js` and | ||
`emberfire.min.js` - are written to the `/dist/` directory. | ||
* `git clone` this repository | ||
* `npm install` | ||
* `bower install` | ||
You can run the test suite by navigating to `file:///path/to/emberfire/test/index.html` or via the | ||
command line using `grunt test`. | ||
### Running | ||
* `ember server` | ||
* Visit your app at http://localhost:4200. | ||
### Running Tests | ||
* `ember test` | ||
* `ember test --server` | ||
### Building | ||
* `ember build` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
33
273
1
264687
1
23
3072
1
+ Addedrimraf@2.3.1
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedglob@4.5.3(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedminimatch@2.0.10(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedrimraf@2.3.1(transitive)
+ Addedwrappy@1.0.2(transitive)