docs: function(key, value, options) {
          var attrs;
          if (_.isObject(key) || isNullOrUndefined(key)) {
            attrs = _.mapObject(key, function(v, k) {
              checkReservedKey(k);
              return AV._decode(v, k);
            });
            options = value;
          } else {
            attrs = {};
            checkReservedKey(key);
            attrs[key] = AV._decode(value, key);
          }
  
          // Extract attributes and options.
          options = options || {};
          if (!attrs) {
            return this;
          }
          if (attrs instanceof AV.Object) {
            attrs = attrs.attributes;
          }
  
          // If the unset option is used, every attribute should be a Unset.
          if (options.unset) {
            AV._objectEach(attrs, function(unused_value, key) {
              attrs[key] = new AV.Op.Unset();
            });
          }
  
          // Apply all the attributes to get the estimated values.
          var dataToValidate = _.clone(attrs);
          var self = this;
          AV._objectEach(dataToValidate, function(value, key) {
            if (value instanceof AV.Op) {
              dataToValidate[key] = value._estimate(
                self.attributes[key],
                self,
                key
              );
              if (dataToValidate[key] === AV.Op._UNSET) {
                delete dataToValidate[key];
              }
            }
          });
  
          // Run validation.
          this._validate(attrs, options);
  
          options.changes = {};
          var escaped = this._escapedAttributes;
  
          // Update attributes.
          AV._arrayEach(_.keys(attrs), function(attr) {
            var val = attrs[attr];
  
            // If this is a relation object we need to set the parent correctly,
            // since the location where it was parsed does not have access to
            // this object.
            if (val instanceof AV.Relation) {
              val.parent = self;
            }
  
            if (!(val instanceof AV.Op)) {
              val = new AV.Op.Set(val);
            }
  
            // See if this change will actually have any effect.
            var isRealChange = true;
            if (
              val instanceof AV.Op.Set &&
              _.isEqual(self.attributes[attr], val.value)
            ) {
              isRealChange = false;
            }
  
            if (isRealChange) {
              delete escaped[attr];
              if (options.silent) {
                self._silent[attr] = true;
              } else {
                options.changes[attr] = true;
              }
            }
  
            var currentChanges = _.last(self._opSetQueue);
            currentChanges[attr] = val._mergeWithPrevious(currentChanges[attr]);
            self._rebuildEstimatedDataForKey(attr);
  
            if (isRealChange) {
              self.changed[attr] = self.attributes[attr];
              if (!options.silent) {
                self._pending[attr] = true;
              }
            } else {
              delete self.changed[attr];
              delete self._pending[attr];
            }
          });
  
          if (!options.silent) {
            this.change(options);
          }
          return this;
        }