blossom
Version:
Modern, Cross-Platform Application Framework
297 lines (277 loc) • 11.1 kB
JavaScript
// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2011 Strobe Inc. and contributors.
// Portions ©2008-2011 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/** @class
Provides support for having relationships propagate by
data provided by the data source.
This means the following interaction is now valid:
App = { store: SC.Store.create(SC.RelationshipSupport) };
App.Person = SC.Record.extend({
primaryKey: 'name',
name: SC.Record.attr(String),
power: SC.Record.toOne('App.Power', {
isMaster: false,
inverse: 'person'
})
});
App.Power = SC.Record.extend({
person: SC.Record.toOne('App.Person', {
isMaster: true,
inverse: 'power'
})
});
var zan = App.store.createRecord(App.Person, { name: 'Zan' }),
jayna = App.store.createRecord(App.Person, { name: 'Janya' });
// Wondertwins activate!
var glacier = App.store.loadRecords(App.Power, [{
guid: 'petunia', // Shape of...
person: 'Jayna'
}, {
guid: 'drizzle', // Form of...
person: 'Zan'
}]);
Leveraging this mixin requires your records to be unambiguously
defined. Note that this mixin does not take into account record
relationship created / destroyed on `dataSourceDidComplete`,
`writeAttribute`, etc. The only support here is for `pushRetrieve`,
`pushDestroy`, and `loadRecords` (under the hood, `loadRecord(s)` uses
`pushRetrieve`).
This mixin also supports lazily creating records when a related
record is pushed in from the store (but it doesn't exist).
This means that the previous example could have been simplified
to this:
App.Power = SC.Record.extend({
person: SC.Record.toOne('App.Person', {
isMaster: true,
lazilyInstantiate: true,
inverse: 'power'
})
});
// Wondertwins activate!
var glacier = App.store.loadRecords(App.Power, [{
guid: 'petunia', // Shape of...
person: 'Jayna'
}, {
guid: 'drizzle', // Form of...
person: 'Zan'
}]);
When the `loadRecords` call is done, there will be four unmaterialized
records in the store, giving the exact same result as the former
example.
As a side note, this mixin was developed primarily for use
in a real-time backend that provides data to SproutCore
as soon as it gets it (no transactions). This means streaming
APIs / protocols like the Twitter streaming API or XMPP (an IM
protocol) can be codified easier.
@since SproutCore 1.6
*/
// SC.RelationshipSupport = {
//
// /** @private
// Relinquish many records.
//
// This happens when a master record (`isMaster` = `true`) removes a reference
// to related records, either through `pushRetrieve` or `pushDestroy`.
// */
// _srs_inverseDidRelinquishRelationships: function (recordType, ids, attr, inverseId) {
// ids.forEach(function (id) {
// this._srs_inverseDidRelinquishRelationship(recordType, id, attr, inverseId);
// },this);
// },
//
// /** @private
// Relinquish the record, removing the reference of the record being
// destroyed on any related records.
// */
// _srs_inverseDidRelinquishRelationship: function (recordType, id, toAttr, relativeID) {
// var storeKey = recordType.storeKeyFor(id),
// dataHash = this.readDataHash(storeKey),
// key = toAttr.inverse,
// proto = recordType.prototype;
//
// if (!dataHash || !key) return;
//
// if (SC.instanceOf(proto[key], SC.SingleAttribute)) {
// delete dataHash[key];
// } else if (SC.instanceOf(proto[key], SC.ManyAttribute)
// && SC.typeOf(dataHash[key]) === SC.T_ARRAY) {
// dataHash[key].removeObject(relativeID);
// }
//
// this.pushRetrieve(recordType, id, dataHash, undefined, true);
// },
//
// /** @private
// Add a relationship to many inverse records.
//
// This happens when a master record (`isMaster` = `true`) adds a reference
// to another record on a `pushRetrieve`.
// */
// _srs_inverseDidAddRelationships: function (recordType, ids, attr, inverseId) {
// ids.forEach(function (id) {
// this._srs_inverseDidAddRelationship(recordType, id, attr, inverseId);
// },this);
// },
//
//
// /** @private
// Add a relationship to an inverse record.
//
// If the flag lazilyInstantiate is set to true, then the inverse record will be
// created lazily.
//
// @param {SC.Record} recordType The inverse record type.
// @param {String} id The id of the recordType to add.
// @param {SC.RecordAttribute} toAttr The record attribute that represents
// the relationship being created.
// @param {String} relativeID The ID of the model that needs to have it's
// relationship updated.
// */
// _srs_inverseDidAddRelationship: function (recordType, id, toAttr, relativeID) {
// var storeKey = recordType.storeKeyFor(id),
// dataHash = this.readDataHash(storeKey),
// status = this.peekStatus(storeKey),
// proto = recordType.prototype,
// key = toAttr.inverse,
// hashKey = proto[key],
// primaryAttr = proto[proto.primaryKey],
// shouldRecurse = false;
//
// // in case the SC.RecordAttribute defines a `key` field, we need to use that
// hashKey = (hashKey && hashKey.get && hashKey.get('key') || hashKey.key) || key;
//
// if ((status === SC.Record.EMPTY) &&
// (SC.typeOf(toAttr.lazilyInstantiate) === SC.T_FUNCTION && toAttr.lazilyInstantiate() ||
// SC.typeOf(toAttr.lazilyInstantiate) !== SC.T_FUNCTION && toAttr.lazilyInstantiate)) {
//
// if (!SC.none(primaryAttr) && primaryAttr.typeClass &&
// SC.typeOf(primaryAttr.typeClass()) === SC.T_CLASS) {
//
// // Recurse to create the record that this primaryKey points to iff it
// // also should be created if the record is empty.
// // Identifies chained relationships where the object up the chain
// // doesn't exist yet.
//
// // TODO: this can lead to an infinite recursion if the relationship
// // graph is cyclic
// shouldRecurse = true;
// }
// dataHash = {};
// dataHash[proto.primaryKey] = id;
// }
//
// if (!dataHash || !key) return;
//
// if (SC.instanceOf(proto[key], SC.SingleAttribute)) {
// dataHash[hashKey] = relativeID;
// } else if (SC.instanceOf(proto[key], SC.ManyAttribute)) {
// dataHash[hashKey] = dataHash[hashKey] || [];
//
// if (dataHash[hashKey].indexOf(relativeID) < 0) {
// dataHash[hashKey].push(relativeID);
// }
// }
//
// this.pushRetrieve(recordType, id, dataHash, undefined, !shouldRecurse);
// },
//
// // ..........................................................
// // ASYNCHRONOUS RECORD RELATIONSHIPS
// //
//
// /** @private
// Iterates over keys on the recordType prototype, looking for RecordAttributes
// that have relationships (toOne or toMany).
//
// @param {SC.Record} recordType The record type to do introspection on to see
// if it has any RecordAttributes that have relationships to other records.
// @param {String} id The id of the record being pushed in.
// @param {Number} storeKey The storeKey
// */
// _srs_pushIterator: function (recordType, id, storeKey, lambda) {
// var proto = recordType.prototype,
// attr, currentHash, key, inverseType;
//
// if (typeof storeKey === "undefined") {
// storeKey = recordType.storeKeyFor(id);
// }
//
// currentHash = this.readDataHash(storeKey) || {};
//
// for (key in proto) {
// attr = proto[key];
// if (attr && attr.typeClass && attr.inverse && attr.isMaster) {
// inverseType = attr.typeClass();
//
// if (SC.typeOf(inverseType) !== SC.T_CLASS) continue;
//
// lambda.apply(this, [inverseType, currentHash, attr,
// attr.get && attr.get('key') || key]);
// }
// }
// },
//
//
// /**
// Disassociate records that are related to the one being destroyed iff this
// record has `isMaster` set to `true`.
// */
// pushDestroy: function (original, recordType, id, storeKey) {
// var existingIDs;
//
// this._srs_pushIterator(recordType, id, storeKey,
// function (inverseType, currentHash, toAttr, keyValue) {
// // update old relationships
// existingIDs = [currentHash[keyValue] || null].flatten().compact().uniq();
// this._srs_inverseDidRelinquishRelationships(inverseType, existingIDs, toAttr, id);
// });
//
// return original(recordType, id, storeKey);
// }.enhance(),
//
// /**
// Associate records that are added via a pushRetrieve, and update subsequent
// relationships to ensure that the master-slave relationship is kept intact.
//
// For use cases, see the test for pushRelationships.
//
// The `ignore` argument is only set to true when adding the inverse
// relationship (to prevent recursion).
// */
// pushRetrieve: function (original, recordType, id, dataHash, storeKey, ignore) {
// // avoid infinite recursions when additional changes are propogated
// // from `_srs_inverseDidAddRelationship`
// if (!ignore) {
// var existingIDs, inverseIDs;
//
// this._srs_pushIterator(recordType, id, storeKey,
// /** @ignore
// @param {SC.Record} inverseType - in a Master-Slave
// relationship when pushing master, Slave is the inverse type
// @param {Object} currentHash - the hash in the data store (data to be replaced by `dataHash`)
// @param {SC.RecordAttribute} toAttr - key in `recordType.prototype` that names isMaster and has an inverse
// @param {String} keyValue - the property name in the datahash that defines this foreign key relationship
// */
// function (inverseType, currentHash, toAttr, keyValue) {
// // update old relationships
// existingIDs = [currentHash[keyValue] || null].flatten().compact().uniq();
//
// // update new relationships
// inverseIDs = [dataHash[keyValue] || null].flatten().compact().uniq();
//
// this._srs_inverseDidRelinquishRelationships(inverseType, existingIDs.filter(
// function (el) {
// return inverseIDs.indexOf(el) === -1;
// }), toAttr, id);
//
// this._srs_inverseDidAddRelationships(inverseType, inverseIDs, toAttr, id);
// });
// }
//
// storeKey = storeKey || recordType.storeKeyFor(id);
// return original(recordType, id, dataHash, storeKey);
// }.enhance()
// };