Skip to content

Commit

Permalink
Add Model#reset
Browse files Browse the repository at this point in the history
- Adds `{ reset: true }` flag to `#set` that deletes any attributes
that are not specified
- Adds ‘reset’ event mirroring `Collection`’s.

Fixes jashkenas#3253, jashkenas#3395.

I could really use help naming the `validateCombined` option. I need to
validate only the passed in `attrs` (both `#clear` and `#reset` specify
_exactly_ what the attributes will be), not the merged `attrs`
`attributes`.
  • Loading branch information
jridgewell committed May 24, 2015
1 parent ea82dad commit 7f6e8d1
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 9 deletions.
25 changes: 20 additions & 5 deletions backbone.js
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@
// the core primitive operation of a model, updating the data and notifying
// anyone who needs to know about the change in state. The heart of the beast.
set: function(key, val, options) {
var attr, attrs, unset, changes, silent, changing, prev, current;
var attr, attrs, unset, reset, changes, silent, changing, prev, current;
if (key == null) return this;

// Handle both `"key", value` and `{key: value}` -style arguments.
Expand All @@ -462,6 +462,7 @@

// Extract attributes and options.
unset = options.unset;
reset = options.reset;
silent = options.silent;
changes = [];
changing = this._changing;
Expand All @@ -485,6 +486,16 @@
unset ? delete current[attr] : current[attr] = val;
}

if (reset) {
for (attr in current) {
if (!(attr in attrs)) {
changes.push(attr);
this.changed[attr] = current[attr];
delete current[attr];
}
}
}

// Update id
this.id = current[this.idAttribute];

Expand All @@ -505,6 +516,7 @@
this._pending = false;
this.trigger('change', this, options);
}
if (reset) this.trigger('reset', this, options);
}
this._pending = false;
this._changing = false;
Expand All @@ -519,9 +531,12 @@

// Clear all attributes on the model, firing `"change"`.
clear: function(options) {
var attrs = {};
for (var key in this.attributes) attrs[key] = void 0;
return this.set(attrs, _.extend({}, options, {unset: true}));
return this.reset({}, options);
},

reset: function(attrs, options) {
if (!_.isObject(attrs)) attrs = _.result(this, 'defaults');
return this.set(attrs, _.extend({validateCombined: false}, options, {reset: true}));
},

// Determine if the model has changed since the last `"change"` event.
Expand Down Expand Up @@ -705,7 +720,7 @@
// returning `true` if all is well. Otherwise, fire an `"invalid"` event.
_validate: function(attrs, options) {
if (!options.validate || !this.validate) return true;
attrs = _.extend({}, this.attributes, attrs);
if (options.validateCombined !== false) attrs = _.extend({}, this.attributes, attrs);
var error = this.validationError = this.validate(attrs, options) || null;
if (!error) return true;
this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
Expand Down
57 changes: 53 additions & 4 deletions test/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,33 @@
equal(model.get('name'), undefined);
});

test("reset", 10, function() {
var changed = 0;
var model = new Backbone.Model({id: 1, name : 'Model'});
model.on('change:id change:name change:test', function(){ changed++; });
model.on('reset', function() {
ok(model.hasChanged('name'));
ok(model.hasChanged('id'));
});
model.reset({name: 'Test'});
equal(changed, 2);
equal(model.get('name'), 'Test');
equal(model.get('id'), undefined);
equal(model.id, undefined);

model.defaults = {
name: 'Test',
test: 'ing'
};
model.off('reset').on('reset', function() {
ok(!model.hasChanged('name'));
});
model.reset();
equal(changed, 3);
equal(model.get('name'), 'Test');
equal(model.get('test'), 'ing');
});

test("defaults", 4, function() {
var Defaulted = Backbone.Model.extend({
defaults: {
Expand Down Expand Up @@ -647,6 +674,16 @@
this.ajaxSettings.success();
});

test("fetch with reset", 2, function() {
var model = new Backbone.Model({name: 'One', test: 'ing'});
model.sync = function(method, model, options) {
options.success({name: 'Two'});
};
model.fetch({reset: true});
equal(model.get('name'), 'Two');
ok(!model.has('test'));
});

test("destroy", 3, function() {
doc.destroy();
equal(this.syncArgs.method, 'delete');
Expand Down Expand Up @@ -703,26 +740,31 @@
equal(model.get('a'), 100);
});

test("validate on unset and clear", 6, function() {
test("validate on unset, reset, and clear", 8, function() {
var error;
var model = new Backbone.Model({name: "One"});
model.validate = function(attrs) {
error = false;
if (!attrs.name) {
error = true;
return "No thanks.";
}
};
model.set({name: "Two"});
equal(model.get('name'), 'Two');
equal(error, undefined);
ok(!error);
model.unset('name', {validate: true});
equal(error, true);
ok(error);
model.reset({test: 'Two'}, {validate: true});
ok(error);
equal(model.get('name'), 'Two');
model.clear({validate:true});
equal(model.get('name'), 'Two');
delete model.validate;
model.clear();
equal(model.get('name'), undefined);
model.reset({test: 'Two'});
equal(model.get('test'), 'Two');
});

test("validate with error callback", 8, function() {
Expand Down Expand Up @@ -1097,7 +1139,7 @@
var model = new Backbone.Model();
var options = {};
model.clear(options);
ok(!options.unset);
ok(!options.reset);
});

test("#1122 - unset does not alter options.", 1, function() {
Expand All @@ -1107,6 +1149,13 @@
ok(!options.unset);
});

test("#1122 - reset does not alter options.", 1, function() {
var model = new Backbone.Model();
var options = {};
model.reset({}, options);
ok(!options.reset);
});

test("#1355 - `options` is passed to success callbacks", 3, function() {
var model = new Backbone.Model();
var opts = {
Expand Down

0 comments on commit 7f6e8d1

Please sign in to comment.