/**
* **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 : '';
}
});