define([
  "Backbone",
  "Underscore",
  "models/TaskCollection"
], function(Backbone, _, TaskCollection) {
  function ValidationError(attribute, message) {
    this.attribute = attribute;
    this.message = message;
  }

  var EDITABLE_ATTRIBUTES = ["cmd", "constraints", "container", "env",
    "executor", "id", "instances", "mem", "ports", "uris"];

  return Backbone.Model.extend({
    defaults: function() {
      return {
        cmd: null,
        constraints: [],
        container: null,
        cpus: 0.1,
        env: {},
        executor: "",
        id: _.uniqueId("app_"),
        instances: 1,
        mem: 16.0,
        ports: [0],
        uris: []
      };
    },
    initialize: function(options) {
      // If this model belongs to a collection when it is instantiated, it has
      // already been persisted to the server.
      this.persisted = (this.collection != null);

      this.tasks = new TaskCollection(null, {appId: this.id});
      this.on({
        "change:id": function(model, value, options) {
          // Inform TaskCollection of new ID so it can send requests to the new
          // endpoint.
          this.tasks.options.appId = value;
        },
        "sync": function(model, response, options) {
          this.persisted = true;
        }
      });
    },
    isNew: function() {
      return !this.persisted;
    },
    save: function(attrs, options) {
      options || (options = {});

      var allAttrs = _.extend({}, this.attributes, attrs);
      var allowedAttrs = _.pick(allAttrs, EDITABLE_ATTRIBUTES);

      options.contentType = "application/json";
      options.data = JSON.stringify(allowedAttrs);

      return Backbone.Model.prototype.save.call(
        this, allowedAttrs, options);
    },
    validate: function(attrs, options) {
      var errors = [];

      if (_.isNaN(attrs.mem) || !_.isNumber(attrs.mem) || attrs.mem < 0) {
        errors.push(
          new ValidationError("mem", "Memory must be a non-negative Number"));
      }

      if (_.isNaN(attrs.cpus) || !_.isNumber(attrs.cpus) || attrs.cpus < 0) {
        errors.push(
          new ValidationError("cpus", "CPUs must be a non-negative Number"));
      }

      if (_.isNaN(attrs.instances) || !_.isNumber(attrs.instances) ||
          attrs.instances < 0) {
        errors.push(
          new ValidationError("instances", "Instances must be a non-negative Number"));
      }

      if (!_.isString(attrs.id) || attrs.id.length < 1) {
        errors.push(
          new ValidationError("id", "ID must be a non-empty String"));
      }

      if (!_.isString(attrs.cmd) || attrs.cmd.length < 1) {
        // Prevent erroring out on UPDATE operations like scale/suspend. 
        // If cmd string is empty, then don't error out if an executor and
        // container are provided.
        if (!_.isString(attrs.executor) || attrs.executor.length < 1 ||
            attrs.container == null || !_.isString(attrs.container.image) ||
            attrs.container.image.length < 1 ||
            attrs.container.image.indexOf('docker') != 0) {
          errors.push(
            new ValidationError("cmd",
              "Command must be a non-empty String if executor and container image are not provided"
            )
          );
        }
      }

      if (errors.length > 0) return errors;
    }
  });
});

@@ -1362,198 +1362,651 @@
     
-save: function(attrs, options) %7B%0A      options %7C%7C (options = %7B%7D);%0A%0A      var allAttrs = _.extend(%7B%7D, this.attributes, attrs);%0A      var allowedAttrs = _.pick(allAttrs, EDITABLE_ATTRIBUTES);%0A
+/* Sends only those attributes listed in %60EDITABLE_ATTRIBUTES%60 to prevent%0A     * sending immutable values like %22tasksRunning%22 and %22tasksStaged%22 and the%0A     * %22version%22 value, which when sent prevents any other attributes from being%0A     * changed.%0A     */%0A    save: function(attrs, options) %7B%0A      options %7C%7C (options = %7B%7D);%0A%0A      var allAttrs = _.extend(%7B%7D, this.attributes, attrs);%0A      var allowedAttrs = _.pick(allAttrs, EDITABLE_ATTRIBUTES);%0A%0A      /* When %60options.data%60 is supplied, Backbone does not attempt to infer its%0A       * content type. It must be explicitly set for the content to be%0A       * interpreted as JSON.%0A       */
 %0A   

Add description of overriden save method