/** * **This class is never created directly. It should be constructed through associations * in `Ext.data.Model`.** * * This is a specialized version of `Ext.data.schema.ManyToOne` that declares a relationship between * a single entity type and a single related entities. The relationship can be declared as a keyed * or keyless relationship. * * // Keyed * Ext.define('User', { * extend: 'Ext.data.Model', * fields: ['id', 'name', { * name: 'userInfoId', * reference: { * type: 'UserInfo', * unique: true * } * }] * }); * * Ext.define('UserInfo', { * extend: 'Ext.data.Model', * fields: ['id', 'secretKey'] * }); * * // Keyless * Ext.define('User', { * extend: 'Ext.data.Model', * fields: ['id', 'name'], * hasOne: 'UserInfo' * }); * * Ext.define('Ticket', { * extend: 'Ext.data.Model', * fields: ['id', 'secretKey'] * }); * * // Generated methods * var user = new User(); * user.getUserInfo(); * user.setUserInfo(); * * var info = new UserInfo(); * info.getUser(); * info.setUser(); * * * var ticket = new Ticket(); * ticket.setCustomer(customer); * console.log(ticket.getCustomer()); // The customer object * * By declaring a keyed relationship, extra functionality is gained that maintains * the key field in the model as changes are made to the association. * * For available configuration options, see {@link Ext.data.schema.Reference}. * Each record type will have a {@link Ext.data.schema.Association#recordGetter getter} and * {@link Ext.data.schema.Association#recordSetter setter}. */Ext.define('Ext.data.schema.OneToOne', { extend: 'Ext.data.schema.Association', isOneToOne: true, isToOne: true, kind: 'one-to-one', Left: Ext.define(null, { extend: 'Ext.data.schema.Role', onDrop: function(rightRecord, session) { var leftRecord = this.getAssociatedItem(rightRecord); rightRecord[this.getInstanceName()] = null; if (leftRecord) { leftRecord[this.inverse.getInstanceName()] = null; } }, onIdChanged: function(rightRecord, oldId, newId) { var leftRecord = this.getAssociatedItem(rightRecord), fieldName = this.association.getFieldName(); if (!rightRecord.session && leftRecord && fieldName) { leftRecord.set(fieldName, newId); } }, createGetter: function() { var me = this; return function() { // 'this' refers to the Model instance inside this function return me.doGet(this); }; }, createSetter: function() { var me = this; return function(value) { // 'this' refers to the Model instance inside this function return me.doSet(this, value); }; }, doGet: function(rightRecord) { // Consider the Department entity with a managerId to a User entity. The // Department is on the left (the FK holder's side) so we are implementing the // guts of the getManagerDepartment method we place on the User entity. Since // we represent the "managerDepartment" role and as such our goal is to get a // Department instance, we start that from the User (rightRecord). Sadly that // record has no FK back to us. var instanceName = this.getInstanceName(), // ex "managerDepartment" ret = rightRecord[instanceName], session = rightRecord.session; if (!ret && session) { // @TODO: session - we'll cache the result on the record as always // but to get it we must ask the session } return ret || null; }, doSet: function(rightRecord, leftRecord) { // We are the guts of the setManagerDepartment method we place on the User // entity. Our goal here is to establish the relationship between the new // Department (leftRecord) and the User (rightRecord). var instanceName = this.getInstanceName(), // ex "managerDepartment" ret = rightRecord[instanceName], inverseSetter = this.inverse.setterName; // setManager for Department if (ret !== leftRecord) { rightRecord[instanceName] = leftRecord; if (inverseSetter) { // Because the FK is owned by the inverse record, we delegate the // majority of work to its setter. We've already locked in the only // thing we keep on this side so we won't recurse back-and-forth. leftRecord[inverseSetter](rightRecord); } rightRecord.onAssociatedRecordSet(leftRecord, this); } return ret; }, read: function(rightRecord, node, fromReader, readOptions) { var me = this, leftRecords = me.callParent([rightRecord, node, fromReader, readOptions]), leftRecord; if (leftRecords) { leftRecord = leftRecords[0]; if (leftRecord) { leftRecord[me.inverse.getInstanceName()] = rightRecord; rightRecord[me.getInstanceName()] = leftRecord; // Inline associations should *not* arrive on the "data" object: delete rightRecord.data[me.role]; } } } }), Right: Ext.define(null, { extend: 'Ext.data.schema.Role', left: false, side: 'right', createGetter: function() { // As the target of the FK (say "manager" for the Department entity) this // getter is responsible for getting the entity referenced by the FK value. var me = this; return function(options, scope) { // 'this' refers to the Model instance inside this function return me.doGetFK(this, options, scope); }; }, createSetter: function() { var me = this; return function(value, options, scope) { // 'this' refers to the Model instance inside this function return me.doSetFK(this, value, options, scope); }; }, onDrop: function(leftRecord, session) { var me = this, field = me.association.field, rightRecord = me.getAssociatedItem(leftRecord), id; if (me.inverse.owner) { if (session && field) { id = leftRecord.get(field.name); if (id || id === 0) { rightRecord = session.getEntry(me.cls, id).record; if (rightRecord) { rightRecord.drop(); } } } else { if (rightRecord) { rightRecord.drop(); } } } if (field) { leftRecord.set(field.name, null); } leftRecord[me.getInstanceName()] = null; if (rightRecord) { rightRecord[me.inverse.getInstanceName()] = null; } }, onValueChange: function(leftRecord, session, newValue) { // Important to get the record before changing the key. var me = this, rightRecord = leftRecord[me.getOldInstanceName()] || me.getAssociatedItem(leftRecord), // eslint-disable-line max-len hasNewValue = newValue || newValue === 0, instanceName = me.getInstanceName(), cls = me.cls; leftRecord.changingKey = true; me.doSetFK(leftRecord, newValue); if (!hasNewValue) { leftRecord[instanceName] = null; } else if (session && cls) { // Setting to undefined is important so that we can load the record later. leftRecord[instanceName] = session.peekRecord(cls, newValue) || undefined; } if (me.inverse.owner && rightRecord) { me.association.schema.queueKeyCheck(rightRecord, me); } leftRecord.changingKey = false; }, checkKeyForDrop: function(rightRecord) { var leftRecord = this.inverse.getAssociatedItem(rightRecord); if (!leftRecord) { // Not reassigned to another parent rightRecord.drop(); } }, read: function(leftRecord, node, fromReader, readOptions) { var me = this, rightRecords = me.callParent([leftRecord, node, fromReader, readOptions]), rightRecord, field, fieldName, session, refs, id, oldId, setKey, data; if (rightRecords) { rightRecord = rightRecords[0]; field = me.association.field; if (field) { fieldName = field.name; } session = leftRecord.session; data = leftRecord.data; if (rightRecord) { if (session) { refs = session.getRefs(rightRecord, this.inverse, true); // If we have an existing reference in the session, or we don't and the data // is undefined, allow the nested load to go ahead setKey = (refs && refs[leftRecord.id]) || (data[fieldName] === undefined); } else { setKey = true; } if (setKey) { // We want to poke the inferred key onto record if it exists, but we don't // want to mess with the dirty or modified state of the record. if (field) { oldId = data[fieldName]; id = rightRecord.id; if (oldId !== id) { data[fieldName] = id; if (session) { session.updateReference(leftRecord, field, id, oldId); } } } rightRecord[me.inverse.getInstanceName()] = leftRecord; leftRecord[me.getInstanceName()] = rightRecord; } // Inline associations should *not* arrive on the "data" object: delete data[me.role]; } } } })});