/** * **This class is never created directly. It should be constructed through associations * in `Ext.data.Model`.** * * Associations enable you to express relationships between different {@link Ext.data.Model Models}. * Consider an ecommerce system where Users can place Orders - there is a one to many relationship * between these Models, one user can have many orders (including 0 orders). Here is what a sample * implementation of this association could look like. This example will be referred to in the * following sections. * * Ext.define('User', { * extend: 'Ext.data.Model', * fields: [{ * name: 'id', * type: 'int' * }, 'name'] * }); * * Ext.define('Order', { * extend: 'Ext.data.Model', * fields: [{ * name: 'id', * type: 'int' * }, { * name: 'userId', * type: 'int', * reference: 'User' * }] * }); * * # Association Types * * Assocations can describe relationships in 3 ways: * * ## Many To One * * A single entity (`A`) has a relationship with many (`B`) entities. An example of this is * an ecommerce system `User` can have many `Order` entities. * * This can be defined using `Ext.data.schema.ManyToOne` for keyed associations, or * `Ext.data.schema.HasMany` for keyless associations. * * ## One To One * * A less common form of Many To One, a single entity (`A`) has a relationship with at most 1 entity * (`B`). This is often used when partitioning data. For example a `User` may have a single * `UserInfo` object that stores extra metadata about the user. * * This can be defined using `Ext.data.schema.OneToOne` for keyed associations, or * `Ext.data.schema.HasOne` for keyless associations. * * ## Many To Many * * An entity (`A`) may have a have a relationship with many (`B`) entities. That (`B`) entity may * also have a relationship with many `A` entities. For example a single `Student` can have many * `Subject` entities and a single `Subject` can have many `Student` entities. * * This can be defined using `Ext.data.schema.ManyToMany`. Many To Many relationships are read-only * unless used with a `Ext.data.Session`. * * * # Keyed vs Keyless Associations * * Associations can be declared in 2 ways, which are outlined below. * * ## Keyed associations * * A keyed association relies on a field in the model matching the id of another model. Membership * is driven by the key. This is the type of relationship that is typically used in a relational * database. This is declared using the ||reference|| configuration on a model field. An example * of this can be seen above for `User/Order`. * * # Keyless associations * * A keyless association relies on data hierarchy to determine membership. Items are members because * they are contained by another entity. This type of relationship is common with NoSQL databases. * formats. A simple example definition using `User/Order`: * * Ext.define('User', { * extend: 'Ext.data.Model', * fields: [{ * name: 'id', * type: 'int' * }, 'name'], * hasMany: 'Order' * }); * * Ext.define('Order', { * extend: 'Ext.data.Model', * fields: [{ * name: 'id', * type: 'int' * }] * }); * * # Advantages of Associations * * Assocations make it easier to work with Models that share a connection. Some of the main * functionality includes: * * ## Generated Accessors/Setters * * Associated models will automatically generate named methods that allow for accessing the * associated data. The names for these are created using a {@link Ext.data.schema.Schema Schema}, * to provide a consistent and predictable naming structure. * * Using the example code above, there will be 3 generated methods: * + `User` will have an `orders()` function that returns a `Ext.data.Store` of`Orders`. * + `Order` will have a `getUser` method which will return a `User` Model. * + `Order` will have a `setUser` method that will accept a `User` model or a key value. * * ## Nested Loading * * Nested loading is the ability to load hierarchical associated data from a remote source within * a single request. In the following example, each `User` in the `users` store has an `orders` * store. Each `orders` store is populated with `Order` models read from the request. Each `Order` * model also has a reference back to the appropriate `User`. * * // Sample JSON data returned by /Users * [{ * "id": 1, * "name": "User Foo", * "orders": [{ * "id": 101, * "userId": 1 * }, { * "id": 102, * "userId": 1 * }, { * "id": 103, * "userId": 1 * }] * }, { * "id": 2, * "name": "User Bar", * "orders": [{ * "id": 201, * "userId": 2 * }, { * "id": 202, * "userId": 2 * }] * }] * * // Application code * var users = new Ext.data.Store({ * model: 'User', * proxy: { * type: 'ajax', * url: '/Users' * } * }); * users.load(function() { * var user1 = users.first(), * user2 = users.last(), * orders1 = user1.orders(), * orders2 = user2.orders(); * * // 3 orders, same reference back to user1 * console.log(orders1.getCount(), orders1.first().getUser() === user1); * // 2 orders, same reference back to user2 * console.log(orders2.getCount(), orders2.first().getUser() === user2); * }); * * ## Binding * * Data binding using {@link Ext.app.ViewModel ViewModels} have functionality to be able * to recognize associated data as part of a bind statement. For example: * + `{user.orders}` binds to the orders store for a user. * + `{order.user.name}` binds to the name of the user taken from the order. * * * # Association Concepts * * ## Roles * * The role is used to determine generated names for an association. By default, the role is * generated from either the field name (in a keyed association) or the model name. This naming * follows a pattern defined by the `Ext.data.schema.Namer`. To change a specific instance, * an explicit role can be specified: * * Ext.define('Thread', { * extend: 'Ext.data.Model', * fields: ['id', 'title'] * }); * * Ext.define('Post', { * extend: 'Ext.data.Model', * fields: ['id', 'content', { * name: 'threadId', * reference: { * type: 'Thread', * role: 'discussion', * inverse: 'comments' * * } * }] * }); * * In the above example, the `Thread` will be decorated with a `comments` method that returns * the store. The `Post` will be decorated with `getDiscussion/setDiscussion` methods. * * ## Generated Methods * * Associations generate methods to allow reading and manipulation on associated data. * * On records that have a "to many" relationship, a single methods that returns a `Ext.data.Store` * is created. See {@link #storeGetter}. On records that have a "to one" relationship, 2 methods * are generated, a {@link #recordGetter getter} and a {@link #recordSetter setter}. * * ## Reflexive * * Associations are reflexive. By declaring one "side" of the relationship, the other is * automatically setup. In the example below, there is no code in the `Thread` entity regarding * the association, however by virtue of the declaration in post, `Thread` is decorated with the * appropriate infrastructure to participate in the association. * * Ext.define('Thread', { * extend: 'Ext.data.Model', * fields: ['id', 'title'] * }); * * Ext.define('Post', { * extend: 'Ext.data.Model', * fields: ['id', 'content', { * name: 'threadId', * reference: 'Thread' * }] * }); * * ## Naming * * Referring to model names in associations depends on their {@link Ext.data.Model#entityName}. See * the "Relative Naming" section in the `Ext.data.schema.Schema` documentation. */Ext.define('Ext.data.schema.Association', { requires: [ 'Ext.data.schema.Role' ], isOneToOne: false, isManyToOne: false, isManyToMany: false, /** * @cfg {String} associationKey * The name of the property in the data to read the association from. Defaults to the * name of the associated model. */ /** * @method storeGetter * **This is not a real method, it is placeholder documentation for a generated method on * a `Ext.data.Model`.** * * Gets a store configured with the model of the "many" record. * @param {Object/Function} [options] The options for the getter, or a callback function * to execute. If specified as a function, it will act as the `callback` option. * * @param {Boolean} [options.reload] `true` to force the store to reload from the server. * * @param {Object} [options.scope] The `this` reference for the callback. * Defaults to the record. * * @param {Function} [options.success] A function to execute when the store loads successfully. * If the store has already loaded, this will be called immediately and the `Operation` will be * `null`. The success is passed the following parameters: * @param {Ext.data.Store} [options.success.store] The store. * @param {Ext.data.operation.Operation} [options.success.operation] The operation. `null` * if no load occurred. * * @param {Function} [options.failure] A function to execute when the store load fails. * If the store has already loaded, this will not be called. * The failure is passed the following parameters: * @param {Ext.data.Store} [options.failure.store] The store. * @param {Ext.data.operation.Operation} [options.failure.operation] The operation * * @param {Function} [options.callback] A function to execute when the store loads, whether * it is successful or failed. If the store has already loaded, this will be called immediately * and the `Operation` will be `null`. The callback is passed the following parameters: * @param {Ext.data.Store} [options.callback.store] The store. * @param {Ext.data.operation.Operation} [options.callback.operation] The operation. `null` * if no load occurred. * @param {Boolean} [options.callback.success] `true` if the load was successful. If already * loaded this will always be true. * * @param {Object} [scope] The `this` reference for the callback. Defaults to the record. * * @return {Ext.data.Store} The store. */ /** * @method recordGetter * **This is not a real method, it is placeholder documentation for a generated method on * a `Ext.data.Model`.** * * Gets a model of the "one" type. * @param {Object/Function} [options] The options for the getter, or a callback function * to execute. If specified as a function, it will act as the `callback` option. * * @param {Boolean} [options.reload] `true` to force the record to reload from the server. * * @param {Object} [options.scope] The `this` reference for the callback. * Defaults to the record. * * @param {Function} [options.success] A function to execute when the record loads successfully. * If the record has already loaded, this will be called immediately and the `Operation` will be * `null`. The success is passed the following parameters: * @param {Ext.data.Model} [options.success.record] The record. * @param {Ext.data.operation.Operation} [options.success.operation] The operation. `null` * if no load occurred. * * @param {Function} [options.failure] A function to execute when the record load fails. * If the record has already loaded, this will not be called. * The failure is passed the following parameters: * @param {Ext.data.Model} [options.failure.record] The record. * @param {Ext.data.operation.Operation} [options.failure.operation] The operation * * @param {Function} [options.callback] A function to execute when the record loads, whether * it is successful or failed. If the record has already loaded, this will be called immediately * and the `Operation` will be `null`. The callback is passed the following parameters: * @param {Ext.data.Model} [options.callback.record] The record. * @param {Ext.data.operation.Operation} [options.callback.operation] The operation. `null` * if no load occurred. * @param {Boolean} [options.callback.success] `true` if the load was successful. If already * loaded this will always be true. * * @param {Object} [scope] The `this` reference for the callback. Defaults to the record. * @return {Ext.data.Model} The record. `null` if the reference has been previously specified * as empty. */ /** * @method recordSetter **This is not a real method, it is placeholder documentation * for a generated method on a `Ext.data.Model`.** * * Sets a model of the "one" type. * @param {Ext.data.Model/Object} value The value to set. This can be a model instance, * a key value (if a keyed association) or `null` to clear the value. * * @param {Object/Function} [options] Options to handle callback. If specified as * a function, it will act as the `callback` option. If specified as an object, the params * are the same as {@link Ext.data.Model#save}. If options is specified, * {@link Ext.data.Model#save} will be called on this record. */ /** * @cfg {String} name * The name of this association. */ /** * @property {Object} owner * Points at either `left` or `right` objects if one is the owning party in this * association or is `null` if there is no owner. * @readonly */ owner: null, /** * @property {Ext.Class} definedBy * @readonly */ /** * @property {Ext.data.field.Field} field * @readonly */ field: null, /** * @property {Ext.data.schema.Schema} schema * @readonly */ /** * @property {Boolean} nullable * @readonly */ /** * @property {Ext.data.schema.Role} left * @readonly */ /** * @property {Ext.data.schema.Role} right * @readonly */ constructor: function(config) { var me = this, left, right; Ext.apply(me, config); me.left = left = new me.Left(me, me.left); me.right = right = new me.Right(me, me.right); left.inverse = right; right.inverse = left; }, hasField: function() { return !!this.field; }, getFieldName: function() { var field = this.field; return field ? field.name : ''; }});