X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/6a7e4474cba9d8be4b2ec445e10f1691f7277c50..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/docs/source/Model.html diff --git a/docs/source/Model.html b/docs/source/Model.html new file mode 100644 index 00000000..596a172b --- /dev/null +++ b/docs/source/Model.html @@ -0,0 +1,1184 @@ +Sencha Documentation Project
/**
+ * @author Ed Spencer
+ * @class Ext.data.Model
+ *
+ * <p>A Model represents some object that your application manages. For example, one might define a Model for Users, Products,
+ * Cars, or any other real-world object that we want to model in the system. Models are registered via the {@link Ext.ModelManager model manager},
+ * and are used by {@link Ext.data.Store stores}, which are in turn used by many of the data-bound components in Ext.</p>
+ *
+ * <p>Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:</p>
+ *
+<pre><code>
+Ext.define('User', {
+    extend: 'Ext.data.Model',
+    fields: [
+        {name: 'name',  type: 'string'},
+        {name: 'age',   type: 'int'},
+        {name: 'phone', type: 'string'},
+        {name: 'alive', type: 'boolean', defaultValue: true}
+    ],
+
+    changeName: function() {
+        var oldName = this.get('name'),
+            newName = oldName + " The Barbarian";
+
+        this.set('name', newName);
+    }
+});
+</code></pre>
+*
+* <p>The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link Ext.ModelManager ModelManager}, and all
+* other functions and properties are copied to the new Model's prototype.</p>
+*
+* <p>Now we can create instances of our User model and call any model logic we defined:</p>
+*
+<pre><code>
+var user = Ext.ModelManager.create({
+    name : 'Conan',
+    age  : 24,
+    phone: '555-555-5555'
+}, 'User');
+
+user.changeName();
+user.get('name'); //returns "Conan The Barbarian"
+</code></pre>
+ *
+ * <p><u>Validations</u></p>
+ *
+ * <p>Models have built-in support for validations, which are executed against the validator functions in
+ * {@link Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to models:</p>
+ *
+<pre><code>
+Ext.define('User', {
+    extend: 'Ext.data.Model',
+    fields: [
+        {name: 'name',     type: 'string'},
+        {name: 'age',      type: 'int'},
+        {name: 'phone',    type: 'string'},
+        {name: 'gender',   type: 'string'},
+        {name: 'username', type: 'string'},
+        {name: 'alive',    type: 'boolean', defaultValue: true}
+    ],
+
+    validations: [
+        {type: 'presence',  field: 'age'},
+        {type: 'length',    field: 'name',     min: 2},
+        {type: 'inclusion', field: 'gender',   list: ['Male', 'Female']},
+        {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
+        {type: 'format',    field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
+    ]
+});
+</code></pre>
+ *
+ * <p>The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
+ * object:</p>
+ *
+<pre><code>
+var instance = Ext.ModelManager.create({
+    name: 'Ed',
+    gender: 'Male',
+    username: 'edspencer'
+}, 'User');
+
+var errors = instance.validate();
+</code></pre>
+ *
+ * <p><u>Associations</u></p>
+ *
+ * <p>Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and
+ * {@link Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
+ * application which deals with Users, Posts and Comments. We can express the relationships between these models like this:</p>
+ *
+<pre><code>
+Ext.define('Post', {
+    extend: 'Ext.data.Model',
+    fields: ['id', 'user_id'],
+
+    belongsTo: 'User',
+    hasMany  : {model: 'Comment', name: 'comments'}
+});
+
+Ext.define('Comment', {
+    extend: 'Ext.data.Model',
+    fields: ['id', 'user_id', 'post_id'],
+
+    belongsTo: 'Post'
+});
+
+Ext.define('User', {
+    extend: 'Ext.data.Model',
+    fields: ['id'],
+
+    hasMany: [
+        'Post',
+        {model: 'Comment', name: 'comments'}
+    ]
+});
+</code></pre>
+ *
+ * <p>See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the usage
+ * and configuration of associations. Note that associations can also be specified like this:</p>
+ *
+<pre><code>
+Ext.define('User', {
+    extend: 'Ext.data.Model',
+    fields: ['id'],
+
+    associations: [
+        {type: 'hasMany', model: 'Post',    name: 'posts'},
+        {type: 'hasMany', model: 'Comment', name: 'comments'}
+    ]
+});
+</code></pre>
+ *
+ * <p><u>Using a Proxy</u></p>
+ *
+ * <p>Models are great for representing types of data and relationships, but sooner or later we're going to want to
+ * load or save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy},
+ * which can be set directly on the Model:</p>
+ *
+<pre><code>
+Ext.define('User', {
+    extend: 'Ext.data.Model',
+    fields: ['id', 'name', 'email'],
+
+    proxy: {
+        type: 'rest',
+        url : '/users'
+    }
+});
+</code></pre>
+ *
+ * <p>Here we've set up a {@link Ext.data.proxy.Rest Rest Proxy}, which knows how to load and save data to and from a
+ * RESTful backend. Let's see how this works:</p>
+ *
+<pre><code>
+var user = Ext.ModelManager.create({name: 'Ed Spencer', email: 'ed@sencha.com'}, 'User');
+
+user.save(); //POST /users
+</code></pre>
+ *
+ * <p>Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this
+ * Model's data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't
+ * have an id, and performs the appropriate action - in this case issuing a POST request to the url we configured
+ * (/users). We configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full
+ * list.</p>
+ *
+ * <p>Loading data via the Proxy is equally easy:</p>
+ *
+<pre><code>
+//get a reference to the User model class
+var User = Ext.ModelManager.getModel('User');
+
+//Uses the configured RestProxy to make a GET request to /users/123
+User.load(123, {
+    success: function(user) {
+        console.log(user.getId()); //logs 123
+    }
+});
+</code></pre>
+ *
+ * <p>Models can also be updated and destroyed easily:</p>
+ *
+<pre><code>
+//the user Model we loaded in the last snippet:
+user.set('name', 'Edward Spencer');
+
+//tells the Proxy to save the Model. In this case it will perform a PUT request to /users/123 as this Model already has an id
+user.save({
+    success: function() {
+        console.log('The User was updated');
+    }
+});
+
+//tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
+user.destroy({
+    success: function() {
+        console.log('The User was destroyed!');
+    }
+});
+</code></pre>
+ *
+ * <p><u>Usage in Stores</u></p>
+ *
+ * <p>It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this
+ * by creating a {@link Ext.data.Store Store}:</p>
+ *
+<pre><code>
+var store = new Ext.data.Store({
+    model: 'User'
+});
+
+//uses the Proxy we set up on Model to load the Store data
+store.load();
+</code></pre>
+ *
+ * <p>A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain
+ * a set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the
+ * {@link Ext.data.Store Store docs} for more information on Stores.</p>
+ *
+ * @constructor
+ * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
+ * @param {Number} id Optional unique ID to assign to this model instance
+ */
+Ext.define('Ext.data.Model', {
+    alternateClassName: 'Ext.data.Record',
+    
+    mixins: {
+        observable: 'Ext.util.Observable'
+    },
+
+    requires: [
+        'Ext.ModelManager',
+        'Ext.data.Field',
+        'Ext.data.Errors',
+        'Ext.data.Operation',
+        'Ext.data.validations',
+        'Ext.data.proxy.Ajax',
+        'Ext.util.MixedCollection'
+    ],
+
+    onClassExtended: function(cls, data) {
+        var onBeforeClassCreated = data.onBeforeClassCreated;
+
+        data.onBeforeClassCreated = function(cls, data) {
+            var me = this,
+                name = Ext.getClassName(cls),
+                prototype = cls.prototype,
+                superCls = cls.prototype.superclass,
+
+                validations = data.validations || [],
+                fields = data.fields || [],
+                associations = data.associations || [],
+                belongsTo = data.belongsTo,
+                hasMany = data.hasMany,
+
+                fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
+                    return field.name;
+                }),
+
+                associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
+                    return association.name;
+                }),
+
+                superValidations = superCls.validations,
+                superFields = superCls.fields,
+                superAssociations = superCls.associations,
+
+                association, i, ln,
+                dependencies = [];
+
+            // Save modelName on class and its prototype
+            cls.modelName = name;
+            prototype.modelName = name;
+
+            // Merge the validations of the superclass and the new subclass
+            if (superValidations) {
+                validations = superValidations.concat(validations);
+            }
+
+            data.validations = validations;
+
+            // Merge the fields of the superclass and the new subclass
+            if (superFields) {
+                fields = superFields.items.concat(fields);
+            }
+
+            for (i = 0, ln = fields.length; i < ln; ++i) {
+                fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
+            }
+
+            data.fields = fieldsMixedCollection;
+
+            //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
+            //we support that here
+            if (belongsTo) {
+                belongsTo = Ext.Array.from(belongsTo);
+
+                for (i = 0, ln = belongsTo.length; i < ln; ++i) {
+                    association = belongsTo[i];
+
+                    if (!Ext.isObject(association)) {
+                        association = {model: association};
+                    }
+
+                    association.type = 'belongsTo';
+                    associations.push(association);
+                }
+
+                delete data.belongsTo;
+            }
+
+            if (hasMany) {
+                hasMany = Ext.Array.from(hasMany);
+                for (i = 0, ln = hasMany.length; i < ln; ++i) {
+                    association = hasMany[i];
+
+                    if (!Ext.isObject(association)) {
+                        association = {model: association};
+                    }
+
+                    association.type = 'hasMany';
+                    associations.push(association);
+                }
+
+                delete data.hasMany;
+            }
+
+            if (superAssociations) {
+                associations = superAssociations.items.concat(associations);
+            }
+
+            for (i = 0, ln = associations.length; i < ln; ++i) {
+                dependencies.push('association.' + associations[i].type.toLowerCase());
+            }
+
+            if (data.proxy) {
+                if (typeof data.proxy === 'string') {
+                    dependencies.push('proxy.' + data.proxy);
+                }
+                else if (typeof data.proxy.type === 'string') {
+                    dependencies.push('proxy.' + data.proxy.type);
+                }
+            }
+
+            Ext.require(dependencies, function() {
+                Ext.ModelManager.registerType(name, cls);
+
+                for (i = 0, ln = associations.length; i < ln; ++i) {
+                    association = associations[i];
+
+                    Ext.apply(association, {
+                        ownerModel: name,
+                        associatedModel: association.model
+                    });
+
+                    if (Ext.ModelManager.getModel(association.model) === undefined) {
+                        Ext.ModelManager.registerDeferredAssociation(association);
+                    } else {
+                        associationsMixedCollection.add(Ext.data.Association.create(association));
+                    }
+                }
+
+                data.associations = associationsMixedCollection;
+
+                onBeforeClassCreated.call(me, cls, data);
+
+                cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
+
+                // Fire the onModelDefined template method on ModelManager
+                Ext.ModelManager.onModelDefined(cls);
+            });
+        }
+    },
+
+    inheritableStatics: {
+        /**
+         * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias}
+         * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
+         * @static
+         */
+        setProxy: function(proxy) {
+            //make sure we have an Ext.data.proxy.Proxy object
+            if (!proxy.isProxy) {
+                if (typeof proxy == "string") {
+                    proxy = {
+                        type: proxy
+                    };
+                }
+                proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
+            }
+            proxy.setModel(this);
+            this.proxy = this.prototype.proxy = proxy;
+
+            return proxy;
+        },
+
+        /**
+         * Returns the configured Proxy for this Model
+         * @return {Ext.data.proxy.Proxy} The proxy
+         */
+        getProxy: function() {
+            return this.proxy;
+        },
+
+        /**
+         * <b>Static</b>. Asynchronously loads a model instance by id. Sample usage:
+    <pre><code>
+    MyApp.User = Ext.define('User', {
+        extend: 'Ext.data.Model',
+        fields: [
+            {name: 'id', type: 'int'},
+            {name: 'name', type: 'string'}
+        ]
+    });
+
+    MyApp.User.load(10, {
+        scope: this,
+        failure: function(record, operation) {
+            //do something if the load failed
+        },
+        success: function(record, operation) {
+            //do something if the load succeeded
+        },
+        callback: function(record, operation) {
+            //do something whether the load succeeded or failed
+        }
+    });
+    </code></pre>
+         * @param {Number} id The id of the model to load
+         * @param {Object} config Optional config object containing success, failure and callback functions, plus optional scope
+         * @member Ext.data.Model
+         * @method load
+         * @static
+         */
+        load: function(id, config) {
+            config = Ext.apply({}, config);
+            config = Ext.applyIf(config, {
+                action: 'read',
+                id    : id
+            });
+
+            var operation  = Ext.create('Ext.data.Operation', config),
+                scope      = config.scope || this,
+                record     = null,
+                callback;
+
+            callback = function(operation) {
+                if (operation.wasSuccessful()) {
+                    record = operation.getRecords()[0];
+                    Ext.callback(config.success, scope, [record, operation]);
+                } else {
+                    Ext.callback(config.failure, scope, [record, operation]);
+                }
+                Ext.callback(config.callback, scope, [record, operation]);
+            };
+
+            this.proxy.read(operation, callback, this);
+        }
+    },
+
+    statics: {
+        PREFIX : 'ext-record',
+        AUTO_ID: 1,
+        EDIT   : 'edit',
+        REJECT : 'reject',
+        COMMIT : 'commit',
+
+        /**
+         * Generates a sequential id. This method is typically called when a record is {@link #create}d
+         * and {@link #Record no id has been specified}. The id will automatically be assigned
+         * to the record. The returned id takes the form:
+         * <tt>&#123;PREFIX}-&#123;AUTO_ID}</tt>.<div class="mdetail-params"><ul>
+         * <li><b><tt>PREFIX</tt></b> : String<p class="sub-desc"><tt>Ext.data.Model.PREFIX</tt>
+         * (defaults to <tt>'ext-record'</tt>)</p></li>
+         * <li><b><tt>AUTO_ID</tt></b> : String<p class="sub-desc"><tt>Ext.data.Model.AUTO_ID</tt>
+         * (defaults to <tt>1</tt> initially)</p></li>
+         * </ul></div>
+         * @param {Ext.data.Model} rec The record being created.  The record does not exist, it's a {@link #phantom}.
+         * @return {String} auto-generated string id, <tt>"ext-record-i++'</tt>;
+         * @static
+         */
+        id: function(rec) {
+            var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
+            rec.phantom = true;
+            rec.internalId = id;
+            return id;
+        }
+    },
+    
+    /**
+     * Internal flag used to track whether or not the model instance is currently being edited. Read-only
+     * @property editing
+     * @type Boolean
+     */
+    editing : false,
+
+    /**
+     * Readonly flag - true if this Record has been modified.
+     * @type Boolean
+     */
+    dirty : false,
+
+    /**
+     * @cfg {String} persistanceProperty The property on this Persistable object that its data is saved to.
+     * Defaults to 'data' (e.g. all persistable data resides in this.data.)
+     */
+    persistanceProperty: 'data',
+
+    evented: false,
+    isModel: true,
+
+    /**
+     * <tt>true</tt> when the record does not yet exist in a server-side database (see
+     * {@link #setDirty}).  Any record which has a real database pk set as its id property
+     * is NOT a phantom -- it's real.
+     * @property phantom
+     * @type {Boolean}
+     */
+    phantom : false,
+
+    /**
+     * @cfg {String} idProperty The name of the field treated as this Model's unique id (defaults to 'id').
+     */
+    idProperty: 'id',
+
+    /**
+     * The string type of the default Model Proxy. Defaults to 'ajax'
+     * @property defaultProxyType
+     * @type String
+     */
+    defaultProxyType: 'ajax',
+
+    /**
+     * An array of the fields defined on this model
+     * @property fields
+     * @type {Array}
+     */
+
+    constructor: function(data, id) {
+        data = data || {};
+        
+        var me = this,
+            fields,
+            length,
+            field,
+            name,
+            i,
+            isArray = Ext.isArray(data),
+            newData = isArray ? {} : null; // to hold mapped array data if needed
+
+        /**
+         * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
+         * @property internalId
+         * @type String
+         * @private
+         */
+        me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
+
+        Ext.applyIf(me, {
+            data: {}    
+        });
+        
+        /**
+         * Key: value pairs of all fields whose values have changed
+         * @property modified
+         * @type Object
+         */
+        me.modified = {};
+
+        me[me.persistanceProperty] = {};
+
+        me.mixins.observable.constructor.call(me);
+
+        //add default field values if present
+        fields = me.fields.items;
+        length = fields.length;
+
+        for (i = 0; i < length; i++) {
+            field = fields[i];
+            name  = field.name;
+
+            if (isArray){ 
+                // Have to map array data so the values get assigned to the named fields
+                // rather than getting set as the field names with undefined values.
+                newData[name] = data[i];
+            }
+            else if (data[name] === undefined) {
+                data[name] = field.defaultValue;
+            }
+        }
+
+        me.set(newData || data);
+        // clear any dirty/modified since we're initializing
+        me.dirty = false;
+        me.modified = {};
+
+        if (me.getId()) {
+            me.phantom = false;
+        }
+
+        if (typeof me.init == 'function') {
+            me.init();
+        }
+
+        me.id = me.modelName + '-' + me.internalId;
+
+        Ext.ModelManager.register(me);
+    },
+    
+    /**
+     * Returns the value of the given field
+     * @param {String} fieldName The field to fetch the value for
+     * @return {Mixed} The value
+     */
+    get: function(field) {
+        return this[this.persistanceProperty][field];
+    },
+    
+    /**
+     * Sets the given field to the given value, marks the instance as dirty
+     * @param {String|Object} fieldName The field to set, or an object containing key/value pairs
+     * @param {Mixed} value The value to set
+     */
+    set: function(fieldName, value) {
+        var me = this,
+            fields = me.fields,
+            modified = me.modified,
+            convertFields = [],
+            field, key, i, currentValue;
+
+        /*
+         * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
+         * set those last so that all other possible data is set before the convert function is called
+         */
+        if (arguments.length == 1 && Ext.isObject(fieldName)) {
+            for (key in fieldName) {
+                if (fieldName.hasOwnProperty(key)) {
+                
+                    //here we check for the custom convert function. Note that if a field doesn't have a convert function,
+                    //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
+                    field = fields.get(key);
+                    if (field && field.convert !== field.type.convert) {
+                        convertFields.push(key);
+                        continue;
+                    }
+                    
+                    me.set(key, fieldName[key]);
+                }
+            }
+
+            for (i = 0; i < convertFields.length; i++) {
+                field = convertFields[i];
+                me.set(field, fieldName[field]);
+            }
+
+        } else {
+            if (fields) {
+                field = fields.get(fieldName);
+
+                if (field && field.convert) {
+                    value = field.convert(value, me);
+                }
+            }
+            currentValue = me.get(fieldName);
+            me[me.persistanceProperty][fieldName] = value;
+            
+            if (field && field.persist && !me.isEqual(currentValue, value)) {
+                me.dirty = true;
+                me.modified[fieldName] = currentValue;
+            }
+
+            if (!me.editing) {
+                me.afterEdit();
+            }
+        }
+    },
+    
+    /**
+     * Checks if two values are equal, taking into account certain
+     * special factors, for example dates.
+     * @private
+     * @param {Object} a The first value
+     * @param {Object} b The second value
+     * @return {Boolean} True if the values are equal
+     */
+    isEqual: function(a, b){
+        if (Ext.isDate(a) && Ext.isDate(b)) {
+            return a.getTime() === b.getTime();
+        }
+        return a === b;
+    },
+    
+    /**
+     * Begin an edit. While in edit mode, no events (e.g.. the <code>update</code> event)
+     * are relayed to the containing store. When an edit has begun, it must be followed
+     * by either {@link #endEdit} or {@link #cancelEdit}.
+     */
+    beginEdit : function(){
+        var me = this;
+        if (!me.editing) {
+            me.editing = true;
+            me.dirtySave = me.dirty;
+            me.dataSave = Ext.apply({}, me[me.persistanceProperty]);
+            me.modifiedSave = Ext.apply({}, me.modified);
+        }
+    },
+    
+    /**
+     * Cancels all changes made in the current edit operation.
+     */
+    cancelEdit : function(){
+        var me = this;
+        if (me.editing) {
+            me.editing = false;
+            // reset the modified state, nothing changed since the edit began
+            me.modified = me.modifiedSave;
+            me[me.persistanceProperty] = me.dataSave;
+            me.dirty = me.dirtySave;
+            delete me.modifiedSave;
+            delete me.dataSave;
+            delete me.dirtySave;
+        }
+    },
+    
+    /**
+     * End an edit. If any data was modified, the containing store is notified
+     * (ie, the store's <code>update</code> event will fire).
+     * @param {Boolean} silent True to not notify the store of the change
+     */
+    endEdit : function(silent){
+        var me = this;
+        if (me.editing) {
+            me.editing = false;
+            delete me.modifiedSave;
+            delete me.dataSave;
+            delete me.dirtySave;
+            if (silent !== true && me.dirty) {
+                me.afterEdit();
+            }
+        }
+    },
+    
+    /**
+     * Gets a hash of only the fields that have been modified since this Model was created or commited.
+     * @return Object
+     */
+    getChanges : function(){
+        var modified = this.modified,
+            changes  = {},
+            field;
+
+        for (field in modified) {
+            if (modified.hasOwnProperty(field)){
+                changes[field] = this.get(field);
+            }
+        }
+
+        return changes;
+    },
+    
+    /**
+     * Returns <tt>true</tt> if the passed field name has been <code>{@link #modified}</code>
+     * since the load or last commit.
+     * @param {String} fieldName {@link Ext.data.Field#name}
+     * @return {Boolean}
+     */
+    isModified : function(fieldName) {
+        return this.modified.hasOwnProperty(fieldName);
+    },
+    
+    /**
+     * <p>Marks this <b>Record</b> as <code>{@link #dirty}</code>.  This method
+     * is used interally when adding <code>{@link #phantom}</code> records to a
+     * {@link Ext.data.Store#writer writer enabled store}.</p>
+     * <br><p>Marking a record <code>{@link #dirty}</code> causes the phantom to
+     * be returned by {@link Ext.data.Store#getModifiedRecords} where it will
+     * have a create action composed for it during {@link Ext.data.Store#save store save}
+     * operations.</p>
+     */
+    setDirty : function() {
+        var me = this,
+            name;
+        
+        me.dirty = true;
+
+        me.fields.each(function(field) {
+            if (field.persist) {
+                name = field.name;
+                me.modified[name] = me.get(name);
+            }
+        }, me);
+    },
+
+    //<debug>
+    markDirty : function() {
+        if (Ext.isDefined(Ext.global.console)) {
+            Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
+        }
+        return this.setDirty.apply(this, arguments);
+    },
+    //</debug>
+    
+    /**
+     * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}.
+     * Rejects all changes made to the model instance since either creation, or the last commit operation.
+     * Modified fields are reverted to their original values.
+     * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
+     * to have their code notified of reject operations.</p>
+     * @param {Boolean} silent (optional) True to skip notification of the owning
+     * store of the change (defaults to false)
+     */
+    reject : function(silent) {
+        var me = this,
+            modified = me.modified,
+            field;
+
+        for (field in modified) {
+            if (modified.hasOwnProperty(field)) {
+                if (typeof modified[field] != "function") {
+                    me[me.persistanceProperty][field] = modified[field];
+                }
+            }
+        }
+
+        me.dirty = false;
+        me.editing = false;
+        me.modified = {};
+
+        if (silent !== true) {
+            me.afterReject();
+        }
+    },
+
+    /**
+     * Usually called by the {@link Ext.data.Store} which owns the model instance.
+     * Commits all changes made to the instance since either creation or the last commit operation.
+     * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
+     * to have their code notified of commit operations.</p>
+     * @param {Boolean} silent (optional) True to skip notification of the owning
+     * store of the change (defaults to false)
+     */
+    commit : function(silent) {
+        var me = this;
+        
+        me.dirty = false;
+        me.editing = false;
+
+        me.modified = {};
+
+        if (silent !== true) {
+            me.afterCommit();
+        }
+    },
+
+    /**
+     * Creates a copy (clone) of this Model instance.
+     * @param {String} id (optional) A new id, defaults to the id
+     * of the instance being copied. See <code>{@link #id}</code>.
+     * To generate a phantom instance with a new id use:<pre><code>
+var rec = record.copy(); // clone the record
+Ext.data.Model.id(rec); // automatically generate a unique sequential id
+     * </code></pre>
+     * @return {Record}
+     */
+    copy : function(newId) {
+        var me = this;
+        
+        return new me.self(Ext.apply({}, me[me.persistanceProperty]), newId || me.internalId);
+    },
+
+    /**
+     * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias}
+     * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
+     * @static
+     */
+    setProxy: function(proxy) {
+        //make sure we have an Ext.data.proxy.Proxy object
+        if (!proxy.isProxy) {
+            if (typeof proxy === "string") {
+                proxy = {
+                    type: proxy
+                };
+            }
+            proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
+        }
+        proxy.setModel(this.self);
+        this.proxy = proxy;
+
+        return proxy;
+    },
+
+    /**
+     * Returns the configured Proxy for this Model
+     * @return {Ext.data.proxy.Proxy} The proxy
+     */
+    getProxy: function() {
+        return this.proxy;
+    },
+
+    /**
+     * Validates the current data against all of its configured {@link #validations} and returns an
+     * {@link Ext.data.Errors Errors} object
+     * @return {Ext.data.Errors} The errors object
+     */
+    validate: function() {
+        var errors      = Ext.create('Ext.data.Errors'),
+            validations = this.validations,
+            validators  = Ext.data.validations,
+            length, validation, field, valid, type, i;
+
+        if (validations) {
+            length = validations.length;
+
+            for (i = 0; i < length; i++) {
+                validation = validations[i];
+                field = validation.field || validation.name;
+                type  = validation.type;
+                valid = validators[type](validation, this.get(field));
+
+                if (!valid) {
+                    errors.add({
+                        field  : field,
+                        message: validation.message || validators[type + 'Message']
+                    });
+                }
+            }
+        }
+
+        return errors;
+    },
+
+    /**
+     * Checks if the model is valid. See {@link #validate}.
+     * @return {Boolean} True if the model is valid.
+     */
+    isValid: function(){
+        return this.validate().isValid();
+    },
+
+    /**
+     * Saves the model instance using the configured proxy
+     * @param {Object} options Options to pass to the proxy
+     * @return {Ext.data.Model} The Model instance
+     */
+    save: function(options) {
+        options = Ext.apply({}, options);
+
+        var me     = this,
+            action = me.phantom ? 'create' : 'update',
+            record = null,
+            scope  = options.scope || me,
+            operation,
+            callback;
+
+        Ext.apply(options, {
+            records: [me],
+            action : action
+        });
+
+        operation = Ext.create('Ext.data.Operation', options);
+
+        callback = function(operation) {
+            if (operation.wasSuccessful()) {
+                record = operation.getRecords()[0];
+                //we need to make sure we've set the updated data here. Ideally this will be redundant once the
+                //ModelCache is in place
+                me.set(record.data);
+                record.dirty = false;
+
+                Ext.callback(options.success, scope, [record, operation]);
+            } else {
+                Ext.callback(options.failure, scope, [record, operation]);
+            }
+
+            Ext.callback(options.callback, scope, [record, operation]);
+        };
+
+        me.getProxy()[action](operation, callback, me);
+
+        return me;
+    },
+
+    /**
+     * Destroys the model using the configured proxy
+     * @param {Object} options Options to pass to the proxy
+     * @return {Ext.data.Model} The Model instance
+     */
+    destroy: function(options){
+        options = Ext.apply({}, options);
+
+        var me     = this,
+            record = null,
+            scope  = options.scope || me,
+            operation,
+            callback;
+
+        Ext.apply(options, {
+            records: [me],
+            action : 'destroy'
+        });
+
+        operation = Ext.create('Ext.data.Operation', options);
+        callback = function(operation) {
+            if (operation.wasSuccessful()) {
+                Ext.callback(options.success, scope, [record, operation]);
+            } else {
+                Ext.callback(options.failure, scope, [record, operation]);
+            }
+            Ext.callback(options.callback, scope, [record, operation]);
+        };
+
+        me.getProxy().destroy(operation, callback, me);
+        return me;
+    },
+
+    /**
+     * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}
+     * @return {Number} The id
+     */
+    getId: function() {
+        return this.get(this.idProperty);
+    },
+
+    /**
+     * Sets the model instance's id field to the given id
+     * @param {Number} id The new id
+     */
+    setId: function(id) {
+        this.set(this.idProperty, id);
+    },
+
+    /**
+     * Tells this model instance that it has been added to a store
+     * @param {Ext.data.Store} store The store that the model has been added to
+     */
+    join : function(store) {
+        /**
+         * The {@link Ext.data.Store} to which this Record belongs.
+         * @property store
+         * @type {Ext.data.Store}
+         */
+        this.store = store;
+    },
+
+    /**
+     * Tells this model instance that it has been removed from the store
+     */
+    unjoin: function() {
+        delete this.store;
+    },
+
+    /**
+     * @private
+     * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
+     * afterEdit method is called
+     */
+    afterEdit : function() {
+        this.callStore('afterEdit');
+    },
+
+    /**
+     * @private
+     * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
+     * afterReject method is called
+     */
+    afterReject : function() {
+        this.callStore("afterReject");
+    },
+
+    /**
+     * @private
+     * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
+     * afterCommit method is called
+     */
+    afterCommit: function() {
+        this.callStore('afterCommit');
+    },
+
+    /**
+     * @private
+     * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
+     * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
+     * will always be called with the model instance as its single argument.
+     * @param {String} fn The function to call on the store
+     */
+    callStore: function(fn) {
+        var store = this.store;
+
+        if (store !== undefined && typeof store[fn] == "function") {
+            store[fn](this);
+        }
+    },
+
+    /**
+     * Gets all of the data from this Models *loaded* associations.
+     * It does this recursively - for example if we have a User which
+     * hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
+     * {
+     *     orders: [
+     *         {
+     *             id: 123,
+     *             status: 'shipped',
+     *             orderItems: [
+     *                 ...
+     *             ]
+     *         }
+     *     ]
+     * }
+     * @return {Object} The nested data set for the Model's loaded associations
+     */
+    getAssociatedData: function(){
+        return this.prepareAssociatedData(this, [], null);
+    },
+
+    /**
+     * @private
+     * This complex-looking method takes a given Model instance and returns an object containing all data from
+     * all of that Model's *loaded* associations. See (@link #getAssociatedData}
+     * @param {Ext.data.Model} record The Model instance
+     * @param {Array} ids PRIVATE. The set of Model instance internalIds that have already been loaded
+     * @param {String} associationType (optional) The name of the type of association to limit to.
+     * @return {Object} The nested data set for the Model's loaded associations
+     */
+    prepareAssociatedData: function(record, ids, associationType) {
+        //we keep track of all of the internalIds of the models that we have loaded so far in here
+        var associations     = record.associations.items,
+            associationCount = associations.length,
+            associationData  = {},
+            associatedStore, associatedName, associatedRecords, associatedRecord,
+            associatedRecordCount, association, id, i, j, type, allow;
+
+        for (i = 0; i < associationCount; i++) {
+            association = associations[i];
+            type = association.type;
+            allow = true;
+            if (associationType) {
+                allow = type == associationType;
+            }
+            if (allow && type == 'hasMany') {
+
+                //this is the hasMany store filled with the associated data
+                associatedStore = record[association.storeName];
+
+                //we will use this to contain each associated record's data
+                associationData[association.name] = [];
+
+                //if it's loaded, put it into the association data
+                if (associatedStore && associatedStore.data.length > 0) {
+                    associatedRecords = associatedStore.data.items;
+                    associatedRecordCount = associatedRecords.length;
+
+                    //now we're finally iterating over the records in the association. We do this recursively
+                    for (j = 0; j < associatedRecordCount; j++) {
+                        associatedRecord = associatedRecords[j];
+                        // Use the id, since it is prefixed with the model name, guaranteed to be unique
+                        id = associatedRecord.id;
+
+                        //when we load the associations for a specific model instance we add it to the set of loaded ids so that
+                        //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
+                        if (Ext.Array.indexOf(ids, id) == -1) {
+                            ids.push(id);
+
+                            associationData[association.name][j] = associatedRecord.data;
+                            Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
+                        }
+                    }
+                }
+            } else if (allow && type == 'belongsTo') {
+                associatedRecord = record[association.instanceName];
+                if (associatedRecord !== undefined) {
+                    id = associatedRecord.id;
+                    if (Ext.Array.indexOf(ids, id) == -1) {
+                        ids.push(id);
+                        associationData[association.name] = associatedRecord.data;
+                        Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
+                    }
+                }
+            }
+        }
+
+        return associationData;
+    }
+});
+
\ No newline at end of file