wildember
Version:
一个方便、快捷连接野狗实时服务适配器。
897 lines (765 loc) • 29.7 kB
JavaScript
import Ember from 'ember';
import DS from 'ember-data';
import Waitable from '../mixins/waitable';
import toPromise from '../utils/to-promise';
import forEach from 'lodash/collection/forEach';
import filter from 'lodash/collection/filter';
import map from 'lodash/collection/map';
import includes from 'lodash/collection/includes';
import indexOf from 'lodash/array/indexOf';
import find from 'lodash/collection/find';
var Promise = Ember.RSVP.Promise;
var uniq = function (arr) {
var ret = Ember.A();
arr.forEach(function(k) {
if (indexOf(ret, k) < 0) {
ret.push(k);
}
});
return ret;
};
/**
* wildember,这是一个连接野狗实时服务的适配器,开发者只需要继承类`WildemberAdapter`,
* 并在适配器中设置对象`wilddogConfig`,此对象是用于连接野狗的URL。比如下面的代码
*
* ```js
* export default WildemberAdapter.extend({
* wilddogConfig: {
* // 注意appId是注册野狗后自己的项目的项目名,
* // 可以在:https://www.wilddog.com/dashboard/查看
* syncDomain: "appId.wilddog.com",
* syncURL: "https://appId.wilddogio.com"
* }
* });
* ```
* 适配器会自动连接野狗实时服务,并且在适当时候(执行save、update操作)持久化数据;
* 另一方面,适配器还会实时监测野狗数据库数据的变化,并且实时更新Ember.js项目中关联的属性值。
*/
export default DS.Adapter.extend(Waitable, {
//
defaultSerializer: '-wildember',
// 连接野狗配置信息
wilddogConfig: null,
// https://docs.wilddog.com/guide/sync/data-limit.html
// wilddog查询最大节点数,默认读取2000个,可以在子类设置
MAX_QUERY_COUNT: 2000,
/**
* 初始化,并获取野狗的连接
*/
init(/*application*/) {
this._super.apply(this, arguments);
// wilddogConfig会在子类中被赋值
let wilddogConfig = this.get('wilddogConfig');
if (!wilddogConfig) {
throw new Error('请在适配器`application`中设置属性`wilddogConfig`!');
}
// var ref = this.get('wildember').getWilddogRef(application);
// 获取野狗连接
wilddog.initializeApp(wilddogConfig);
let ref = wilddog.sync().ref();
if (!ref) {
throw new Error('连接wilddog失败。');
}
// If provided wilddog reference was a query (eg: limits), make it a ref.
this._ref = 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 = [];
},
/**
* 调用push()方法得到一个按时间排序的唯一ID,ID格式:-KSbIkOwC91KABpN1pbi
* push: https://docs.wilddog.com/api/sync/web/api.html#push
* @return {String} 唯一id值
*/
generateIdForRecord() {
return this._getKey(this._ref.push());
},
/**
* Use the Wilddog DataSnapshot's key as the record id
*
* @param {Object} snapshot - A Wilddog snapshot
* @param {Object} payload - The payload that will be pushed into the store
* @return {Object} payload
*/
_assignIdToPayload(snapshot) {
var payload = snapshot.val();
if (payload !== null && typeof payload === 'object' && typeof payload.id === 'undefined') {
payload.id = this._getKey(snapshot);
}
return payload;
},
/**
* store调用此方法时候回根据id查询`typeClass`的数据。
* 此方法返回的是一个promises,当promises执行成功的时候可以wilddog获取到数据。
*
* 此外,当wilddog后端数据库的数据有变化时候此方法会自动更新(ember项目的属性自动更新)
*/
findRecord(store, typeClass, id) {
var ref = this._getCollectionRef(typeClass, id);
var log = `DS: WildemberAdapter#findRecord ${typeClass.modelName} to ${ref.toString()}`;
return this._fetch(ref, log).then((snapshot) => {
// 得到从wilddog返回的数据
var payload = this._assignIdToPayload(snapshot);
//缓存一下得到的数据
this._updateRecordCacheForType(typeClass, payload, store);
if (payload === null) {
var error = new Error(`no record was found at ${ref.toString()}`);
error.recordId = id;
throw error;
}
return payload;
});
},
/**
* 获取数据并返回promises
*
* @param {Wilddog} ref
* @param {String} log
* @return {Promise<DataSnapshot>}
* @private
*/
_fetch(ref, log) {
this._incrementWaiters();
return new Promise((resolve, reject) => {
ref.once('value', (snapshot) => {
this._decrementWaiters();
Ember.run(null, resolve, snapshot);
}, (err) => {
this._decrementWaiters();
Ember.run(null, reject, err);
});
}, log);
},
recordWasPushed(store, modelName, record) {
if (!record.__listening) {
var typeClass = store.modelFor(modelName);
this.listenForChanges(store, typeClass, record);
}
},
recordWillUnload(store, record) {
if (record.__listening) {
this.stopListening(store, record.constructor, record);
}
},
recordWillDelete(store, record) {
record.eachRelationship((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 = this._getCollectionRef(inverseKey.type, parentRecord.get('id'));
this._removeHasManyRecord(store, parentRef, inverseKey.name, record.constructor, record.id);
}
}
});
},
listenForChanges(store, typeClass, record) {
// embedded records will get their changes from parent listeners
if (!this.isRecordEmbedded(record)) {
record.__listening = true;
var ref = this._getCollectionRef(typeClass, record.id);
var called = false;
ref.on('value', (snapshot) => {
if (called) {
Ember.run(() => {
this._handleChildValue(store, typeClass, snapshot);
});
}
called = true;
}, (error) => {
Ember.Logger.error(error);
});
}
},
stopListening(store, typeClass, record) {
if (record.__listening) {
var ref = this._getCollectionRef(typeClass, record.id);
ref.off('value');
record.__listening = false;
}
},
/**
* 查询所有
* 由于wilddog限制了每次查询的最大数量,特此在内部实现自动分页查询,然后把分页查询到的数组放到一个集合中返回
*/
findAll(store, typeClass, startPosition) {
var ref = this._getCollectionRef(typeClass);
//设置查询参数
var query = {
startAt: startPosition, //this.get("startAt"),
orderByChild: 'timestamp',
limitToFirst: this.get('MAX_QUERY_COUNT') //每页显示的条数
};
// store.set('typeMaps.metadata', { 'isPagination':true } );
var typeMaps = {
metadata: {
isPagination: false
}
};
ref = this.applyQueryToRef(ref, query, typeMaps);
var log = `DS: WildemberAdapter#findAll ${typeClass.modelName} to ${ref.toString()}`;
// 执行查询
return this._fetch(ref, log).then((snapshot) => {
if (!this._findAllHasEventsForType(typeClass)) {
this._findAllAddEventListeners(store, typeClass, ref);
}
var results = [];
snapshot.forEach((childSnapshot) => {
var payload = this._assignIdToPayload(childSnapshot);
this._updateRecordCacheForType(typeClass, payload, store);
results.push(payload);
});
/*
每次获取100条记录,取最后一条作为下一页的开始,然后再查询下一页的数据,
只要下一页还有超过2条数据就继续分页查询,
直至查询完当前节点的所有数据。
*/
var len = results.length;
if (len < this.get('MAX_QUERY_COUNT')) { //最有一页不足100条,不需要再继续分页查询
return results;
} else {
//最后一个元素
var startPosition = results[len - 1].id; //
// 递归查询下一页
return this.findAll(store, typeClass, startPosition).then((list) => {
var len = results.length-1; // len - 1目的是为了让后一页的第一条数据覆盖掉前一页的最后一条数据
// 把后面的数据拼接上去,
list.forEach((item) => {
// var payload = this._assignIdToPayload(item);
// this._updateRecordCacheForType(typeClass, payload, store);
// results.push(payload);
//后一页的第一个数据会覆盖到上一页的最后一个数据,完美解决了因为多获取一条数据导致重复的问题
//https://coding.net/u/wilddog/p/wilddog-gist-js/git/tree/master/src/pagination#user-content-yi-kao-shang--ye-de-zui-hou--tiao-ji-lu-huo-qu-xia--ye-shu-ju
// results[len++] = Ember.Object.create(item);
results[len++] = item;
});
return results;
});
} // if else
}); //_fetch
},
query(store, typeClass, query, recordArray) {
var ref = this._getCollectionRef(typeClass);
var modelName = typeClass.modelName;
ref = this.applyQueryToRef(ref, query, store.get("typeMaps"));
ref.on('child_added', Ember.run.bind(this, function (snapshot) {
var record = store.peekRecord(modelName, this._getKey(snapshot));
if (!record || !record.__listening) {
var payload = this._assignIdToPayload(snapshot);
var normalizedData = store.normalize(typeClass.modelName, payload);
this._updateRecordCacheForType(typeClass, payload, store);
record = store.push(normalizedData);
}
if (record) {
recordArray.get('content').addObject(record._internalModel);
}
}));
// `child_changed` is already handled by the record's
// value listener after a store.push. `child_moved` is
// a much less common case because it relates to priority
ref.on('child_removed', Ember.run.bind(this, function (snapshot) {
var record = store.peekRecord(modelName, this._getKey(snapshot));
if (record) {
recordArray.get('content').removeObject(record._internalModel);
}
}));
// clean up event handlers when the array is being destroyed
// so that future wilddog events wont keep trying to use a
// destroyed store/serializer
recordArray.__firebaseCleanup = function () {
ref.off('child_added');
ref.off('child_removed');
};
var log = `DS: WildemberAdapter#query ${modelName} with ${query}`;
// _fetch返回一个查询数据的promise
return this._fetch(ref, log).then((snapshot) => {
if (!this._findAllHasEventsForType(typeClass)) {
this._findAllAddEventListeners(store, typeClass, ref);
}
var results = [];
snapshot.forEach((childSnapshot) => {
var payload = this._assignIdToPayload(childSnapshot);
this._updateRecordCacheForType(typeClass, payload, store);
// 剔除id为空的数据
if (payload.id) {
results.push(payload);
}
// results.push(payload);
});
return results;
});
},
applyQueryToRef(ref, query, typeMaps) {
// 默认排序
if (!query.orderBy) {
query.orderBy = '_key';
}
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 = ref.orderByChild(query.orderBy);
}
['limitToFirst', 'limitToLast', 'startAt', 'endAt', 'equalTo'].forEach(function (key) {
// 非null
if (query[key] || query[key] === '' || query[key] === false) {
// 分页
if (typeMaps && typeof(typeMaps.metadata) !== 'undefined' && typeMaps.metadata.isPagination) {
Ember.Logger.debug("Adapter.applyQueryToRef:分页处理。");
//查询数量加一,为何要这样做请看:https://coding.net/u/wilddog/p/wilddog-gist-js/git/tree/master/src/pagination#user-content-yi-kao-shang--ye-de-zui-hou--tiao-ji-lu-huo-qu-xia--ye-shu-ju
if (key === 'limitToFirst') {
ref = ref.limitToFirst(parseInt(query[key])+1);
} else if (key === 'limitToLast') {
ref = ref.limitToLast(parseInt(query[key])+1);
} else {
ref = ref[key](query[key]);
}
} else {
ref = ref[key](query[key]); // ref[startAt]('3') --> ref.startAt('3')
}
}
});
return ref;
},
/**
* 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(typeClass) {
return !Ember.isNone(this._findAllMapForType[typeClass.modelName]);
},
/**
* After `.findAll()` is called on a modelName, continue to listen for
* `child_added`, `child_removed`, and `child_changed`
*/
_findAllAddEventListeners(store, typeClass, ref) {
var modelName = typeClass.modelName;
this._findAllMapForType[modelName] = true;
ref.on('child_added', Ember.run.bind(this, function (snapshot) {
if (!store.hasRecordForId(modelName, this._getKey(snapshot))) {
this._handleChildValue(store, typeClass, snapshot);
}
}));
},
/**
* Push a new child record into the store
*/
_handleChildValue(store, typeClass, snapshot) {
// No idea why we need this, we are already 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.peekRecord(typeClass.modelName, id);
// TODO: refactor using ED
if (!record.get('isDeleted')) {
record.deleteRecord();
}
} else {
var payload = this._assignIdToPayload(snapshot);
this._enqueue(function WildemberAdapter$enqueueStorePush() {
if (!store.isDestroying) {
var normalizedData = store.normalize(typeClass.modelName, payload);
store.push(normalizedData);
}
});
}
},
/**
* `createRecord` is an alias for `updateRecord` because calling \
* `ref.set()` would wipe out any existing relationships
*/
createRecord(store, typeClass, snapshot) {
return this.updateRecord(store, typeClass, snapshot).then(() => {
this.listenForChanges(store, typeClass, snapshot.record);
});
},
/**
* 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 Wilddog 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 Wilddog.
*
* We take an optional record reference, in order for this method to be usable
* for saving nested records as well.
*/
updateRecord(store, typeClass, snapshot) {
var recordRef = this._getAbsoluteRef(snapshot.record);
var recordCache = this._getRecordCache(typeClass, snapshot.id);
var pathPieces = recordRef.path.toString().split('/');
var lastPiece = pathPieces[pathPieces.length-1];
var serializedRecord = snapshot.serialize({
includeId: (lastPiece !== snapshot.id) // record has no wilddog `key` in path
});
const serializer = store.serializerFor(typeClass.modelName);
return new Promise((resolve, reject) => {
var relationshipsToSave = [];
// first we remove all relationships data from the serialized record, we backup the
// removed data so that we can save it at a later stage.
snapshot.record.eachRelationship((key, relationship) => {
const relationshipKey = serializer.keyForRelationship(key);
const data = serializedRecord[relationshipKey];
const isEmbedded = this.isRelationshipEmbedded(store, typeClass.modelName, relationship);
const hasMany = relationship.kind === 'hasMany';
if (hasMany || isEmbedded) {
if (!Ember.isNone(data)) {
relationshipsToSave.push({
data:data,
relationship:relationship,
isEmbedded:isEmbedded,
hasMany:hasMany
});
}
delete serializedRecord[relationshipKey];
}
});
var reportError = (errors) => {
var error = new Error(`Some errors were encountered while saving ${typeClass} ${snapshot.id}`);
error.errors = errors;
reject(error);
};
this._updateRecord(recordRef, serializedRecord).then(() => {
// and now we construct the list of promise to save relationships.
var savedRelationships = relationshipsToSave.map((relationshipToSave) => {
const data = relationshipToSave.data;
const relationship = relationshipToSave.relationship;
if (relationshipToSave.hasMany) {
return this._saveHasManyRelationship(store, typeClass, relationship, data, recordRef, recordCache);
} else {
// embedded belongsTo, we need to fill in the informations.
if (relationshipToSave.isEmbedded) {
return this._saveEmbeddedBelongsToRecord(store, typeClass, relationship, data, recordRef);
}
}
}
);
return Ember.RSVP.allSettled(savedRelationships);
}).catch((e) => {
reportError([e]);
}).then((results) => {
var rejected = Ember.A(results).filterBy('state', 'rejected');
if (rejected.length !== 0) {
reportError(rejected.mapBy('reason').toArray());
} else {
resolve();
}
});
}, `DS: WildemberAdapter#updateRecord ${typeClass} to ${recordRef.toString()}`);
},
/**
* Update a single record without caring for the relationships
* @param {Wilddog} recordRef
* @param {Object} serializedRecord
* @return {Promise}
*/
_updateRecord(recordRef, serializedRecord) {
return toPromise(recordRef.update, recordRef, [serializedRecord]);
},
/**
* Call _saveHasManyRelationshipRecord on each record in the relationship
* and then resolve once they have all settled
*/
_saveHasManyRelationship(store, typeClass, relationship, ids, recordRef, recordCache) {
if (!Ember.isArray(ids)) {
throw new Error('hasMany relationships must must be an array');
}
var idsCache = Ember.A(recordCache[relationship.key]);
var dirtyRecords = [];
// Added
var addedRecords = filter(ids, (id) => {
return !idsCache.includes(id);
});
// Dirty
dirtyRecords = filter(ids, (id) => {
var relatedModelName = relationship.type;
return store.hasRecordForId(relatedModelName, id) && store.peekRecord(relatedModelName, id).get('hasDirtyAttributes') === true;
});
dirtyRecords = map(uniq(dirtyRecords.concat(addedRecords)), (id) => {
return this._saveHasManyRecord(store, typeClass, relationship, recordRef, id);
});
// Removed
var removedRecords = filter(idsCache, (id) => {
return !includes(ids, id);
});
removedRecords = map(removedRecords, (id) => {
return this._removeHasManyRecord(store, recordRef, relationship.key, typeClass, id);
});
// Combine all the saved records
var savedRecords = dirtyRecords.concat(removedRecords);
// Wait for all the updates to finish
return Ember.RSVP.allSettled(savedRecords).then((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;
}
else {
var error = new Error(`Some errors were encountered while saving a hasMany relationship ${relationship.parentType} -> ${relationship.type}`);
error.errors = Ember.A(rejected).mapBy('reason');
throw error;
}
});
},
/**
* If the relationship is `async: true`, create a child ref
* named with the record id and set the value to true
* 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(store, typeClass, relationship, parentRef, id) {
const serializer = store.serializerFor(typeClass.modelName);
var ref = this._getRelationshipRef(parentRef, serializer.keyForRelationship(relationship.key), id);
var record = store.peekRecord(relationship.type, id);
var isEmbedded = this.isRelationshipEmbedded(store, typeClass.modelName, relationship);
if (isEmbedded) {
return record.save();
}
return toPromise(ref.set, ref, [true]);
},
/**
* Determine from the serializer if the relationship is embedded via the
* serializer's `attrs` hash.
*
* @return {Boolean} Is the relationship embedded?
*/
isRelationshipEmbedded(store, modelName, relationship) {
// var serializer = store.serializerFor(modelName);
// return serializer.hasDeserializeRecordsOption(relationship.key);
return this.hasDeserializeRecordsOption(relationship.key);
},
// checks config for attrs option to deserialize records
// a defined option object for a resource is treated the same as
// `deserialize: 'records'`
// 注意:从ember-data2.8版本开始没有这个方法!
hasDeserializeRecordsOption(attr) {
var alwaysEmbed = this.hasEmbeddedAlwaysOption(attr);
var option = this.attrsOption(attr);
return alwaysEmbed || (option && option.deserialize === 'records');
},
// checks config for attrs option to embedded (always) - serialize and deserialize
hasEmbeddedAlwaysOption(attr) {
var option = this.attrsOption(attr);
return option && option.embedded === 'always';
},
attrsOption(attr) {
var attrs = this.get('attrs');
return attrs && (attrs[camelize(attr)] || attrs[attr]);
},
/**
* Determine from if the record is embedded via implicit relationships.
*
* @return {Boolean} Is the relationship embedded?
*/
isRecordEmbedded(record) {
if (record._internalModel) {
record = record._internalModel;
}
var found = this.getFirstEmbeddingParent(record);
return !!found;
},
/**
* Remove a relationship
*/
_removeHasManyRecord(store, parentRef, key, typeClass, id) {
const relationshipKey = store.serializerFor(typeClass.modelName).keyForRelationship(key);
var ref = this._getRelationshipRef(parentRef, relationshipKey, id);
return toPromise(ref.remove, ref, [], ref.toString());
},
/**
* Save an embedded belongsTo record and set its internal wilddog ref
*
* @return {Promise<DS.Model>}
*/
_saveEmbeddedBelongsToRecord(store, typeClass, relationship, id, parentRef) {
var record = store.peekRecord(relationship.type, id);
if (record) {
return record.save();
}
return Ember.RSVP.Promise.reject(new Error(`Unable to find record with id ${id} from embedded relationship: ${JSON.stringify(relationship)}`));
},
/**
* Called by the store when a record is deleted.
*/
deleteRecord(store, typeClass, snapshot) {
var ref = this._getAbsoluteRef(snapshot.record);
ref.off('value');
return toPromise(ref.remove, ref);
},
/**
* Determines a path fo a given type
*/
pathForType(modelName) {
var camelized = Ember.String.camelize(modelName);
return Ember.String.pluralize(camelized);
},
/**
* Return a Wilddog reference for a given modelName and optional ID.
*/
_getCollectionRef(typeClass, id) {
var ref = this._ref;
if (typeClass) {
ref = ref.child(this.pathForType(typeClass.modelName));
}
if (id) {
ref = ref.child(id);
}
return ref;
},
/**
* Returns a Wilddog reference for a record taking into account if the record is embedded
*
* @param {DS.Model} record
* @return {Wilddog}
*/
_getAbsoluteRef(record) {
if (record._internalModel) {
record = record._internalModel;
}
var embeddingParent = this.getFirstEmbeddingParent(record);
if (embeddingParent) {
var { record: parent, relationship } = embeddingParent;
const embeddedKey = parent.store.serializerFor(parent.modelName).keyForRelationship(relationship.key);
var recordRef = this._getAbsoluteRef(parent).child(embeddedKey);
if (relationship.kind === 'hasMany') {
recordRef = recordRef.child(record.id);
}
return recordRef;
}
return this._getCollectionRef(record.type, record.id);
},
/**
* Returns the parent record and relationship where any embedding is detected
*
* @param {DS.InternalModel} internalModel
* @return {Object}
*/
getFirstEmbeddingParent(internalModel) {
var embeddingParentRel = find(internalModel._implicitRelationships, (implicitRel) => {
var members = implicitRel.members.toArray();
var parent = members[0];
if (!parent) {
return false;
}
var parentRel = parent._relationships.get(implicitRel.inverseKey);
return this.isRelationshipEmbedded(this.store, parent.type.modelName, parentRel.relationshipMeta);
});
if (embeddingParentRel) {
var parent = embeddingParentRel.members.toArray()[0];
var parentKey = embeddingParentRel.inverseKey;
var parentRel = parent._relationships.get(parentKey).relationshipMeta;
return { record: parent, relationship: parentRel };
}
},
/**
* Return a Wilddog reference based on a relationship key and record id
*/
_getRelationshipRef(ref, key, id) {
return ref.child(key).child(id);
},
/**
* The amount of time (ms) before the _queue is flushed
*/
_queueFlushDelay: (1000/60), // 60fps
/**
* Called after the first item is pushed into the _queue
*/
_queueScheduleFlush() {
Ember.run.later(this, this._queueFlush, this._queueFlushDelay);
},
/**
* Call each function in the _queue and the reset the _queue
*/
_queueFlush() {
forEach(this._queue, function WildemberAdapter$flushQueueItem(queueItem) {
var fn = queueItem[0];
var args = queueItem[1];
fn.apply(null, args);
});
this._queue.length = 0;
},
/**
* Push a new function into the _queue and then schedule a
* flush if the item is the first to be pushed
*/
_enqueue(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);
}
},
/**
* A cache of hasMany relationships that can be used to
* diff against new relationships when a model is saved
*/
_recordCacheForType: undefined,
/**
* _updateHasManyCacheForType
*/
_updateRecordCacheForType(typeClass, payload, store) {
if (!payload) { return; }
var id = payload.id;
var cache = this._getRecordCache(typeClass, id);
const serializer = store.serializerFor(typeClass.modelName);
// Only cache relationships for now
typeClass.eachRelationship((key, relationship) => {
if (relationship.kind === 'hasMany') {
var ids = payload[serializer.keyForRelationship(key)];
cache[key] = !Ember.isNone(ids) ? Ember.A(Object.keys(ids)) : Ember.A();
}
});
},
/**
* 缓存record,如果record不在缓存中则创建一个新的缓存record
*/
_getRecordCache(typeClass, id) {
var modelName = typeClass.modelName;
var cache = this._recordCacheForType;
cache[modelName] = cache[modelName] || {};
cache[modelName][id] = cache[modelName][id] || {};
return cache[modelName][id];
},
/**
* 获得当前路径下节点的名称。
* key() :https://docs.wilddog.com/api/sync/web/api.html#key
*/
_getKey(refOrSnapshot) {
var key;
if (typeof refOrSnapshot.key === 'function') {
key = refOrSnapshot.key();
} else if (typeof refOrSnapshot.key === 'string') {
key = refOrSnapshot.key;
} else {
key = refOrSnapshot.name();
}
return key;
},
/**
* We don't need background reloading, because wilddog!
*/
shouldBackgroundReloadRecord() {
return false;
}
});