Please find below about the tersest reproduction I could fathom of a sequence of steps in a Backbone application that leads to a Collection
getting into a state of internal confusion.
A working copy can be found in this JSFiddle .
What it demonstrates is a collection
keeping an internal (_byId
) reference to a remove()
-ed Model
. That Model
is no longer part of collection.models
, however, it can still be retrieved through its (former) id
.
var
MyView = Backbone.View.extend( {
initialize: function () {
this.model = new Backbone.Model( {
id: 'foo'
, foo: 'bar'
} );
this.collection = new Backbone.Collection( this.model );
this.listenTo( this.model, 'change:id', function ( model, id ) {
if ( id == null ) {
this.collection.remove( model );
}
} );
this.model.unset( 'id' );
$('#models' ).val( JSON.stringify( this.collection.models ));
$('#_byId' ).val( JSON.stringify( this.collection._byId ));
$('#get-foo').val( JSON.stringify( this.collection.get( 'foo' )));
}
})
, foo = new MyView()
;
Hmm, interesting. When we add model to collection, __byId_ add two properties: model.id and model.cid, their reference to model. Issue on 1134 line, id === undefined, but __byId_ contains model.id - _'foo'_.
Collection don't has some special logic when change model.id to value == null, check line 1183.
Also you can change unset to set _null_ or _undefined_. this.model.set('id', null);
P.S.: I think you never need set your model id to null or undefined, it is anti pattern.
Yes — you should never do this, but it's still a bug.
We should fix this by making sure that if an id
is changing from a value to null
, we delete this._byId[id]
first.
If you remove a model from a collection without the use of an event, it works as expected. The problem is in _onModelEvent
which checks if the name of the event is change
, when the actual event name being sent in this case is change:id
.
Most helpful comment
Yes — you should never do this, but it's still a bug.
We should fix this by making sure that if an
id
is changing from a value tonull
, wedelete this._byId[id]
first.